mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-21 01:45:47 +10:00
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92d2b224e7 | |||
| 34a6b5d895 | |||
| 8b0bdd9a46 | |||
| 45d914de57 | |||
| 2c49534955 | |||
| c56cf9c3ed | |||
| fee4cd13be | |||
| a53d5ccfb6 | |||
| e5d06b1c7e | |||
| e95a55e4f0 | |||
| 422ca941c2 | |||
| 3098d860e9 | |||
| f0d34f7503 | |||
| daa0c3f9c3 | |||
| 09e1cd2b8d | |||
| fa514533eb | |||
| 75b8b8af70 | |||
| 115667a27c | |||
| cfb51d96ff | |||
| 75356fe20d | |||
| c43df67fac | |||
| e2b9b58d7d | |||
| d6794bc8d7 | |||
| 72216e2cf7 | |||
| 2a2275ec31 | |||
| dff037535d | |||
| 297e609b3e | |||
| 20171c491f | |||
| cc43f4d198 | |||
| 537384ea5b | |||
| a0be63b2e7 | |||
| 1cc887e5bb | |||
| 26d9029538 | |||
| 30bcbedf5e | |||
| 3fdd8f5eaf | |||
| f4ec732de8 | |||
| f790604d23 | |||
| 8e3b563aba | |||
| ee3b0a3126 | |||
| 31d633ee0b | |||
| c269365d81 | |||
| 9a9f59e53f | |||
| 9cb667fad0 | |||
| 3fef594fe5 | |||
| 8387304d2a | |||
| 2acba9eb84 | |||
| 30ba1799e1 | |||
| 13f9c5058a | |||
| 98fc2d6e0a | |||
| 2becbb342c | |||
| 5b2d5a494c | |||
| 153736d36e | |||
| 6c8a149e1b | |||
| b41ccee4f9 | |||
| 04a713bb76 | |||
| 714aecd7e6 | |||
| 2e1a5e0fbf | |||
| 1f0b7d8d7b | |||
| dffea23ce2 | |||
| e0a8fb7ec0 | |||
| 06fc08c41f | |||
| c22bfed680 | |||
| 316c76e5b4 | |||
| 4b215ad574 | |||
| 09e60cebd9 | |||
| 6782347cf4 | |||
| 1726119c3e | |||
| 988806dccd | |||
| 14ff8250c0 | |||
| 2a04ebb8b6 | |||
| a14462978d | |||
| df7fb45683 | |||
| f01eff07ff | |||
| 7cc7183e0c | |||
| a6b2756d0d | |||
| 614f3d4601 | |||
| 7c33647119 | |||
| fde8b686f5 | |||
| 9bc3a27b53 | |||
| a8f387b0da | |||
| dd1a73c247 | |||
| e36f6b7eb9 | |||
| fcef82be63 | |||
| 6ddb8f1a3d | |||
| 7a22223756 |
@@ -0,0 +1,76 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
android:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
cache: true
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('android/gradle/wrapper/gradle-wrapper.properties', 'android/build.gradle', 'android/settings.gradle', 'android/app/build.gradle', 'pubspec.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- run: flutter pub get
|
||||
- run: flutter build apk --release --no-pub
|
||||
|
||||
ios:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
cache: true
|
||||
- run: flutter pub get
|
||||
- run: flutter build ios --release --no-codesign --no-pub
|
||||
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
cache: true
|
||||
- name: Install Linux build deps
|
||||
run: sudo apt-get update && sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev
|
||||
- run: flutter pub get
|
||||
- run: flutter build linux --release --no-pub
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
cache: true
|
||||
- run: flutter pub get
|
||||
- run: flutter build macos --release --no-pub
|
||||
|
||||
web:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
cache: true
|
||||
- run: flutter pub get
|
||||
- run: flutter build web --release --no-pub
|
||||
@@ -0,0 +1,23 @@
|
||||
name: Flutter Analyze
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: stable
|
||||
|
||||
- name: Install dependencies
|
||||
run: flutter pub get
|
||||
|
||||
- name: Analyze
|
||||
run: flutter analyze --fatal-infos --fatal-warnings
|
||||
@@ -70,6 +70,7 @@ secrets.dart
|
||||
**/android/local.properties
|
||||
**/android/.externalNativeBuild/
|
||||
*.jks
|
||||
key.properties
|
||||
keystore.properties
|
||||
|
||||
# Generated files
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
@@ -5,6 +7,12 @@ plugins {
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
val keystoreProperties = Properties()
|
||||
val keystorePropertiesFile = rootProject.file("key.properties")
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystorePropertiesFile.inputStream().use { keystoreProperties.load(it) }
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.meshcore.meshcore_open"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
@@ -40,11 +48,25 @@ android {
|
||||
// }
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
val storeFilePath = keystoreProperties["storeFile"] as String?
|
||||
if (storeFilePath != null) {
|
||||
storeFile = file(storeFilePath)
|
||||
storePassword = keystoreProperties["storePassword"] as String?
|
||||
keyAlias = keystoreProperties["keyAlias"] as String?
|
||||
keyPassword = keystoreProperties["keyPassword"] as String?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
signingConfig = if (keystorePropertiesFile.exists()) {
|
||||
signingConfigs.getByName("release")
|
||||
} else {
|
||||
signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<!-- Camera permission for QR code scanning -->
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||
|
||||
<application
|
||||
android:label="meshcore_open"
|
||||
@@ -64,5 +67,14 @@
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
<!-- URL launcher intents for opening links -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="http"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="https"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 579 KiB |
@@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
platform :ios, '12.0'
|
||||
platform :ios, '15.5'
|
||||
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
PODS:
|
||||
- Flutter (1.0.0)
|
||||
- flutter_blue_plus_darwin (0.0.2):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- flutter_foreground_task (0.0.1):
|
||||
- Flutter
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- Flutter
|
||||
- GoogleDataTransport (10.1.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
|
||||
- GoogleMLKit/BarcodeScanning (~> 7.0.0)
|
||||
- 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):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- PromisesObjC (2.4.0)
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_blue_plus_darwin (from `.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
|
||||
- flutter_foreground_task (from `.symlinks/plugins/flutter_foreground_task/ios`)
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- 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:
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_blue_plus_darwin:
|
||||
:path: ".symlinks/plugins/flutter_blue_plus_darwin/darwin"
|
||||
flutter_foreground_task:
|
||||
:path: ".symlinks/plugins/flutter_foreground_task/ios"
|
||||
flutter_local_notifications:
|
||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||
mobile_scanner:
|
||||
:path: ".symlinks/plugins/mobile_scanner/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sqflite_darwin:
|
||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
wakelock_plus:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
|
||||
flutter_foreground_task: a159d2c2173b33699ddb3e6c2a067045d7cebb89
|
||||
flutter_local_notifications: 395056b3175ba4f08480a7c5de30cd36d69827e4
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
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
|
||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
|
||||
PODFILE CHECKSUM: 570da2a631486c6bd6496bed1e605e63e2471be5
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
@@ -14,6 +14,7 @@
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
9A698254711B63C3940A64CB /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4268181FCF3E12817B700E9C /* libPods-Runner.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -42,9 +43,13 @@
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
24A76623340E493BD4C25C5C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
40AC50CE3E1D4278E82498CF /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
4268181FCF3E12817B700E9C /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
718BC7DCCFC5C370705C12E5 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
@@ -62,6 +67,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9A698254711B63C3940A64CB /* libPods-Runner.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -94,6 +100,8 @@
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
DEE6F094D3B70E76087722E1 /* Pods */,
|
||||
DAE613E34DF694C2E33B64C7 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -121,6 +129,25 @@
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DAE613E34DF694C2E33B64C7 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4268181FCF3E12817B700E9C /* libPods-Runner.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DEE6F094D3B70E76087722E1 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
40AC50CE3E1D4278E82498CF /* Pods-Runner.debug.xcconfig */,
|
||||
24A76623340E493BD4C25C5C /* Pods-Runner.release.xcconfig */,
|
||||
718BC7DCCFC5C370705C12E5 /* Pods-Runner.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -145,12 +172,14 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
DE3B2E091393835C0B38492E /* [CP] Check Pods Manifest.lock */,
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
B788CEDB957A87EE8AC593BB /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -253,6 +282,45 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
B788CEDB957A87EE8AC593BB /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
DE3B2E091393835C0B38492E /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -368,7 +436,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.meshcoreOpen;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.monitormx.meshcoreopen;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -384,7 +452,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.meshcoreOpen.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.monitormx.meshcoreopen.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@@ -401,7 +469,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.meshcoreOpen.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.monitormx.meshcoreopen.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
@@ -416,7 +484,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.meshcoreOpen.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.monitormx.meshcoreopen.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
@@ -547,7 +615,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.meshcoreOpen;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.monitormx.meshcoreopen;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@@ -569,7 +637,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.meshcoreOpen;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.monitormx.meshcoreopen;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
+3
@@ -4,4 +4,7 @@
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
@@ -53,5 +53,12 @@
|
||||
<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>
|
||||
</plist>
|
||||
|
||||
@@ -3,3 +3,4 @@ template-arb-file: app_en.arb
|
||||
output-localization-file: app_localizations.dart
|
||||
output-class: AppLocalizations
|
||||
nullable-getter: false
|
||||
untranslated-messages-file: untranslated.json
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,8 @@ class BufferReader {
|
||||
|
||||
Uint8List readRemainingBytes() => readBytes(remaining);
|
||||
|
||||
String readString() => utf8.decode(readRemainingBytes(), allowMalformed: true);
|
||||
String readString() =>
|
||||
utf8.decode(readRemainingBytes(), allowMalformed: true);
|
||||
|
||||
String readCString(int maxLength) {
|
||||
final value = <int>[];
|
||||
@@ -38,13 +39,19 @@ class BufferReader {
|
||||
|
||||
int readUInt8() => readBytes(1).buffer.asByteData().getUint8(0);
|
||||
int readInt8() => readBytes(1).buffer.asByteData().getInt8(0);
|
||||
int readUInt16LE() => readBytes(2).buffer.asByteData().getUint16(0, Endian.little);
|
||||
int readUInt16BE() => readBytes(2).buffer.asByteData().getUint16(0, Endian.big);
|
||||
int readUInt32LE() => readBytes(4).buffer.asByteData().getUint32(0, Endian.little);
|
||||
int readUInt32BE() => readBytes(4).buffer.asByteData().getUint32(0, Endian.big);
|
||||
int readInt16LE() => readBytes(2).buffer.asByteData().getInt16(0, Endian.little);
|
||||
int readUInt16LE() =>
|
||||
readBytes(2).buffer.asByteData().getUint16(0, Endian.little);
|
||||
int readUInt16BE() =>
|
||||
readBytes(2).buffer.asByteData().getUint16(0, Endian.big);
|
||||
int readUInt32LE() =>
|
||||
readBytes(4).buffer.asByteData().getUint32(0, Endian.little);
|
||||
int readUInt32BE() =>
|
||||
readBytes(4).buffer.asByteData().getUint32(0, Endian.big);
|
||||
int readInt16LE() =>
|
||||
readBytes(2).buffer.asByteData().getInt16(0, Endian.little);
|
||||
int readInt16BE() => readBytes(2).buffer.asByteData().getInt16(0, Endian.big);
|
||||
int readInt32LE() => readBytes(4).buffer.asByteData().getInt32(0, Endian.little);
|
||||
int readInt32LE() =>
|
||||
readBytes(4).buffer.asByteData().getInt32(0, Endian.little);
|
||||
|
||||
int readInt24BE() {
|
||||
var value = (readByte() << 16) | (readByte() << 8) | readByte();
|
||||
@@ -63,21 +70,25 @@ class BufferWriter {
|
||||
void writeBytes(Uint8List bytes) => _builder.add(bytes);
|
||||
|
||||
void writeUInt16LE(int num) {
|
||||
final bytes = Uint8List(2)..buffer.asByteData().setUint16(0, num, Endian.little);
|
||||
final bytes = Uint8List(2)
|
||||
..buffer.asByteData().setUint16(0, num, Endian.little);
|
||||
writeBytes(bytes);
|
||||
}
|
||||
|
||||
void writeUInt32LE(int num) {
|
||||
final bytes = Uint8List(4)..buffer.asByteData().setUint32(0, num, Endian.little);
|
||||
final bytes = Uint8List(4)
|
||||
..buffer.asByteData().setUint32(0, num, Endian.little);
|
||||
writeBytes(bytes);
|
||||
}
|
||||
|
||||
void writeInt32LE(int num) {
|
||||
final bytes = Uint8List(4)..buffer.asByteData().setInt32(0, num, Endian.little);
|
||||
final bytes = Uint8List(4)
|
||||
..buffer.asByteData().setInt32(0, num, Endian.little);
|
||||
writeBytes(bytes);
|
||||
}
|
||||
|
||||
void writeString(String string) => writeBytes(Uint8List.fromList(utf8.encode(string)));
|
||||
void writeString(String string) =>
|
||||
writeBytes(Uint8List.fromList(utf8.encode(string)));
|
||||
|
||||
void writeCString(String string, int maxLength) {
|
||||
final bytes = Uint8List(maxLength);
|
||||
@@ -118,6 +129,8 @@ const int cmdGetChannel = 31;
|
||||
const int cmdSetChannel = 32;
|
||||
const int cmdGetRadioSettings = 57;
|
||||
const int cmdGetTelemetryReq = 39;
|
||||
const int cmdGetCustomVar = 40;
|
||||
const int cmdSetCustomVar = 41;
|
||||
const int cmdSendBinaryReq = 50;
|
||||
|
||||
// Text message types
|
||||
@@ -152,6 +165,7 @@ const int respCodeContactMsgRecvV3 = 16;
|
||||
const int respCodeChannelMsgRecvV3 = 17;
|
||||
const int respCodeChannelInfo = 18;
|
||||
const int respCodeRadioSettings = 25;
|
||||
const int respCodeCustomVars = 21;
|
||||
|
||||
// Push codes (async from device)
|
||||
const int pushCodeAdvert = 0x80;
|
||||
@@ -166,7 +180,6 @@ const int pushCodeNewAdvert = 0x8A;
|
||||
const int pushCodeTelemetryResponse = 0x8B;
|
||||
const int pushCodeBinaryResponse = 0x8C;
|
||||
|
||||
|
||||
// Contact/advertisement types
|
||||
const int advTypeChat = 1;
|
||||
const int advTypeRepeater = 2;
|
||||
@@ -182,8 +195,8 @@ const int maxFrameSize = 172;
|
||||
const int appProtocolVersion = 3;
|
||||
// Matches firmware MAX_TEXT_LEN (10 * CIPHER_BLOCK_SIZE).
|
||||
const int maxTextPayloadBytes = 160;
|
||||
const int _sendTextMsgOverheadBytes = 1 + 1 + 1 + 4 + 6 + 1;
|
||||
const int _sendChannelTextMsgOverheadBytes = 1 + 1 + 1 + 4 + 1;
|
||||
const int _sendTextMsgOverheadBytes = 1 + 1 + 1 + 4 + 6 + 1 + 2; // +2 safety margin
|
||||
const int _sendChannelTextMsgOverheadBytes = 1 + 1 + 1 + 4 + 1 + 2; // +2 safety margin
|
||||
|
||||
int maxContactMessageBytes() {
|
||||
final byFrame = maxFrameSize - _sendTextMsgOverheadBytes;
|
||||
@@ -233,10 +246,7 @@ class ParsedContactText {
|
||||
final Uint8List senderPrefix;
|
||||
final String text;
|
||||
|
||||
const ParsedContactText({
|
||||
required this.senderPrefix,
|
||||
required this.text,
|
||||
});
|
||||
const ParsedContactText({required this.senderPrefix, required this.text});
|
||||
}
|
||||
|
||||
ParsedContactText? parseContactMessageText(Uint8List frame) {
|
||||
@@ -265,10 +275,17 @@ ParsedContactText? parseContactMessageText(Uint8List frame) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var text = readCString(frame, baseTextOffset, frame.length - baseTextOffset).trim();
|
||||
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();
|
||||
text = readCString(
|
||||
frame,
|
||||
baseTextOffset + 4,
|
||||
frame.length - (baseTextOffset + 4),
|
||||
).trim();
|
||||
}
|
||||
if (text.isEmpty) return null;
|
||||
|
||||
@@ -362,7 +379,8 @@ Uint8List buildSendTextMsgFrame(
|
||||
int attempt = 0,
|
||||
int? timestampSeconds,
|
||||
}) {
|
||||
final timestamp = timestampSeconds ?? (DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||
final timestamp =
|
||||
timestampSeconds ?? (DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||
final writer = BufferWriter();
|
||||
writer.writeByte(cmdSendTxtMsg);
|
||||
writer.writeByte(txtTypePlain);
|
||||
@@ -444,7 +462,9 @@ Uint8List buildSendSelfAdvertFrame({bool flood = false}) {
|
||||
// Format: [cmd][name...]
|
||||
Uint8List buildSetAdvertNameFrame(String name) {
|
||||
final nameBytes = utf8.encode(name);
|
||||
final nameLen = nameBytes.length < maxNameSize ? nameBytes.length : maxNameSize - 1;
|
||||
final nameLen = nameBytes.length < maxNameSize
|
||||
? nameBytes.length
|
||||
: maxNameSize - 1;
|
||||
final writer = BufferWriter();
|
||||
writer.writeByte(cmdSetAdvertName);
|
||||
writer.writeBytes(Uint8List.fromList(nameBytes.sublist(0, nameLen)));
|
||||
@@ -461,6 +481,14 @@ Uint8List buildSetAdvertLatLonFrame(double lat, double lon) {
|
||||
return writer.toBytes();
|
||||
}
|
||||
|
||||
Uint8List buildSetCustomVarFrame(String value) {
|
||||
final writer = BufferWriter();
|
||||
writer.writeByte(cmdSetCustomVar);
|
||||
writer.writeString(value);
|
||||
writer.writeByte(0);
|
||||
return writer.toBytes();
|
||||
}
|
||||
|
||||
// Build CMD_REBOOT frame
|
||||
// Format: [cmd]["reboot"]
|
||||
Uint8List buildRebootFrame() {
|
||||
@@ -544,7 +572,9 @@ Uint8List buildUpdateContactPathFrame(
|
||||
// Path data (64 bytes, zero-padded)
|
||||
final pathPadded = Uint8List(maxPathSize);
|
||||
if (customPath.isNotEmpty && pathLen > 0) {
|
||||
final copyLen = customPath.length < maxPathSize ? customPath.length : maxPathSize;
|
||||
final copyLen = customPath.length < maxPathSize
|
||||
? customPath.length
|
||||
: maxPathSize;
|
||||
for (int i = 0; i < copyLen; i++) {
|
||||
pathPadded[i] = customPath[i];
|
||||
}
|
||||
@@ -575,6 +605,11 @@ Uint8List buildGetRadioSettingsFrame() {
|
||||
return Uint8List.fromList([cmdGetRadioSettings]);
|
||||
}
|
||||
|
||||
//Build CMD_GET_CUSTOM_VARS frame
|
||||
Uint8List buildGetCustomVarsFrame() {
|
||||
return Uint8List.fromList([cmdGetCustomVar]);
|
||||
}
|
||||
|
||||
// Calculate LoRa airtime for a packet
|
||||
// Based on Semtech SX127x datasheet formula
|
||||
// Returns airtime in milliseconds
|
||||
@@ -598,9 +633,11 @@ int calculateLoRaAirtime({
|
||||
final crc = 1; // CRC enabled
|
||||
final de = lowDataRateOptimize ? 1 : 0;
|
||||
|
||||
final numerator = 8 * payloadBytes - 4 * spreadingFactor + 28 + 16 * crc - headerBytes;
|
||||
final numerator =
|
||||
8 * payloadBytes - 4 * spreadingFactor + 28 + 16 * crc - headerBytes;
|
||||
final denominator = 4 * (spreadingFactor - 2 * de);
|
||||
var payloadSymbols = 8 + ((numerator / denominator).ceil()) * (codingRate + 4);
|
||||
var payloadSymbols =
|
||||
8 + ((numerator / denominator).ceil()) * (codingRate + 4);
|
||||
|
||||
if (payloadSymbols < 0) {
|
||||
payloadSymbols = 8;
|
||||
@@ -647,7 +684,8 @@ Uint8List buildSendCliCommandFrame(
|
||||
int attempt = 0,
|
||||
int? timestampSeconds,
|
||||
}) {
|
||||
final timestamp = timestampSeconds ?? (DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||
final timestamp =
|
||||
timestampSeconds ?? (DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||
final writer = BufferWriter();
|
||||
writer.writeByte(cmdSendTxtMsg);
|
||||
writer.writeByte(txtTypeCliData);
|
||||
@@ -661,10 +699,7 @@ Uint8List buildSendCliCommandFrame(
|
||||
|
||||
// Build a telemetry request frame
|
||||
// Format: [cmd][pub_key x32][payload]
|
||||
Uint8List buildSendBinaryReq(
|
||||
Uint8List repeaterPubKey, {
|
||||
Uint8List? payload,
|
||||
}) {
|
||||
Uint8List buildSendBinaryReq(Uint8List repeaterPubKey, {Uint8List? payload}) {
|
||||
final writer = BufferWriter();
|
||||
writer.writeByte(cmdSendBinaryReq);
|
||||
writer.writeBytes(repeaterPubKey);
|
||||
@@ -672,4 +707,4 @@ Uint8List buildSendBinaryReq(
|
||||
writer.writeBytes(payload);
|
||||
}
|
||||
return writer.toBytes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChatScrollController extends ScrollController {
|
||||
final ValueNotifier<bool> showJumpToBottom = ValueNotifier(false);
|
||||
VoidCallback? onScrollNearTop;
|
||||
|
||||
static const _bottomThreshold = 100.0;
|
||||
static const _topThreshold = 50.0;
|
||||
|
||||
ChatScrollController() {
|
||||
addListener(_handleScroll);
|
||||
}
|
||||
|
||||
void _handleScroll() {
|
||||
if (!hasClients) return;
|
||||
final pos = position;
|
||||
|
||||
// With reverse: true, position 0 is bottom, maxScrollExtent is top
|
||||
// Show jump button when scrolled away from bottom (position > threshold)
|
||||
final isAtBottom = pos.pixels <= _bottomThreshold;
|
||||
if (showJumpToBottom.value == isAtBottom) {
|
||||
showJumpToBottom.value = !isAtBottom;
|
||||
}
|
||||
|
||||
// Pagination trigger when scrolled near top (maxScrollExtent)
|
||||
if (pos.pixels >= pos.maxScrollExtent - _topThreshold) {
|
||||
onScrollNearTop?.call();
|
||||
}
|
||||
}
|
||||
|
||||
void jumpToBottom() {
|
||||
if (hasClients && position.maxScrollExtent > 0) {
|
||||
animateTo(
|
||||
0, // With reverse: true, position 0 is bottom
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void handleKeyboardOpen() {
|
||||
// Simple: just scroll to bottom when keyboard opens
|
||||
if (hasClients) {
|
||||
animateTo(
|
||||
0, // With reverse: true, position 0 is bottom
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void scrollToBottomIfAtBottom() {
|
||||
// Only scroll if jump button is NOT showing (i.e., already at bottom)
|
||||
if (!showJumpToBottom.value && hasClients && position.maxScrollExtent > 0) {
|
||||
animateTo(
|
||||
0, // With reverse: true, position 0 is bottom
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
showJumpToBottom.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
|
||||
class LinkHandler {
|
||||
static Future<void> handleLinkTap(BuildContext context, String url) async {
|
||||
// Show confirmation dialog
|
||||
final shouldOpen = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(context.l10n.chat_openLink),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.l10n.chat_openLinkConfirmation,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: SelectableText(
|
||||
url,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: Text(context.l10n.common_cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.l10n.chat_open),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (shouldOpen != true) return;
|
||||
|
||||
// Launch URL
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.chat_couldNotOpenLink(url)),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.chat_invalidLink),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+199
-1
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Отваряне на връзката?",
|
||||
"chat_openLinkConfirmation": "Искате ли да отворите тази връзка в браузъра си?",
|
||||
"chat_open": "Отвори",
|
||||
"chat_couldNotOpenLink": "Не можа да се отвори връзката: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Невалиден формат на връзката",
|
||||
"map_title": "Карта на възлите",
|
||||
"map_noNodesWithLocation": "Няма възли с данни за местоположение.",
|
||||
"map_nodesNeedGps": "Възлагат се възлозите да споделят техните GPS координати,\nза да се появят на картата.",
|
||||
@@ -821,6 +833,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"login_failedMessage": "Входът не беше успешен. Или паролата е грешна, или повторителят е недостъпен.",
|
||||
"common_reload": "Презареди",
|
||||
"common_clear": "Изчисти",
|
||||
"path_currentPath": "Текущ път: {path}",
|
||||
@@ -1335,5 +1348,190 @@
|
||||
"listFilter_repeaters": "Повторители",
|
||||
"listFilter_roomServers": "Сървъри на стая",
|
||||
"listFilter_unreadOnly": "Само непрочетените",
|
||||
"listFilter_newGroup": "Нова група"
|
||||
"listFilter_newGroup": "Нова група",
|
||||
"@neighbors_errorLoading": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_neighboursSubtitle": "Преглед на съседни възли с нулев скок.",
|
||||
"repeater_neighbours": "Съседи",
|
||||
"neighbors_receivedData": "Получени данни за съседи",
|
||||
"neighbors_requestTimedOut": "Съседите поискат изтичане на време.",
|
||||
"neighbors_errorLoading": "Грешка при зареждане на съседи: {error}",
|
||||
"neighbors_repeatersNeighbours": "Повторители Съседи",
|
||||
"neighbors_noData": "Няма налични данни за съседи.",
|
||||
"channels_createPrivateChannel": "Създай Частен Канал",
|
||||
"channels_joinPrivateChannel": "Присъедини се към Частен Канал",
|
||||
"channels_createPrivateChannelDesc": "Защитено с таен ключ.",
|
||||
"channels_joinPrivateChannelDesc": "Ръчно въведете таен ключ.",
|
||||
"channels_joinPublicChannel": "Присъединете се към Публичния канал",
|
||||
"channels_joinPublicChannelDesc": "Всеки може да се присъедини към този канал.",
|
||||
"channels_joinHashtagChannel": "Присъедини се към Хаштаг Канал",
|
||||
"channels_joinHashtagChannelDesc": "Всеки може да се присъедини към хаштаговите канали.",
|
||||
"channels_scanQrCode": "Сканирайте QR код",
|
||||
"channels_scanQrCodeComingSoon": "Ще излезе скоро",
|
||||
"channels_enterHashtag": "Въведете хаштаг",
|
||||
"channels_hashtagHint": "напр. #отбор",
|
||||
"@neighbors_unknownContact": {
|
||||
"placeholders": {
|
||||
"pubkey": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@neighbors_heardAgo": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"neighbors_heardAgo": "Слушано преди {time}.",
|
||||
"neighbors_unknownContact": "Неизвестна {pubkey}",
|
||||
"settings_locationIntervalSec": "Интервал за GPS (Секунди)",
|
||||
"settings_locationGPSEnable": "Активиране на GPS",
|
||||
"settings_locationGPSEnableSubtitle": "Активирайте автоматичното актуализиране на местоположението чрез GPS.",
|
||||
"settings_locationIntervalInvalid": "Интервалът трябва да бъде поне 60 секунди и по-малко от 86400 секунди.",
|
||||
"room_management": "Управление на сървъра за стая",
|
||||
"contacts_manageRoom": "Управление на сървър за стая",
|
||||
"@community_joinConfirmation": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_created": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_joined": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_qrInstructions": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_alreadyMemberMessage": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleteConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleted": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_forCommunity": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_title": "Общност",
|
||||
"common_ok": "Добре",
|
||||
"community_createDesc": "Създайте нова общност и я споделете чрез QR код.",
|
||||
"community_create": "Създай общност",
|
||||
"community_joinTitle": "Присъедини се към общността",
|
||||
"community_joinConfirmation": "Искате ли да се присъедините към общността \"{name}\"?",
|
||||
"community_scanQr": "Сканирайте QR кода на общността",
|
||||
"community_scanInstructions": "Насочете камерата към QR код на общността",
|
||||
"community_showQr": "Покажи QR код",
|
||||
"community_publicChannel": "Обществено общност",
|
||||
"community_hashtagChannel": "Хаштаг на общността",
|
||||
"community_name": "Име на общността",
|
||||
"community_enterName": "Въведете име на общността",
|
||||
"community_created": "Общността \"{name}\" е създадена",
|
||||
"community_joined": "Присъединено общност \"{name}\"",
|
||||
"community_qrTitle": "Споделяне в общността",
|
||||
"community_join": "Присъедини се",
|
||||
"community_qrInstructions": "Сканирайте този QR код, за да се присъедините към {name}.",
|
||||
"community_hashtagPrivacyHint": "Хаштаг каналите на общността са достъпни само за членове на общността",
|
||||
"community_invalidQrCode": "Невалиден QR код на общността",
|
||||
"community_alreadyMember": "Вече съм член",
|
||||
"community_alreadyMemberMessage": "Вие вече сте член на \"{name}\".",
|
||||
"community_addPublicChannel": "Добави публичен общностен канал",
|
||||
"community_addPublicChannelHint": "Автоматично добавете публичния канал за тази общност.",
|
||||
"community_noCommunities": "Няма присъединени общности още.",
|
||||
"community_scanOrCreate": "Сканирайте QR код или създайте общност, за да започнете.",
|
||||
"community_manageCommunities": "Управление на общности",
|
||||
"community_delete": "Напусни общността",
|
||||
"community_deleteConfirm": "Напускате \"{name}\"?",
|
||||
"community_deleteChannelsWarning": "Това ще изтрие също {count} канал(а) и техните съобщения.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Остави общността \"{name}\"",
|
||||
"community_addHashtagChannel": "Добави общностен хаштаг",
|
||||
"community_addHashtagChannelDesc": "Добавете хаштаг канал за тази общност",
|
||||
"community_selectCommunity": "Изберете общност",
|
||||
"community_regularHashtag": "Обикновен хаштаг",
|
||||
"community_regularHashtagDesc": "Общ хаштаг (всеки може да се присъедини)",
|
||||
"community_communityHashtag": "Общностен хаштаг",
|
||||
"community_communityHashtagDesc": "Само за членове на общността",
|
||||
"community_forCommunity": "За {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerateSecretConfirm": "Регенерация на секретния ключ за \"{name}\"? Всички членове ще трябва да сканират новия QR код, за да продължат комуникацията.",
|
||||
"community_secretRegenerated": "Секретно презареждане за \"{name}\"",
|
||||
"community_regenerateSecret": "Регенерейрай секрет",
|
||||
"community_regenerate": "Регенерация",
|
||||
"community_updateSecret": "Актуализирай тайна",
|
||||
"community_scanToUpdateSecret": "Сканьорвайте новия QR код, за да актуализирате секрета за \"{name}\"",
|
||||
"community_secretUpdated": "Секретно обновено за \"{name}\""
|
||||
}
|
||||
|
||||
+330
-132
@@ -69,7 +69,7 @@
|
||||
},
|
||||
"scanner_stop": "Stopp",
|
||||
"scanner_scan": "Scannen",
|
||||
"device_quickSwitch": "Schneller Umschalten",
|
||||
"device_quickSwitch": "Schnelles Umschalten",
|
||||
"device_meshcore": "MeshCore",
|
||||
"settings_title": "Einstellungen",
|
||||
"settings_deviceInfo": "Geräteinformationen",
|
||||
@@ -78,7 +78,7 @@
|
||||
"settings_nodeSettings": "Knoten-Einstellungen",
|
||||
"settings_nodeName": "Knotenname",
|
||||
"settings_nodeNameNotSet": "Nicht festgelegt",
|
||||
"settings_nodeNameHint": "Gib den Knotenamen ein",
|
||||
"settings_nodeNameHint": "Gebe den Knotenamen ein",
|
||||
"settings_nodeNameUpdated": "Name aktualisiert",
|
||||
"settings_radioSettings": "Funk Einstellungen",
|
||||
"settings_radioSettingsSubtitle": "Frequenz, Leistung, Verbreitungsfaktor",
|
||||
@@ -90,17 +90,17 @@
|
||||
"settings_locationInvalid": "Ungültige Breiten- oder Längengrade.",
|
||||
"settings_latitude": "Breitengrad",
|
||||
"settings_longitude": "Längengrad",
|
||||
"settings_privacyMode": "Privatschutzzustand",
|
||||
"settings_privacyModeSubtitle": "Verstecken Sie Name/Ort in Anzeigen",
|
||||
"settings_privacyModeToggle": "Aktivieren Sie den Datenschutzzustand, um Ihren Namen und Ihre Standortdaten in Anzeigen zu verbergen.",
|
||||
"settings_privacyModeEnabled": "Privatschutzzustand aktiviert",
|
||||
"settings_privacyMode": "Privatsphäreeinstellung",
|
||||
"settings_privacyModeSubtitle": "Verstecken Sie Name/Ort in Ankündigungen",
|
||||
"settings_privacyModeToggle": "Aktivieren Sie die Privatsphäreeinstellung, um Ihren Namen und Ihre Standortdaten in Ankündigungen zu verbergen.",
|
||||
"settings_privacyModeEnabled": "Datenschutzmodus aktiviert",
|
||||
"settings_privacyModeDisabled": "Datenschutzmodus deaktiviert",
|
||||
"settings_actions": "Aktionen",
|
||||
"settings_sendAdvertisement": "Senden Sie Anzeige",
|
||||
"settings_sendAdvertisementSubtitle": "Sendungsstatus jetzt",
|
||||
"settings_advertisementSent": "Anzeige gesendet",
|
||||
"settings_syncTime": "Synchronisierungszeit",
|
||||
"settings_syncTimeSubtitle": "Stelle die Gerätewielfalt auf die Uhrzeit des Telefons ein",
|
||||
"settings_sendAdvertisement": "Sende eine Ankündigung",
|
||||
"settings_sendAdvertisementSubtitle": "Sende Ankündigung",
|
||||
"settings_advertisementSent": "Ankündigung gesendet",
|
||||
"settings_syncTime": "Zeitsynchronisierung",
|
||||
"settings_syncTimeSubtitle": "Stelle die Gerätezeit auf die Uhrzeit des Telefons ein",
|
||||
"settings_timeSynchronized": "Zeit synchronisiert",
|
||||
"settings_refreshContacts": "Kontakte aktualisieren",
|
||||
"settings_refreshContactsSubtitle": "Kontakte-Liste vom Gerät neu laden",
|
||||
@@ -128,8 +128,8 @@
|
||||
"settings_infoStatus": "Status",
|
||||
"settings_infoBattery": "Akku",
|
||||
"settings_infoPublicKey": "Öffentlicher Schlüssel",
|
||||
"settings_infoContactsCount": "Kontakte Anzahl",
|
||||
"settings_infoChannelCount": "Kanalanzahl",
|
||||
"settings_infoContactsCount": "Anzahl Kontakte",
|
||||
"settings_infoChannelCount": "Anzahl Kanäle",
|
||||
"settings_presets": "Voreinstellungen",
|
||||
"settings_preset915Mhz": "915 MHz",
|
||||
"settings_preset868Mhz": "868 MHz",
|
||||
@@ -139,11 +139,11 @@
|
||||
"settings_frequencyInvalid": "Ungültige Frequenz (300-2500 MHz)",
|
||||
"settings_bandwidth": "Bandbreite",
|
||||
"settings_spreadingFactor": "Verteilungsfaktor",
|
||||
"settings_codingRate": "Programmierpauschale",
|
||||
"settings_codingRate": "Kodierungsrate",
|
||||
"settings_txPower": "TX-Leistung (dBm)",
|
||||
"settings_txPowerHelper": "0 - 22",
|
||||
"settings_txPowerInvalid": "Ungültige TX-Leistung (0-22 dBm)",
|
||||
"settings_longRange": "Langreich",
|
||||
"settings_longRange": "Grosse Reichweite",
|
||||
"settings_fastSpeed": "Schnelle Geschwindigkeit",
|
||||
"settings_error": "Fehler: {message}",
|
||||
"@settings_error": {
|
||||
@@ -157,7 +157,7 @@
|
||||
"appSettings_appearance": "Aussehen",
|
||||
"appSettings_theme": "Theme",
|
||||
"appSettings_themeSystem": "Systemstandard",
|
||||
"appSettings_themeLight": "Helligkeit",
|
||||
"appSettings_themeLight": "Hell",
|
||||
"appSettings_themeDark": "Dunkel",
|
||||
"appSettings_language": "Sprache",
|
||||
"appSettings_languageSystem": "Systemstandard",
|
||||
@@ -176,19 +176,19 @@
|
||||
"appSettings_languageBg": "Български",
|
||||
"appSettings_notifications": "Benachrichtigungen",
|
||||
"appSettings_enableNotifications": "Benachrichtigungen aktivieren",
|
||||
"appSettings_enableNotificationsSubtitle": "Erhalte Benachrichtigungen für Nachrichten und Anzeigen",
|
||||
"appSettings_enableNotificationsSubtitle": "Erhalte Benachrichtigungen für Nachrichten und Ankündigungen",
|
||||
"appSettings_notificationPermissionDenied": "Erlaubnis zur Benachrichtigung verweigert",
|
||||
"appSettings_notificationsEnabled": "Benachrichtigungen aktiviert",
|
||||
"appSettings_notificationsDisabled": "Benachrichtigungen deaktiviert",
|
||||
"appSettings_messageNotifications": "Nachrichtenbenachrichtigungen",
|
||||
"appSettings_messageNotificationsSubtitle": "Zeige Benachrichtigung beim Empfang neuer Nachrichten",
|
||||
"appSettings_channelMessageNotifications": "Kanal-Nachrichten-Benachrichtigungen",
|
||||
"appSettings_messageNotifications": "Direktnachrichten Benachrichtigungen",
|
||||
"appSettings_messageNotificationsSubtitle": "Zeige Benachrichtigung beim Empfang neuer Direktnachrichten",
|
||||
"appSettings_channelMessageNotifications": "Kanalnachrichten Benachrichtigungen",
|
||||
"appSettings_channelMessageNotificationsSubtitle": "Zeige Benachrichtigung beim Empfangen von Kanalnachrichten",
|
||||
"appSettings_advertisementNotifications": "Werbeanzeigenbenachrichtigungen",
|
||||
"appSettings_advertisementNotifications": "Ankündigungsbenachrichtigungen",
|
||||
"appSettings_advertisementNotificationsSubtitle": "Zeige Benachrichtigung, wenn neue Knoten entdeckt werden.",
|
||||
"appSettings_messaging": "Nachrichten",
|
||||
"appSettings_clearPathOnMaxRetry": "Klares Pfad bei Max Wiederholungsversuch",
|
||||
"appSettings_clearPathOnMaxRetrySubtitle": "Zurücksetzen des Kontaktpfads nach 5 fehlgeschlagenen Sendeverboten",
|
||||
"appSettings_clearPathOnMaxRetry": "Lösche Pfade bei Max Wiederholungsversuchen",
|
||||
"appSettings_clearPathOnMaxRetrySubtitle": "Zurücksetzen der Kontaktpfade nach 5 fehlgeschlagenen Sendeabbrüchen",
|
||||
"appSettings_pathsWillBeCleared": "Die Pfade werden nach 5 fehlgeschlagenen Versuchen gelöscht.",
|
||||
"appSettings_pathsWillNotBeCleared": "Die Pfade werden nicht automatisch gelöscht.",
|
||||
"appSettings_autoRouteRotation": "Automatische Routenrotation",
|
||||
@@ -226,10 +226,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_mapTimeFilter": "Kartent Zeitfilter",
|
||||
"appSettings_mapTimeFilter": "Karten Zeitfilter",
|
||||
"appSettings_showNodesDiscoveredWithin": "Zeige Knoten, die innerhalb von:",
|
||||
"appSettings_allTime": "Alle Zeit",
|
||||
"appSettings_lastHour": "Letzter Stunde",
|
||||
"appSettings_allTime": "Ganzer Zeitverlauf",
|
||||
"appSettings_lastHour": "Letzte Stunde",
|
||||
"appSettings_last6Hours": "Letzte 6 Stunden",
|
||||
"appSettings_last24Hours": "Letzte 24 Stunden",
|
||||
"appSettings_lastWeek": "Letzte Woche",
|
||||
@@ -252,13 +252,13 @@
|
||||
"appSettings_appDebugLoggingEnabled": "App-Debug-Protokollierung aktiviert",
|
||||
"appSettings_appDebugLoggingDisabled": "App-Debug-Protokollierung deaktiviert",
|
||||
"contacts_title": "Kontakte",
|
||||
"contacts_noContacts": "No Contacts noch",
|
||||
"contacts_contactsWillAppear": "Kontakte werden angezeigt, wenn Geräte Werbung machen.",
|
||||
"contacts_noContacts": "Noch keine Kontakte vorhanden.",
|
||||
"contacts_contactsWillAppear": "Kontakte werden angezeigt, wenn Geräte eine Ankündigung machen.",
|
||||
"contacts_searchContacts": "Suche Kontakte...",
|
||||
"contacts_noUnreadContacts": "Keine ungeklärten Kontakte",
|
||||
"contacts_noUnreadContacts": "Keine ungesehene Kontakte",
|
||||
"contacts_noContactsFound": "Keine Kontakte oder Gruppen gefunden.",
|
||||
"contacts_deleteContact": "Löschen Sie Kontakt",
|
||||
"contacts_removeConfirm": "Entfernen {contactName} aus den Kontakten?",
|
||||
"contacts_deleteContact": "Lösche den Kontakt",
|
||||
"contacts_removeConfirm": "{contactName} aus den Kontakten entfernen?",
|
||||
"@contacts_removeConfirm": {
|
||||
"placeholders": {
|
||||
"contactName": {
|
||||
@@ -266,12 +266,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts_manageRepeater": "Wiederholung verwalten",
|
||||
"contacts_manageRepeater": "Wiederholungen verwalten",
|
||||
"contacts_roomLogin": "Raum-Login",
|
||||
"contacts_openChat": "Öffnen Sie Chat",
|
||||
"contacts_editGroup": "Gruppen bearbeiten",
|
||||
"contacts_openChat": "Öffne Chat",
|
||||
"contacts_editGroup": "Gruppe bearbeiten",
|
||||
"contacts_deleteGroup": "Löschen Gruppe",
|
||||
"contacts_deleteGroupConfirm": "Löschen Sie \"{groupName}\"?",
|
||||
"contacts_deleteGroupConfirm": "Löschen von \"{groupName}\"?",
|
||||
"@contacts_deleteGroupConfirm": {
|
||||
"placeholders": {
|
||||
"groupName": {
|
||||
@@ -293,8 +293,8 @@
|
||||
"contacts_filterContacts": "Filtert Kontakte...",
|
||||
"contacts_noContactsMatchFilter": "Keine Kontakte passen zu Ihrem Filter",
|
||||
"contacts_noMembers": "Keine Mitglieder",
|
||||
"contacts_lastSeenNow": "Letztes Ansehen jetzt",
|
||||
"contacts_lastSeenMinsAgo": "Letzte Sichtung {minutes} Minuten her.",
|
||||
"contacts_lastSeenNow": "gerade gesehen",
|
||||
"contacts_lastSeenMinsAgo": "Letzte Sichtung vor {minutes} Minuten.",
|
||||
"@contacts_lastSeenMinsAgo": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
@@ -303,7 +303,7 @@
|
||||
}
|
||||
},
|
||||
"contacts_lastSeenHourAgo": "Letzte Sichtung vor 1 Stunde.",
|
||||
"contacts_lastSeenHoursAgo": "Letzte Aktivität vor {hours} Stunden.",
|
||||
"contacts_lastSeenHoursAgo": "Letzte Sichtung vor {hours} Stunden.",
|
||||
"@contacts_lastSeenHoursAgo": {
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
@@ -339,8 +339,8 @@
|
||||
"channels_publicChannel": "Öffentlicher Kanal",
|
||||
"channels_privateChannel": "Privater Kanal",
|
||||
"channels_editChannel": "Kanal bearbeiten",
|
||||
"channels_deleteChannel": "Löschen Sie Kanal",
|
||||
"channels_deleteChannelConfirm": "Löschen \"{name}\"? Dies kann nicht rückgängig gemacht werden.",
|
||||
"channels_deleteChannel": "Lösche den Kanal",
|
||||
"channels_deleteChannelConfirm": "Löschen von \"{name}\"? Dies kann nicht rückgängig gemacht werden.",
|
||||
"@channels_deleteChannelConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
@@ -373,7 +373,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"channels_editChannelTitle": "Bearbeiteten Kanal {index}",
|
||||
"channels_editChannelTitle": "Bearbeiteter Kanal {index}",
|
||||
"@channels_editChannelTitle": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
@@ -392,10 +392,10 @@
|
||||
},
|
||||
"channels_publicChannelAdded": "Öffentlicher Kanal hinzugefügt",
|
||||
"channels_sortBy": "Sortiere nach",
|
||||
"channels_sortManual": "Manuelle",
|
||||
"channels_sortManual": "Manuell",
|
||||
"channels_sortAZ": "A bis Z",
|
||||
"channels_sortLatestMessages": "Letzte Nachrichten",
|
||||
"channels_sortUnread": "Unlescht",
|
||||
"channels_sortUnread": "Ungelesen",
|
||||
"chat_noMessages": "Noch keine Nachrichten.",
|
||||
"chat_sendMessageToStart": "Eine Nachricht senden, um anzufangen.",
|
||||
"chat_originalMessageNotFound": "Originalmeldung nicht gefunden",
|
||||
@@ -407,7 +407,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_replyTo": "Antworten Sie {name}",
|
||||
"chat_replyTo": "Antwort an {name}",
|
||||
"@chat_replyTo": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
@@ -436,7 +436,7 @@
|
||||
"chat_messageCopied": "Nachricht kopiert",
|
||||
"chat_messageDeleted": "Nachricht gelöscht",
|
||||
"chat_retryingMessage": "Versuche es erneut.",
|
||||
"chat_retryCount": "Versuchen {current}/{max}",
|
||||
"chat_retryCount": "Versuche {current}/{max}",
|
||||
"@chat_retryCount": {
|
||||
"placeholders": {
|
||||
"current": {
|
||||
@@ -457,22 +457,22 @@
|
||||
"emojiCategoryObjects": "Objekte",
|
||||
"gifPicker_title": "Wähle ein GIF",
|
||||
"gifPicker_searchHint": "Suche nach GIFs...",
|
||||
"gifPicker_poweredBy": "Angetrieben von GIPHY",
|
||||
"gifPicker_poweredBy": "Bereitgestellt von GIPHY",
|
||||
"gifPicker_noGifsFound": "Keine GIFs gefunden",
|
||||
"gifPicker_failedLoad": "GIF-Dateien konnten nicht geladen werden.",
|
||||
"gifPicker_failedLoad": "GIF-Datei konnten nicht geladen werden.",
|
||||
"gifPicker_failedSearch": "Suche nach GIFs fehlgeschlagen",
|
||||
"gifPicker_noInternet": "Keine Internetverbindung",
|
||||
"debugLog_appTitle": "App-Debug-Protokoll",
|
||||
"debugLog_bleTitle": "BLE-Debug-Protokoll",
|
||||
"debugLog_copyLog": "Kopieren Sie Protokoll",
|
||||
"debugLog_clearLog": "Log löschen",
|
||||
"debugLog_copyLog": "Kopieren des Protokolls",
|
||||
"debugLog_clearLog": "Protokoll löschen",
|
||||
"debugLog_copied": "Debug-Protokoll kopiert",
|
||||
"debugLog_bleCopied": "BLE-Protokoll kopiert",
|
||||
"debugLog_noEntries": "No Debug-Protokolle noch verfügbar",
|
||||
"debugLog_enableInSettings": "Aktivieren Sie das App-Debug-Logging in den Einstellungen",
|
||||
"debugLog_frames": "Rahmen",
|
||||
"debugLog_rawLogRx": "Roh-Log-RX",
|
||||
"debugLog_noBleActivity": "No BLE-Aktivität bisher",
|
||||
"debugLog_noBleActivity": "Bisher keine BLE-Aktivität",
|
||||
"debugFrame_length": "Rahmenlänge: {count} Bytes",
|
||||
"@debugFrame_length": {
|
||||
"placeholders": {
|
||||
@@ -539,12 +539,12 @@
|
||||
"chat_pathManagement": "Pfadverwaltung",
|
||||
"chat_routingMode": "Routenmodus",
|
||||
"chat_autoUseSavedPath": "Automatisch (gespeicherten Pfad verwenden)",
|
||||
"chat_forceFloodMode": "Zwangsgelände-Modus erzwingen",
|
||||
"chat_forceFloodMode": "Flut-Modus erzwingen",
|
||||
"chat_recentAckPaths": "Aktuelle ACK-Pfade (tasten, um zu verwenden):",
|
||||
"chat_pathHistoryFull": "Die Pfadhistorie ist voll. Entferne Einträge, um neue hinzuzufügen.",
|
||||
"chat_hopSingular": "Springe",
|
||||
"chat_hopPlural": "Hops",
|
||||
"chat_hopsCount": "{count} {count, plural, =1{Hop} other{Hops}}",
|
||||
"chat_hopSingular": "Sprung",
|
||||
"chat_hopPlural": "Sprünge",
|
||||
"chat_hopsCount": "{count} {count, plural, =1{Sprung} other{Sprünge}}",
|
||||
"@chat_hopsCount": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
@@ -552,17 +552,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_successes": "Erfolgreiche",
|
||||
"chat_successes": "Erfolgreich",
|
||||
"chat_removePath": "Pfad entfernen",
|
||||
"chat_noPathHistoryYet": "Noe eine Pfadhistorie vorhanden.\nSende eine Nachricht, um Pfade zu entdecken.",
|
||||
"chat_noPathHistoryYet": "Keine eine Pfadhistorie vorhanden.\nSende eine Nachricht, um Pfade zu entdecken.",
|
||||
"chat_pathActions": "Pfadaktionen:",
|
||||
"chat_setCustomPath": "Lege benutzerdefinierten Pfad fest",
|
||||
"chat_setCustomPathSubtitle": "Manuelle Routenpfad festlegen",
|
||||
"chat_clearPath": "Klares Pfad",
|
||||
"chat_clearPathSubtitle": "Zwinge bei nächster Sendung eine erneute Entdeckung durch.",
|
||||
"chat_pathCleared": "Pfad freigelegt. Nächste Nachricht wird Route neu entdecken.",
|
||||
"chat_setCustomPathSubtitle": "Manuellen Routenpfad festlegen",
|
||||
"chat_clearPath": "Pfad zurücksetzen",
|
||||
"chat_clearPathSubtitle": "Setze Pfad zurück, erkenne neuen Pfad bei nächster Sendung.",
|
||||
"chat_pathCleared": "Pfad zurückgesetzt. Nächste Nachricht wird Route neu entdecken.",
|
||||
"chat_floodModeSubtitle": "Verwende den Routingschalter in der App-Leiste",
|
||||
"chat_floodModeEnabled": "Flutmodus aktiviert. Über den Routing-Icon in der App-Leiste wieder aktivieren.",
|
||||
"chat_floodModeEnabled": "Flutmodus aktiviert.",
|
||||
"chat_fullPath": "Vollständiger Pfad",
|
||||
"chat_pathDetailsNotAvailable": "Die Pfaddetails sind noch nicht verfügbar. Versuchen Sie, eine Nachricht zu senden, um zu aktualisieren.",
|
||||
"chat_pathSetHops": "Pfad gesetzt: {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}",
|
||||
@@ -576,15 +576,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_pathSavedLocally": "Gespeichert lokal. Mit Verbinden zum Synchronisieren.",
|
||||
"chat_pathSavedLocally": "Lokal Gespeichert. Bitte Verbinden zum Synchronisieren.",
|
||||
"chat_pathDeviceConfirmed": "Gerät bestätigt.",
|
||||
"chat_pathDeviceNotConfirmed": "Gerät noch nicht bestätigt.",
|
||||
"chat_type": "Gib ein",
|
||||
"chat_type": "Gebe ein",
|
||||
"chat_path": "Pfad",
|
||||
"chat_publicKey": "Öffentlicher Schlüssel",
|
||||
"chat_compressOutgoingMessages": "Komprimieren ausgehende Nachrichten",
|
||||
"chat_floodForced": "Überschwemmung (erzwungen)",
|
||||
"chat_directForced": "Direkt (gezwungen)",
|
||||
"chat_compressOutgoingMessages": "Komprimieren ausgehender Nachrichten",
|
||||
"chat_floodForced": "Geflutet (erzwungen)",
|
||||
"chat_directForced": "Direkt (erzwungen)",
|
||||
"chat_hopsForced": "{count} Sprünge (erzwungen)",
|
||||
"@chat_hopsForced": {
|
||||
"placeholders": {
|
||||
@@ -593,10 +593,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_floodAuto": "Überschwemmung (automatisch)",
|
||||
"chat_floodAuto": "Geflutet (automatisch)",
|
||||
"chat_direct": "Direkt",
|
||||
"chat_poiShared": "Gemeinsamer POI",
|
||||
"chat_unread": "Unlescht: {count}",
|
||||
"chat_poiShared": "Geteilter POI",
|
||||
"chat_unread": "Ungelesen: {count}",
|
||||
"@chat_unread": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
@@ -604,9 +604,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"map_title": "Knotenkarte",
|
||||
"chat_openLink": "Link öffnen?",
|
||||
"chat_openLinkConfirmation": "Möchten Sie diesen Link in Ihrem Browser öffnen?",
|
||||
"chat_open": "Öffnen",
|
||||
"chat_couldNotOpenLink": "Link konnte nicht geöffnet werden: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Ungültiges Link-Format",
|
||||
"map_title": "Karte",
|
||||
"map_noNodesWithLocation": "Keine Knoten mit Standortdaten",
|
||||
"map_nodesNeedGps": "Knoten müssen ihre GPS-Koordinaten\nteilen,\num auf der Karte\nerscheinen.",
|
||||
"map_nodesNeedGps": "Knoten müssen ihre GPS-Koordinaten teilen,\num auf der Karte zu erscheinen.",
|
||||
"map_nodesCount": "Knoten: {count}",
|
||||
"@map_nodesCount": {
|
||||
"placeholders": {
|
||||
@@ -623,24 +635,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"map_chat": "Chat",
|
||||
"map_repeater": "Wiederholung",
|
||||
"map_chat": "Benutzer",
|
||||
"map_repeater": "Repeater",
|
||||
"map_room": "Raum",
|
||||
"map_sensor": "Sensor",
|
||||
"map_pinDm": "Sperren (DM)",
|
||||
"map_pinPrivate": "Privat-Pin",
|
||||
"map_pinPublic": "Öffentliche Taste (PIN)",
|
||||
"map_pinDm": "Pin (Kontakt)",
|
||||
"map_pinPrivate": "Pin (Channel)",
|
||||
"map_pinPublic": "Pin (Public)",
|
||||
"map_lastSeen": "Letzte Sichtung",
|
||||
"map_disconnectConfirm": "Sind Sie sicher, dass Sie sich von diesem Gerät trennen möchten?",
|
||||
"map_from": "Von",
|
||||
"map_source": "Quelle",
|
||||
"map_flags": "Flaggen",
|
||||
"map_shareMarkerHere": "Teilen Sie hier das Marker.",
|
||||
"map_pinLabel": "Kennzeichnungslabel",
|
||||
"map_flags": "Flags",
|
||||
"map_shareMarkerHere": "Teilen Sie den Marker hier.",
|
||||
"map_pinLabel": "Pin Name",
|
||||
"map_label": "Label",
|
||||
"map_pointOfInterest": "Punkt von Interesse",
|
||||
"map_sendToContact": "Senden an Kontakt",
|
||||
"map_sendToChannel": "Senden Sie Kanal",
|
||||
"map_sendToChannel": "Senden an Kanal",
|
||||
"map_noChannelsAvailable": "Keine Kanäle verfügbar",
|
||||
"map_publicLocationShare": "Öffentliche Standortfreigabe",
|
||||
"map_publicLocationShareConfirm": "Sie werden kurz darauf einen Ort in {channelLabel} teilen. Dieser Kanal ist öffentlich und jeder mit dem PSK kann ihn sehen.",
|
||||
@@ -652,25 +664,25 @@
|
||||
}
|
||||
},
|
||||
"map_connectToShareMarkers": "Verbinde ein Gerät, um Marker zu teilen",
|
||||
"map_filterNodes": "Filter Knoten",
|
||||
"map_filterNodes": "Knotenfilter",
|
||||
"map_nodeTypes": "Knotentypen",
|
||||
"map_chatNodes": "Chat-Knoten",
|
||||
"map_repeaters": "Wiederholer",
|
||||
"map_repeaters": "Repeater",
|
||||
"map_otherNodes": "Andere Knoten",
|
||||
"map_keyPrefix": "Schlüsselpräfix",
|
||||
"map_filterByKeyPrefix": "Filter nach Schlüsselpräfix",
|
||||
"map_publicKeyPrefix": "Öffentlicher Schlüsselpräfix",
|
||||
"map_publicKeyPrefix": "Schlüsselpräfix",
|
||||
"map_markers": "Marker",
|
||||
"map_showSharedMarkers": "Zeige gemeinsam genutzte Marker",
|
||||
"map_lastSeenTime": "Letzte Sichtung",
|
||||
"map_sharedPin": "Gemeinsames Passwort",
|
||||
"map_joinRoom": "Beitreten Sie dem Raum",
|
||||
"map_manageRepeater": "Wiederholung verwalten",
|
||||
"map_manageRepeater": "Repeater verwalten",
|
||||
"mapCache_title": "Offline-Karten-Cache",
|
||||
"mapCache_selectAreaFirst": "Wählen Sie zuerst einen Bereich zum Zwischenspeichern aus.",
|
||||
"mapCache_noTilesToDownload": "Keine Tiles für diese Region zum Herunterladen verfügbar.",
|
||||
"mapCache_downloadTilesTitle": "Herunterladen von Tiles",
|
||||
"mapCache_downloadTilesPrompt": "Laden {count} Tiles für den Offline-Bereich herunter?",
|
||||
"mapCache_noTilesToDownload": "Keine Kacheln für diese Region zum Herunterladen verfügbar.",
|
||||
"mapCache_downloadTilesTitle": "Herunterladen von Kacheln",
|
||||
"mapCache_downloadTilesPrompt": "Laden {count} Kacheln für den Offline-Bereich herunter?",
|
||||
"@mapCache_downloadTilesPrompt": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
@@ -679,7 +691,7 @@
|
||||
}
|
||||
},
|
||||
"mapCache_downloadAction": "Herunterladen",
|
||||
"mapCache_cachedTiles": "Zwischengespeicherte {count} Fliesen",
|
||||
"mapCache_cachedTiles": "Zwischengespeicherte {count} Kacheln",
|
||||
"@mapCache_cachedTiles": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
@@ -687,7 +699,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mapCache_cachedTilesWithFailed": "Zwischengespeicherte {downloaded} Tiles ({failed} fehlgeschlagen)",
|
||||
"mapCache_cachedTilesWithFailed": "Zwischengespeicherte {downloaded} Kacheln ({failed} fehlgeschlagen)",
|
||||
"@mapCache_cachedTilesWithFailed": {
|
||||
"placeholders": {
|
||||
"downloaded": {
|
||||
@@ -698,7 +710,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mapCache_clearOfflineCacheTitle": "Leeren Offline-Cache",
|
||||
"mapCache_clearOfflineCacheTitle": "Leere Offline-Cache",
|
||||
"mapCache_clearOfflineCachePrompt": "Alle zwischengespeicherten Kartenraster entfernen?",
|
||||
"mapCache_offlineCacheCleared": "Offline-Cache gelöscht",
|
||||
"mapCache_noAreaSelected": "Kein Bereich ausgewählt",
|
||||
@@ -724,7 +736,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mapCache_downloadTilesButton": "Herunterladen von Tiles",
|
||||
"mapCache_downloadTilesButton": "Herunterladen von Kacheln",
|
||||
"mapCache_clearCacheButton": "Cache leeren",
|
||||
"mapCache_failedDownloads": "Fehlgeschlagene Downloads: {count}",
|
||||
"@mapCache_failedDownloads": {
|
||||
@@ -785,10 +797,10 @@
|
||||
"time_month": "Monat",
|
||||
"time_months": "Monate",
|
||||
"time_minutes": "Minuten",
|
||||
"time_allTime": "Alle Zeit",
|
||||
"time_allTime": "Ganzer Zeitraum",
|
||||
"dialog_disconnect": "Trennen",
|
||||
"dialog_disconnectConfirm": "Sind Sie sicher, dass Sie sich von diesem Gerät trennen möchten?",
|
||||
"login_repeaterLogin": "Wiederholungseingang anmelden",
|
||||
"login_repeaterLogin": "Beim Repeater anmelden",
|
||||
"login_roomLogin": "Raum-Login",
|
||||
"login_password": "Passwort",
|
||||
"login_enterPassword": "Passwort eingeben",
|
||||
@@ -799,7 +811,7 @@
|
||||
"login_routing": "Routen",
|
||||
"login_routingMode": "Routenmodus",
|
||||
"login_autoUseSavedPath": "Automatisch (gespeicherten Pfad verwenden)",
|
||||
"login_forceFloodMode": "Zwangsgelände-Modus erzwingen",
|
||||
"login_forceFloodMode": "Flut-Modus erzwingen",
|
||||
"login_managePaths": "Pfadverwaltung",
|
||||
"login_login": "Anmelden",
|
||||
"login_attempt": "Versuche {current}/{max}",
|
||||
@@ -821,9 +833,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"login_failedMessage": "Anmeldung fehlgeschlagen. Entweder ist das Passwort falsch oder der Repeater ist nicht erreichbar.",
|
||||
"common_reload": "Neu laden",
|
||||
"common_clear": "Löschen",
|
||||
"path_currentPath": "Aktiger Pfad: {path}",
|
||||
"path_currentPath": "Aktiver Pfad: {path}",
|
||||
"@path_currentPath": {
|
||||
"placeholders": {
|
||||
"path": {
|
||||
@@ -839,9 +852,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"path_enterCustomPath": "Gib Pfad an",
|
||||
"path_enterCustomPath": "Gebe Pfad ein",
|
||||
"path_currentPathLabel": "Aktueller Pfad",
|
||||
"path_hexPrefixInstructions": "Gib für jeden Hopfen 2-stellige Hex-Präfixe ein, getrennt durch Kommas.",
|
||||
"path_hexPrefixInstructions": "Gebe für jeden Hopfen 2-stellige Hex-Präfixe ein, getrennt durch Kommas.",
|
||||
"path_hexPrefixExample": "Beispiel: A1,F2,3C (jeder Knoten verwendet den ersten Byte seines öffentlichen Schlüssels)",
|
||||
"path_labelHexPrefixes": "Pfad (Hex-Präfixe)",
|
||||
"path_helperMaxHops": "Max 64 Sprünge. Jede Präfixe ist 2 Hexadezimalzeichen (1 Byte)",
|
||||
@@ -858,7 +871,7 @@
|
||||
},
|
||||
"path_tooLong": "Pfad zu lang. Maximal 64 Hops erlaubt.",
|
||||
"path_setPath": "Pfad festlegen",
|
||||
"repeater_management": "Wiederholungselement-Verwaltung",
|
||||
"repeater_management": "Repeater-Verwaltung",
|
||||
"repeater_managementTools": "Verwaltungs-Tools",
|
||||
"repeater_status": "Status",
|
||||
"repeater_statusSubtitle": "Status, Statistiken und Nachbarn anzeigen",
|
||||
@@ -867,11 +880,11 @@
|
||||
"repeater_cli": "CLI",
|
||||
"repeater_cliSubtitle": "Sende Befehle an den Repeater",
|
||||
"repeater_settings": "Einstellungen",
|
||||
"repeater_settingsSubtitle": "Wiederholungsparameter konfigurieren",
|
||||
"repeater_statusTitle": "Wiederholungszustand",
|
||||
"repeater_settingsSubtitle": "Repeater-parameter konfigurieren",
|
||||
"repeater_statusTitle": "Repeaterstatus",
|
||||
"repeater_routingMode": "Routenmodus",
|
||||
"repeater_autoUseSavedPath": "Automatisch (gespeicherten Pfad verwenden)",
|
||||
"repeater_forceFloodMode": "Zwangsgelände-Modus erzwingen",
|
||||
"repeater_forceFloodMode": "Flut-Modus erzwingen",
|
||||
"repeater_pathManagement": "Pfadverwaltung",
|
||||
"repeater_refresh": "Aktualisieren",
|
||||
"repeater_statusRequestTimeout": "Statusanfrage zeitweise fehlgeschlagen.",
|
||||
@@ -963,9 +976,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_settingsTitle": "Wiederholungseinstellungen",
|
||||
"repeater_settingsTitle": "Repeater Einstellungen",
|
||||
"repeater_basicSettings": "Grundlegende Einstellungen",
|
||||
"repeater_repeaterName": "Wiederholungseintrag",
|
||||
"repeater_repeaterName": "Repeater Name",
|
||||
"repeater_repeaterNameHelper": "Anzeigename für diesen Repeater",
|
||||
"repeater_adminPassword": "Admin-Passwort",
|
||||
"repeater_adminPasswordHelper": "Vollzugriffspasswort",
|
||||
@@ -978,7 +991,7 @@
|
||||
"repeater_txPowerHelper": "1-30 dBm",
|
||||
"repeater_bandwidth": "Bandbreite",
|
||||
"repeater_spreadingFactor": "Verteilungsfaktor",
|
||||
"repeater_codingRate": "Programmierpauschale",
|
||||
"repeater_codingRate": "Kodierungsrate",
|
||||
"repeater_locationSettings": "Standort Einstellungen",
|
||||
"repeater_latitude": "Breitengrad",
|
||||
"repeater_latitudeHelper": "Dezimalgrad (z.B. 37,7749)",
|
||||
@@ -989,10 +1002,10 @@
|
||||
"repeater_packetForwardingSubtitle": "Aktivieren Sie den Repeater, um Pakete weiterzuleiten.",
|
||||
"repeater_guestAccess": "Gastzugriff",
|
||||
"repeater_guestAccessSubtitle": "Gast-Zugriff mit beschränkten Rechten zulassen",
|
||||
"repeater_privacyMode": "Privatschutzzustand",
|
||||
"repeater_privacyModeSubtitle": "Verstecken Sie Name/Ort in Anzeigen",
|
||||
"repeater_advertisementSettings": "Werbe Einstellungen",
|
||||
"repeater_localAdvertInterval": "Lokaler Werbeintervall",
|
||||
"repeater_privacyMode": "Privatsphäreeinstellung",
|
||||
"repeater_privacyModeSubtitle": "Verstecken Sie Name/Ort in Ankündigungen",
|
||||
"repeater_advertisementSettings": "Ankündigungseinstellungen",
|
||||
"repeater_localAdvertInterval": "Intervall der lokalen Ankündigungen",
|
||||
"repeater_localAdvertIntervalMinutes": "{minutes} Minuten",
|
||||
"@repeater_localAdvertIntervalMinutes": {
|
||||
"placeholders": {
|
||||
@@ -1001,7 +1014,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_floodAdvertInterval": "Überschwemmungsanzeige-Intervall",
|
||||
"repeater_floodAdvertInterval": "Intervall der gefluteten Ankündigungen",
|
||||
"repeater_floodAdvertIntervalHours": "{hours} Stunden",
|
||||
"@repeater_floodAdvertIntervalHours": {
|
||||
"placeholders": {
|
||||
@@ -1010,7 +1023,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_encryptedAdvertInterval": "Verschlüsselte Werbeintervall",
|
||||
"repeater_encryptedAdvertInterval": "Intervall der verschlüsselten Ankündigung",
|
||||
"repeater_dangerZone": "Gefahrenzone",
|
||||
"repeater_rebootRepeater": "Neustart Repeater",
|
||||
"repeater_rebootRepeaterSubtitle": "Wiederholen Sie das Repeater-Gerät.",
|
||||
@@ -1050,12 +1063,12 @@
|
||||
},
|
||||
"repeater_refreshBasicSettings": "Grundlegende Einstellungen aktualisieren",
|
||||
"repeater_refreshRadioSettings": "Radio-Einstellungen aktualisieren",
|
||||
"repeater_refreshTxPower": "Batterie-Strom aktualisieren",
|
||||
"repeater_refreshTxPower": "Sendeleistung aktualisieren",
|
||||
"repeater_refreshLocationSettings": "Aktualisieren Sie die Standort Einstellungen",
|
||||
"repeater_refreshPacketForwarding": "Aktualisieren Paketweiterleitung",
|
||||
"repeater_refreshGuestAccess": "Aktualisieren Sie den Gastzugriff",
|
||||
"repeater_refreshPrivacyMode": "Wiederherstellen des Datenschutzzustands",
|
||||
"repeater_refreshAdvertisementSettings": "Aktualisieren Sie die Werbe Einstellungen",
|
||||
"repeater_refreshAdvertisementSettings": "Aktualisieren Sie die Ankündigungseinstellungen",
|
||||
"repeater_refreshed": "{label} wurde aktualisiert",
|
||||
"@repeater_refreshed": {
|
||||
"placeholders": {
|
||||
@@ -1072,10 +1085,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_cliTitle": "Wiederholung CLI",
|
||||
"repeater_cliTitle": "Repeater CLI",
|
||||
"repeater_debugNextCommand": "Fehlersuche Nächster Befehl",
|
||||
"repeater_commandHelp": "Hilfe",
|
||||
"repeater_clearHistory": "Löschung der Historie",
|
||||
"repeater_clearHistory": "Löschen der Historie",
|
||||
"repeater_noCommandsSent": "Noch keine Befehle gesendet.",
|
||||
"repeater_typeCommandOrUseQuick": "Geben Sie einen Befehl unten ein oder verwenden Sie Schnellbefehle",
|
||||
"repeater_enterCommandHint": "Geben Sie den Befehl ein...",
|
||||
@@ -1096,37 +1109,37 @@
|
||||
"repeater_cliQuickGetTx": "Erhalte TX",
|
||||
"repeater_cliQuickNeighbors": "Nachbarn",
|
||||
"repeater_cliQuickVersion": "Version",
|
||||
"repeater_cliQuickAdvertise": "Werben",
|
||||
"repeater_cliQuickAdvertise": "Ankündigungen",
|
||||
"repeater_cliQuickClock": "Uhr",
|
||||
"repeater_cliHelpAdvert": "Sendet ein Werbepaket",
|
||||
"repeater_cliHelpAdvert": "Sendet eine Ankündigung",
|
||||
"repeater_cliHelpReboot": "Startet das Gerät neu. (Beachten Sie, dass es möglicherweise zu einer 'Timeout'-Situation kommt, was normal ist.)",
|
||||
"repeater_cliHelpClock": "Zeigt die aktuelle Uhrzeit pro Gerät an.",
|
||||
"repeater_cliHelpPassword": "Legt ein neues Administrator-Passwort für das Gerät fest.",
|
||||
"repeater_cliHelpVersion": "Zeigt die Geräteversion und das Datum des Firmware-Builds an.",
|
||||
"repeater_cliHelpClearStats": "Setzt verschiedene Statistikkalkulate auf Null zurück.",
|
||||
"repeater_cliHelpClearStats": "Setzt verschiedene Statistikberechnungen auf Null zurück.",
|
||||
"repeater_cliHelpSetAf": "Legt den Luftzeitfaktor fest.",
|
||||
"repeater_cliHelpSetTx": "Legt die LoRa-Übertragungspower in dBm (bezogen auf 1 Watt) fest. (Neustart erforderlich, um die Änderungen anzuwenden)",
|
||||
"repeater_cliHelpSetRepeat": "Aktiviert oder deaktiviert die Repeater-Rolle für diesen Knoten.",
|
||||
"repeater_cliHelpSetAllowReadOnly": "(Raumspeicher) Wenn 'an', dann wird die Anmeldung mit einem leeren Passwort erlaubt sein, aber kann nicht in den Raum geschickt werden. (nur lesen möglich).",
|
||||
"repeater_cliHelpSetAllowReadOnly": "(Raumspeicher) Wenn 'an', dann wird die Anmeldung mit einem leeren Passwort erlaubt sein, aber es kann nicht in den Raum gesendet werden. (nur lesen möglich).",
|
||||
"repeater_cliHelpSetFloodMax": "Legt die maximale Anzahl an Hops für Pakete der eingehenden Flut (wenn >= max, wird das Paket nicht weitergeleitet)",
|
||||
"repeater_cliHelpSetIntThresh": "Legt den Interferenzeniveau (in dB) fest. Der Standardwert ist 14. Auf 0 setzen, um die Erkennung von Kanalinterferenzen zu deaktivieren.",
|
||||
"repeater_cliHelpSetAgcResetInterval": "Legt das Intervall für das Zurücksetzen des Auto Gain Controllers fest. Auf 0 setzen, um die Funktion zu deaktivieren.",
|
||||
"repeater_cliHelpSetMultiAcks": "Aktiviert oder deaktiviert die Funktion 'Doppel-ACKs'.",
|
||||
"repeater_cliHelpSetAdvertInterval": "Legt das Timer-Intervall in Minuten fest, um ein lokales (ohne-Weiterleitung) Werbe-Paket zu senden. Auf 0 setzen, um die Funktion zu deaktivieren.",
|
||||
"repeater_cliHelpSetFloodAdvertInterval": "Legt das Timer-Intervall in Stunden für den Versand eines Flut-Werbungspakets fest. Auf 0 setzen, um es zu deaktivieren.",
|
||||
"repeater_cliHelpSetAdvertInterval": "Legt das Timer-Intervall in Minuten fest, um ein lokales (ohne-Weiterleitung) Ankündigungspaket zu senden. Auf 0 setzen, um die Funktion zu deaktivieren.",
|
||||
"repeater_cliHelpSetFloodAdvertInterval": "Legt das Timer-Intervall in Stunden für den Versand eines Flut-Ankündigungspacket fest. Auf 0 setzen, um es zu deaktivieren.",
|
||||
"repeater_cliHelpSetGuestPassword": "Legt/aktualisiert das Gastpasswort fest. (für Repeater können Gast-Logins die \"Get Stats\"-Anfrage senden)",
|
||||
"repeater_cliHelpSetName": "Legt den Anzeigenamen fest.",
|
||||
"repeater_cliHelpSetLat": "Legt die Breitengrad-Angabe der Werbekarte fest. (dezimale Grad)",
|
||||
"repeater_cliHelpSetLon": "Legt die Längengrade der Werbe-Map fest. (dezimale Grad)",
|
||||
"repeater_cliHelpSetLat": "Legt die Breitengrad der Ankündigung fest. (dezimale Grad)",
|
||||
"repeater_cliHelpSetLon": "Legt die Längengrade der Ankündigung fest. (dezimale Grad)",
|
||||
"repeater_cliHelpSetRadio": "Legt komplett neue Radio-Parameter fest und speichert diese als Präferenzen. Benötigt einen \"Reboot\"-Befehl, um sie anzuwenden.",
|
||||
"repeater_cliHelpSetRxDelay": "Sets (experimentell) als Basis (muss > 1 sein für den Effekt) zur Anwendung einer leichten Verzögerung bei empfangenen Paketen, basierend auf Signalstärke/Punktzahl. Auf 0 setzen, um die Funktion zu deaktivieren.",
|
||||
"repeater_cliHelpSetTxDelay": "Legt einen Faktor fest, der mit der Zeit bei voller Zuluft für ein Flood-Mode-Paket und mit einem zufälligen Slot-System multipliziert wird, um dessen Weiterleitung zu verzögern (um Kollisionen zu vermeiden).",
|
||||
"repeater_cliHelpSetDirectTxDelay": "Ähnlich wie txdelay, aber zum Anwenden einer zufälligen Verzögerung bei der Weiterleitung von Direktmodus-Paketen.",
|
||||
"repeater_cliHelpSetBridgeEnabled": "Brücke aktivieren/deaktivieren.",
|
||||
"repeater_cliHelpSetBridgeDelay": "Setze Verzögerung vor erneuter Übertragung von Paketen.",
|
||||
"repeater_cliHelpSetBridgeSource": "Wählen Sie, ob die Brücke empfangene oder gesendete Pakete erneut übertragen soll.",
|
||||
"repeater_cliHelpSetBridgeSource": "Wählen Sie, ob über die Brücke empfangene oder gesendete Pakete erneut übertragen soll.",
|
||||
"repeater_cliHelpSetBridgeBaud": "Setze die serielle Link-Baudrate für RS232-Brücken.",
|
||||
"repeater_cliHelpSetBridgeSecret": "Richte das Espnow-Brücken-Geheimnis ein.",
|
||||
"repeater_cliHelpSetBridgeSecret": "Richte das Brückenpassword ein.",
|
||||
"repeater_cliHelpSetAdcMultiplier": "Legt einen benutzerdefinierten Faktor zur Anpassung der gemeldeten Batteriewirkspannung fest (nur auf ausgewählten Boards unterstützt).",
|
||||
"repeater_cliHelpTempRadio": "Legt vorübergehende Funkparameter für die angegebene Anzahl von Minuten fest und kehrt anschließend zu den ursprünglichen Funkparametern zurück (wird nicht in den Einstellungen gespeichert).",
|
||||
"repeater_cliHelpSetPerm": "Ändert die ACL. Entfernt das passende Eintragen (durch Pubkey-Präfix), wenn \"permissions\" auf 0 steht. Fügt ein neues Eintragen hinzu, wenn die Pubkey-Hex-Länge vollständig ist und nicht bereits in der ACL vorhanden ist. Aktualisiert das Eintragen anhand des übereinstimmenden Pubkey-Präfix. Berechtigungsbits variieren je nach Firmware-Rolle, aber die unteren 2 Bits sind: 0 (Gast), 1 (Nur Lesen), 2 (Lesen/Schreiben), 3 (Admin)",
|
||||
@@ -1134,9 +1147,9 @@
|
||||
"repeater_cliHelpLogStart": "Beginnt die Paketprotokollierung in das Dateisystem.",
|
||||
"repeater_cliHelpLogStop": "Stoppt das Paketprotokollieren in das Dateisystem.",
|
||||
"repeater_cliHelpLogErase": "Löscht die Paketprotokolle aus dem Dateisystem.",
|
||||
"repeater_cliHelpNeighbors": "Zeigt eine Liste anderer Repeater-Knoten an, die über Zero-Hop-Werbung gehört wurden. Jede Zeile ist id-prefix-hex:timestamp:snr-times-4",
|
||||
"repeater_cliHelpNeighbors": "Zeigt eine Liste anderer Repeater-Knoten an, die über Zero-Hop-Ankündigung gehört wurden. Jede Zeile ist id-prefix-hex:timestamp:snr-times-4",
|
||||
"repeater_cliHelpNeighborRemove": "Entfernt das erste übereinstimmende Element (über Pubkey-Präfix (hex)) aus der Liste der Nachbarn.",
|
||||
"repeater_cliHelpRegion": "(Serien nur) Listet alle definierten Regionen und aktuelle Hochwassermissungen auf.",
|
||||
"repeater_cliHelpRegion": "Listet alle definierten Regionen auf.",
|
||||
"repeater_cliHelpRegionLoad": "Hinweis: Dies ist ein spezieller Mehrbefehl-Aufruf. Jeder nachfolgende Befehl ist ein Regionsname (eingedruckt mit Leerzeichen zur Angabe der übergeordneten Hierarchie, mit mindestens einem Leerzeichen). Beendet durch das Senden einer Leerzeile/des Befehls.",
|
||||
"repeater_cliHelpRegionGet": "Sucht die Region mit dem gegebenen Namenspräfix (oder \"\\\" für den globalen Scope) und antwortet mit \"-> region-name (parent-name) 'F'\".",
|
||||
"repeater_cliHelpRegionPut": "Fügt eine Region-Definition mit dem angegebenen Namen hinzu oder aktualisiert diese.",
|
||||
@@ -1228,12 +1241,12 @@
|
||||
"channelPath_title": "Paketpfad",
|
||||
"channelPath_viewMap": "Karte anzeigen",
|
||||
"channelPath_otherObservedPaths": "Sonstige beobachtete Pfade",
|
||||
"channelPath_repeaterHops": "Wiederholungs-Sprünge",
|
||||
"channelPath_repeaterHops": "Repeater-Sprünge",
|
||||
"channelPath_noHopDetails": "Die Detailangaben für dieses Paket sind nicht verfügbar.",
|
||||
"channelPath_messageDetails": "Nachrichtsdetails",
|
||||
"channelPath_senderLabel": "Sender",
|
||||
"channelPath_timeLabel": "Zeit",
|
||||
"channelPath_repeatsLabel": "Wiederholung",
|
||||
"channelPath_repeatsLabel": "Wiederholungen",
|
||||
"channelPath_pathLabel": "Pfad {index}",
|
||||
"channelPath_observedLabel": "Beobachtet",
|
||||
"channelPath_observedPathTitle": "Beobachteter Pfad {index} • {hops}",
|
||||
@@ -1271,7 +1284,7 @@
|
||||
}
|
||||
},
|
||||
"channelPath_unknownPath": "Unbekannt",
|
||||
"channelPath_floodPath": "Überschwemmung",
|
||||
"channelPath_floodPath": "Geflutet",
|
||||
"channelPath_directPath": "Direkt",
|
||||
"channelPath_observedZeroOf": "0 von {total} Sprüngen",
|
||||
"@channelPath_observedZeroOf": {
|
||||
@@ -1327,13 +1340,198 @@
|
||||
"listFilter_tooltip": "Filteren und sortieren",
|
||||
"listFilter_sortBy": "Sortiere nach",
|
||||
"listFilter_latestMessages": "Letzte Nachrichten",
|
||||
"listFilter_heardRecently": "Hörte kürzlich",
|
||||
"listFilter_heardRecently": "Kürzlich gehört",
|
||||
"listFilter_az": "A-Z",
|
||||
"listFilter_filters": "Filtere",
|
||||
"listFilter_all": "Alle",
|
||||
"listFilter_users": "Benutzer",
|
||||
"listFilter_repeaters": "Wiederholer",
|
||||
"listFilter_repeaters": "Repeater",
|
||||
"listFilter_roomServers": "Raumserver",
|
||||
"listFilter_unreadOnly": "Nur nicht gelesen",
|
||||
"listFilter_newGroup": "Neue Gruppe"
|
||||
"listFilter_newGroup": "Neue Gruppe",
|
||||
"@neighbors_errorLoading": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_neighbours": "Nachbarn",
|
||||
"repeater_neighboursSubtitle": "Anzahl der Hop-Nachbarn anzeigen.",
|
||||
"neighbors_receivedData": "Empfangene Nachbarendaten",
|
||||
"neighbors_requestTimedOut": "Nachbarn melden zeitweise Ausfall.",
|
||||
"neighbors_errorLoading": "Fehler beim Laden der Nachbarn: {error}",
|
||||
"neighbors_repeatersNeighbours": "Wiederholer Nachbarn",
|
||||
"neighbors_noData": "Keine Nachbardaten verfügbar.",
|
||||
"channels_joinPrivateChannel": "Treten Sie einem privaten Kanal bei",
|
||||
"channels_joinPrivateChannelDesc": "Manuelle Eingabe eines geheimen Schlüssels.",
|
||||
"channels_createPrivateChannel": "Erstelle einen privaten Kanal",
|
||||
"channels_createPrivateChannelDesc": "Verschlüsselt mit einem geheimen Schlüssel.",
|
||||
"channels_joinPublicChannel": "Tritt dem öffentlichen Kanal bei",
|
||||
"channels_joinPublicChannelDesc": "Jeder kann diesem Kanal beitreten.",
|
||||
"channels_joinHashtagChannel": "Treten Sie einem Hashtag-Kanal bei",
|
||||
"channels_joinHashtagChannelDesc": "Jeder kann sich bei Hashtag-Kanälen beteiligen.",
|
||||
"channels_scanQrCode": "Scannen Sie einen QR-Code",
|
||||
"channels_scanQrCodeComingSoon": "Bald verfügbar",
|
||||
"channels_enterHashtag": "Gib Hashtag ein",
|
||||
"channels_hashtagHint": "z.B. #team",
|
||||
"@neighbors_unknownContact": {
|
||||
"placeholders": {
|
||||
"pubkey": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@neighbors_heardAgo": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"neighbors_heardAgo": "Hörte: {time} vor her.",
|
||||
"neighbors_unknownContact": "Unbekannte {pubkey}",
|
||||
"settings_locationGPSEnable": "GPS aktivieren",
|
||||
"settings_locationGPSEnableSubtitle": "Aktiviert GPS zur automatischen Aktualisierung des Standorts.",
|
||||
"settings_locationIntervalSec": "Intervall für GPS (Sekunden)",
|
||||
"settings_locationIntervalInvalid": "Das Intervall muss mindestens 60 Sekunden und weniger als 86400 Sekunden betragen.",
|
||||
"contacts_manageRoom": "Raum-Server verwalten",
|
||||
"room_management": "Raum-Server-Verwaltung",
|
||||
"@community_joinConfirmation": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_created": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_joined": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_qrInstructions": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_alreadyMemberMessage": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleteConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleted": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_forCommunity": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"common_ok": "OK",
|
||||
"community_create": "Erstelle Community",
|
||||
"community_createDesc": "Erstelle eine neue Community und teile sie über den QR-Code.",
|
||||
"community_join": "Beitreten",
|
||||
"community_joinTitle": "Tritt der Community bei",
|
||||
"community_joinConfirmation": "Möchten Sie sich der Community \"{name}\" anschließen?",
|
||||
"community_scanQr": "Scannen Sie die Community QR-Code",
|
||||
"community_scanInstructions": "Richten Sie die Kamera auf einen Community-QR-Code.",
|
||||
"community_showQr": "Zeige QR-Code",
|
||||
"community_publicChannel": "Community Öffentlich",
|
||||
"community_enterName": "Bitte Community-Name eingeben",
|
||||
"community_title": "Community",
|
||||
"community_created": "Community \"{name}\" wurde erstellt",
|
||||
"community_joined": "Community \"{name}\" beigetreten",
|
||||
"community_qrTitle": "Teile Community",
|
||||
"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_hashtagChannel": "Community Hashtag",
|
||||
"community_name": "Community Name",
|
||||
"community_invalidQrCode": "Ungültiger Community-QR-Code",
|
||||
"community_alreadyMember": "Bereits registriert",
|
||||
"community_alreadyMemberMessage": "Sie sind bereits Mitglied von \"{name}\".",
|
||||
"community_addPublicChannel": "Füge einen öffentlichen Community-Kanal hinzu",
|
||||
"community_addPublicChannelHint": "Automatisch den öffentlichen Kanal für diese Community hinzufügen",
|
||||
"community_noCommunities": "Noch keiner Community beigetreten",
|
||||
"community_scanOrCreate": "Scannen Sie einen QR-Code oder eine Community erstellen, um loszulegen.",
|
||||
"community_manageCommunities": "Verwalten von Communities",
|
||||
"community_delete": "Verlasse Community",
|
||||
"community_deleteConfirm": "\"{name}\" verlassen?",
|
||||
"community_deleteChannelsWarning": "Dies löscht auch {count} Kanal/Kanäle und deren Nachrichten.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Community \"{name}\" verlassen",
|
||||
"community_addHashtagChannel": "Füge einen Community-Hashtag hinzu",
|
||||
"community_addHashtagChannelDesc": "Füge einen Hashtag-Kanal für diese Community hinzu",
|
||||
"community_selectCommunity": "Wählen Sie Community",
|
||||
"community_regularHashtag": "Regulärer Hashtag",
|
||||
"community_regularHashtagDesc": "Öffentliches Hashtag (jeder kann teilnehmen)",
|
||||
"community_communityHashtagDesc": "Nur für Mitglieder der Community",
|
||||
"community_forCommunity": "Für {name}",
|
||||
"community_communityHashtag": "Community Hashtag",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerate": "Neu generieren",
|
||||
"community_secretRegenerated": "Geheime Wiederherstellung für \"{name}\" erfolgreich",
|
||||
"community_regenerateSecretConfirm": "Nehmen Sie den geheimen Schlüssel für \"{name}\" neu auf? Alle Mitglieder müssen den neuen QR-Code scannen, um die Kommunikation fortzusetzen.",
|
||||
"community_regenerateSecret": "Neu generieren Sie das Geheimnis",
|
||||
"community_secretUpdated": "Geheime für \"{name}\" aktualisiert",
|
||||
"community_scanToUpdateSecret": "Scannen Sie den neuen QR-Code, um das Geheimnis für \"{name}\" zu aktualisieren.",
|
||||
"community_updateSecret": "Aktualisieren Sie das Geheimnis"
|
||||
}
|
||||
|
||||
+171
-3
@@ -8,6 +8,7 @@
|
||||
"nav_map": "Map",
|
||||
|
||||
"common_cancel": "Cancel",
|
||||
"common_ok": "OK",
|
||||
"common_connect": "Connect",
|
||||
"common_unknownDevice": "Unknown Device",
|
||||
"common_save": "Save",
|
||||
@@ -83,9 +84,13 @@
|
||||
"settings_radioSettingsUpdated": "Radio settings updated",
|
||||
"settings_location": "Location",
|
||||
"settings_locationSubtitle": "GPS coordinates",
|
||||
"settings_locationUpdated": "Location updated",
|
||||
"settings_locationUpdated": "Location and GPS settings updated",
|
||||
"settings_locationBothRequired": "Enter both latitude and longitude.",
|
||||
"settings_locationInvalid": "Invalid latitude or longitude.",
|
||||
"settings_locationGPSEnable": "GPS Enable",
|
||||
"settings_locationGPSEnableSubtitle": "Enables GPS to automatically update location.",
|
||||
"settings_locationIntervalSec": "Interval for GPS (Seconds)",
|
||||
"settings_locationIntervalInvalid": "Interval must be at least 60 seconds, and less than 86400 seconds.",
|
||||
"settings_latitude": "Latitude",
|
||||
"settings_longitude": "Longitude",
|
||||
"settings_privacyMode": "Privacy Mode",
|
||||
@@ -253,7 +258,8 @@
|
||||
}
|
||||
},
|
||||
"contacts_manageRepeater": "Manage Repeater",
|
||||
"contacts_roomLogin": "Room Login",
|
||||
"contacts_manageRoom": "Manage Room Server",
|
||||
"contacts_roomLogin": "Room Server Login",
|
||||
"contacts_openChat": "Open Chat",
|
||||
"contacts_editGroup": "Edit Group",
|
||||
"contacts_deleteGroup": "Delete Group",
|
||||
@@ -361,6 +367,18 @@
|
||||
"channels_sortAZ": "A-Z",
|
||||
"channels_sortLatestMessages": "Latest messages",
|
||||
"channels_sortUnread": "Unread",
|
||||
"channels_createPrivateChannel": "Create a Private Channel",
|
||||
"channels_createPrivateChannelDesc": "Secured with a secret key.",
|
||||
"channels_joinPrivateChannel": "Join a Private Channel",
|
||||
"channels_joinPrivateChannelDesc": "Manually enter a secret key.",
|
||||
"channels_joinPublicChannel": "Join the Public Channel",
|
||||
"channels_joinPublicChannelDesc": "Anyone can join this channel.",
|
||||
"channels_joinHashtagChannel": "Join a Hashtag Channel",
|
||||
"channels_joinHashtagChannelDesc": "Anyone can join hashtag channels.",
|
||||
"channels_scanQrCode": "Scan a QR Code",
|
||||
"channels_scanQrCodeComingSoon": "Coming soon",
|
||||
"channels_enterHashtag": "Enter hashtag",
|
||||
"channels_hashtagHint": "e.g. #team",
|
||||
|
||||
"chat_noMessages": "No messages yet",
|
||||
"chat_sendMessageToStart": "Send a message to get started",
|
||||
@@ -532,6 +550,16 @@
|
||||
"count": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Open Link?",
|
||||
"chat_openLinkConfirmation": "Do you want to open this link in your browser?",
|
||||
"chat_open": "Open",
|
||||
"chat_couldNotOpenLink": "Could not open link: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Invalid link format",
|
||||
|
||||
"map_title": "Node Map",
|
||||
"map_noNodesWithLocation": "No nodes with location data",
|
||||
@@ -685,7 +713,7 @@
|
||||
"dialog_disconnectConfirm": "Are you sure you want to disconnect from this device?",
|
||||
|
||||
"login_repeaterLogin": "Repeater Login",
|
||||
"login_roomLogin": "Room Login",
|
||||
"login_roomLogin": "Room Server Login",
|
||||
"login_password": "Password",
|
||||
"login_enterPassword": "Enter password",
|
||||
"login_savePassword": "Save password",
|
||||
@@ -711,6 +739,8 @@
|
||||
"error": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"login_failedMessage": "Login failed. Either the password is incorrect or the repeater is unreachable.",
|
||||
|
||||
|
||||
"common_reload": "Reload",
|
||||
"common_clear": "Clear",
|
||||
@@ -746,6 +776,7 @@
|
||||
"path_setPath": "Set Path",
|
||||
|
||||
"repeater_management": "Repeater Management",
|
||||
"room_management": "Room Server Management",
|
||||
"repeater_managementTools": "Management Tools",
|
||||
"repeater_status": "Status",
|
||||
"repeater_statusSubtitle": "View repeater status, stats, and neighbors",
|
||||
@@ -753,6 +784,8 @@
|
||||
"repeater_telemetrySubtitle": "View telemetry of sensors and system stats",
|
||||
"repeater_cli": "CLI",
|
||||
"repeater_cliSubtitle": "Send commands to the repeater",
|
||||
"repeater_neighbours": "Neighbors",
|
||||
"repeater_neighboursSubtitle": "View zero hop neighbors.",
|
||||
"repeater_settings": "Settings",
|
||||
"repeater_settingsSubtitle": "Configure repeater parameters",
|
||||
|
||||
@@ -1055,6 +1088,29 @@
|
||||
"fahrenheit": {"type": "String"}
|
||||
}
|
||||
},
|
||||
|
||||
"neighbors_receivedData": "Received Neighbours Data",
|
||||
"neighbors_requestTimedOut": "Neighbours request timed out.",
|
||||
"neighbors_errorLoading": "Error loading neighbors: {error}",
|
||||
"@neighbors_errorLoading": {
|
||||
"placeholders": {
|
||||
"error": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"neighbors_repeatersNeighbours": "Repeaters Neighbours",
|
||||
"neighbors_noData": "No neighbours data available.",
|
||||
"neighbors_unknownContact": "Unknown {pubkey}",
|
||||
"@neighbors_unknownContact": {
|
||||
"placeholders": {
|
||||
"pubkey": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"neighbors_heardAgo": "Heard: {time} ago",
|
||||
"@neighbors_heardAgo": {
|
||||
"placeholders": {
|
||||
"time": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"channelPath_title": "Packet Path",
|
||||
"channelPath_viewMap": "View map",
|
||||
"channelPath_otherObservedPaths": "Other Observed Paths",
|
||||
@@ -1129,6 +1185,118 @@
|
||||
},
|
||||
"channelPath_noHopDetailsAvailable": "No hop details available for this packet.",
|
||||
"channelPath_unknownRepeater": "Unknown Repeater",
|
||||
|
||||
"community_title": "Community",
|
||||
"community_create": "Create Community",
|
||||
"community_createDesc": "Create a new community and share via QR code.",
|
||||
"community_join": "Join",
|
||||
"community_joinTitle": "Join Community",
|
||||
"community_joinConfirmation": "Do you want to join the community \"{name}\"?",
|
||||
"@community_joinConfirmation": {
|
||||
"placeholders": {
|
||||
"name": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"community_scanQr": "Scan Community QR",
|
||||
"community_scanInstructions": "Point the camera at a community QR code",
|
||||
"community_showQr": "Show QR Code",
|
||||
"community_publicChannel": "Community Public",
|
||||
"community_hashtagChannel": "Community Hashtag",
|
||||
"community_name": "Community Name",
|
||||
"community_enterName": "Enter community name",
|
||||
"community_created": "Community \"{name}\" created",
|
||||
"@community_created": {
|
||||
"placeholders": {
|
||||
"name": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"community_joined": "Joined community \"{name}\"",
|
||||
"@community_joined": {
|
||||
"placeholders": {
|
||||
"name": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"community_qrTitle": "Share Community",
|
||||
"community_qrInstructions": "Scan this QR code to join \"{name}\"",
|
||||
"@community_qrInstructions": {
|
||||
"placeholders": {
|
||||
"name": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"community_hashtagPrivacyHint": "Community hashtag channels are only joinable by members of the community",
|
||||
"community_invalidQrCode": "Invalid community QR code",
|
||||
"community_alreadyMember": "Already a Member",
|
||||
"community_alreadyMemberMessage": "You are already a member of \"{name}\".",
|
||||
"@community_alreadyMemberMessage": {
|
||||
"placeholders": {
|
||||
"name": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"community_addPublicChannel": "Add Community Public Channel",
|
||||
"community_addPublicChannelHint": "Automatically add the public channel for this community",
|
||||
"community_noCommunities": "No communities joined yet",
|
||||
"community_scanOrCreate": "Scan a QR code or create a community to get started",
|
||||
"community_manageCommunities": "Manage Communities",
|
||||
"community_delete": "Leave Community",
|
||||
"community_deleteConfirm": "Leave \"{name}\"?",
|
||||
"@community_deleteConfirm": {
|
||||
"placeholders": {
|
||||
"name": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"community_deleteChannelsWarning": "This will also delete {count} channel(s) and their messages.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Left community \"{name}\"",
|
||||
"@community_deleted": {
|
||||
"placeholders": {
|
||||
"name": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"community_regenerateSecret": "Regenerate Secret",
|
||||
"community_regenerateSecretConfirm": "Regenerate the secret key for \"{name}\"? All members will need to scan the new QR code to continue communicating.",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"community_regenerate": "Regenerate",
|
||||
"community_secretRegenerated": "Secret regenerated for \"{name}\"",
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"community_updateSecret": "Update Secret",
|
||||
"community_secretUpdated": "Secret updated for \"{name}\"",
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"community_scanToUpdateSecret": "Scan the new QR code to update the secret for \"{name}\"",
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"community_addHashtagChannel": "Add Community Hashtag",
|
||||
"community_addHashtagChannelDesc": "Add a hashtag channel for this community",
|
||||
"community_selectCommunity": "Select Community",
|
||||
"community_regularHashtag": "Regular Hashtag",
|
||||
"community_regularHashtagDesc": "Public hashtag (anyone can join)",
|
||||
"community_communityHashtag": "Community Hashtag",
|
||||
"community_communityHashtagDesc": "Private to community members",
|
||||
"community_forCommunity": "For {name}",
|
||||
"@community_forCommunity": {
|
||||
"placeholders": {
|
||||
"name": {"type": "String"}
|
||||
}
|
||||
},
|
||||
|
||||
"listFilter_tooltip": "Filter and sort",
|
||||
"listFilter_sortBy": "Sort by",
|
||||
"listFilter_latestMessages": "Latest messages",
|
||||
|
||||
+199
-1
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "¿Abrir enlace?",
|
||||
"chat_openLinkConfirmation": "¿Quiere abrir este enlace en su navegador?",
|
||||
"chat_open": "Abrir",
|
||||
"chat_couldNotOpenLink": "No se pudo abrir el enlace: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Formato de enlace no válido",
|
||||
"map_title": "Mapa de Nodos",
|
||||
"map_noNodesWithLocation": "No hay nodos con datos de ubicación",
|
||||
"map_nodesNeedGps": "Los nodos necesitan compartir sus coordenadas GPS\npara aparecer en el mapa",
|
||||
@@ -821,6 +833,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"login_failedMessage": "Inicio fallido. La contraseña es incorrecta o el repetidor no está disponible.",
|
||||
"common_reload": "Recargar",
|
||||
"common_clear": "Borrar",
|
||||
"path_currentPath": "Ruta actual: {path}",
|
||||
@@ -1335,5 +1348,190 @@
|
||||
"listFilter_repeaters": "Repetidores",
|
||||
"listFilter_roomServers": "Servidores de la sala",
|
||||
"listFilter_unreadOnly": "Solo sin leer",
|
||||
"listFilter_newGroup": "Nuevo grupo"
|
||||
"listFilter_newGroup": "Nuevo grupo",
|
||||
"@neighbors_errorLoading": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_neighbours": "Vecinos",
|
||||
"repeater_neighboursSubtitle": "Ver vecinos de salto cero.",
|
||||
"neighbors_receivedData": "Recibidas Datos de Vecinos",
|
||||
"neighbors_requestTimedOut": "Los vecinos solicitan que se desconecte.",
|
||||
"neighbors_errorLoading": "Error al cargar vecinos: {error}",
|
||||
"neighbors_repeatersNeighbours": "Repetidores Vecinos",
|
||||
"neighbors_noData": "No hay datos de vecinos disponibles.",
|
||||
"channels_joinPrivateChannel": "Únete a un Canal Privado",
|
||||
"channels_createPrivateChannel": "Crear un Canal Privado",
|
||||
"channels_createPrivateChannelDesc": "Cifrado con una clave secreta.",
|
||||
"channels_joinPrivateChannelDesc": "Introducir manualmente una clave secreta.",
|
||||
"channels_joinPublicChannel": "Únete al Canal Público",
|
||||
"channels_joinPublicChannelDesc": "Cualquiera puede unirse a este canal.",
|
||||
"channels_joinHashtagChannel": "Únete a un Canal con Hashtag",
|
||||
"channels_joinHashtagChannelDesc": "Cualquiera puede unirse a los canales de hashtag.",
|
||||
"channels_scanQrCode": "Escanear un Código QR",
|
||||
"channels_scanQrCodeComingSoon": "Próximamente",
|
||||
"channels_enterHashtag": "Introducir hashtag",
|
||||
"channels_hashtagHint": "ej. #equipo",
|
||||
"@neighbors_unknownContact": {
|
||||
"placeholders": {
|
||||
"pubkey": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@neighbors_heardAgo": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"neighbors_unknownContact": "Clave pública desconocida {pubkey}",
|
||||
"neighbors_heardAgo": "Escuchado: {time} hace atrás",
|
||||
"settings_locationGPSEnable": "Habilitar GPS",
|
||||
"settings_locationGPSEnableSubtitle": "Habilita la actualización automática de la ubicación mediante GPS.",
|
||||
"settings_locationIntervalSec": "Intervalo para GPS (Segundos)",
|
||||
"settings_locationIntervalInvalid": "El intervalo debe ser de al menos 60 segundos y menor que 86400 segundos.",
|
||||
"contacts_manageRoom": "Gestionar Servidor de Habitación",
|
||||
"room_management": "Administración del Servidor de Habitación",
|
||||
"@community_joinConfirmation": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_created": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_joined": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_qrInstructions": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_alreadyMemberMessage": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleteConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleted": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_forCommunity": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_create": "Crear Comunidad",
|
||||
"community_createDesc": "Crear una nueva comunidad y compartir a través de código QR.",
|
||||
"community_title": "Comunidad",
|
||||
"community_join": "Únete",
|
||||
"community_joinTitle": "Únete a la comunidad",
|
||||
"community_joinConfirmation": "¿Quieres unirte a la comunidad \"{name}\"?",
|
||||
"community_scanQr": "Escanear Código QR de la Comunidad",
|
||||
"community_scanInstructions": "Apunte la cámara a un código QR de la comunidad",
|
||||
"community_showQr": "Mostrar Código QR",
|
||||
"community_publicChannel": "Comunidad Pública",
|
||||
"community_hashtagChannel": "Hashtag de la Comunidad",
|
||||
"community_name": "Nombre de la comunidad",
|
||||
"common_ok": "De acuerdo",
|
||||
"community_enterName": "Introducir nombre de comunidad",
|
||||
"community_created": "Comunidad \"{name}\" creada",
|
||||
"community_joined": "Se unió a la comunidad \"{name}\"",
|
||||
"community_qrTitle": "Compartir Comunidad",
|
||||
"community_qrInstructions": "Escanear este código QR para unirte a {name}",
|
||||
"community_hashtagPrivacyHint": "Los canales de hashtag de la comunidad solo son accesibles para los miembros de la comunidad",
|
||||
"community_invalidQrCode": "Código QR de comunidad no válido",
|
||||
"community_alreadyMember": "Ya eres Miembro",
|
||||
"community_alreadyMemberMessage": "Ya eres miembro de \"{name}\".",
|
||||
"community_addPublicChannel": "Añadir Canal Público de la Comunidad",
|
||||
"community_addPublicChannelHint": "Añade automáticamente el canal público para esta comunidad.",
|
||||
"community_noCommunities": "Aún no se han unido comunidades.",
|
||||
"community_scanOrCreate": "Escanear un código QR o crear una comunidad para comenzar",
|
||||
"community_manageCommunities": "Gestionar Comunidades",
|
||||
"community_delete": "Salir de la Comunidad",
|
||||
"community_deleteConfirm": "¿Salir de \"{name}\"?",
|
||||
"community_deleteChannelsWarning": "Esto también eliminará {count} canal(es) y sus mensajes.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Has salido de la comunidad \"{name}\"",
|
||||
"community_addHashtagChannel": "Añadir Hashtag de la Comunidad",
|
||||
"community_addHashtagChannelDesc": "Añadir un canal con hashtag para esta comunidad",
|
||||
"community_selectCommunity": "Seleccionar Comunidad",
|
||||
"community_regularHashtag": "Etiqueta de Hashtag Regular",
|
||||
"community_regularHashtagDesc": "Hashtag público (cualquiera puede unirse)",
|
||||
"community_communityHashtag": "Hashtag de la Comunidad",
|
||||
"community_communityHashtagDesc": "Exclusivo para miembros de la comunidad",
|
||||
"community_forCommunity": "Para {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerateSecret": "Regenerar Contraseña Secreta",
|
||||
"community_regenerateSecretConfirm": "Regenerar la clave secreta para \"{name}\"? Todos los miembros deberán escanear el nuevo código QR para seguir comunicándose.",
|
||||
"community_secretRegenerated": "Código secreto regenerado para \"{name}\"",
|
||||
"community_regenerate": "Regenerar",
|
||||
"community_secretUpdated": "Confidencialidad actualizada para \"{name}\"",
|
||||
"community_scanToUpdateSecret": "Escanear el nuevo código QR para actualizar el secreto de \"{name}\"",
|
||||
"community_updateSecret": "Actualizar Contraseña"
|
||||
}
|
||||
|
||||
+244
-46
@@ -91,12 +91,12 @@
|
||||
"settings_latitude": "Latitude",
|
||||
"settings_longitude": "Longitude",
|
||||
"settings_privacyMode": "Mode de confidentialité",
|
||||
"settings_privacyModeSubtitle": "Cacher le nom/l'emplacement dans les publicités",
|
||||
"settings_privacyModeToggle": "Activer le mode confidentialité pour masquer votre nom et votre localisation dans les publicités.",
|
||||
"settings_privacyModeSubtitle": "Cacher le nom/l'emplacement dans les annonces",
|
||||
"settings_privacyModeToggle": "Activer le mode confidentialité pour masquer votre nom et votre localisation dans les annonces.",
|
||||
"settings_privacyModeEnabled": "Mode de confidentialité activé",
|
||||
"settings_privacyModeDisabled": "Mode de confidentialité désactivé",
|
||||
"settings_actions": "Actions",
|
||||
"settings_sendAdvertisement": "Envoyer la publicité",
|
||||
"settings_sendAdvertisement": "S'annoncer",
|
||||
"settings_sendAdvertisementSubtitle": "Présence diffusée maintenant",
|
||||
"settings_advertisementSent": "Annonce envoyée",
|
||||
"settings_syncTime": "Temps de synchronisation",
|
||||
@@ -104,7 +104,7 @@
|
||||
"settings_timeSynchronized": "Synchronisation temporelle",
|
||||
"settings_refreshContacts": "Rafraîchir les Contacts",
|
||||
"settings_refreshContactsSubtitle": "Recharger la liste des contacts depuis l'appareil",
|
||||
"settings_rebootDevice": "Réinitialiser l'appareil",
|
||||
"settings_rebootDevice": "Redémarrer l'appareil",
|
||||
"settings_rebootDeviceSubtitle": "Redémarrer l'appareil MeshCore",
|
||||
"settings_rebootDeviceConfirm": "Êtes-vous sûr de vouloir redémarrer l'appareil ? Vous serez déconnecté.",
|
||||
"settings_debug": "Déboguer",
|
||||
@@ -176,7 +176,7 @@
|
||||
"appSettings_languageBg": "Български",
|
||||
"appSettings_notifications": "Notifications",
|
||||
"appSettings_enableNotifications": "Activer les Notifications",
|
||||
"appSettings_enableNotificationsSubtitle": "Recevoir des notifications pour les messages et les publicités",
|
||||
"appSettings_enableNotificationsSubtitle": "Recevoir des notifications pour les messages et les annonces",
|
||||
"appSettings_notificationPermissionDenied": "Permission de notification refusée",
|
||||
"appSettings_notificationsEnabled": "Notifications activées",
|
||||
"appSettings_notificationsDisabled": "Notifications désactivées",
|
||||
@@ -184,7 +184,7 @@
|
||||
"appSettings_messageNotificationsSubtitle": "Afficher une notification lors de la réception de nouveaux messages",
|
||||
"appSettings_channelMessageNotifications": "Notifications des Messages de Canal",
|
||||
"appSettings_channelMessageNotificationsSubtitle": "Afficher une notification lors de la réception des messages de canal",
|
||||
"appSettings_advertisementNotifications": "Notifications publicitaires",
|
||||
"appSettings_advertisementNotifications": "Notifications d'annonces",
|
||||
"appSettings_advertisementNotificationsSubtitle": "Afficher une notification lors de la découverte de nouveaux nœuds",
|
||||
"appSettings_messaging": "Messagerie",
|
||||
"appSettings_clearPathOnMaxRetry": "Effacer le chemin sur Max Retry",
|
||||
@@ -192,7 +192,7 @@
|
||||
"appSettings_pathsWillBeCleared": "Les chemins seront effacés après 5 tentatives infructueuses.",
|
||||
"appSettings_pathsWillNotBeCleared": "Les chemins ne seront pas effacés automatiquement.",
|
||||
"appSettings_autoRouteRotation": "Rotation de l'itinéraire automatique",
|
||||
"appSettings_autoRouteRotationSubtitle": "Alterner entre les meilleurs chemins et le mode inondation",
|
||||
"appSettings_autoRouteRotationSubtitle": "Alterner entre les meilleurs chemins et le mode d'envoi sur tout le réseau (flood)",
|
||||
"appSettings_autoRouteRotationEnabled": "Rotation du routage automatique activée",
|
||||
"appSettings_autoRouteRotationDisabled": "Rotation de l'itinéraire automatique désactivée",
|
||||
"appSettings_battery": "Batterie",
|
||||
@@ -279,7 +279,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts_newGroup": "Nouvelle Groupe",
|
||||
"contacts_newGroup": "Nouveau Groupe",
|
||||
"contacts_groupName": "Nom du groupe",
|
||||
"contacts_groupNameRequired": "Le nom du groupe est obligatoire.",
|
||||
"contacts_groupAlreadyExists": "Le groupe \"{name}\" existe déjà.",
|
||||
@@ -293,8 +293,8 @@
|
||||
"contacts_filterContacts": "Filtrer les contacts...",
|
||||
"contacts_noContactsMatchFilter": "Aucun contact ne correspond à votre filtre.",
|
||||
"contacts_noMembers": "Aucun membre",
|
||||
"contacts_lastSeenNow": "Dernière fois vu maintenant",
|
||||
"contacts_lastSeenMinsAgo": "Dernière fois vu il y a {minutes} minutes.",
|
||||
"contacts_lastSeenNow": "Vu maintenant",
|
||||
"contacts_lastSeenMinsAgo": "Vu il y a {minutes} minutes",
|
||||
"@contacts_lastSeenMinsAgo": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
@@ -302,8 +302,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts_lastSeenHourAgo": "Dernière fois vu il y a 1 heure.",
|
||||
"contacts_lastSeenHoursAgo": "Dernière fois vu il y a {hours} heures.",
|
||||
"contacts_lastSeenHourAgo": "Vu il y a 1 heure",
|
||||
"contacts_lastSeenHoursAgo": "Vu il y a {hours} heures",
|
||||
"@contacts_lastSeenHoursAgo": {
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
@@ -311,8 +311,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts_lastSeenDayAgo": "Dernière fois vu il y a 1 jour",
|
||||
"contacts_lastSeenDaysAgo": "Dernière activité il y a {days} jours",
|
||||
"contacts_lastSeenDayAgo": "Vu il y a 1 jour",
|
||||
"contacts_lastSeenDaysAgo": "Vu il y a {days} jours",
|
||||
"@contacts_lastSeenDaysAgo": {
|
||||
"placeholders": {
|
||||
"days": {
|
||||
@@ -394,7 +394,7 @@
|
||||
"channels_sortBy": "Trier par",
|
||||
"channels_sortManual": "Manuel",
|
||||
"channels_sortAZ": "A à Z",
|
||||
"channels_sortLatestMessages": "Dernières messages",
|
||||
"channels_sortLatestMessages": "Derniers messages",
|
||||
"channels_sortUnread": "Non lu",
|
||||
"chat_noMessages": "Aucun message pour le moment.",
|
||||
"chat_sendMessageToStart": "Envoyer un message pour commencer",
|
||||
@@ -436,7 +436,7 @@
|
||||
"chat_messageCopied": "Message copié",
|
||||
"chat_messageDeleted": "Message supprimé",
|
||||
"chat_retryingMessage": "Tentative de récupération.",
|
||||
"chat_retryCount": "Réessayer {current}/{max}",
|
||||
"chat_retryCount": "Essai {current}/{max}",
|
||||
"@chat_retryCount": {
|
||||
"placeholders": {
|
||||
"current": {
|
||||
@@ -539,7 +539,7 @@
|
||||
"chat_pathManagement": "Gestion des chemins",
|
||||
"chat_routingMode": "Mode de routage",
|
||||
"chat_autoUseSavedPath": "Auto (utiliser le chemin sauvegardé)",
|
||||
"chat_forceFloodMode": "Mode Inondation Forcée",
|
||||
"chat_forceFloodMode": "Mode tout le réseau forcé",
|
||||
"chat_recentAckPaths": "Chemins ACK récents (touchez pour utiliser) :",
|
||||
"chat_pathHistoryFull": "L'historique du chemin est plein. Supprimez les entrées pour en ajouter de nouvelles.",
|
||||
"chat_hopSingular": "Sautez",
|
||||
@@ -562,7 +562,7 @@
|
||||
"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_floodModeSubtitle": "Utiliser le commutateur de routage dans la barre d'application",
|
||||
"chat_floodModeEnabled": "Le mode inondation est activé. Réactiver 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_pathDetailsNotAvailable": "Les détails du chemin ne sont pas encore disponibles. Essayez d'envoyer un message pour rafraîchir.",
|
||||
"chat_pathSetHops": "Chemin défini : {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}",
|
||||
@@ -583,7 +583,7 @@
|
||||
"chat_path": "Chemin",
|
||||
"chat_publicKey": "Clé Publique",
|
||||
"chat_compressOutgoingMessages": "Compresser les messages sortants",
|
||||
"chat_floodForced": "Inondation (forcée)",
|
||||
"chat_floodForced": "Tout le réseau (forcée)",
|
||||
"chat_directForced": "Direct (forcé)",
|
||||
"chat_hopsForced": "{count} sauts (forcés)",
|
||||
"@chat_hopsForced": {
|
||||
@@ -593,7 +593,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_floodAuto": "Inondation (auto)",
|
||||
"chat_floodAuto": "Tout le réseau (auto)",
|
||||
"chat_direct": "Afficher",
|
||||
"chat_poiShared": "Point d'intérêt Partagé",
|
||||
"chat_unread": "Non lu : {count}",
|
||||
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Ouvrir le lien ?",
|
||||
"chat_openLinkConfirmation": "Voulez-vous ouvrir ce lien dans votre navigateur ?",
|
||||
"chat_open": "Ouvrir",
|
||||
"chat_couldNotOpenLink": "Impossible d'ouvrir le lien : {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Format de lien invalide",
|
||||
"map_title": "Carte des nœuds",
|
||||
"map_noNodesWithLocation": "Aucun nœud avec des données de localisation",
|
||||
"map_nodesNeedGps": "Les nœuds doivent partager leurs coordonnées GPS\npour apparaître sur la carte.",
|
||||
@@ -687,7 +699,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mapCache_cachedTilesWithFailed": "Tiles mis en cache ({downloaded}) ({failed} ratés)",
|
||||
"mapCache_cachedTilesWithFailed": "Tuiles mis en cache ({downloaded}) ({failed} ratés)",
|
||||
"@mapCache_cachedTilesWithFailed": {
|
||||
"placeholders": {
|
||||
"downloaded": {
|
||||
@@ -734,7 +746,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mapCache_boundsLabel": "N {north}, S {south}, E {east}, W {west}",
|
||||
"mapCache_boundsLabel": "N {north}, S {south}, E {east}, O {west}",
|
||||
"@mapCache_boundsLabel": {
|
||||
"placeholders": {
|
||||
"north": {
|
||||
@@ -751,7 +763,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"time_justNow": "Il y a tout juste maintenant",
|
||||
"time_justNow": "Maintenant",
|
||||
"time_minutesAgo": "{minutes} minutes auparavant",
|
||||
"@time_minutesAgo": {
|
||||
"placeholders": {
|
||||
@@ -799,7 +811,7 @@
|
||||
"login_routing": "Redirection",
|
||||
"login_routingMode": "Mode de routage",
|
||||
"login_autoUseSavedPath": "Auto (utiliser le chemin sauvegardé)",
|
||||
"login_forceFloodMode": "Mode Inondation Forcée",
|
||||
"login_forceFloodMode": "Mode tout le réseau forcé",
|
||||
"login_managePaths": "Gérer les chemins",
|
||||
"login_login": "Connexion",
|
||||
"login_attempt": "Essayer {current}/{max}",
|
||||
@@ -821,6 +833,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"login_failedMessage": "Connexion échouée. Soit le mot de passe est incorrect, soit le relais est injoignable.",
|
||||
"common_reload": "Recharger",
|
||||
"common_clear": "Effacer",
|
||||
"path_currentPath": "Chemin actuel : {path}",
|
||||
@@ -871,7 +884,7 @@
|
||||
"repeater_statusTitle": "État du répétiteur",
|
||||
"repeater_routingMode": "Mode de routage",
|
||||
"repeater_autoUseSavedPath": "Auto (utiliser le chemin sauvegardé)",
|
||||
"repeater_forceFloodMode": "Mode de submersion forcée",
|
||||
"repeater_forceFloodMode": "Mode tout le réseau forcé",
|
||||
"repeater_pathManagement": "Gestion des chemins",
|
||||
"repeater_refresh": "Rafraîchir",
|
||||
"repeater_statusRequestTimeout": "Demande de statut délai dépassé.",
|
||||
@@ -898,7 +911,7 @@
|
||||
"repeater_packetStatistics": "Statistiques des paquets",
|
||||
"repeater_sent": "Envoyé",
|
||||
"repeater_received": "Reçu",
|
||||
"repeater_duplicates": "Dupliques",
|
||||
"repeater_duplicates": "Doublons",
|
||||
"repeater_daysHoursMinsSecs": "{days} jours {hours}h {minutes}m {seconds}s",
|
||||
"@repeater_daysHoursMinsSecs": {
|
||||
"placeholders": {
|
||||
@@ -916,7 +929,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_packetTxTotal": "Total : {total}, Inondation : {flood}, Direct : {direct}",
|
||||
"repeater_packetTxTotal": "Total : {total}, Tout le réseau : {flood}, Direct : {direct}",
|
||||
"@repeater_packetTxTotal": {
|
||||
"placeholders": {
|
||||
"total": {
|
||||
@@ -930,7 +943,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_packetRxTotal": "Total : {total}, Inondation : {flood}, Direct : {direct}",
|
||||
"repeater_packetRxTotal": "Total : {total}, Tout le réseau : {flood}, Direct : {direct}",
|
||||
"@repeater_packetRxTotal": {
|
||||
"placeholders": {
|
||||
"total": {
|
||||
@@ -944,7 +957,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_duplicatesFloodDirect": "Inondation : {flood}, Direct : {direct}",
|
||||
"repeater_duplicatesFloodDirect": "Tout le réseau : {flood}, Direct : {direct}",
|
||||
"@repeater_duplicatesFloodDirect": {
|
||||
"placeholders": {
|
||||
"flood": {
|
||||
@@ -990,9 +1003,9 @@
|
||||
"repeater_guestAccess": "Accès Invité",
|
||||
"repeater_guestAccessSubtitle": "Autoriser l'accès invité en lecture seule",
|
||||
"repeater_privacyMode": "Mode de confidentialité",
|
||||
"repeater_privacyModeSubtitle": "Cacher le nom/l'emplacement dans les publicités",
|
||||
"repeater_advertisementSettings": "Paramètres de Publicité",
|
||||
"repeater_localAdvertInterval": "Intervalle Publicité Locale",
|
||||
"repeater_privacyModeSubtitle": "Cacher le nom/l'emplacement dans les annonces",
|
||||
"repeater_advertisementSettings": "Paramètres d'annonces",
|
||||
"repeater_localAdvertInterval": "Intervalle des annonces Locale (0 saut)",
|
||||
"repeater_localAdvertIntervalMinutes": "{minutes} minutes",
|
||||
"@repeater_localAdvertIntervalMinutes": {
|
||||
"placeholders": {
|
||||
@@ -1001,7 +1014,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_floodAdvertInterval": "Intervalle de Publicité Inondation",
|
||||
"repeater_floodAdvertInterval": "Intervalle des annonces à tout le réseau (flood)",
|
||||
"repeater_floodAdvertIntervalHours": "{hours} heures",
|
||||
"@repeater_floodAdvertIntervalHours": {
|
||||
"placeholders": {
|
||||
@@ -1010,8 +1023,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_encryptedAdvertInterval": "Intervalle publicitaire crypté",
|
||||
"repeater_dangerZone": "Zone d'alerte",
|
||||
"repeater_encryptedAdvertInterval": "Intervalle d'annonces cryptées",
|
||||
"repeater_dangerZone": "Zone dangereuse",
|
||||
"repeater_rebootRepeater": "Redémarrer Répéteur",
|
||||
"repeater_rebootRepeaterSubtitle": "Réinitialiser l'appareil répétiteur",
|
||||
"repeater_rebootRepeaterConfirm": "Êtes-vous sûr de vouloir redémarrer ce répétiteur ?",
|
||||
@@ -1055,7 +1068,7 @@
|
||||
"repeater_refreshPacketForwarding": "Rafraîchir le routage des paquets",
|
||||
"repeater_refreshGuestAccess": "Rafraîchir l'accès invité",
|
||||
"repeater_refreshPrivacyMode": "Rafraîchir le Mode Confidentialité",
|
||||
"repeater_refreshAdvertisementSettings": "Rafraîchir les Paramètres de la Publicité",
|
||||
"repeater_refreshAdvertisementSettings": "Rafraîchir les Paramètres des annonces",
|
||||
"repeater_refreshed": "{label} rafraîchi",
|
||||
"@repeater_refreshed": {
|
||||
"placeholders": {
|
||||
@@ -1098,7 +1111,7 @@
|
||||
"repeater_cliQuickVersion": "Version",
|
||||
"repeater_cliQuickAdvertise": "Publier",
|
||||
"repeater_cliQuickClock": "Horloge",
|
||||
"repeater_cliHelpAdvert": "Envoie un paquet publicitaire",
|
||||
"repeater_cliHelpAdvert": "Envoie un paquet d'annonce",
|
||||
"repeater_cliHelpReboot": "Redémarre l'appareil. (Note, vous risquez d'obtenir 'Timeout' ce qui est normal)",
|
||||
"repeater_cliHelpClock": "Affiche l'heure actuelle par l'horloge de chaque appareil.",
|
||||
"repeater_cliHelpPassword": "Définit un nouveau mot de passe administrateur pour l'appareil.",
|
||||
@@ -1107,7 +1120,7 @@
|
||||
"repeater_cliHelpSetAf": "Définit le facteur de temps d'air.",
|
||||
"repeater_cliHelpSetTx": "Définit la puissance de transmission LoRa en dBm (réinitialisation requise pour appliquer).",
|
||||
"repeater_cliHelpSetRepeat": "Active ou désactive le rôle du répétiteur pour ce nœud.",
|
||||
"repeater_cliHelpSetAllowReadOnly": "(Serveur de pièce) Si \"activé\", alors un mot de passe vide permettra la connexion, mais ne permettra pas de publier dans la pièce. (lecture seule uniquement)",
|
||||
"repeater_cliHelpSetAllowReadOnly": "(Room server) Si \"activé\", alors un mot de passe vide permettra la connexion, mais ne permettra pas de publier dans la pièce. (lecture seule uniquement)",
|
||||
"repeater_cliHelpSetFloodMax": "Définit le nombre maximal de sauts pour les paquets de balayage entrants (si >= max, le paquet n'est pas acheminé).",
|
||||
"repeater_cliHelpSetIntThresh": "Définit le seuil d'interférence (en dB). La valeur par défaut est de 14. Définir sur 0 désactive la détection des interférences de canal.",
|
||||
"repeater_cliHelpSetAgcResetInterval": "Définit l'intervalle pour réinitialiser le contrôleur de gain automatique. Mettez à 0 pour désactiver.",
|
||||
@@ -1115,12 +1128,12 @@
|
||||
"repeater_cliHelpSetAdvertInterval": "Définit l'intervalle du minuteur pour envoyer un paquet d'annonce local (sans relais). Définir sur 0 pour désactiver.",
|
||||
"repeater_cliHelpSetFloodAdvertInterval": "Définit l'intervalle du minuteur en heures pour envoyer un paquet d'annonce massive. Définir sur 0 pour désactiver.",
|
||||
"repeater_cliHelpSetGuestPassword": "Définit/met à jour le mot de passe de l'invité. (pour les répéteurs, les connexions d'invités peuvent envoyer la requête \"Get Stats\")",
|
||||
"repeater_cliHelpSetName": "Définit le nom de la publicité.",
|
||||
"repeater_cliHelpSetName": "Définit le nom de l'annonce.",
|
||||
"repeater_cliHelpSetLat": "Définit la latitude de la carte des annonces. (degrés décimaux)",
|
||||
"repeater_cliHelpSetLon": "Définit la longitude de la carte de l'annonce. (degrés décimaux)",
|
||||
"repeater_cliHelpSetRadio": "Définit complètement de nouveaux paramètres de radio et les enregistre dans les préférences. Nécessite une commande \"redémarrage\" pour les appliquer.",
|
||||
"repeater_cliHelpSetRxDelay": "Paramètres (expérimental) de base pour appliquer un léger délai aux paquets reçus, en fonction de la force du signal/score. Définir sur 0 pour désactiver.",
|
||||
"repeater_cliHelpSetTxDelay": "Définit un facteur multiplié par le temps de fonctionnement en mode inondation pour un paquet et avec un système de slot aléatoire, afin de retarder son envoi (pour diminuer la probabilité de collisions).",
|
||||
"repeater_cliHelpSetTxDelay": "Définit un facteur multiplié par le temps de fonctionnement en mode vers tout le réseau (flood) pour un paquet et avec un système de slot aléatoire, afin de retarder son envoi (pour diminuer la probabilité de collisions).",
|
||||
"repeater_cliHelpSetDirectTxDelay": "Identique à txdelay, mais pour appliquer un délai aléatoire au transfert des paquets en mode direct.",
|
||||
"repeater_cliHelpSetBridgeEnabled": "Activer/Désactiver le pont.",
|
||||
"repeater_cliHelpSetBridgeDelay": "Définir le délai avant de renvoyer les paquets.",
|
||||
@@ -1134,9 +1147,9 @@
|
||||
"repeater_cliHelpLogStart": "Démarre l'enregistrement des paquets dans le système de fichiers.",
|
||||
"repeater_cliHelpLogStop": "Arrêter de journaliser les paquets vers le système de fichiers.",
|
||||
"repeater_cliHelpLogErase": "Supprime les journaux de paquets du système de fichiers.",
|
||||
"repeater_cliHelpNeighbors": "Affiche une liste d'autres nœuds répétiteurs entendus via des publicités sans relais. Chaque ligne est id-préfixe-hexadécimal:timestamp:snr-fois-4",
|
||||
"repeater_cliHelpNeighbors": "Affiche une liste d'autres nœuds répétiteurs entendus via des annonces sans relais. Chaque ligne est id-préfixe-hexadécimal:timestamp:snr-fois-4",
|
||||
"repeater_cliHelpNeighborRemove": "Supprime la première entrée correspondante (par préfixe de clé publique (hexadécimal)) de la liste des voisins.",
|
||||
"repeater_cliHelpRegion": "(série uniquement) Liste toutes les régions définies et les autorisations de débordement actuelles.",
|
||||
"repeater_cliHelpRegion": "(série uniquement) Liste toutes les régions définies et les autorisations actuelles d'annonces sur tout le réseau (flood).",
|
||||
"repeater_cliHelpRegionLoad": "REMARQUE : il s'agit d'une invocation multi-commande spéciale. Chaque commande subséquente est un nom de région (indenté avec des espaces pour indiquer la hiérarchie parent, avec un minimum d'un espace). Terminé par l'envoi d'une ligne vide/commande.",
|
||||
"repeater_cliHelpRegionGet": "Recherche la région avec le préfixe de nom donné (ou \"\" pour l'étendue globale). Répond avec \"-> nom-de-région (nom-parent) 'F'\"",
|
||||
"repeater_cliHelpRegionPut": "Ajoute ou met à jour une définition de région avec le nom donné.",
|
||||
@@ -1271,7 +1284,7 @@
|
||||
}
|
||||
},
|
||||
"channelPath_unknownPath": "Inconnu",
|
||||
"channelPath_floodPath": "Inondation",
|
||||
"channelPath_floodPath": "Tout le réseau",
|
||||
"channelPath_directPath": "Afficher",
|
||||
"channelPath_observedZeroOf": "0 de {total} sauts",
|
||||
"@channelPath_observedZeroOf": {
|
||||
@@ -1326,14 +1339,199 @@
|
||||
"channelPath_unknownRepeater": "Répéteur Inconnu",
|
||||
"listFilter_tooltip": "Filtrer et trier",
|
||||
"listFilter_sortBy": "Trier par",
|
||||
"listFilter_latestMessages": "Dernières messages",
|
||||
"listFilter_latestMessages": "Derniers messages",
|
||||
"listFilter_heardRecently": "Écoute récemment",
|
||||
"listFilter_az": "A à Z",
|
||||
"listFilter_filters": "Filtres",
|
||||
"listFilter_all": "Tout",
|
||||
"listFilter_users": "Utilisateurs",
|
||||
"listFilter_repeaters": "Répéteurs",
|
||||
"listFilter_roomServers": "Serveurs de pièce",
|
||||
"listFilter_roomServers": "Room servers",
|
||||
"listFilter_unreadOnly": "Messages non lus seulement",
|
||||
"listFilter_newGroup": "Nouvelle groupe"
|
||||
"listFilter_newGroup": "Nouveau groupe",
|
||||
"@neighbors_errorLoading": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_neighbours": "Voisins",
|
||||
"repeater_neighboursSubtitle": "Afficher les voisins de saut nuls.",
|
||||
"neighbors_receivedData": "Données des voisins reçues",
|
||||
"neighbors_requestTimedOut": "Les voisins demandent un délai.",
|
||||
"neighbors_errorLoading": "Erreur lors du chargement des voisins : {error}",
|
||||
"neighbors_repeatersNeighbours": "Répéteurs Voisins",
|
||||
"neighbors_noData": "Aucune donnée concernant les voisins disponible.",
|
||||
"channels_createPrivateChannelDesc": "Sécurisé avec une clé secrète.",
|
||||
"channels_joinPrivateChannel": "Rejoindre un Canal Privé",
|
||||
"channels_createPrivateChannel": "Créer un Canal Privé",
|
||||
"channels_joinPrivateChannelDesc": "Entrer manuellement une clé secrète.",
|
||||
"channels_joinPublicChannel": "Rejoindre le canal public",
|
||||
"channels_joinPublicChannelDesc": "Tout le monde peut rejoindre ce canal.",
|
||||
"channels_joinHashtagChannel": "Rejoindre un Canal Hashtag",
|
||||
"channels_joinHashtagChannelDesc": "N'importe qui peut rejoindre les canaux #hashtag.",
|
||||
"channels_scanQrCode": "Scanner un code QR",
|
||||
"channels_scanQrCodeComingSoon": "Bientôt disponible",
|
||||
"channels_enterHashtag": "Entrez le hashtag",
|
||||
"channels_hashtagHint": "ex. #equipe",
|
||||
"@neighbors_unknownContact": {
|
||||
"placeholders": {
|
||||
"pubkey": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@neighbors_heardAgo": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"neighbors_unknownContact": "Clé publique inconnue {pubkey}",
|
||||
"neighbors_heardAgo": "Écouté : {time} auparavant",
|
||||
"settings_locationGPSEnable": "Activer le GPS",
|
||||
"settings_locationGPSEnableSubtitle": "Activer la mise à jour automatique de la position via GPS",
|
||||
"settings_locationIntervalSec": "Intervalle de mise-à-jour du GPS (Secondes)",
|
||||
"settings_locationIntervalInvalid": "L'intervalle doit être compris entre 60 et 86400 secondes.",
|
||||
"contacts_manageRoom": "Gérer le Room Server",
|
||||
"room_management": "Administración del Servidor de Habitación",
|
||||
"@community_joinConfirmation": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_created": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_joined": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_qrInstructions": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_alreadyMemberMessage": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleteConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleted": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_forCommunity": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"common_ok": "OK",
|
||||
"community_title": "Communauté",
|
||||
"community_create": "Créer une Communauté",
|
||||
"community_createDesc": "Créer une nouvelle communauté et la partager via QR code.",
|
||||
"community_join": "Rejoindre",
|
||||
"community_joinTitle": "Rejoindre la communauté",
|
||||
"community_joinConfirmation": "Souhaitez-vous rejoindre la communauté \"{name}\" ?",
|
||||
"community_scanQr": "Scanner la communauté QR",
|
||||
"community_scanInstructions": "Pointez l'appareil photo vers un code QR communautaire.",
|
||||
"community_showQr": "Afficher le QR Code",
|
||||
"community_publicChannel": "Communauté Publique",
|
||||
"community_hashtagChannel": "Hashtag Communauté",
|
||||
"community_name": "Nom de la communauté",
|
||||
"community_enterName": "Entrez le nom de la communauté",
|
||||
"community_created": "Communauté \"{name}\" créée",
|
||||
"community_joined": "Rejoint la communauté \"{name}\"",
|
||||
"community_qrTitle": "Partager Communauté",
|
||||
"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_invalidQrCode": "Code QR de communauté non valide",
|
||||
"community_alreadyMember": "Déjà membre",
|
||||
"community_alreadyMemberMessage": "Vous êtes déjà membre de \"{name}\".",
|
||||
"community_addPublicChannel": "Ajouter un Canal Public de la Communauté",
|
||||
"community_addPublicChannelHint": "Ajouter automatiquement le canal public pour cette communauté",
|
||||
"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_manageCommunities": "Gérer les Communautés",
|
||||
"community_delete": "Quitter la communauté",
|
||||
"community_deleteConfirm": "Quitter \"{name}\" ?",
|
||||
"community_deleteChannelsWarning": "Cela supprimera également {count} canal/canaux et leurs messages.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Communauté \"{name}\" quittée",
|
||||
"community_addHashtagChannel": "Ajouter un Hashtag Communauté",
|
||||
"community_addHashtagChannelDesc": "Ajouter un canal hashtag pour cette communauté",
|
||||
"community_selectCommunity": "Sélectionner Communauté",
|
||||
"community_regularHashtag": "Hashtag régulier",
|
||||
"community_regularHashtagDesc": "Hashtag public (tout le monde peut rejoindre)",
|
||||
"community_communityHashtag": "Hashtag de la communauté",
|
||||
"community_communityHashtagDesc": "Exclusif aux membres de la communauté",
|
||||
"community_forCommunity": "Pour {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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_regenerate": "Régénérer",
|
||||
"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_updateSecret": "Mettre à jour le secret",
|
||||
"community_secretUpdated": "Modification secrète mise à jour pour \"{name}\""
|
||||
}
|
||||
|
||||
+199
-1
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Aprire il link?",
|
||||
"chat_openLinkConfirmation": "Vuoi aprire questo link nel tuo browser?",
|
||||
"chat_open": "Apri",
|
||||
"chat_couldNotOpenLink": "Impossibile aprire il link: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Formato di link non valido",
|
||||
"map_title": "Mappa Nodi",
|
||||
"map_noNodesWithLocation": "Nessun nodo con dati di posizione",
|
||||
"map_nodesNeedGps": "I nodi devono condividere le loro coordinate GPS\nper apparire sulla mappa",
|
||||
@@ -821,6 +833,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"login_failedMessage": "Accesso fallito. La password non è corretta oppure il ripetitore non è raggiungibile.",
|
||||
"common_reload": "Ricaricare",
|
||||
"common_clear": "Cancella",
|
||||
"path_currentPath": "Percorso corrente: {path}",
|
||||
@@ -1335,5 +1348,190 @@
|
||||
"listFilter_repeaters": "Ripetitori",
|
||||
"listFilter_roomServers": "Server della stanza",
|
||||
"listFilter_unreadOnly": "Solo non letto",
|
||||
"listFilter_newGroup": "Nuovo gruppo"
|
||||
"listFilter_newGroup": "Nuovo gruppo",
|
||||
"@neighbors_errorLoading": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_neighbours": "Vicini",
|
||||
"repeater_neighboursSubtitle": "Visualizza vicini di salto pari a zero.",
|
||||
"neighbors_receivedData": "Ricevute dati vicini",
|
||||
"neighbors_requestTimedOut": "I vicini richiedono un timeout.",
|
||||
"neighbors_errorLoading": "Errore nel caricamento dei vicini: {error}",
|
||||
"neighbors_repeatersNeighbours": "Ripetitori Vicini",
|
||||
"neighbors_noData": "Nessun dato sugli vicini disponibile.",
|
||||
"channels_createPrivateChannel": "Crea un Canale Privato",
|
||||
"channels_createPrivateChannelDesc": "Protetta con una chiave segreta.",
|
||||
"channels_joinPrivateChannel": "Unisciti a un Canale Privato",
|
||||
"channels_joinPrivateChannelDesc": "Inserire manualmente una chiave segreta.",
|
||||
"channels_joinPublicChannel": "Unisciti al Canale Pubblico",
|
||||
"channels_joinPublicChannelDesc": "Chiunque può unirsi a questo canale.",
|
||||
"channels_joinHashtagChannel": "Unisciti a un Canale con Hashtag",
|
||||
"channels_joinHashtagChannelDesc": "Chiunque può unirsi ai canali hashtag.",
|
||||
"channels_scanQrCode": "Scansiona un codice QR",
|
||||
"channels_scanQrCodeComingSoon": "Arriverà presto",
|
||||
"channels_enterHashtag": "Inserisci hashtag",
|
||||
"channels_hashtagHint": "es. #team",
|
||||
"@neighbors_unknownContact": {
|
||||
"placeholders": {
|
||||
"pubkey": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@neighbors_heardAgo": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"neighbors_heardAgo": "Sentito: {time} fa",
|
||||
"neighbors_unknownContact": "Chiave pubblica sconosciuta {pubkey}",
|
||||
"settings_locationGPSEnable": "Abilita GPS",
|
||||
"settings_locationGPSEnableSubtitle": "Abilita l'aggiornamento automatico della posizione tramite GPS.",
|
||||
"settings_locationIntervalSec": "Intervallo GPS (Secondi)",
|
||||
"settings_locationIntervalInvalid": "L'intervallo deve essere di almeno 60 secondi e inferiore a 86400 secondi.",
|
||||
"contacts_manageRoom": "Gestisci Server Camera",
|
||||
"room_management": "Gestione del Server di Camera",
|
||||
"@community_joinConfirmation": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_created": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_joined": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_qrInstructions": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_alreadyMemberMessage": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleteConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleted": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_forCommunity": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"common_ok": "OK",
|
||||
"community_title": "Comunità",
|
||||
"community_create": "Crea Comunità",
|
||||
"community_createDesc": "Crea una nuova comunità e condividila tramite codice QR.",
|
||||
"community_join": "Unisciti",
|
||||
"community_joinTitle": "Unisciti alla Community",
|
||||
"community_joinConfirmation": "Vuoi unirti alla community \"{name}\"?",
|
||||
"community_scanQr": "Scansiona il QR Code della Community",
|
||||
"community_scanInstructions": "Punta la fotocamera su un codice QR della comunità",
|
||||
"community_showQr": "Mostra il codice QR",
|
||||
"community_publicChannel": "Comunità Pubblica",
|
||||
"community_hashtagChannel": "Hashtag della Comunità",
|
||||
"community_name": "Nome della Comunità",
|
||||
"community_enterName": "Inserisci il nome della comunità",
|
||||
"community_created": "Comunità \"{name}\" creata",
|
||||
"community_joined": "Unito alla comunità \"{name}\"",
|
||||
"community_qrTitle": "Condividi Comunità",
|
||||
"community_qrInstructions": "Scansiona questo codice QR per unirti a {name}",
|
||||
"community_hashtagPrivacyHint": "I canali hashtag della community sono accessibili solo ai membri della community",
|
||||
"community_invalidQrCode": "Codice QR della community non valido",
|
||||
"community_alreadyMember": "Già membro",
|
||||
"community_alreadyMemberMessage": "Sei già un membro di \"{name}\".",
|
||||
"community_addPublicChannel": "Aggiungi Canale Pubblico della Comunità",
|
||||
"community_addPublicChannelHint": "Aggiungi automaticamente il canale pubblico per questa community",
|
||||
"community_noCommunities": "Nessun gruppo aggiunto finora",
|
||||
"community_scanOrCreate": "Scansiona un codice QR o crea una community per iniziare.",
|
||||
"community_manageCommunities": "Gestisci Comunità",
|
||||
"community_delete": "Lascia la Comunità",
|
||||
"community_deleteConfirm": "Uscire da \"{name}\"?",
|
||||
"community_deleteChannelsWarning": "Questo eliminerà anche {count} canale/i e i loro messaggi.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Hai lasciato la comunità \"{name}\"",
|
||||
"community_addHashtagChannel": "Aggiungi Hashtag della Community",
|
||||
"community_addHashtagChannelDesc": "Aggiungi un canale con hashtag per questa community",
|
||||
"community_selectCommunity": "Seleziona Comunità",
|
||||
"community_regularHashtag": "Hashtag regolare",
|
||||
"community_regularHashtagDesc": "Hashtag pubblico (chiunque può unirsi)",
|
||||
"community_communityHashtag": "Hashtag della Comunità",
|
||||
"community_communityHashtagDesc": "Visibile solo ai membri della comunità",
|
||||
"community_forCommunity": "Per {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerateSecretConfirm": "Regenera la chiave segreta per \"{name}\"? Tutti i membri dovranno scansionare il nuovo codice QR per continuare a comunicare.",
|
||||
"community_regenerateSecret": "Ri genera la chiave segreta",
|
||||
"community_regenerate": "Rigenera",
|
||||
"community_secretRegenerated": "Codice segreto rigenerato per \"{name}\"",
|
||||
"community_updateSecret": "Aggiorna Segreto",
|
||||
"community_secretUpdated": "Segreto aggiornato per \"{name}\"",
|
||||
"community_scanToUpdateSecret": "Scansiona il nuovo codice QR per aggiornare il segreto di \"{name}\""
|
||||
}
|
||||
|
||||
@@ -14,9 +14,11 @@ import 'app_localizations_it.dart';
|
||||
import 'app_localizations_nl.dart';
|
||||
import 'app_localizations_pl.dart';
|
||||
import 'app_localizations_pt.dart';
|
||||
import 'app_localizations_ru.dart';
|
||||
import 'app_localizations_sk.dart';
|
||||
import 'app_localizations_sl.dart';
|
||||
import 'app_localizations_sv.dart';
|
||||
import 'app_localizations_uk.dart';
|
||||
import 'app_localizations_zh.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
@@ -114,9 +116,11 @@ abstract class AppLocalizations {
|
||||
Locale('nl'),
|
||||
Locale('pl'),
|
||||
Locale('pt'),
|
||||
Locale('ru'),
|
||||
Locale('sk'),
|
||||
Locale('sl'),
|
||||
Locale('sv'),
|
||||
Locale('uk'),
|
||||
Locale('zh'),
|
||||
];
|
||||
|
||||
@@ -150,6 +154,12 @@ abstract class AppLocalizations {
|
||||
/// **'Cancel'**
|
||||
String get common_cancel;
|
||||
|
||||
/// No description provided for @common_ok.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'OK'**
|
||||
String get common_ok;
|
||||
|
||||
/// No description provided for @common_connect.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -465,7 +475,7 @@ abstract class AppLocalizations {
|
||||
/// No description provided for @settings_locationUpdated.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Location updated'**
|
||||
/// **'Location and GPS settings updated'**
|
||||
String get settings_locationUpdated;
|
||||
|
||||
/// No description provided for @settings_locationBothRequired.
|
||||
@@ -480,6 +490,30 @@ abstract class AppLocalizations {
|
||||
/// **'Invalid latitude or longitude.'**
|
||||
String get settings_locationInvalid;
|
||||
|
||||
/// No description provided for @settings_locationGPSEnable.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'GPS Enable'**
|
||||
String get settings_locationGPSEnable;
|
||||
|
||||
/// No description provided for @settings_locationGPSEnableSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enables GPS to automatically update location.'**
|
||||
String get settings_locationGPSEnableSubtitle;
|
||||
|
||||
/// No description provided for @settings_locationIntervalSec.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Interval for GPS (Seconds)'**
|
||||
String get settings_locationIntervalSec;
|
||||
|
||||
/// No description provided for @settings_locationIntervalInvalid.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Interval must be at least 60 seconds, and less than 86400 seconds.'**
|
||||
String get settings_locationIntervalInvalid;
|
||||
|
||||
/// No description provided for @settings_latitude.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -1284,10 +1318,16 @@ abstract class AppLocalizations {
|
||||
/// **'Manage Repeater'**
|
||||
String get contacts_manageRepeater;
|
||||
|
||||
/// No description provided for @contacts_manageRoom.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Manage Room Server'**
|
||||
String get contacts_manageRoom;
|
||||
|
||||
/// No description provided for @contacts_roomLogin.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Room Login'**
|
||||
/// **'Room Server Login'**
|
||||
String get contacts_roomLogin;
|
||||
|
||||
/// No description provided for @contacts_openChat.
|
||||
@@ -1596,6 +1636,78 @@ abstract class AppLocalizations {
|
||||
/// **'Unread'**
|
||||
String get channels_sortUnread;
|
||||
|
||||
/// No description provided for @channels_createPrivateChannel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Create a Private Channel'**
|
||||
String get channels_createPrivateChannel;
|
||||
|
||||
/// No description provided for @channels_createPrivateChannelDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Secured with a secret key.'**
|
||||
String get channels_createPrivateChannelDesc;
|
||||
|
||||
/// No description provided for @channels_joinPrivateChannel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Join a Private Channel'**
|
||||
String get channels_joinPrivateChannel;
|
||||
|
||||
/// No description provided for @channels_joinPrivateChannelDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Manually enter a secret key.'**
|
||||
String get channels_joinPrivateChannelDesc;
|
||||
|
||||
/// No description provided for @channels_joinPublicChannel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Join the Public Channel'**
|
||||
String get channels_joinPublicChannel;
|
||||
|
||||
/// No description provided for @channels_joinPublicChannelDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Anyone can join this channel.'**
|
||||
String get channels_joinPublicChannelDesc;
|
||||
|
||||
/// No description provided for @channels_joinHashtagChannel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Join a Hashtag Channel'**
|
||||
String get channels_joinHashtagChannel;
|
||||
|
||||
/// No description provided for @channels_joinHashtagChannelDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Anyone can join hashtag channels.'**
|
||||
String get channels_joinHashtagChannelDesc;
|
||||
|
||||
/// No description provided for @channels_scanQrCode.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Scan a QR Code'**
|
||||
String get channels_scanQrCode;
|
||||
|
||||
/// No description provided for @channels_scanQrCodeComingSoon.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Coming soon'**
|
||||
String get channels_scanQrCodeComingSoon;
|
||||
|
||||
/// No description provided for @channels_enterHashtag.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enter hashtag'**
|
||||
String get channels_enterHashtag;
|
||||
|
||||
/// No description provided for @channels_hashtagHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'e.g. #team'**
|
||||
String get channels_hashtagHint;
|
||||
|
||||
/// No description provided for @chat_noMessages.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -2118,6 +2230,36 @@ abstract class AppLocalizations {
|
||||
/// **'Unread: {count}'**
|
||||
String chat_unread(int count);
|
||||
|
||||
/// No description provided for @chat_openLink.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Open Link?'**
|
||||
String get chat_openLink;
|
||||
|
||||
/// No description provided for @chat_openLinkConfirmation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Do you want to open this link in your browser?'**
|
||||
String get chat_openLinkConfirmation;
|
||||
|
||||
/// No description provided for @chat_open.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Open'**
|
||||
String get chat_open;
|
||||
|
||||
/// No description provided for @chat_couldNotOpenLink.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Could not open link: {url}'**
|
||||
String chat_couldNotOpenLink(String url);
|
||||
|
||||
/// No description provided for @chat_invalidLink.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Invalid link format'**
|
||||
String get chat_invalidLink;
|
||||
|
||||
/// No description provided for @map_title.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -2600,7 +2742,7 @@ abstract class AppLocalizations {
|
||||
/// No description provided for @login_roomLogin.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Room Login'**
|
||||
/// **'Room Server Login'**
|
||||
String get login_roomLogin;
|
||||
|
||||
/// No description provided for @login_password.
|
||||
@@ -2687,6 +2829,12 @@ abstract class AppLocalizations {
|
||||
/// **'Login failed: {error}'**
|
||||
String login_failed(String error);
|
||||
|
||||
/// No description provided for @login_failedMessage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Login failed. Either the password is incorrect or the repeater is unreachable.'**
|
||||
String get login_failedMessage;
|
||||
|
||||
/// No description provided for @common_reload.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -2789,6 +2937,12 @@ abstract class AppLocalizations {
|
||||
/// **'Repeater Management'**
|
||||
String get repeater_management;
|
||||
|
||||
/// No description provided for @room_management.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Room Server Management'**
|
||||
String get room_management;
|
||||
|
||||
/// No description provided for @repeater_managementTools.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -2831,6 +2985,18 @@ abstract class AppLocalizations {
|
||||
/// **'Send commands to the repeater'**
|
||||
String get repeater_cliSubtitle;
|
||||
|
||||
/// No description provided for @repeater_neighbours.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Neighbors'**
|
||||
String get repeater_neighbours;
|
||||
|
||||
/// No description provided for @repeater_neighboursSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'View zero hop neighbors.'**
|
||||
String get repeater_neighboursSubtitle;
|
||||
|
||||
/// No description provided for @repeater_settings.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -3970,6 +4136,48 @@ abstract class AppLocalizations {
|
||||
/// **'{celsius}°C / {fahrenheit}°F'**
|
||||
String telemetry_temperatureValue(String celsius, String fahrenheit);
|
||||
|
||||
/// No description provided for @neighbors_receivedData.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Received Neighbours Data'**
|
||||
String get neighbors_receivedData;
|
||||
|
||||
/// No description provided for @neighbors_requestTimedOut.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Neighbours request timed out.'**
|
||||
String get neighbors_requestTimedOut;
|
||||
|
||||
/// No description provided for @neighbors_errorLoading.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Error loading neighbors: {error}'**
|
||||
String neighbors_errorLoading(String error);
|
||||
|
||||
/// No description provided for @neighbors_repeatersNeighbours.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Repeaters Neighbours'**
|
||||
String get neighbors_repeatersNeighbours;
|
||||
|
||||
/// No description provided for @neighbors_noData.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No neighbours data available.'**
|
||||
String get neighbors_noData;
|
||||
|
||||
/// No description provided for @neighbors_unknownContact.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Unknown {pubkey}'**
|
||||
String neighbors_unknownContact(String pubkey);
|
||||
|
||||
/// No description provided for @neighbors_heardAgo.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Heard: {time} ago'**
|
||||
String neighbors_heardAgo(String time);
|
||||
|
||||
/// No description provided for @channelPath_title.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -4138,6 +4346,276 @@ abstract class AppLocalizations {
|
||||
/// **'Unknown Repeater'**
|
||||
String get channelPath_unknownRepeater;
|
||||
|
||||
/// No description provided for @community_title.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Community'**
|
||||
String get community_title;
|
||||
|
||||
/// No description provided for @community_create.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Create Community'**
|
||||
String get community_create;
|
||||
|
||||
/// No description provided for @community_createDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Create a new community and share via QR code.'**
|
||||
String get community_createDesc;
|
||||
|
||||
/// No description provided for @community_join.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Join'**
|
||||
String get community_join;
|
||||
|
||||
/// No description provided for @community_joinTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Join Community'**
|
||||
String get community_joinTitle;
|
||||
|
||||
/// No description provided for @community_joinConfirmation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Do you want to join the community \"{name}\"?'**
|
||||
String community_joinConfirmation(String name);
|
||||
|
||||
/// No description provided for @community_scanQr.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Scan Community QR'**
|
||||
String get community_scanQr;
|
||||
|
||||
/// No description provided for @community_scanInstructions.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Point the camera at a community QR code'**
|
||||
String get community_scanInstructions;
|
||||
|
||||
/// No description provided for @community_showQr.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Show QR Code'**
|
||||
String get community_showQr;
|
||||
|
||||
/// No description provided for @community_publicChannel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Community Public'**
|
||||
String get community_publicChannel;
|
||||
|
||||
/// No description provided for @community_hashtagChannel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Community Hashtag'**
|
||||
String get community_hashtagChannel;
|
||||
|
||||
/// No description provided for @community_name.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Community Name'**
|
||||
String get community_name;
|
||||
|
||||
/// No description provided for @community_enterName.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enter community name'**
|
||||
String get community_enterName;
|
||||
|
||||
/// No description provided for @community_created.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Community \"{name}\" created'**
|
||||
String community_created(String name);
|
||||
|
||||
/// No description provided for @community_joined.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Joined community \"{name}\"'**
|
||||
String community_joined(String name);
|
||||
|
||||
/// No description provided for @community_qrTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Share Community'**
|
||||
String get community_qrTitle;
|
||||
|
||||
/// No description provided for @community_qrInstructions.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Scan this QR code to join \"{name}\"'**
|
||||
String community_qrInstructions(String name);
|
||||
|
||||
/// No description provided for @community_hashtagPrivacyHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Community hashtag channels are only joinable by members of the community'**
|
||||
String get community_hashtagPrivacyHint;
|
||||
|
||||
/// No description provided for @community_invalidQrCode.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Invalid community QR code'**
|
||||
String get community_invalidQrCode;
|
||||
|
||||
/// No description provided for @community_alreadyMember.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Already a Member'**
|
||||
String get community_alreadyMember;
|
||||
|
||||
/// No description provided for @community_alreadyMemberMessage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You are already a member of \"{name}\".'**
|
||||
String community_alreadyMemberMessage(String name);
|
||||
|
||||
/// No description provided for @community_addPublicChannel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add Community Public Channel'**
|
||||
String get community_addPublicChannel;
|
||||
|
||||
/// No description provided for @community_addPublicChannelHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Automatically add the public channel for this community'**
|
||||
String get community_addPublicChannelHint;
|
||||
|
||||
/// No description provided for @community_noCommunities.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No communities joined yet'**
|
||||
String get community_noCommunities;
|
||||
|
||||
/// No description provided for @community_scanOrCreate.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Scan a QR code or create a community to get started'**
|
||||
String get community_scanOrCreate;
|
||||
|
||||
/// No description provided for @community_manageCommunities.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Manage Communities'**
|
||||
String get community_manageCommunities;
|
||||
|
||||
/// No description provided for @community_delete.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Leave Community'**
|
||||
String get community_delete;
|
||||
|
||||
/// No description provided for @community_deleteConfirm.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Leave \"{name}\"?'**
|
||||
String community_deleteConfirm(String name);
|
||||
|
||||
/// No description provided for @community_deleteChannelsWarning.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'This will also delete {count} channel(s) and their messages.'**
|
||||
String community_deleteChannelsWarning(int count);
|
||||
|
||||
/// No description provided for @community_deleted.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Left community \"{name}\"'**
|
||||
String community_deleted(String name);
|
||||
|
||||
/// No description provided for @community_regenerateSecret.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Regenerate Secret'**
|
||||
String get community_regenerateSecret;
|
||||
|
||||
/// No description provided for @community_regenerateSecretConfirm.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Regenerate the secret key for \"{name}\"? All members will need to scan the new QR code to continue communicating.'**
|
||||
String community_regenerateSecretConfirm(String name);
|
||||
|
||||
/// No description provided for @community_regenerate.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Regenerate'**
|
||||
String get community_regenerate;
|
||||
|
||||
/// No description provided for @community_secretRegenerated.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Secret regenerated for \"{name}\"'**
|
||||
String community_secretRegenerated(String name);
|
||||
|
||||
/// No description provided for @community_updateSecret.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Update Secret'**
|
||||
String get community_updateSecret;
|
||||
|
||||
/// No description provided for @community_secretUpdated.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Secret updated for \"{name}\"'**
|
||||
String community_secretUpdated(String name);
|
||||
|
||||
/// No description provided for @community_scanToUpdateSecret.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Scan the new QR code to update the secret for \"{name}\"'**
|
||||
String community_scanToUpdateSecret(String name);
|
||||
|
||||
/// No description provided for @community_addHashtagChannel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add Community Hashtag'**
|
||||
String get community_addHashtagChannel;
|
||||
|
||||
/// No description provided for @community_addHashtagChannelDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add a hashtag channel for this community'**
|
||||
String get community_addHashtagChannelDesc;
|
||||
|
||||
/// No description provided for @community_selectCommunity.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select Community'**
|
||||
String get community_selectCommunity;
|
||||
|
||||
/// No description provided for @community_regularHashtag.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Regular Hashtag'**
|
||||
String get community_regularHashtag;
|
||||
|
||||
/// No description provided for @community_regularHashtagDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Public hashtag (anyone can join)'**
|
||||
String get community_regularHashtagDesc;
|
||||
|
||||
/// No description provided for @community_communityHashtag.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Community Hashtag'**
|
||||
String get community_communityHashtag;
|
||||
|
||||
/// No description provided for @community_communityHashtagDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Private to community members'**
|
||||
String get community_communityHashtagDesc;
|
||||
|
||||
/// No description provided for @community_forCommunity.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'For {name}'**
|
||||
String community_forCommunity(String name);
|
||||
|
||||
/// No description provided for @listFilter_tooltip.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -4231,9 +4709,11 @@ class _AppLocalizationsDelegate
|
||||
'nl',
|
||||
'pl',
|
||||
'pt',
|
||||
'ru',
|
||||
'sk',
|
||||
'sl',
|
||||
'sv',
|
||||
'uk',
|
||||
'zh',
|
||||
].contains(locale.languageCode);
|
||||
|
||||
@@ -4262,12 +4742,16 @@ AppLocalizations lookupAppLocalizations(Locale locale) {
|
||||
return AppLocalizationsPl();
|
||||
case 'pt':
|
||||
return AppLocalizationsPt();
|
||||
case 'ru':
|
||||
return AppLocalizationsRu();
|
||||
case 'sk':
|
||||
return AppLocalizationsSk();
|
||||
case 'sl':
|
||||
return AppLocalizationsSl();
|
||||
case 'sv':
|
||||
return AppLocalizationsSv();
|
||||
case 'uk':
|
||||
return AppLocalizationsUk();
|
||||
case 'zh':
|
||||
return AppLocalizationsZh();
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get common_cancel => 'Отказ';
|
||||
|
||||
@override
|
||||
String get common_ok => 'Добре';
|
||||
|
||||
@override
|
||||
String get common_connect => 'Свържи се';
|
||||
|
||||
@@ -201,6 +204,20 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get settings_locationInvalid => 'Невалидна ширина или дължина.';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnable => 'Активиране на GPS';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnableSubtitle =>
|
||||
'Активирайте автоматичното актуализиране на местоположението чрез GPS.';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalSec => 'Интервал за GPS (Секунди)';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalInvalid =>
|
||||
'Интервалът трябва да бъде поне 60 секунди и по-малко от 86400 секунди.';
|
||||
|
||||
@override
|
||||
String get settings_latitude => 'Широчина';
|
||||
|
||||
@@ -650,6 +667,9 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_manageRepeater => 'Управление на Повтарящ се Елемент';
|
||||
|
||||
@override
|
||||
String get contacts_manageRoom => 'Управление на сървър за стая';
|
||||
|
||||
@override
|
||||
String get contacts_roomLogin => 'Вход в стаята';
|
||||
|
||||
@@ -830,6 +850,45 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get channels_sortUnread => 'Непрочетено';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannel => 'Създай Частен Канал';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannelDesc => 'Защитено с таен ключ.';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannel => 'Присъедини се към Частен Канал';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannelDesc => 'Ръчно въведете таен ключ.';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannel =>
|
||||
'Присъединете се към Публичния канал';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannelDesc =>
|
||||
'Всеки може да се присъедини към този канал.';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannel => 'Присъедини се към Хаштаг Канал';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannelDesc =>
|
||||
'Всеки може да се присъедини към хаштаговите канали.';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCode => 'Сканирайте QR код';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCodeComingSoon => 'Ще излезе скоро';
|
||||
|
||||
@override
|
||||
String get channels_enterHashtag => 'Въведете хаштаг';
|
||||
|
||||
@override
|
||||
String get channels_hashtagHint => 'напр. #отбор';
|
||||
|
||||
@override
|
||||
String get chat_noMessages => 'Няма съобщения.';
|
||||
|
||||
@@ -1148,6 +1207,24 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
return 'Непрочетени: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Отваряне на връзката?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Искате ли да отворите тази връзка в браузъра си?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Отвори';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Не можа да се отвори връзката: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Невалиден формат на връзката';
|
||||
|
||||
@override
|
||||
String get map_title => 'Карта на възлите';
|
||||
|
||||
@@ -1475,6 +1552,10 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
return 'Входът не беше успешен: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get login_failedMessage =>
|
||||
'Входът не беше успешен. Или паролата е грешна, или повторителят е недостъпен.';
|
||||
|
||||
@override
|
||||
String get common_reload => 'Презареди';
|
||||
|
||||
@@ -1544,6 +1625,9 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_management => 'Управление на повторители';
|
||||
|
||||
@override
|
||||
String get room_management => 'Управление на сървъра за стая';
|
||||
|
||||
@override
|
||||
String get repeater_managementTools => 'Инструменти за управление';
|
||||
|
||||
@@ -1567,6 +1651,13 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_cliSubtitle => 'Изпрати команди към ретранслатора';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Съседи';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle =>
|
||||
'Преглед на съседни възли с нулев скок.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Настройки';
|
||||
|
||||
@@ -2252,6 +2343,33 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Получени данни за съседи';
|
||||
|
||||
@override
|
||||
String get neighbors_requestTimedOut => 'Съседите поискат изтичане на време.';
|
||||
|
||||
@override
|
||||
String neighbors_errorLoading(String error) {
|
||||
return 'Грешка при зареждане на съседи: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_repeatersNeighbours => 'Повторители Съседи';
|
||||
|
||||
@override
|
||||
String get neighbors_noData => 'Няма налични данни за съседи.';
|
||||
|
||||
@override
|
||||
String neighbors_unknownContact(String pubkey) {
|
||||
return 'Неизвестна $pubkey';
|
||||
}
|
||||
|
||||
@override
|
||||
String neighbors_heardAgo(String time) {
|
||||
return 'Слушано преди $time.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get channelPath_title => 'Пътеки пъзел';
|
||||
|
||||
@@ -2355,6 +2473,174 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get channelPath_unknownRepeater => 'Неизвестен повторител';
|
||||
|
||||
@override
|
||||
String get community_title => 'Общност';
|
||||
|
||||
@override
|
||||
String get community_create => 'Създай общност';
|
||||
|
||||
@override
|
||||
String get community_createDesc =>
|
||||
'Създайте нова общност и я споделете чрез QR код.';
|
||||
|
||||
@override
|
||||
String get community_join => 'Присъедини се';
|
||||
|
||||
@override
|
||||
String get community_joinTitle => 'Присъедини се към общността';
|
||||
|
||||
@override
|
||||
String community_joinConfirmation(String name) {
|
||||
return 'Искате ли да се присъедините към общността \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_scanQr => 'Сканирайте QR кода на общността';
|
||||
|
||||
@override
|
||||
String get community_scanInstructions =>
|
||||
'Насочете камерата към QR код на общността';
|
||||
|
||||
@override
|
||||
String get community_showQr => 'Покажи QR код';
|
||||
|
||||
@override
|
||||
String get community_publicChannel => 'Обществено общност';
|
||||
|
||||
@override
|
||||
String get community_hashtagChannel => 'Хаштаг на общността';
|
||||
|
||||
@override
|
||||
String get community_name => 'Име на общността';
|
||||
|
||||
@override
|
||||
String get community_enterName => 'Въведете име на общността';
|
||||
|
||||
@override
|
||||
String community_created(String name) {
|
||||
return 'Общността \"$name\" е създадена';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_joined(String name) {
|
||||
return 'Присъединено общност \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_qrTitle => 'Споделяне в общността';
|
||||
|
||||
@override
|
||||
String community_qrInstructions(String name) {
|
||||
return 'Сканирайте този QR код, за да се присъедините към $name.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_hashtagPrivacyHint =>
|
||||
'Хаштаг каналите на общността са достъпни само за членове на общността';
|
||||
|
||||
@override
|
||||
String get community_invalidQrCode => 'Невалиден QR код на общността';
|
||||
|
||||
@override
|
||||
String get community_alreadyMember => 'Вече съм член';
|
||||
|
||||
@override
|
||||
String community_alreadyMemberMessage(String name) {
|
||||
return 'Вие вече сте член на \"$name\".';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addPublicChannel => 'Добави публичен общностен канал';
|
||||
|
||||
@override
|
||||
String get community_addPublicChannelHint =>
|
||||
'Автоматично добавете публичния канал за тази общност.';
|
||||
|
||||
@override
|
||||
String get community_noCommunities => 'Няма присъединени общности още.';
|
||||
|
||||
@override
|
||||
String get community_scanOrCreate =>
|
||||
'Сканирайте QR код или създайте общност, за да започнете.';
|
||||
|
||||
@override
|
||||
String get community_manageCommunities => 'Управление на общности';
|
||||
|
||||
@override
|
||||
String get community_delete => 'Напусни общността';
|
||||
|
||||
@override
|
||||
String community_deleteConfirm(String name) {
|
||||
return 'Напускате \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleteChannelsWarning(int count) {
|
||||
return 'Това ще изтрие също $count канал(а) и техните съобщения.';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleted(String name) {
|
||||
return 'Остави общността \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Регенерейрай секрет';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Регенерация на секретния ключ за \"$name\"? Всички членове ще трябва да сканират новия QR код, за да продължат комуникацията.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Регенерация';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Секретно презареждане за \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Актуализирай тайна';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Секретно обновено за \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Сканьорвайте новия QR код, за да актуализирате секрета за \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannel => 'Добави общностен хаштаг';
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannelDesc =>
|
||||
'Добавете хаштаг канал за тази общност';
|
||||
|
||||
@override
|
||||
String get community_selectCommunity => 'Изберете общност';
|
||||
|
||||
@override
|
||||
String get community_regularHashtag => 'Обикновен хаштаг';
|
||||
|
||||
@override
|
||||
String get community_regularHashtagDesc =>
|
||||
'Общ хаштаг (всеки може да се присъедини)';
|
||||
|
||||
@override
|
||||
String get community_communityHashtag => 'Общностен хаштаг';
|
||||
|
||||
@override
|
||||
String get community_communityHashtagDesc => 'Само за членове на общността';
|
||||
|
||||
@override
|
||||
String community_forCommunity(String name) {
|
||||
return 'За $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get listFilter_tooltip => 'Филтрирайте и сортирайте';
|
||||
|
||||
|
||||
+425
-136
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get common_cancel => 'Cancel';
|
||||
|
||||
@override
|
||||
String get common_ok => 'OK';
|
||||
|
||||
@override
|
||||
String get common_connect => 'Connect';
|
||||
|
||||
@@ -190,7 +193,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get settings_locationSubtitle => 'GPS coordinates';
|
||||
|
||||
@override
|
||||
String get settings_locationUpdated => 'Location updated';
|
||||
String get settings_locationUpdated => 'Location and GPS settings updated';
|
||||
|
||||
@override
|
||||
String get settings_locationBothRequired =>
|
||||
@@ -199,6 +202,20 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get settings_locationInvalid => 'Invalid latitude or longitude.';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnable => 'GPS Enable';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnableSubtitle =>
|
||||
'Enables GPS to automatically update location.';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalSec => 'Interval for GPS (Seconds)';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalInvalid =>
|
||||
'Interval must be at least 60 seconds, and less than 86400 seconds.';
|
||||
|
||||
@override
|
||||
String get settings_latitude => 'Latitude';
|
||||
|
||||
@@ -641,7 +658,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get contacts_manageRepeater => 'Manage Repeater';
|
||||
|
||||
@override
|
||||
String get contacts_roomLogin => 'Room Login';
|
||||
String get contacts_manageRoom => 'Manage Room Server';
|
||||
|
||||
@override
|
||||
String get contacts_roomLogin => 'Room Server Login';
|
||||
|
||||
@override
|
||||
String get contacts_openChat => 'Open Chat';
|
||||
@@ -818,6 +838,43 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get channels_sortUnread => 'Unread';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannel => 'Create a Private Channel';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannelDesc => 'Secured with a secret key.';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannel => 'Join a Private Channel';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannelDesc => 'Manually enter a secret key.';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannel => 'Join the Public Channel';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannelDesc => 'Anyone can join this channel.';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannel => 'Join a Hashtag Channel';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannelDesc =>
|
||||
'Anyone can join hashtag channels.';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCode => 'Scan a QR Code';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCodeComingSoon => 'Coming soon';
|
||||
|
||||
@override
|
||||
String get channels_enterHashtag => 'Enter hashtag';
|
||||
|
||||
@override
|
||||
String get channels_hashtagHint => 'e.g. #team';
|
||||
|
||||
@override
|
||||
String get chat_noMessages => 'No messages yet';
|
||||
|
||||
@@ -1129,6 +1186,24 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
return 'Unread: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Open Link?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Do you want to open this link in your browser?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Open';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Could not open link: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Invalid link format';
|
||||
|
||||
@override
|
||||
String get map_title => 'Node Map';
|
||||
|
||||
@@ -1402,7 +1477,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get login_repeaterLogin => 'Repeater Login';
|
||||
|
||||
@override
|
||||
String get login_roomLogin => 'Room Login';
|
||||
String get login_roomLogin => 'Room Server Login';
|
||||
|
||||
@override
|
||||
String get login_password => 'Password';
|
||||
@@ -1453,6 +1528,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
return 'Login failed: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get login_failedMessage =>
|
||||
'Login failed. Either the password is incorrect or the repeater is unreachable.';
|
||||
|
||||
@override
|
||||
String get common_reload => 'Reload';
|
||||
|
||||
@@ -1520,6 +1599,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_management => 'Repeater Management';
|
||||
|
||||
@override
|
||||
String get room_management => 'Room Server Management';
|
||||
|
||||
@override
|
||||
String get repeater_managementTools => 'Management Tools';
|
||||
|
||||
@@ -1543,6 +1625,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_cliSubtitle => 'Send commands to the repeater';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Neighbors';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'View zero hop neighbors.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Settings';
|
||||
|
||||
@@ -2216,6 +2304,33 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Received Neighbours Data';
|
||||
|
||||
@override
|
||||
String get neighbors_requestTimedOut => 'Neighbours request timed out.';
|
||||
|
||||
@override
|
||||
String neighbors_errorLoading(String error) {
|
||||
return 'Error loading neighbors: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_repeatersNeighbours => 'Repeaters Neighbours';
|
||||
|
||||
@override
|
||||
String get neighbors_noData => 'No neighbours data available.';
|
||||
|
||||
@override
|
||||
String neighbors_unknownContact(String pubkey) {
|
||||
return 'Unknown $pubkey';
|
||||
}
|
||||
|
||||
@override
|
||||
String neighbors_heardAgo(String time) {
|
||||
return 'Heard: $time ago';
|
||||
}
|
||||
|
||||
@override
|
||||
String get channelPath_title => 'Packet Path';
|
||||
|
||||
@@ -2319,6 +2434,173 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get channelPath_unknownRepeater => 'Unknown Repeater';
|
||||
|
||||
@override
|
||||
String get community_title => 'Community';
|
||||
|
||||
@override
|
||||
String get community_create => 'Create Community';
|
||||
|
||||
@override
|
||||
String get community_createDesc =>
|
||||
'Create a new community and share via QR code.';
|
||||
|
||||
@override
|
||||
String get community_join => 'Join';
|
||||
|
||||
@override
|
||||
String get community_joinTitle => 'Join Community';
|
||||
|
||||
@override
|
||||
String community_joinConfirmation(String name) {
|
||||
return 'Do you want to join the community \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_scanQr => 'Scan Community QR';
|
||||
|
||||
@override
|
||||
String get community_scanInstructions =>
|
||||
'Point the camera at a community QR code';
|
||||
|
||||
@override
|
||||
String get community_showQr => 'Show QR Code';
|
||||
|
||||
@override
|
||||
String get community_publicChannel => 'Community Public';
|
||||
|
||||
@override
|
||||
String get community_hashtagChannel => 'Community Hashtag';
|
||||
|
||||
@override
|
||||
String get community_name => 'Community Name';
|
||||
|
||||
@override
|
||||
String get community_enterName => 'Enter community name';
|
||||
|
||||
@override
|
||||
String community_created(String name) {
|
||||
return 'Community \"$name\" created';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_joined(String name) {
|
||||
return 'Joined community \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_qrTitle => 'Share Community';
|
||||
|
||||
@override
|
||||
String community_qrInstructions(String name) {
|
||||
return 'Scan this QR code to join \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_hashtagPrivacyHint =>
|
||||
'Community hashtag channels are only joinable by members of the community';
|
||||
|
||||
@override
|
||||
String get community_invalidQrCode => 'Invalid community QR code';
|
||||
|
||||
@override
|
||||
String get community_alreadyMember => 'Already a Member';
|
||||
|
||||
@override
|
||||
String community_alreadyMemberMessage(String name) {
|
||||
return 'You are already a member of \"$name\".';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addPublicChannel => 'Add Community Public Channel';
|
||||
|
||||
@override
|
||||
String get community_addPublicChannelHint =>
|
||||
'Automatically add the public channel for this community';
|
||||
|
||||
@override
|
||||
String get community_noCommunities => 'No communities joined yet';
|
||||
|
||||
@override
|
||||
String get community_scanOrCreate =>
|
||||
'Scan a QR code or create a community to get started';
|
||||
|
||||
@override
|
||||
String get community_manageCommunities => 'Manage Communities';
|
||||
|
||||
@override
|
||||
String get community_delete => 'Leave Community';
|
||||
|
||||
@override
|
||||
String community_deleteConfirm(String name) {
|
||||
return 'Leave \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleteChannelsWarning(int count) {
|
||||
return 'This will also delete $count channel(s) and their messages.';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleted(String name) {
|
||||
return 'Left community \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerate Secret';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerate the secret key for \"$name\"? All members will need to scan the new QR code to continue communicating.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerate';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Secret regenerated for \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Update Secret';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Secret updated for \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scan the new QR code to update the secret for \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannel => 'Add Community Hashtag';
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannelDesc =>
|
||||
'Add a hashtag channel for this community';
|
||||
|
||||
@override
|
||||
String get community_selectCommunity => 'Select Community';
|
||||
|
||||
@override
|
||||
String get community_regularHashtag => 'Regular Hashtag';
|
||||
|
||||
@override
|
||||
String get community_regularHashtagDesc => 'Public hashtag (anyone can join)';
|
||||
|
||||
@override
|
||||
String get community_communityHashtag => 'Community Hashtag';
|
||||
|
||||
@override
|
||||
String get community_communityHashtagDesc => 'Private to community members';
|
||||
|
||||
@override
|
||||
String community_forCommunity(String name) {
|
||||
return 'For $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get listFilter_tooltip => 'Filter and sort';
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get common_cancel => 'Cancelar';
|
||||
|
||||
@override
|
||||
String get common_ok => 'De acuerdo';
|
||||
|
||||
@override
|
||||
String get common_connect => 'Conectar';
|
||||
|
||||
@@ -200,6 +203,20 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get settings_locationInvalid => 'Latitud o longitud inválidos.';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnable => 'Habilitar GPS';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnableSubtitle =>
|
||||
'Habilita la actualización automática de la ubicación mediante GPS.';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalSec => 'Intervalo para GPS (Segundos)';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalInvalid =>
|
||||
'El intervalo debe ser de al menos 60 segundos y menor que 86400 segundos.';
|
||||
|
||||
@override
|
||||
String get settings_latitude => 'Latitud';
|
||||
|
||||
@@ -648,6 +665,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_manageRepeater => 'Gestionar Repetidor';
|
||||
|
||||
@override
|
||||
String get contacts_manageRoom => 'Gestionar Servidor de Habitación';
|
||||
|
||||
@override
|
||||
String get contacts_roomLogin => 'Inicio de Sala';
|
||||
|
||||
@@ -829,6 +849,46 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get channels_sortUnread => 'Sin leer';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannel => 'Crear un Canal Privado';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannelDesc =>
|
||||
'Cifrado con una clave secreta.';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannel => 'Únete a un Canal Privado';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannelDesc =>
|
||||
'Introducir manualmente una clave secreta.';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannel => 'Únete al Canal Público';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannelDesc =>
|
||||
'Cualquiera puede unirse a este canal.';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannel => 'Únete a un Canal con Hashtag';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannelDesc =>
|
||||
'Cualquiera puede unirse a los canales de hashtag.';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCode => 'Escanear un Código QR';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCodeComingSoon => 'Próximamente';
|
||||
|
||||
@override
|
||||
String get channels_enterHashtag => 'Introducir hashtag';
|
||||
|
||||
@override
|
||||
String get channels_hashtagHint => 'ej. #equipo';
|
||||
|
||||
@override
|
||||
String get chat_noMessages => 'Aún no hay mensajes';
|
||||
|
||||
@@ -1144,6 +1204,24 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
return 'Sin leer: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => '¿Abrir enlace?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'¿Quiere abrir este enlace en su navegador?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Abrir';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'No se pudo abrir el enlace: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Formato de enlace no válido';
|
||||
|
||||
@override
|
||||
String get map_title => 'Mapa de Nodos';
|
||||
|
||||
@@ -1472,6 +1550,10 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
return 'Inicio fallido: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get login_failedMessage =>
|
||||
'Inicio fallido. La contraseña es incorrecta o el repetidor no está disponible.';
|
||||
|
||||
@override
|
||||
String get common_reload => 'Recargar';
|
||||
|
||||
@@ -1541,6 +1623,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_management => 'Gestión de Repetidores';
|
||||
|
||||
@override
|
||||
String get room_management => 'Administración del Servidor de Habitación';
|
||||
|
||||
@override
|
||||
String get repeater_managementTools => 'Herramientas de Gestión';
|
||||
|
||||
@@ -1564,6 +1649,12 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_cliSubtitle => 'Enviar comandos al repetidor';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Vecinos';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'Ver vecinos de salto cero.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Configuración';
|
||||
|
||||
@@ -2248,6 +2339,34 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Recibidas Datos de Vecinos';
|
||||
|
||||
@override
|
||||
String get neighbors_requestTimedOut =>
|
||||
'Los vecinos solicitan que se desconecte.';
|
||||
|
||||
@override
|
||||
String neighbors_errorLoading(String error) {
|
||||
return 'Error al cargar vecinos: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_repeatersNeighbours => 'Repetidores Vecinos';
|
||||
|
||||
@override
|
||||
String get neighbors_noData => 'No hay datos de vecinos disponibles.';
|
||||
|
||||
@override
|
||||
String neighbors_unknownContact(String pubkey) {
|
||||
return 'Clave pública desconocida $pubkey';
|
||||
}
|
||||
|
||||
@override
|
||||
String neighbors_heardAgo(String time) {
|
||||
return 'Escuchado: $time hace atrás';
|
||||
}
|
||||
|
||||
@override
|
||||
String get channelPath_title => 'Ruta del Paquete';
|
||||
|
||||
@@ -2351,6 +2470,176 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get channelPath_unknownRepeater => 'Repetidor Desconocido';
|
||||
|
||||
@override
|
||||
String get community_title => 'Comunidad';
|
||||
|
||||
@override
|
||||
String get community_create => 'Crear Comunidad';
|
||||
|
||||
@override
|
||||
String get community_createDesc =>
|
||||
'Crear una nueva comunidad y compartir a través de código QR.';
|
||||
|
||||
@override
|
||||
String get community_join => 'Únete';
|
||||
|
||||
@override
|
||||
String get community_joinTitle => 'Únete a la comunidad';
|
||||
|
||||
@override
|
||||
String community_joinConfirmation(String name) {
|
||||
return '¿Quieres unirte a la comunidad \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_scanQr => 'Escanear Código QR de la Comunidad';
|
||||
|
||||
@override
|
||||
String get community_scanInstructions =>
|
||||
'Apunte la cámara a un código QR de la comunidad';
|
||||
|
||||
@override
|
||||
String get community_showQr => 'Mostrar Código QR';
|
||||
|
||||
@override
|
||||
String get community_publicChannel => 'Comunidad Pública';
|
||||
|
||||
@override
|
||||
String get community_hashtagChannel => 'Hashtag de la Comunidad';
|
||||
|
||||
@override
|
||||
String get community_name => 'Nombre de la comunidad';
|
||||
|
||||
@override
|
||||
String get community_enterName => 'Introducir nombre de comunidad';
|
||||
|
||||
@override
|
||||
String community_created(String name) {
|
||||
return 'Comunidad \"$name\" creada';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_joined(String name) {
|
||||
return 'Se unió a la comunidad \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_qrTitle => 'Compartir Comunidad';
|
||||
|
||||
@override
|
||||
String community_qrInstructions(String name) {
|
||||
return 'Escanear este código QR para unirte a $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_hashtagPrivacyHint =>
|
||||
'Los canales de hashtag de la comunidad solo son accesibles para los miembros de la comunidad';
|
||||
|
||||
@override
|
||||
String get community_invalidQrCode => 'Código QR de comunidad no válido';
|
||||
|
||||
@override
|
||||
String get community_alreadyMember => 'Ya eres Miembro';
|
||||
|
||||
@override
|
||||
String community_alreadyMemberMessage(String name) {
|
||||
return 'Ya eres miembro de \"$name\".';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addPublicChannel =>
|
||||
'Añadir Canal Público de la Comunidad';
|
||||
|
||||
@override
|
||||
String get community_addPublicChannelHint =>
|
||||
'Añade automáticamente el canal público para esta comunidad.';
|
||||
|
||||
@override
|
||||
String get community_noCommunities => 'Aún no se han unido comunidades.';
|
||||
|
||||
@override
|
||||
String get community_scanOrCreate =>
|
||||
'Escanear un código QR o crear una comunidad para comenzar';
|
||||
|
||||
@override
|
||||
String get community_manageCommunities => 'Gestionar Comunidades';
|
||||
|
||||
@override
|
||||
String get community_delete => 'Salir de la Comunidad';
|
||||
|
||||
@override
|
||||
String community_deleteConfirm(String name) {
|
||||
return '¿Salir de \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleteChannelsWarning(int count) {
|
||||
return 'Esto también eliminará $count canal(es) y sus mensajes.';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleted(String name) {
|
||||
return 'Has salido de la comunidad \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerar Contraseña Secreta';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerar la clave secreta para \"$name\"? Todos los miembros deberán escanear el nuevo código QR para seguir comunicándose.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerar';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Código secreto regenerado para \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Actualizar Contraseña';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Confidencialidad actualizada para \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Escanear el nuevo código QR para actualizar el secreto de \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannel => 'Añadir Hashtag de la Comunidad';
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannelDesc =>
|
||||
'Añadir un canal con hashtag para esta comunidad';
|
||||
|
||||
@override
|
||||
String get community_selectCommunity => 'Seleccionar Comunidad';
|
||||
|
||||
@override
|
||||
String get community_regularHashtag => 'Etiqueta de Hashtag Regular';
|
||||
|
||||
@override
|
||||
String get community_regularHashtagDesc =>
|
||||
'Hashtag público (cualquiera puede unirse)';
|
||||
|
||||
@override
|
||||
String get community_communityHashtag => 'Hashtag de la Comunidad';
|
||||
|
||||
@override
|
||||
String get community_communityHashtagDesc =>
|
||||
'Exclusivo para miembros de la comunidad';
|
||||
|
||||
@override
|
||||
String community_forCommunity(String name) {
|
||||
return 'Para $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get listFilter_tooltip => 'Filtrar y ordenar';
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get common_cancel => 'Annuler';
|
||||
|
||||
@override
|
||||
String get common_ok => 'OK';
|
||||
|
||||
@override
|
||||
String get common_connect => 'Connecter';
|
||||
|
||||
@@ -200,6 +203,21 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get settings_locationInvalid => 'Latitude ou longitude invalide.';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnable => 'Activer le GPS';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnableSubtitle =>
|
||||
'Activer la mise à jour automatique de la position via GPS';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalSec =>
|
||||
'Intervalle de mise-à-jour du GPS (Secondes)';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalInvalid =>
|
||||
'L\'intervalle doit être compris entre 60 et 86400 secondes.';
|
||||
|
||||
@override
|
||||
String get settings_latitude => 'Latitude';
|
||||
|
||||
@@ -211,11 +229,11 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get settings_privacyModeSubtitle =>
|
||||
'Cacher le nom/l\'emplacement dans les publicités';
|
||||
'Cacher le nom/l\'emplacement dans les annonces';
|
||||
|
||||
@override
|
||||
String get settings_privacyModeToggle =>
|
||||
'Activer le mode confidentialité pour masquer votre nom et votre localisation dans les publicités.';
|
||||
'Activer le mode confidentialité pour masquer votre nom et votre localisation dans les annonces.';
|
||||
|
||||
@override
|
||||
String get settings_privacyModeEnabled => 'Mode de confidentialité activé';
|
||||
@@ -228,7 +246,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get settings_actions => 'Actions';
|
||||
|
||||
@override
|
||||
String get settings_sendAdvertisement => 'Envoyer la publicité';
|
||||
String get settings_sendAdvertisement => 'S\'annoncer';
|
||||
|
||||
@override
|
||||
String get settings_sendAdvertisementSubtitle =>
|
||||
@@ -255,7 +273,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
'Recharger la liste des contacts depuis l\'appareil';
|
||||
|
||||
@override
|
||||
String get settings_rebootDevice => 'Réinitialiser l\'appareil';
|
||||
String get settings_rebootDevice => 'Redémarrer l\'appareil';
|
||||
|
||||
@override
|
||||
String get settings_rebootDeviceSubtitle => 'Redémarrer l\'appareil MeshCore';
|
||||
@@ -438,7 +456,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get appSettings_enableNotificationsSubtitle =>
|
||||
'Recevoir des notifications pour les messages et les publicités';
|
||||
'Recevoir des notifications pour les messages et les annonces';
|
||||
|
||||
@override
|
||||
String get appSettings_notificationPermissionDenied =>
|
||||
@@ -467,7 +485,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get appSettings_advertisementNotifications =>
|
||||
'Notifications publicitaires';
|
||||
'Notifications d\'annonces';
|
||||
|
||||
@override
|
||||
String get appSettings_advertisementNotificationsSubtitle =>
|
||||
@@ -498,7 +516,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get appSettings_autoRouteRotationSubtitle =>
|
||||
'Alterner entre les meilleurs chemins et le mode inondation';
|
||||
'Alterner entre les meilleurs chemins et le mode d\'envoi sur tout le réseau (flood)';
|
||||
|
||||
@override
|
||||
String get appSettings_autoRouteRotationEnabled =>
|
||||
@@ -649,6 +667,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_manageRepeater => 'Gérer le répétiteur';
|
||||
|
||||
@override
|
||||
String get contacts_manageRoom => 'Gérer le Room Server';
|
||||
|
||||
@override
|
||||
String get contacts_roomLogin => 'Connexion Salle';
|
||||
|
||||
@@ -667,7 +688,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get contacts_newGroup => 'Nouvelle Groupe';
|
||||
String get contacts_newGroup => 'Nouveau Groupe';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Nom du groupe';
|
||||
@@ -691,27 +712,27 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get contacts_noMembers => 'Aucun membre';
|
||||
|
||||
@override
|
||||
String get contacts_lastSeenNow => 'Dernière fois vu maintenant';
|
||||
String get contacts_lastSeenNow => 'Vu maintenant';
|
||||
|
||||
@override
|
||||
String contacts_lastSeenMinsAgo(int minutes) {
|
||||
return 'Dernière fois vu il y a $minutes minutes.';
|
||||
return 'Vu il y a $minutes minutes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contacts_lastSeenHourAgo => 'Dernière fois vu il y a 1 heure.';
|
||||
String get contacts_lastSeenHourAgo => 'Vu il y a 1 heure';
|
||||
|
||||
@override
|
||||
String contacts_lastSeenHoursAgo(int hours) {
|
||||
return 'Dernière fois vu il y a $hours heures.';
|
||||
return 'Vu il y a $hours heures';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contacts_lastSeenDayAgo => 'Dernière fois vu il y a 1 jour';
|
||||
String get contacts_lastSeenDayAgo => 'Vu il y a 1 jour';
|
||||
|
||||
@override
|
||||
String contacts_lastSeenDaysAgo(int days) {
|
||||
return 'Dernière activité il y a $days jours';
|
||||
return 'Vu il y a $days jours';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -825,11 +846,51 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get channels_sortAZ => 'A à Z';
|
||||
|
||||
@override
|
||||
String get channels_sortLatestMessages => 'Dernières messages';
|
||||
String get channels_sortLatestMessages => 'Derniers messages';
|
||||
|
||||
@override
|
||||
String get channels_sortUnread => 'Non lu';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannel => 'Créer un Canal Privé';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannelDesc =>
|
||||
'Sécurisé avec une clé secrète.';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannel => 'Rejoindre un Canal Privé';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannelDesc =>
|
||||
'Entrer manuellement une clé secrète.';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannel => 'Rejoindre le canal public';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannelDesc =>
|
||||
'Tout le monde peut rejoindre ce canal.';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannel => 'Rejoindre un Canal Hashtag';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannelDesc =>
|
||||
'N\'importe qui peut rejoindre les canaux #hashtag.';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCode => 'Scanner un code QR';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCodeComingSoon => 'Bientôt disponible';
|
||||
|
||||
@override
|
||||
String get channels_enterHashtag => 'Entrez le hashtag';
|
||||
|
||||
@override
|
||||
String get channels_hashtagHint => 'ex. #equipe';
|
||||
|
||||
@override
|
||||
String get chat_noMessages => 'Aucun message pour le moment.';
|
||||
|
||||
@@ -876,7 +937,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String chat_retryCount(int current, int max) {
|
||||
return 'Réessayer $current/$max';
|
||||
return 'Essai $current/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1016,7 +1077,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get chat_autoUseSavedPath => 'Auto (utiliser le chemin sauvegardé)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Mode Inondation Forcée';
|
||||
String get chat_forceFloodMode => 'Mode tout le réseau forcé';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths =>
|
||||
@@ -1080,7 +1141,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'Le mode inondation est activé. Réactiver via l\'icône de routage dans la barre d\'outils.';
|
||||
'Le mode envoi à tout le réseau est activé. Changer via l\'icône de routage dans la barre d\'outils.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Chemin complet';
|
||||
@@ -1125,7 +1186,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
'Compresser les messages sortants';
|
||||
|
||||
@override
|
||||
String get chat_floodForced => 'Inondation (forcée)';
|
||||
String get chat_floodForced => 'Tout le réseau (forcée)';
|
||||
|
||||
@override
|
||||
String get chat_directForced => 'Direct (forcé)';
|
||||
@@ -1136,7 +1197,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_floodAuto => 'Inondation (auto)';
|
||||
String get chat_floodAuto => 'Tout le réseau (auto)';
|
||||
|
||||
@override
|
||||
String get chat_direct => 'Afficher';
|
||||
@@ -1149,6 +1210,24 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
return 'Non lu : $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Ouvrir le lien ?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Voulez-vous ouvrir ce lien dans votre navigateur ?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Ouvrir';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Impossible d\'ouvrir le lien : $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Format de lien invalide';
|
||||
|
||||
@override
|
||||
String get map_title => 'Carte des nœuds';
|
||||
|
||||
@@ -1311,7 +1390,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String mapCache_cachedTilesWithFailed(int downloaded, int failed) {
|
||||
return 'Tiles mis en cache ($downloaded) ($failed ratés)';
|
||||
return 'Tuiles mis en cache ($downloaded) ($failed ratés)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1365,11 +1444,11 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String east,
|
||||
String west,
|
||||
) {
|
||||
return 'N $north, S $south, E $east, W $west';
|
||||
return 'N $north, S $south, E $east, O $west';
|
||||
}
|
||||
|
||||
@override
|
||||
String get time_justNow => 'Il y a tout juste maintenant';
|
||||
String get time_justNow => 'Maintenant';
|
||||
|
||||
@override
|
||||
String time_minutesAgo(int minutes) {
|
||||
@@ -1460,7 +1539,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get login_autoUseSavedPath => 'Auto (utiliser le chemin sauvegardé)';
|
||||
|
||||
@override
|
||||
String get login_forceFloodMode => 'Mode Inondation Forcée';
|
||||
String get login_forceFloodMode => 'Mode tout le réseau forcé';
|
||||
|
||||
@override
|
||||
String get login_managePaths => 'Gérer les chemins';
|
||||
@@ -1478,6 +1557,10 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
return 'Connexion échouée : $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get login_failedMessage =>
|
||||
'Connexion échouée. Soit le mot de passe est incorrect, soit le relais est injoignable.';
|
||||
|
||||
@override
|
||||
String get common_reload => 'Recharger';
|
||||
|
||||
@@ -1547,6 +1630,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_management => 'Gestion des répétiteurs';
|
||||
|
||||
@override
|
||||
String get room_management => 'Administración del Servidor de Habitación';
|
||||
|
||||
@override
|
||||
String get repeater_managementTools => 'Outils de Gestion';
|
||||
|
||||
@@ -1570,6 +1656,13 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_cliSubtitle => 'Envoyer des commandes au répétiteur';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Voisins';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle =>
|
||||
'Afficher les voisins de saut nuls.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Paramètres';
|
||||
|
||||
@@ -1588,7 +1681,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
'Auto (utiliser le chemin sauvegardé)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Mode de submersion forcée';
|
||||
String get repeater_forceFloodMode => 'Mode tout le réseau forcé';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Gestion des chemins';
|
||||
@@ -1651,7 +1744,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get repeater_received => 'Reçu';
|
||||
|
||||
@override
|
||||
String get repeater_duplicates => 'Dupliques';
|
||||
String get repeater_duplicates => 'Doublons';
|
||||
|
||||
@override
|
||||
String repeater_daysHoursMinsSecs(
|
||||
@@ -1665,17 +1758,17 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String repeater_packetTxTotal(int total, String flood, String direct) {
|
||||
return 'Total : $total, Inondation : $flood, Direct : $direct';
|
||||
return 'Total : $total, Tout le réseau : $flood, Direct : $direct';
|
||||
}
|
||||
|
||||
@override
|
||||
String repeater_packetRxTotal(int total, String flood, String direct) {
|
||||
return 'Total : $total, Inondation : $flood, Direct : $direct';
|
||||
return 'Total : $total, Tout le réseau : $flood, Direct : $direct';
|
||||
}
|
||||
|
||||
@override
|
||||
String repeater_duplicatesFloodDirect(String flood, String direct) {
|
||||
return 'Inondation : $flood, Direct : $direct';
|
||||
return 'Tout le réseau : $flood, Direct : $direct';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1771,13 +1864,14 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_privacyModeSubtitle =>
|
||||
'Cacher le nom/l\'emplacement dans les publicités';
|
||||
'Cacher le nom/l\'emplacement dans les annonces';
|
||||
|
||||
@override
|
||||
String get repeater_advertisementSettings => 'Paramètres de Publicité';
|
||||
String get repeater_advertisementSettings => 'Paramètres d\'annonces';
|
||||
|
||||
@override
|
||||
String get repeater_localAdvertInterval => 'Intervalle Publicité Locale';
|
||||
String get repeater_localAdvertInterval =>
|
||||
'Intervalle des annonces Locale (0 saut)';
|
||||
|
||||
@override
|
||||
String repeater_localAdvertIntervalMinutes(int minutes) {
|
||||
@@ -1786,7 +1880,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_floodAdvertInterval =>
|
||||
'Intervalle de Publicité Inondation';
|
||||
'Intervalle des annonces à tout le réseau (flood)';
|
||||
|
||||
@override
|
||||
String repeater_floodAdvertIntervalHours(int hours) {
|
||||
@@ -1795,10 +1889,10 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_encryptedAdvertInterval =>
|
||||
'Intervalle publicitaire crypté';
|
||||
'Intervalle d\'annonces cryptées';
|
||||
|
||||
@override
|
||||
String get repeater_dangerZone => 'Zone d\'alerte';
|
||||
String get repeater_dangerZone => 'Zone dangereuse';
|
||||
|
||||
@override
|
||||
String get repeater_rebootRepeater => 'Redémarrer Répéteur';
|
||||
@@ -1886,7 +1980,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_refreshAdvertisementSettings =>
|
||||
'Rafraîchir les Paramètres de la Publicité';
|
||||
'Rafraîchir les Paramètres des annonces';
|
||||
|
||||
@override
|
||||
String repeater_refreshed(String label) {
|
||||
@@ -1960,7 +2054,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get repeater_cliQuickClock => 'Horloge';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Envoie un paquet publicitaire';
|
||||
String get repeater_cliHelpAdvert => 'Envoie un paquet d\'annonce';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpReboot =>
|
||||
@@ -1995,7 +2089,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetAllowReadOnly =>
|
||||
'(Serveur de pièce) Si \"activé\", alors un mot de passe vide permettra la connexion, mais ne permettra pas de publier dans la pièce. (lecture seule uniquement)';
|
||||
'(Room server) Si \"activé\", alors un mot de passe vide permettra la connexion, mais ne permettra pas de publier dans la pièce. (lecture seule uniquement)';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetFloodMax =>
|
||||
@@ -2026,7 +2120,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
'Définit/met à jour le mot de passe de l\'invité. (pour les répéteurs, les connexions d\'invités peuvent envoyer la requête \"Get Stats\")';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetName => 'Définit le nom de la publicité.';
|
||||
String get repeater_cliHelpSetName => 'Définit le nom de l\'annonce.';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetLat =>
|
||||
@@ -2046,7 +2140,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetTxDelay =>
|
||||
'Définit un facteur multiplié par le temps de fonctionnement en mode inondation pour un paquet et avec un système de slot aléatoire, afin de retarder son envoi (pour diminuer la probabilité de collisions).';
|
||||
'Définit un facteur multiplié par le temps de fonctionnement en mode vers tout le réseau (flood) pour un paquet et avec un système de slot aléatoire, afin de retarder son envoi (pour diminuer la probabilité de collisions).';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetDirectTxDelay =>
|
||||
@@ -2101,7 +2195,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpNeighbors =>
|
||||
'Affiche une liste d\'autres nœuds répétiteurs entendus via des publicités sans relais. Chaque ligne est id-préfixe-hexadécimal:timestamp:snr-fois-4';
|
||||
'Affiche une liste d\'autres nœuds répétiteurs entendus via des annonces sans relais. Chaque ligne est id-préfixe-hexadécimal:timestamp:snr-fois-4';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpNeighborRemove =>
|
||||
@@ -2109,7 +2203,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpRegion =>
|
||||
'(série uniquement) Liste toutes les régions définies et les autorisations de débordement actuelles.';
|
||||
'(série uniquement) Liste toutes les régions définies et les autorisations actuelles d\'annonces sur tout le réseau (flood).';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpRegionLoad =>
|
||||
@@ -2261,6 +2355,34 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Données des voisins reçues';
|
||||
|
||||
@override
|
||||
String get neighbors_requestTimedOut => 'Les voisins demandent un délai.';
|
||||
|
||||
@override
|
||||
String neighbors_errorLoading(String error) {
|
||||
return 'Erreur lors du chargement des voisins : $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_repeatersNeighbours => 'Répéteurs Voisins';
|
||||
|
||||
@override
|
||||
String get neighbors_noData =>
|
||||
'Aucune donnée concernant les voisins disponible.';
|
||||
|
||||
@override
|
||||
String neighbors_unknownContact(String pubkey) {
|
||||
return 'Clé publique inconnue $pubkey';
|
||||
}
|
||||
|
||||
@override
|
||||
String neighbors_heardAgo(String time) {
|
||||
return 'Écouté : $time auparavant';
|
||||
}
|
||||
|
||||
@override
|
||||
String get channelPath_title => 'Chemin de paquet';
|
||||
|
||||
@@ -2319,7 +2441,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get channelPath_unknownPath => 'Inconnu';
|
||||
|
||||
@override
|
||||
String get channelPath_floodPath => 'Inondation';
|
||||
String get channelPath_floodPath => 'Tout le réseau';
|
||||
|
||||
@override
|
||||
String get channelPath_directPath => 'Afficher';
|
||||
@@ -2364,6 +2486,177 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get channelPath_unknownRepeater => 'Répéteur Inconnu';
|
||||
|
||||
@override
|
||||
String get community_title => 'Communauté';
|
||||
|
||||
@override
|
||||
String get community_create => 'Créer une Communauté';
|
||||
|
||||
@override
|
||||
String get community_createDesc =>
|
||||
'Créer une nouvelle communauté et la partager via QR code.';
|
||||
|
||||
@override
|
||||
String get community_join => 'Rejoindre';
|
||||
|
||||
@override
|
||||
String get community_joinTitle => 'Rejoindre la communauté';
|
||||
|
||||
@override
|
||||
String community_joinConfirmation(String name) {
|
||||
return 'Souhaitez-vous rejoindre la communauté \"$name\" ?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_scanQr => 'Scanner la communauté QR';
|
||||
|
||||
@override
|
||||
String get community_scanInstructions =>
|
||||
'Pointez l\'appareil photo vers un code QR communautaire.';
|
||||
|
||||
@override
|
||||
String get community_showQr => 'Afficher le QR Code';
|
||||
|
||||
@override
|
||||
String get community_publicChannel => 'Communauté Publique';
|
||||
|
||||
@override
|
||||
String get community_hashtagChannel => 'Hashtag Communauté';
|
||||
|
||||
@override
|
||||
String get community_name => 'Nom de la communauté';
|
||||
|
||||
@override
|
||||
String get community_enterName => 'Entrez le nom de la communauté';
|
||||
|
||||
@override
|
||||
String community_created(String name) {
|
||||
return 'Communauté \"$name\" créée';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_joined(String name) {
|
||||
return 'Rejoint la communauté \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_qrTitle => 'Partager Communauté';
|
||||
|
||||
@override
|
||||
String community_qrInstructions(String name) {
|
||||
return 'Scanner ce QR code pour rejoindre $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_hashtagPrivacyHint =>
|
||||
'Les canaux hashtag de la communauté ne sont accessibles qu\'aux membres de la communauté';
|
||||
|
||||
@override
|
||||
String get community_invalidQrCode => 'Code QR de communauté non valide';
|
||||
|
||||
@override
|
||||
String get community_alreadyMember => 'Déjà membre';
|
||||
|
||||
@override
|
||||
String community_alreadyMemberMessage(String name) {
|
||||
return 'Vous êtes déjà membre de \"$name\".';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addPublicChannel =>
|
||||
'Ajouter un Canal Public de la Communauté';
|
||||
|
||||
@override
|
||||
String get community_addPublicChannelHint =>
|
||||
'Ajouter automatiquement le canal public pour cette communauté';
|
||||
|
||||
@override
|
||||
String get community_noCommunities =>
|
||||
'Aucun groupe n\'a été rejoint pour le moment.';
|
||||
|
||||
@override
|
||||
String get community_scanOrCreate =>
|
||||
'Scanner un code QR ou créer une communauté pour commencer';
|
||||
|
||||
@override
|
||||
String get community_manageCommunities => 'Gérer les Communautés';
|
||||
|
||||
@override
|
||||
String get community_delete => 'Quitter la communauté';
|
||||
|
||||
@override
|
||||
String community_deleteConfirm(String name) {
|
||||
return 'Quitter \"$name\" ?';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleteChannelsWarning(int count) {
|
||||
return 'Cela supprimera également $count canal/canaux et leurs messages.';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleted(String name) {
|
||||
return 'Communauté \"$name\" quittée';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Régénérer le secret';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Régénérer la clé secrète pour \"$name\" ? Tous les membres devront scanner le nouveau code QR pour continuer à communiquer.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Régénérer';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Mot de passe secret régénéré pour \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Mettre à jour le secret';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Modification secrète mise à jour pour \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scanner le nouveau code QR pour mettre à jour le mot de passe pour \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannel => 'Ajouter un Hashtag Communauté';
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannelDesc =>
|
||||
'Ajouter un canal hashtag pour cette communauté';
|
||||
|
||||
@override
|
||||
String get community_selectCommunity => 'Sélectionner Communauté';
|
||||
|
||||
@override
|
||||
String get community_regularHashtag => 'Hashtag régulier';
|
||||
|
||||
@override
|
||||
String get community_regularHashtagDesc =>
|
||||
'Hashtag public (tout le monde peut rejoindre)';
|
||||
|
||||
@override
|
||||
String get community_communityHashtag => 'Hashtag de la communauté';
|
||||
|
||||
@override
|
||||
String get community_communityHashtagDesc =>
|
||||
'Exclusif aux membres de la communauté';
|
||||
|
||||
@override
|
||||
String community_forCommunity(String name) {
|
||||
return 'Pour $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get listFilter_tooltip => 'Filtrer et trier';
|
||||
|
||||
@@ -2371,7 +2664,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get listFilter_sortBy => 'Trier par';
|
||||
|
||||
@override
|
||||
String get listFilter_latestMessages => 'Dernières messages';
|
||||
String get listFilter_latestMessages => 'Derniers messages';
|
||||
|
||||
@override
|
||||
String get listFilter_heardRecently => 'Écoute récemment';
|
||||
@@ -2392,11 +2685,11 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get listFilter_repeaters => 'Répéteurs';
|
||||
|
||||
@override
|
||||
String get listFilter_roomServers => 'Serveurs de pièce';
|
||||
String get listFilter_roomServers => 'Room servers';
|
||||
|
||||
@override
|
||||
String get listFilter_unreadOnly => 'Messages non lus seulement';
|
||||
|
||||
@override
|
||||
String get listFilter_newGroup => 'Nouvelle groupe';
|
||||
String get listFilter_newGroup => 'Nouveau groupe';
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get common_cancel => 'Annulla';
|
||||
|
||||
@override
|
||||
String get common_ok => 'OK';
|
||||
|
||||
@override
|
||||
String get common_connect => 'Connetti';
|
||||
|
||||
@@ -200,6 +203,20 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get settings_locationInvalid => 'Latitudine o longitudine non valida.';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnable => 'Abilita GPS';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnableSubtitle =>
|
||||
'Abilita l\'aggiornamento automatico della posizione tramite GPS.';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalSec => 'Intervallo GPS (Secondi)';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalInvalid =>
|
||||
'L\'intervallo deve essere di almeno 60 secondi e inferiore a 86400 secondi.';
|
||||
|
||||
@override
|
||||
String get settings_latitude => 'Latitudine';
|
||||
|
||||
@@ -646,6 +663,9 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_manageRepeater => 'Gestisci Ripetitore';
|
||||
|
||||
@override
|
||||
String get contacts_manageRoom => 'Gestisci Server Camera';
|
||||
|
||||
@override
|
||||
String get contacts_roomLogin => 'Login Camera';
|
||||
|
||||
@@ -827,6 +847,46 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get channels_sortUnread => 'Non letto';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannel => 'Crea un Canale Privato';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannelDesc =>
|
||||
'Protetta con una chiave segreta.';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannel => 'Unisciti a un Canale Privato';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannelDesc =>
|
||||
'Inserire manualmente una chiave segreta.';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannel => 'Unisciti al Canale Pubblico';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannelDesc =>
|
||||
'Chiunque può unirsi a questo canale.';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannel => 'Unisciti a un Canale con Hashtag';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannelDesc =>
|
||||
'Chiunque può unirsi ai canali hashtag.';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCode => 'Scansiona un codice QR';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCodeComingSoon => 'Arriverà presto';
|
||||
|
||||
@override
|
||||
String get channels_enterHashtag => 'Inserisci hashtag';
|
||||
|
||||
@override
|
||||
String get channels_hashtagHint => 'es. #team';
|
||||
|
||||
@override
|
||||
String get chat_noMessages => 'Nessun messaggio ancora';
|
||||
|
||||
@@ -1143,6 +1203,24 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
return 'Non letti: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Aprire il link?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Vuoi aprire questo link nel tuo browser?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Apri';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Impossibile aprire il link: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Formato di link non valido';
|
||||
|
||||
@override
|
||||
String get map_title => 'Mappa Nodi';
|
||||
|
||||
@@ -1470,6 +1548,10 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
return 'Accesso fallito: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get login_failedMessage =>
|
||||
'Accesso fallito. La password non è corretta oppure il ripetitore non è raggiungibile.';
|
||||
|
||||
@override
|
||||
String get common_reload => 'Ricaricare';
|
||||
|
||||
@@ -1539,6 +1621,9 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_management => 'Gestione Ripetitori';
|
||||
|
||||
@override
|
||||
String get room_management => 'Gestione del Server di Camera';
|
||||
|
||||
@override
|
||||
String get repeater_managementTools => 'Strumenti di Gestione';
|
||||
|
||||
@@ -1562,6 +1647,13 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_cliSubtitle => 'Invia comandi al ripetitore';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Vicini';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle =>
|
||||
'Visualizza vicini di salto pari a zero.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Impostazioni';
|
||||
|
||||
@@ -2248,6 +2340,33 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Ricevute dati vicini';
|
||||
|
||||
@override
|
||||
String get neighbors_requestTimedOut => 'I vicini richiedono un timeout.';
|
||||
|
||||
@override
|
||||
String neighbors_errorLoading(String error) {
|
||||
return 'Errore nel caricamento dei vicini: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_repeatersNeighbours => 'Ripetitori Vicini';
|
||||
|
||||
@override
|
||||
String get neighbors_noData => 'Nessun dato sugli vicini disponibile.';
|
||||
|
||||
@override
|
||||
String neighbors_unknownContact(String pubkey) {
|
||||
return 'Chiave pubblica sconosciuta $pubkey';
|
||||
}
|
||||
|
||||
@override
|
||||
String neighbors_heardAgo(String time) {
|
||||
return 'Sentito: $time fa';
|
||||
}
|
||||
|
||||
@override
|
||||
String get channelPath_title => 'Percorso Pacchetto';
|
||||
|
||||
@@ -2351,6 +2470,176 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get channelPath_unknownRepeater => 'Ripetitore sconosciuto';
|
||||
|
||||
@override
|
||||
String get community_title => 'Comunità';
|
||||
|
||||
@override
|
||||
String get community_create => 'Crea Comunità';
|
||||
|
||||
@override
|
||||
String get community_createDesc =>
|
||||
'Crea una nuova comunità e condividila tramite codice QR.';
|
||||
|
||||
@override
|
||||
String get community_join => 'Unisciti';
|
||||
|
||||
@override
|
||||
String get community_joinTitle => 'Unisciti alla Community';
|
||||
|
||||
@override
|
||||
String community_joinConfirmation(String name) {
|
||||
return 'Vuoi unirti alla community \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_scanQr => 'Scansiona il QR Code della Community';
|
||||
|
||||
@override
|
||||
String get community_scanInstructions =>
|
||||
'Punta la fotocamera su un codice QR della comunità';
|
||||
|
||||
@override
|
||||
String get community_showQr => 'Mostra il codice QR';
|
||||
|
||||
@override
|
||||
String get community_publicChannel => 'Comunità Pubblica';
|
||||
|
||||
@override
|
||||
String get community_hashtagChannel => 'Hashtag della Comunità';
|
||||
|
||||
@override
|
||||
String get community_name => 'Nome della Comunità';
|
||||
|
||||
@override
|
||||
String get community_enterName => 'Inserisci il nome della comunità';
|
||||
|
||||
@override
|
||||
String community_created(String name) {
|
||||
return 'Comunità \"$name\" creata';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_joined(String name) {
|
||||
return 'Unito alla comunità \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_qrTitle => 'Condividi Comunità';
|
||||
|
||||
@override
|
||||
String community_qrInstructions(String name) {
|
||||
return 'Scansiona questo codice QR per unirti a $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_hashtagPrivacyHint =>
|
||||
'I canali hashtag della community sono accessibili solo ai membri della community';
|
||||
|
||||
@override
|
||||
String get community_invalidQrCode => 'Codice QR della community non valido';
|
||||
|
||||
@override
|
||||
String get community_alreadyMember => 'Già membro';
|
||||
|
||||
@override
|
||||
String community_alreadyMemberMessage(String name) {
|
||||
return 'Sei già un membro di \"$name\".';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addPublicChannel =>
|
||||
'Aggiungi Canale Pubblico della Comunità';
|
||||
|
||||
@override
|
||||
String get community_addPublicChannelHint =>
|
||||
'Aggiungi automaticamente il canale pubblico per questa community';
|
||||
|
||||
@override
|
||||
String get community_noCommunities => 'Nessun gruppo aggiunto finora';
|
||||
|
||||
@override
|
||||
String get community_scanOrCreate =>
|
||||
'Scansiona un codice QR o crea una community per iniziare.';
|
||||
|
||||
@override
|
||||
String get community_manageCommunities => 'Gestisci Comunità';
|
||||
|
||||
@override
|
||||
String get community_delete => 'Lascia la Comunità';
|
||||
|
||||
@override
|
||||
String community_deleteConfirm(String name) {
|
||||
return 'Uscire da \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleteChannelsWarning(int count) {
|
||||
return 'Questo eliminerà anche $count canale/i e i loro messaggi.';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleted(String name) {
|
||||
return 'Hai lasciato la comunità \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Ri genera la chiave segreta';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenera la chiave segreta per \"$name\"? Tutti i membri dovranno scansionare il nuovo codice QR per continuare a comunicare.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Rigenera';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Codice segreto rigenerato per \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Aggiorna Segreto';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Segreto aggiornato per \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scansiona il nuovo codice QR per aggiornare il segreto di \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannel => 'Aggiungi Hashtag della Community';
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannelDesc =>
|
||||
'Aggiungi un canale con hashtag per questa community';
|
||||
|
||||
@override
|
||||
String get community_selectCommunity => 'Seleziona Comunità';
|
||||
|
||||
@override
|
||||
String get community_regularHashtag => 'Hashtag regolare';
|
||||
|
||||
@override
|
||||
String get community_regularHashtagDesc =>
|
||||
'Hashtag pubblico (chiunque può unirsi)';
|
||||
|
||||
@override
|
||||
String get community_communityHashtag => 'Hashtag della Comunità';
|
||||
|
||||
@override
|
||||
String get community_communityHashtagDesc =>
|
||||
'Visibile solo ai membri della comunità';
|
||||
|
||||
@override
|
||||
String community_forCommunity(String name) {
|
||||
return 'Per $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get listFilter_tooltip => 'Filtra e ordina';
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get nav_contacts => 'Contacten';
|
||||
|
||||
@override
|
||||
String get nav_channels => 'Kanaal';
|
||||
String get nav_channels => 'Kanalen';
|
||||
|
||||
@override
|
||||
String get nav_map => 'Kaart';
|
||||
@@ -23,6 +23,9 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
@override
|
||||
String get common_cancel => 'Annuleren';
|
||||
|
||||
@override
|
||||
String get common_ok => 'OK';
|
||||
|
||||
@override
|
||||
String get common_connect => 'Verbinden';
|
||||
|
||||
@@ -39,7 +42,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get common_close => 'Sluiten';
|
||||
|
||||
@override
|
||||
String get common_edit => 'Bewerk';
|
||||
String get common_edit => 'Bewerken';
|
||||
|
||||
@override
|
||||
String get common_add => 'Toevoegen';
|
||||
@@ -48,13 +51,13 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get common_settings => 'Instellingen';
|
||||
|
||||
@override
|
||||
String get common_disconnect => 'Verbinden verbreken';
|
||||
String get common_disconnect => 'Verbinding verbreken';
|
||||
|
||||
@override
|
||||
String get common_connected => 'Verbonden';
|
||||
|
||||
@override
|
||||
String get common_disconnected => 'Ontkoppeld';
|
||||
String get common_disconnected => 'Verbinding verbroken';
|
||||
|
||||
@override
|
||||
String get common_create => 'Maak';
|
||||
@@ -78,7 +81,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get common_remove => 'Verwijderen';
|
||||
|
||||
@override
|
||||
String get common_enable => 'Aktivatie';
|
||||
String get common_enable => 'Activeren';
|
||||
|
||||
@override
|
||||
String get common_disable => 'Uitschakelen';
|
||||
@@ -87,7 +90,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get common_reboot => 'Herstarten';
|
||||
|
||||
@override
|
||||
String get common_loading => 'Laad...';
|
||||
String get common_loading => 'Laden...';
|
||||
|
||||
@override
|
||||
String get common_notAvailable => '—';
|
||||
@@ -168,7 +171,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get settings_nodeNameNotSet => 'Niet ingesteld';
|
||||
|
||||
@override
|
||||
String get settings_nodeNameHint => 'Voer knooppuntnaam in';
|
||||
String get settings_nodeNameHint => 'Voer nodenaam in';
|
||||
|
||||
@override
|
||||
String get settings_nodeNameUpdated => 'Naam bijgewerkt';
|
||||
@@ -200,6 +203,20 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get settings_locationInvalid =>
|
||||
'Ongeldige breedtegraad of lengtegraad.';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnable => 'GPS inschakelen';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnableSubtitle =>
|
||||
'Activeer automatisch locatieupdates via GPS.';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalSec => 'Interval voor GPS (Seconden)';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalInvalid =>
|
||||
'De intervallen moeten minstens 60 seconden zijn en minder dan 86400 seconden.';
|
||||
|
||||
@override
|
||||
String get settings_latitude => 'Breedtegraad';
|
||||
|
||||
@@ -312,7 +329,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get settings_infoContactsCount => 'Aantal Contacten';
|
||||
|
||||
@override
|
||||
String get settings_infoChannelCount => 'Kanaal Aantal';
|
||||
String get settings_infoChannelCount => 'Aantal Kanalen';
|
||||
|
||||
@override
|
||||
String get settings_presets => 'Presets';
|
||||
@@ -354,10 +371,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get settings_txPowerInvalid => 'Ongeldige TX-vermogen (0-22 dBm)';
|
||||
|
||||
@override
|
||||
String get settings_longRange => 'Lang Bereik';
|
||||
String get settings_longRange => 'Lange Afstand';
|
||||
|
||||
@override
|
||||
String get settings_fastSpeed => 'Snelle Snelheid';
|
||||
String get settings_fastSpeed => 'Hoge Snelheid';
|
||||
|
||||
@override
|
||||
String settings_error(String message) {
|
||||
@@ -377,7 +394,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get appSettings_themeSystem => 'Standaardinstelling';
|
||||
|
||||
@override
|
||||
String get appSettings_themeLight => 'Helder';
|
||||
String get appSettings_themeLight => 'Licht';
|
||||
|
||||
@override
|
||||
String get appSettings_themeDark => 'Donker';
|
||||
@@ -469,13 +486,13 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get appSettings_advertisementNotificationsSubtitle =>
|
||||
'Toon notificatie wanneer nieuwe knooppunten worden ontdekt';
|
||||
'Toon notificatie wanneer nieuwe nodes worden ontdekt';
|
||||
|
||||
@override
|
||||
String get appSettings_messaging => 'Berichten';
|
||||
|
||||
@override
|
||||
String get appSettings_clearPathOnMaxRetry => 'Duidelijke Pad op Max Retry';
|
||||
String get appSettings_clearPathOnMaxRetry => 'Wis Pad op Max Retry';
|
||||
|
||||
@override
|
||||
String get appSettings_clearPathOnMaxRetrySubtitle =>
|
||||
@@ -490,19 +507,19 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
'Padoms worden niet automatisch verwijderd';
|
||||
|
||||
@override
|
||||
String get appSettings_autoRouteRotation => 'Automatische Route Rotatie';
|
||||
String get appSettings_autoRouteRotation => 'Route Automatisch Roteren';
|
||||
|
||||
@override
|
||||
String get appSettings_autoRouteRotationSubtitle =>
|
||||
'Wissel tussen de beste paden en floodmodus over.';
|
||||
'Verwissel tussen beste pad en floodmodus.';
|
||||
|
||||
@override
|
||||
String get appSettings_autoRouteRotationEnabled =>
|
||||
'Automatische routeplanning rotatie ingeschakeld';
|
||||
'Automatische route rotatie ingeschakeld';
|
||||
|
||||
@override
|
||||
String get appSettings_autoRouteRotationDisabled =>
|
||||
'Automatische routeplanning rotatie is uitgeschakeld';
|
||||
'Automatische route rotatie is uitgeschakeld';
|
||||
|
||||
@override
|
||||
String get appSettings_battery => 'Batterij';
|
||||
@@ -532,11 +549,11 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get appSettings_mapDisplay => 'Kaartweergave';
|
||||
|
||||
@override
|
||||
String get appSettings_showRepeaters => 'Toon Herhalingen';
|
||||
String get appSettings_showRepeaters => 'Toon Repeaters';
|
||||
|
||||
@override
|
||||
String get appSettings_showRepeatersSubtitle =>
|
||||
'Toon herhalende knoopjes op de kaart';
|
||||
'Toon repeaternodes op de kaart';
|
||||
|
||||
@override
|
||||
String get appSettings_showChatNodes => 'Chat Nodes tonen';
|
||||
@@ -546,21 +563,21 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
'Chatnodes weergeven op de kaart';
|
||||
|
||||
@override
|
||||
String get appSettings_showOtherNodes => 'Toon Andere Knopen';
|
||||
String get appSettings_showOtherNodes => 'Toon Andere Nodes';
|
||||
|
||||
@override
|
||||
String get appSettings_showOtherNodesSubtitle =>
|
||||
'Toon andere knooptypes op de kaart';
|
||||
'Toon andere nodetypes op de kaart';
|
||||
|
||||
@override
|
||||
String get appSettings_timeFilter => 'Filter op tijd';
|
||||
|
||||
@override
|
||||
String get appSettings_timeFilterShowAll => 'Alle knooppunten tonen';
|
||||
String get appSettings_timeFilterShowAll => 'Alle nodes tonen';
|
||||
|
||||
@override
|
||||
String appSettings_timeFilterShowLast(int hours) {
|
||||
return 'Toon knopen van de laatste $hours uur';
|
||||
return 'Toon nodes van de laatste $hours uur';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -568,10 +585,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get appSettings_showNodesDiscoveredWithin =>
|
||||
'Toon knooppunten ontdekt binnen:';
|
||||
'Toon nodes ontdekt binnen:';
|
||||
|
||||
@override
|
||||
String get appSettings_allTime => 'Alle tijd';
|
||||
String get appSettings_allTime => 'Altijd';
|
||||
|
||||
@override
|
||||
String get appSettings_lastHour => 'Laat uur';
|
||||
@@ -642,7 +659,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get contacts_manageRepeater => 'Beheer Herhaling';
|
||||
String get contacts_manageRepeater => 'Beheer Repeater';
|
||||
|
||||
@override
|
||||
String get contacts_manageRoom => 'Beheer Ruimte Server';
|
||||
|
||||
@override
|
||||
String get contacts_roomLogin => 'Ruimte Inloggen';
|
||||
@@ -690,7 +710,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String contacts_lastSeenMinsAgo(int minutes) {
|
||||
return 'Laast gezien $minutes minuten geleden';
|
||||
return 'Laatst gezien $minutes minuten geleden';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -824,6 +844,46 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
@override
|
||||
String get channels_sortUnread => 'Ongelezen';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannel => 'Maak een Privé Kanaal';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannelDesc =>
|
||||
'Beveiligd met een geheime sleutel.';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannel => 'Sluit een Privé Kanaal aan';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannelDesc =>
|
||||
'Handmatig een geheime sleutel invoeren.';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannel => 'Sluit het Open Kanaal';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannelDesc =>
|
||||
'Iedereen kan dit kanaal aanmelden.';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannel => 'Sluit een Hashtag Kanaal';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannelDesc =>
|
||||
'Iedereen kan lid worden van hashtag-kanalen.';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCode => 'Scan een QR-code';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCodeComingSoon => 'Komt later';
|
||||
|
||||
@override
|
||||
String get channels_enterHashtag => 'Voer hashtag in';
|
||||
|
||||
@override
|
||||
String get channels_hashtagHint => 'bijv. #team';
|
||||
|
||||
@override
|
||||
String get chat_noMessages => 'Nog geen berichten.';
|
||||
|
||||
@@ -1009,7 +1069,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get chat_autoUseSavedPath => 'Automatisch (gebruik opgeslagen pad)';
|
||||
|
||||
@override
|
||||
String get chat_forceFloodMode => 'Dwing Overstromingsmodus';
|
||||
String get chat_forceFloodMode => 'Dwing Floodsmodus';
|
||||
|
||||
@override
|
||||
String get chat_recentAckPaths => 'Recente ACK Paden (tik om te gebruiken):';
|
||||
@@ -1071,7 +1131,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get chat_floodModeEnabled =>
|
||||
'Overstromingsmodus is ingeschakeld. Schakel dit uit via het route-icoon in de app-balk.';
|
||||
'Floodmodus is ingeschakeld. Schakel dit uit via het route-icoon in de app-balk.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Volledige Pad';
|
||||
@@ -1115,18 +1175,18 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
'Verzenden van uitgaande berichten comprimeren';
|
||||
|
||||
@override
|
||||
String get chat_floodForced => 'Overstroming (gedwongen)';
|
||||
String get chat_floodForced => 'Flood (afgedwongen)';
|
||||
|
||||
@override
|
||||
String get chat_directForced => 'Direct (gedwongen)';
|
||||
String get chat_directForced => 'Direct (afgedwongen)';
|
||||
|
||||
@override
|
||||
String chat_hopsForced(int count) {
|
||||
return '$count sprongen (gedwongen)';
|
||||
return '$count hops (afgedwongen)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_floodAuto => 'Overstroming (auto)';
|
||||
String get chat_floodAuto => 'Flood (auto)';
|
||||
|
||||
@override
|
||||
String get chat_direct => 'Direct';
|
||||
@@ -1139,11 +1199,29 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
return 'Nieuw: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Link openen?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Wilt u deze link in uw browser openen?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Openen';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Kan link niet openen: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Ongeldig linkformaat';
|
||||
|
||||
@override
|
||||
String get map_title => 'Node Map';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation => 'Geen knopen met locatiegegevens';
|
||||
String get map_noNodesWithLocation => 'Geen nodes met locatiegegevens';
|
||||
|
||||
@override
|
||||
String get map_nodesNeedGps =>
|
||||
@@ -1163,7 +1241,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get map_chat => 'Chat';
|
||||
|
||||
@override
|
||||
String get map_repeater => 'Herhaling';
|
||||
String get map_repeater => 'Repeater';
|
||||
|
||||
@override
|
||||
String get map_room => 'Ruimte';
|
||||
@@ -1230,19 +1308,19 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
'Verbind met een apparaat om markers te delen';
|
||||
|
||||
@override
|
||||
String get map_filterNodes => 'Filter Knopen';
|
||||
String get map_filterNodes => 'Filter Nodes';
|
||||
|
||||
@override
|
||||
String get map_nodeTypes => 'Node Types';
|
||||
String get map_nodeTypes => 'Nodetypes';
|
||||
|
||||
@override
|
||||
String get map_chatNodes => 'Chat Nodes';
|
||||
String get map_chatNodes => 'Chatnodes';
|
||||
|
||||
@override
|
||||
String get map_repeaters => 'Herhalingen';
|
||||
String get map_repeaters => 'Repeaters';
|
||||
|
||||
@override
|
||||
String get map_otherNodes => 'Andere knooppunten';
|
||||
String get map_otherNodes => 'Andere Nodes';
|
||||
|
||||
@override
|
||||
String get map_keyPrefix => 'Prefix sleutel';
|
||||
@@ -1269,7 +1347,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get map_joinRoom => 'Sluit Kamer';
|
||||
|
||||
@override
|
||||
String get map_manageRepeater => 'Beheer Herhaling';
|
||||
String get map_manageRepeater => 'Beheer Repeater';
|
||||
|
||||
@override
|
||||
String get mapCache_title => 'Offline Kaarten Cache';
|
||||
@@ -1412,7 +1490,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
'Ben je er zeker van dat je verbinding met dit apparaat wilt verbreken?';
|
||||
|
||||
@override
|
||||
String get login_repeaterLogin => 'Herhaalders Inloggen';
|
||||
String get login_repeaterLogin => 'Inloggen Repeater';
|
||||
|
||||
@override
|
||||
String get login_roomLogin => 'Ruimte Inloggen';
|
||||
@@ -1432,7 +1510,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get login_repeaterDescription =>
|
||||
'Voer het wachtwoord van de herhaling in om instellingen en status te openen.';
|
||||
'Voer het wachtwoord van de repeater in om instellingen en status te openen.';
|
||||
|
||||
@override
|
||||
String get login_roomDescription =>
|
||||
@@ -1448,7 +1526,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get login_autoUseSavedPath => 'Automatisch (gebruik opgeslagen pad)';
|
||||
|
||||
@override
|
||||
String get login_forceFloodMode => 'Dwing Overstromingsmodus';
|
||||
String get login_forceFloodMode => 'Dwing Floodmodus Af';
|
||||
|
||||
@override
|
||||
String get login_managePaths => 'Padbeheer';
|
||||
@@ -1466,6 +1544,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
return 'Inloggen mislukt: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get login_failedMessage =>
|
||||
'Inloggen mislukt. Het wachtwoord is onjuist of de repeater is niet bereikbaar.';
|
||||
|
||||
@override
|
||||
String get common_reload => 'Opnieuw laden';
|
||||
|
||||
@@ -1513,8 +1595,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get path_selectFromContacts => 'Of select contacten:';
|
||||
|
||||
@override
|
||||
String get path_noRepeatersFound =>
|
||||
'Geen herhalingen of zaalservers gevonden.';
|
||||
String get path_noRepeatersFound => 'Geen repeaters of roomservers gevonden.';
|
||||
|
||||
@override
|
||||
String get path_customPathsRequire =>
|
||||
@@ -1533,7 +1614,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get path_setPath => 'Stel Pad in';
|
||||
|
||||
@override
|
||||
String get repeater_management => 'Beheer Herhalingen';
|
||||
String get repeater_management => 'Beheer Repeaters';
|
||||
|
||||
@override
|
||||
String get room_management => 'Beheer Server Kamer';
|
||||
|
||||
@override
|
||||
String get repeater_managementTools => 'Beheerinstrumenten';
|
||||
@@ -1556,7 +1640,13 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get repeater_cli => 'CLI';
|
||||
|
||||
@override
|
||||
String get repeater_cliSubtitle => 'Verzend commando\'s naar de herhaaldere';
|
||||
String get repeater_cliSubtitle => 'Verzend commando\'s naar de repeater';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Buren';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'Bekijk nul hops buren.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Instellingen';
|
||||
@@ -1565,7 +1655,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get repeater_settingsSubtitle => 'Configureer repeaterparameters';
|
||||
|
||||
@override
|
||||
String get repeater_statusTitle => 'Status herhalen';
|
||||
String get repeater_statusTitle => 'Status repeater';
|
||||
|
||||
@override
|
||||
String get repeater_routingMode => 'Routeerwijze';
|
||||
@@ -1575,7 +1665,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
'Automatisch (gebruik opgeslagen pad)';
|
||||
|
||||
@override
|
||||
String get repeater_forceFloodMode => 'Dwing Overloopmodus';
|
||||
String get repeater_forceFloodMode => 'Dwing Floodmodus Af';
|
||||
|
||||
@override
|
||||
String get repeater_pathManagement => 'Beheer van paden';
|
||||
@@ -1592,7 +1682,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get repeater_systemInformation => 'Systeem Informatie';
|
||||
String get repeater_systemInformation => 'Systeeminformatie';
|
||||
|
||||
@override
|
||||
String get repeater_battery => 'Batterij';
|
||||
@@ -1607,10 +1697,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get repeater_queueLength => 'Wachttijd';
|
||||
|
||||
@override
|
||||
String get repeater_debugFlags => 'Debug Flags';
|
||||
String get repeater_debugFlags => 'Debugvlaggen';
|
||||
|
||||
@override
|
||||
String get repeater_radioStatistics => 'Radio Statistieken';
|
||||
String get repeater_radioStatistics => 'Radiostatistieken';
|
||||
|
||||
@override
|
||||
String get repeater_lastRssi => 'Laatste RSSI';
|
||||
@@ -1619,7 +1709,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get repeater_lastSnr => 'Laatste SNR';
|
||||
|
||||
@override
|
||||
String get repeater_noiseFloor => 'Ruishoordniveau';
|
||||
String get repeater_noiseFloor => 'Ruisvloer';
|
||||
|
||||
@override
|
||||
String get repeater_txAirtime => 'TX Airtime';
|
||||
@@ -1628,7 +1718,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get repeater_rxAirtime => 'RX Airtime';
|
||||
|
||||
@override
|
||||
String get repeater_packetStatistics => 'Pakket Statistieken';
|
||||
String get repeater_packetStatistics => 'Pakketstatistieken';
|
||||
|
||||
@override
|
||||
String get repeater_sent => 'Verzonden';
|
||||
@@ -1637,7 +1727,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get repeater_received => 'Ontvangen';
|
||||
|
||||
@override
|
||||
String get repeater_duplicates => 'Dubbele';
|
||||
String get repeater_duplicates => 'Duplicaat';
|
||||
|
||||
@override
|
||||
String repeater_daysHoursMinsSecs(
|
||||
@@ -1651,17 +1741,17 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String repeater_packetTxTotal(int total, String flood, String direct) {
|
||||
return 'Totaal: $total, Overstroming: $flood, Direct: $direct';
|
||||
return 'Totaal: $total, Flood: $flood, Direct: $direct';
|
||||
}
|
||||
|
||||
@override
|
||||
String repeater_packetRxTotal(int total, String flood, String direct) {
|
||||
return 'Totaal: $total, Overstroming: $flood, Direct: $direct';
|
||||
return 'Totaal: $total, Flood: $flood, Direct: $direct';
|
||||
}
|
||||
|
||||
@override
|
||||
String repeater_duplicatesFloodDirect(String flood, String direct) {
|
||||
return 'Overstroming: $flood, Direct: $direct';
|
||||
return 'Flood: $flood, Direct: $direct';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1670,16 +1760,16 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get repeater_settingsTitle => 'Herstelinstellingen';
|
||||
String get repeater_settingsTitle => 'Repeater Instellingen';
|
||||
|
||||
@override
|
||||
String get repeater_basicSettings => 'Basisinstellingen';
|
||||
|
||||
@override
|
||||
String get repeater_repeaterName => 'Herhaalnaam';
|
||||
String get repeater_repeaterName => 'Repeaternaam';
|
||||
|
||||
@override
|
||||
String get repeater_repeaterNameHelper => 'Weergave naam voor deze herhaling';
|
||||
String get repeater_repeaterNameHelper => 'Weergave naam voor deze repeater';
|
||||
|
||||
@override
|
||||
String get repeater_adminPassword => 'Admin wachtwoord';
|
||||
@@ -1712,7 +1802,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get repeater_bandwidth => 'Bandbreedte';
|
||||
|
||||
@override
|
||||
String get repeater_spreadingFactor => 'Spreadsnelheid';
|
||||
String get repeater_spreadingFactor => 'Spreidingsfactor';
|
||||
|
||||
@override
|
||||
String get repeater_codingRate => 'Codeertarief';
|
||||
@@ -1736,11 +1826,11 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get repeater_features => 'Kenmerken';
|
||||
|
||||
@override
|
||||
String get repeater_packetForwarding => 'Pakketdoorstrooming';
|
||||
String get repeater_packetForwarding => 'Pakketdoorvoering';
|
||||
|
||||
@override
|
||||
String get repeater_packetForwardingSubtitle =>
|
||||
'Herstel activeren om pakketten door te sturen';
|
||||
'Repeater instellen om pakketten door te sturen';
|
||||
|
||||
@override
|
||||
String get repeater_guestAccess => 'Toegang voor Gasten';
|
||||
@@ -1750,7 +1840,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
'Toegestane leesbeheer toegang voor gasten.';
|
||||
|
||||
@override
|
||||
String get repeater_privacyMode => 'Privacy Mode';
|
||||
String get repeater_privacyMode => 'Privacy Modus';
|
||||
|
||||
@override
|
||||
String get repeater_privacyModeSubtitle =>
|
||||
@@ -1768,8 +1858,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get repeater_floodAdvertInterval =>
|
||||
'Advertentie Interval bij overstroming';
|
||||
String get repeater_floodAdvertInterval => 'Flood Advertentie Interval';
|
||||
|
||||
@override
|
||||
String repeater_floodAdvertIntervalHours(int hours) {
|
||||
@@ -1784,11 +1873,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get repeater_dangerZone => 'Gevaarzone';
|
||||
|
||||
@override
|
||||
String get repeater_rebootRepeater => 'Herstart Herhaalder';
|
||||
String get repeater_rebootRepeater => 'Herstart Repeater';
|
||||
|
||||
@override
|
||||
String get repeater_rebootRepeaterSubtitle =>
|
||||
'De herstart van het herhalerapparaat';
|
||||
String get repeater_rebootRepeaterSubtitle => 'Herstart het Repeaterapparaat';
|
||||
|
||||
@override
|
||||
String get repeater_rebootRepeaterConfirm =>
|
||||
@@ -1804,18 +1892,18 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_regenerateIdentityKeyConfirm =>
|
||||
'Dit genereert een nieuwe identiteit voor de herhaling. Doorgaan?';
|
||||
'Dit genereert een nieuwe identiteit voor de repeater. Doorgaan?';
|
||||
|
||||
@override
|
||||
String get repeater_eraseFileSystem => 'Verwijder Besturingssysteem';
|
||||
|
||||
@override
|
||||
String get repeater_eraseFileSystemSubtitle =>
|
||||
'Formateer het herhalende bestandsysteem';
|
||||
'Formateer het bestandsysteem van de repeater';
|
||||
|
||||
@override
|
||||
String get repeater_eraseFileSystemConfirm =>
|
||||
'WAARSCHUWING: Dit zal alle gegevens op de herhaling wissen. Dit kan niet worden teruggedraaid!';
|
||||
'WAARSCHUWING: Dit zal alle gegevens op de repeater wissen. Dit kan niet worden teruggedraaid!';
|
||||
|
||||
@override
|
||||
String get repeater_eraseSerialOnly =>
|
||||
@@ -1847,7 +1935,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_refreshRadioSettings =>
|
||||
'Radiozenders Instellingen Bijwerken';
|
||||
'Radiozender Instellingen Verversen';
|
||||
|
||||
@override
|
||||
String get repeater_refreshTxPower => 'Nieuw laden TX-vermogen';
|
||||
@@ -1881,7 +1969,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get repeater_cliTitle => 'Herhaling CLI';
|
||||
String get repeater_cliTitle => 'Repeater CLI';
|
||||
|
||||
@override
|
||||
String get repeater_debugNextCommand => 'Debug Volgende Commando';
|
||||
@@ -1972,7 +2060,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetRepeat =>
|
||||
'Activeert of deactiveert de herhalerrol voor dit knoop.';
|
||||
'Activeert of deactiveert de repeater rol van deze node.';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetAllowReadOnly =>
|
||||
@@ -1980,7 +2068,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetFloodMax =>
|
||||
'Stelt het maximale aantal hops van een inkomend overlastpakket in (indien >= max, wordt het pakket niet doorgestuurd)';
|
||||
'Stelt het maximale aantal hops van een inkomend floodpakket in (indien >= max, wordt het pakket niet doorgestuurd)';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetIntThresh =>
|
||||
@@ -1992,7 +2080,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetMultiAcks =>
|
||||
'Activeert of deactiveert de functie \'dubbele ACKs\'.';
|
||||
'Activeert of deactiveert de functie \'duplicate ACKs\'.';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetAdvertInterval =>
|
||||
@@ -2000,7 +2088,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetFloodAdvertInterval =>
|
||||
'Stelt het timerinterval in uren in om een overstromingsadvertentiepakket te versturen. Stel in op 0 om dit uit te schakelen.';
|
||||
'Stelt het timerinterval in uren in om een floodadvertentiepakket te versturen. Stel in op 0 om dit uit te schakelen.';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetGuestPassword =>
|
||||
@@ -2091,7 +2179,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpRegion =>
|
||||
'(reeks alleen) Lijst alle gedefinieerde regio\'s en huidige overstromingsrechten.';
|
||||
'(Alleen Serieel) Lijst alle gedefinieerde regio\'s en huidige floodrechten.';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpRegionLoad =>
|
||||
@@ -2136,11 +2224,11 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get repeater_cliHelpGpsOnOff => 'Schakel de GPS-standby aan/uit.';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpGpsSync => 'Synchroniseer knooptime met GPS-klok.';
|
||||
String get repeater_cliHelpGpsSync => 'Synchroniseer node met GPS-klok.';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpGpsSetLoc =>
|
||||
'Stel de positie van het knoop vast naar GPS-coördinaten en sla de voorkeuren op.';
|
||||
'Stel de positie van de node vast als GPS-coördinaten en sla de voorkeuren op.';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpGpsAdvert =>
|
||||
@@ -2170,11 +2258,11 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get repeater_logging => 'Logging';
|
||||
|
||||
@override
|
||||
String get repeater_neighborsRepeaterOnly => 'Buren (Alleen herhaald)';
|
||||
String get repeater_neighborsRepeaterOnly => 'Buren (Alleen repeaters)';
|
||||
|
||||
@override
|
||||
String get repeater_regionManagementRepeaterOnly =>
|
||||
'Regiobeheer (Alleen voor Repeater)';
|
||||
'Regiobeheer (Alleen Repeater)';
|
||||
|
||||
@override
|
||||
String get repeater_regionNote =>
|
||||
@@ -2241,6 +2329,34 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Ontvangen Buurdata';
|
||||
|
||||
@override
|
||||
String get neighbors_requestTimedOut =>
|
||||
'Buren vragen om tijdelijk uitgeschakeld.';
|
||||
|
||||
@override
|
||||
String neighbors_errorLoading(String error) {
|
||||
return 'Fout bij het laden van buren: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_repeatersNeighbours => 'Herhalingen Buren';
|
||||
|
||||
@override
|
||||
String get neighbors_noData => 'Geen gegevens van buren beschikbaar.';
|
||||
|
||||
@override
|
||||
String neighbors_unknownContact(String pubkey) {
|
||||
return 'Onbekende $pubkey';
|
||||
}
|
||||
|
||||
@override
|
||||
String neighbors_heardAgo(String time) {
|
||||
return 'Horen: $time geleden';
|
||||
}
|
||||
|
||||
@override
|
||||
String get channelPath_title => 'Pakketpad';
|
||||
|
||||
@@ -2251,7 +2367,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get channelPath_otherObservedPaths => 'Overige Waargenomen Paden';
|
||||
|
||||
@override
|
||||
String get channelPath_repeaterHops => 'Herhalingstapjes';
|
||||
String get channelPath_repeaterHops => 'Repeater Hops';
|
||||
|
||||
@override
|
||||
String get channelPath_noHopDetails =>
|
||||
@@ -2267,7 +2383,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get channelPath_timeLabel => 'Tijd';
|
||||
|
||||
@override
|
||||
String get channelPath_repeatsLabel => 'Herhalen';
|
||||
String get channelPath_repeatsLabel => 'Repeats';
|
||||
|
||||
@override
|
||||
String channelPath_pathLabel(int index) {
|
||||
@@ -2299,7 +2415,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get channelPath_unknownPath => 'Onbekend';
|
||||
|
||||
@override
|
||||
String get channelPath_floodPath => 'Overstroming';
|
||||
String get channelPath_floodPath => 'Flood';
|
||||
|
||||
@override
|
||||
String get channelPath_directPath => 'Direct';
|
||||
@@ -2319,7 +2435,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get channelPath_noRepeaterLocations =>
|
||||
'Geen herhaler locaties beschikbaar voor deze route.';
|
||||
'Geen repeaters beschikbaar voor deze route.';
|
||||
|
||||
@override
|
||||
String channelPath_primaryPath(int index) {
|
||||
@@ -2342,7 +2458,178 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
'Geen details beschikbaar voor dit pakket.';
|
||||
|
||||
@override
|
||||
String get channelPath_unknownRepeater => 'Onbekend Herhaalaar';
|
||||
String get channelPath_unknownRepeater => 'Onbekend Repeater';
|
||||
|
||||
@override
|
||||
String get community_title => 'Gemeenschap';
|
||||
|
||||
@override
|
||||
String get community_create => 'Maak Gemeenschap';
|
||||
|
||||
@override
|
||||
String get community_createDesc =>
|
||||
'Maak een nieuwe community en deel deze via QR-code.';
|
||||
|
||||
@override
|
||||
String get community_join => 'Sluit aan';
|
||||
|
||||
@override
|
||||
String get community_joinTitle => 'Worden lid van de community';
|
||||
|
||||
@override
|
||||
String community_joinConfirmation(String name) {
|
||||
return 'Wil je je aansluiten bij de community \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_scanQr => 'Scan Gemeenschap QR';
|
||||
|
||||
@override
|
||||
String get community_scanInstructions =>
|
||||
'Richt de camera op een gemeenschappelijke QR-code';
|
||||
|
||||
@override
|
||||
String get community_showQr => 'Toon QR-code';
|
||||
|
||||
@override
|
||||
String get community_publicChannel => 'Gemeenschap Openbaar';
|
||||
|
||||
@override
|
||||
String get community_hashtagChannel => 'Gemeenschappelijk Hashtag';
|
||||
|
||||
@override
|
||||
String get community_name => 'Gemeenschapnaam';
|
||||
|
||||
@override
|
||||
String get community_enterName => 'Voer de gemeenschapsnaam in';
|
||||
|
||||
@override
|
||||
String community_created(String name) {
|
||||
return 'Gemeenschap \"$name\" is aangemaakt';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_joined(String name) {
|
||||
return 'Gevonden in de community \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_qrTitle => 'Deel Gemeenschap';
|
||||
|
||||
@override
|
||||
String community_qrInstructions(String name) {
|
||||
return 'Scan deze QR-code om je aan te sluiten bij $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_hashtagPrivacyHint =>
|
||||
'Community hashtag-kanalen zijn alleen toegankelijk voor leden van de community';
|
||||
|
||||
@override
|
||||
String get community_invalidQrCode => 'Ongeldige community QR-code';
|
||||
|
||||
@override
|
||||
String get community_alreadyMember => 'Alleen al lid';
|
||||
|
||||
@override
|
||||
String community_alreadyMemberMessage(String name) {
|
||||
return 'U bent al lid van \"$name\".';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addPublicChannel =>
|
||||
'Voeg een Openbaar Gemeenschapskanaal toe';
|
||||
|
||||
@override
|
||||
String get community_addPublicChannelHint =>
|
||||
'Automatisch de publieke kanaal toevoegen voor deze community';
|
||||
|
||||
@override
|
||||
String get community_noCommunities =>
|
||||
'Nog geen gemeenschappen zijn bijgesloten.';
|
||||
|
||||
@override
|
||||
String get community_scanOrCreate =>
|
||||
'Scan een QR-code of een community aanmaken om te beginnen';
|
||||
|
||||
@override
|
||||
String get community_manageCommunities => 'Beheer Gemeenschappen';
|
||||
|
||||
@override
|
||||
String get community_delete => 'Laat Gemeenschap';
|
||||
|
||||
@override
|
||||
String community_deleteConfirm(String name) {
|
||||
return '\"$name\" verlaten?';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleteChannelsWarning(int count) {
|
||||
return 'Dit verwijdert ook $count kanaal/kanalen en hun berichten.';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleted(String name) {
|
||||
return 'Community \"$name\" verlaten';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regeneer Geheimwoord';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regeneere de geheime sleutel voor \"$name\"? Alle leden moeten de nieuwe QR-code scannen om verder te communiceren.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regeneer';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Geheim hersteld voor \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Bijwerken Geheime';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Geheim gewijzigd voor \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scan de nieuwe QR-code om het geheim voor \"$name\" bij te werken';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannel => 'Voeg Community Hashtag toe';
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannelDesc =>
|
||||
'Voeg een hashtag-kanaal toe aan deze community';
|
||||
|
||||
@override
|
||||
String get community_selectCommunity => 'Selecteer Gemeenschap';
|
||||
|
||||
@override
|
||||
String get community_regularHashtag => 'Gewone Hashtag';
|
||||
|
||||
@override
|
||||
String get community_regularHashtagDesc =>
|
||||
'Open hashtag (iedereen kan deelnemen)';
|
||||
|
||||
@override
|
||||
String get community_communityHashtag => 'Gemeenschappelijk Hashtag';
|
||||
|
||||
@override
|
||||
String get community_communityHashtagDesc =>
|
||||
'Alleen zichtbaar voor leden van de community';
|
||||
|
||||
@override
|
||||
String community_forCommunity(String name) {
|
||||
return 'Voor $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get listFilter_tooltip => 'Filteren en sorteren';
|
||||
@@ -2369,10 +2656,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get listFilter_users => 'Gebruikers';
|
||||
|
||||
@override
|
||||
String get listFilter_repeaters => 'Herhalingen';
|
||||
String get listFilter_repeaters => 'Repeaters';
|
||||
|
||||
@override
|
||||
String get listFilter_roomServers => 'Kamervirtualisatie';
|
||||
String get listFilter_roomServers => 'Roomservers';
|
||||
|
||||
@override
|
||||
String get listFilter_unreadOnly => 'Alleen ongelezen';
|
||||
|
||||
@@ -23,6 +23,9 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
@override
|
||||
String get common_cancel => 'Anuluj';
|
||||
|
||||
@override
|
||||
String get common_ok => 'OK';
|
||||
|
||||
@override
|
||||
String get common_connect => 'Połącz';
|
||||
|
||||
@@ -202,6 +205,20 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
String get settings_locationInvalid =>
|
||||
'Nieprawidłowa szerokość geograficzna lub długość geograficzna.';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnable => 'Włącz GPS';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnableSubtitle =>
|
||||
'Włącza automatyczne aktualizowanie pozycji za pomocą GPS.';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalSec => 'Interwał dla GPS (Sekundy)';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalInvalid =>
|
||||
'Interwał musi wynosić co najmniej 60 sekund i mniej niż 86400 sekund.';
|
||||
|
||||
@override
|
||||
String get settings_latitude => 'Szerokość';
|
||||
|
||||
@@ -649,6 +666,9 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_manageRepeater => 'Zarządzaj Powtórzami';
|
||||
|
||||
@override
|
||||
String get contacts_manageRoom => 'Zarządzaj Serwerem Pokoju';
|
||||
|
||||
@override
|
||||
String get contacts_roomLogin => 'Logowanie do pokoju';
|
||||
|
||||
@@ -828,6 +848,46 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
@override
|
||||
String get channels_sortUnread => 'Niezgłoszone';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannel => 'Utwórz Prywatny Kanał';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannelDesc =>
|
||||
'Zabezpieczone kluczem szyfrowym.';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannel => 'Dołącz do Prywatnego Kanału';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannelDesc => 'Ręcznie wprowadź klucz tajny.';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannel => 'Dołącz do kanału publicznego.';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannelDesc =>
|
||||
'Każdy może dołączyć do tego kanału.';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannel =>
|
||||
'Dołącz do kanału oznaczanego hashtagiem';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannelDesc =>
|
||||
'Każdy może dołączyć do kanałów z hashtagami.';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCode => 'Skanuj kod QR';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCodeComingSoon => 'Wkrótce';
|
||||
|
||||
@override
|
||||
String get channels_enterHashtag => 'Wprowadź hashtag';
|
||||
|
||||
@override
|
||||
String get channels_hashtagHint => 'np. #zespół';
|
||||
|
||||
@override
|
||||
String get chat_noMessages => 'Brak jeszcze wiadomości';
|
||||
|
||||
@@ -1145,6 +1205,24 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
return 'Niezgłoszone: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Otworzyć link?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Czy chcesz otworzyć ten link w przeglądarce?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Otwórz';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Nie można otworzyć linku: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Nieprawidłowy format linku';
|
||||
|
||||
@override
|
||||
String get map_title => 'Mapa węzłów';
|
||||
|
||||
@@ -1474,6 +1552,10 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
return 'Zalogowanie się nie powiodło: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get login_failedMessage =>
|
||||
'Logowanie nie powiodło się. Hasło jest nieprawidłowe albo repeater jest nieosiągalny.';
|
||||
|
||||
@override
|
||||
String get common_reload => 'Ponownie załadować';
|
||||
|
||||
@@ -1543,6 +1625,9 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_management => 'Zarządzanie Powtórzami';
|
||||
|
||||
@override
|
||||
String get room_management => 'Zarządzanie Serwerem Pokoju';
|
||||
|
||||
@override
|
||||
String get repeater_managementTools => 'Narzędzia Zarządzania';
|
||||
|
||||
@@ -1566,6 +1651,13 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_cliSubtitle => 'Wyślij polecenia do powielacza';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Sąsiedzi';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle =>
|
||||
'Wyświetl sąsiedztwo zerowych hopów.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Ustawienia';
|
||||
|
||||
@@ -2246,6 +2338,34 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Otrzymano dane sąsiedztwa';
|
||||
|
||||
@override
|
||||
String get neighbors_requestTimedOut =>
|
||||
'Sąsiedzi proszą o wyłączenie timingu.';
|
||||
|
||||
@override
|
||||
String neighbors_errorLoading(String error) {
|
||||
return 'Błąd podczas ładowania sąsiadów: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_repeatersNeighbours => 'Powtarzacze Sąsiedzi';
|
||||
|
||||
@override
|
||||
String get neighbors_noData => 'Brak danych dotyczących sąsiadów.';
|
||||
|
||||
@override
|
||||
String neighbors_unknownContact(String pubkey) {
|
||||
return 'Nieznana $pubkey';
|
||||
}
|
||||
|
||||
@override
|
||||
String neighbors_heardAgo(String time) {
|
||||
return 'Usłyszano: $time temu';
|
||||
}
|
||||
|
||||
@override
|
||||
String get channelPath_title => 'Ścieżka pakietu';
|
||||
|
||||
@@ -2349,6 +2469,176 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
@override
|
||||
String get channelPath_unknownRepeater => 'Nieznany Powtarzacz';
|
||||
|
||||
@override
|
||||
String get community_title => 'Społeczność';
|
||||
|
||||
@override
|
||||
String get community_create => 'Utwórz Społeczność';
|
||||
|
||||
@override
|
||||
String get community_createDesc =>
|
||||
'Utwórz nową społeczność i udostępnij za pomocą kodu QR.';
|
||||
|
||||
@override
|
||||
String get community_join => 'Dołącz';
|
||||
|
||||
@override
|
||||
String get community_joinTitle => 'Dołącz do społeczności';
|
||||
|
||||
@override
|
||||
String community_joinConfirmation(String name) {
|
||||
return 'Czy chcesz dołączyć do społeczności \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_scanQr => 'Skanuj QR kod społeczności';
|
||||
|
||||
@override
|
||||
String get community_scanInstructions =>
|
||||
'Skieruj kamerę w kierunku kodu QR społeczności.';
|
||||
|
||||
@override
|
||||
String get community_showQr => 'Pokaż kod QR';
|
||||
|
||||
@override
|
||||
String get community_publicChannel => 'Społeczność Publiczna';
|
||||
|
||||
@override
|
||||
String get community_hashtagChannel => 'Hashtag Społeczności';
|
||||
|
||||
@override
|
||||
String get community_name => 'Nazwa Społeczności';
|
||||
|
||||
@override
|
||||
String get community_enterName => 'Wprowadź nazwę społeczności';
|
||||
|
||||
@override
|
||||
String community_created(String name) {
|
||||
return 'Społeczność \"$name\" została utworzona';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_joined(String name) {
|
||||
return 'Dołączył do społeczności \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_qrTitle => 'Dziel się Społecznością';
|
||||
|
||||
@override
|
||||
String community_qrInstructions(String name) {
|
||||
return 'Skanuj ten kod QR, aby dołączyć $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_hashtagPrivacyHint =>
|
||||
'Kanały hashtagowe społeczności są dostępne tylko dla członków społeczności';
|
||||
|
||||
@override
|
||||
String get community_invalidQrCode => 'Nieprawidłowy kod QR społeczności.';
|
||||
|
||||
@override
|
||||
String get community_alreadyMember => 'Już jesteś członkiem.';
|
||||
|
||||
@override
|
||||
String community_alreadyMemberMessage(String name) {
|
||||
return 'Jesteś już członkiem \"$name\".';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addPublicChannel => 'Dodaj Kanał Publiczny Społeczności';
|
||||
|
||||
@override
|
||||
String get community_addPublicChannelHint =>
|
||||
'Automatycznie dodaj kanał publiczny dla tej społeczności.';
|
||||
|
||||
@override
|
||||
String get community_noCommunities =>
|
||||
'Nie dołączono jeszcze żadnych społeczności.';
|
||||
|
||||
@override
|
||||
String get community_scanOrCreate =>
|
||||
'Skanuj kod QR lub utwórz społeczność, aby zacząć.';
|
||||
|
||||
@override
|
||||
String get community_manageCommunities => 'Zarządzaj Grupami';
|
||||
|
||||
@override
|
||||
String get community_delete => 'Opuszczenie Społeczności';
|
||||
|
||||
@override
|
||||
String community_deleteConfirm(String name) {
|
||||
return 'Opuścić \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleteChannelsWarning(int count) {
|
||||
return 'Spowoduje to również usunięcie $count kanału/kanałów i ich wiadomości.';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleted(String name) {
|
||||
return 'Opuszczono społeczność \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Zregeneruj sekret';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regeneruj tajny klucz dla \"$name\"? Wszyscy członkowie będą musieli zeskanować nowy kod QR, aby kontynuować komunikację.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Zregeneruj';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Hasło ponownie wygenerowane dla \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Zaktualizuj tajny klucz';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Hasło zaktualizowane dla \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Skanuj nowy kod QR, aby zaktualizować sekret dla \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannel => 'Dodaj hashtag społeczności';
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannelDesc =>
|
||||
'Dodaj kanał z hashtagiem dla tej społeczności';
|
||||
|
||||
@override
|
||||
String get community_selectCommunity => 'Wybierz społeczność';
|
||||
|
||||
@override
|
||||
String get community_regularHashtag => 'Hashtag regular';
|
||||
|
||||
@override
|
||||
String get community_regularHashtagDesc =>
|
||||
'Publiczny hashtag (każdy może dołączyć)';
|
||||
|
||||
@override
|
||||
String get community_communityHashtag => 'Hashtag Społeczności';
|
||||
|
||||
@override
|
||||
String get community_communityHashtagDesc =>
|
||||
'Dostępne tylko dla członków społeczności';
|
||||
|
||||
@override
|
||||
String community_forCommunity(String name) {
|
||||
return 'Dla $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get listFilter_tooltip => 'Filtruj i sortuj';
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get common_cancel => 'Cancelar';
|
||||
|
||||
@override
|
||||
String get common_ok => 'OK';
|
||||
|
||||
@override
|
||||
String get common_connect => 'Conectar';
|
||||
|
||||
@@ -201,6 +204,20 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get settings_locationInvalid => 'Latitude ou longitude inválidos.';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnable => 'Ativar GPS';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnableSubtitle =>
|
||||
'Habilita a atualização automática da localização via GPS.';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalSec => 'Intervalo para GPS (Segundos)';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalInvalid =>
|
||||
'O intervalo deve ser de pelo menos 60 segundos e inferior a 86400 segundos.';
|
||||
|
||||
@override
|
||||
String get settings_latitude => 'Latitude';
|
||||
|
||||
@@ -649,6 +666,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_manageRepeater => 'Gerenciar Repetidor';
|
||||
|
||||
@override
|
||||
String get contacts_manageRoom => 'Gerenciar Servidor de Sala';
|
||||
|
||||
@override
|
||||
String get contacts_roomLogin => 'Login no Quarto';
|
||||
|
||||
@@ -829,6 +849,46 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get channels_sortUnread => 'Não lido';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannel => 'Criar um Canal Privado';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannelDesc =>
|
||||
'Protegido com uma chave secreta.';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannel => 'Junte-se a um Canal Privado';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannelDesc =>
|
||||
'Inserir uma chave secreta manualmente.';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannel => 'Junte-se ao Canal Público';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannelDesc =>
|
||||
'Qualquer pessoa pode entrar neste canal.';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannel => 'Junte-se a um Canal com Hashtag';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannelDesc =>
|
||||
'Qualquer pessoa pode participar de canais com hashtag.';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCode => 'Digitalizar um Código QR';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCodeComingSoon => 'Em breve';
|
||||
|
||||
@override
|
||||
String get channels_enterHashtag => 'Insira hashtag';
|
||||
|
||||
@override
|
||||
String get channels_hashtagHint => 'ex. #equipe';
|
||||
|
||||
@override
|
||||
String get chat_noMessages => 'Ainda não existem mensagens.';
|
||||
|
||||
@@ -1144,6 +1204,24 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
return 'Não lido: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Abrir link?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Deseja abrir este link no seu navegador?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Abrir';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Não foi possível abrir o link: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Formato de link inválido';
|
||||
|
||||
@override
|
||||
String get map_title => 'Mapa de Nós';
|
||||
|
||||
@@ -1472,6 +1550,10 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
return 'Login falhou: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get login_failedMessage =>
|
||||
'Falha no login. A senha está incorreta ou o repetidor está inacessível.';
|
||||
|
||||
@override
|
||||
String get common_reload => 'Recarregar';
|
||||
|
||||
@@ -1541,6 +1623,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_management => 'Gerenciamento de Repetidor';
|
||||
|
||||
@override
|
||||
String get room_management => 'Gerenciamento de Servidor de Sala';
|
||||
|
||||
@override
|
||||
String get repeater_managementTools => 'Ferramentas de Gerenciamento';
|
||||
|
||||
@@ -1564,6 +1649,13 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_cliSubtitle => 'Enviar comandos ao repetidor';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Vizinhos';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle =>
|
||||
'Visualizar vizinhos de salto zero.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Configurações';
|
||||
|
||||
@@ -2248,6 +2340,34 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Dados dos Vizinhos Recebidos';
|
||||
|
||||
@override
|
||||
String get neighbors_requestTimedOut =>
|
||||
'Vizinhos solicitam tempo limite esgotado.';
|
||||
|
||||
@override
|
||||
String neighbors_errorLoading(String error) {
|
||||
return 'Erro ao carregar vizinhos: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_repeatersNeighbours => 'Repetidores Vizinhos';
|
||||
|
||||
@override
|
||||
String get neighbors_noData => 'Não estão disponíveis dados de vizinhos.';
|
||||
|
||||
@override
|
||||
String neighbors_unknownContact(String pubkey) {
|
||||
return '$pubkey Desconhecido';
|
||||
}
|
||||
|
||||
@override
|
||||
String neighbors_heardAgo(String time) {
|
||||
return 'Ouvido: $time atrás';
|
||||
}
|
||||
|
||||
@override
|
||||
String get channelPath_title => 'Rótulo de Caminho de Pacote';
|
||||
|
||||
@@ -2351,6 +2471,177 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get channelPath_unknownRepeater => 'Repetidor Desconhecido';
|
||||
|
||||
@override
|
||||
String get community_title => 'Comunidade';
|
||||
|
||||
@override
|
||||
String get community_create => 'Criar Comunidade';
|
||||
|
||||
@override
|
||||
String get community_createDesc =>
|
||||
'Crie uma nova comunidade e compartilhe via código QR.';
|
||||
|
||||
@override
|
||||
String get community_join => 'Junte-se';
|
||||
|
||||
@override
|
||||
String get community_joinTitle => 'Junte-se à Comunidade';
|
||||
|
||||
@override
|
||||
String community_joinConfirmation(String name) {
|
||||
return 'Você gostaria de se juntar à comunidade \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_scanQr => 'Digitalizar a QR Code da Comunidade';
|
||||
|
||||
@override
|
||||
String get community_scanInstructions =>
|
||||
'Aponte a câmera para um código QR da comunidade';
|
||||
|
||||
@override
|
||||
String get community_showQr => 'Mostrar Código QR';
|
||||
|
||||
@override
|
||||
String get community_publicChannel => 'Comunidade Pública';
|
||||
|
||||
@override
|
||||
String get community_hashtagChannel => 'Hashtag da Comunidade';
|
||||
|
||||
@override
|
||||
String get community_name => 'Nome da Comunidade';
|
||||
|
||||
@override
|
||||
String get community_enterName => 'Insira o nome da comunidade';
|
||||
|
||||
@override
|
||||
String community_created(String name) {
|
||||
return 'Comunidade \"$name\" criada';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_joined(String name) {
|
||||
return 'Juntou-se à comunidade \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_qrTitle => 'Partilhar Comunidade';
|
||||
|
||||
@override
|
||||
String community_qrInstructions(String name) {
|
||||
return 'Escanear este código QR para juntar-se a $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_hashtagPrivacyHint =>
|
||||
'Os canais de hashtag da comunidade só podem ser acessados por membros da comunidade';
|
||||
|
||||
@override
|
||||
String get community_invalidQrCode => 'Código QR da comunidade inválido';
|
||||
|
||||
@override
|
||||
String get community_alreadyMember => 'Já é Membro';
|
||||
|
||||
@override
|
||||
String community_alreadyMemberMessage(String name) {
|
||||
return 'Você já é membro de \"$name\".';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addPublicChannel =>
|
||||
'Adicionar Canal Público da Comunidade';
|
||||
|
||||
@override
|
||||
String get community_addPublicChannelHint =>
|
||||
'Adicionar automaticamente o canal público para esta comunidade';
|
||||
|
||||
@override
|
||||
String get community_noCommunities =>
|
||||
'Ainda não foram adicionadas comunidades.';
|
||||
|
||||
@override
|
||||
String get community_scanOrCreate =>
|
||||
'Escaneie um código QR ou crie uma comunidade para começar.';
|
||||
|
||||
@override
|
||||
String get community_manageCommunities => 'Gerenciar Comunidades';
|
||||
|
||||
@override
|
||||
String get community_delete => 'Deixar Comunidade';
|
||||
|
||||
@override
|
||||
String community_deleteConfirm(String name) {
|
||||
return 'Sair de \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleteChannelsWarning(int count) {
|
||||
return 'Isso também excluirá $count canal/canais e suas mensagens.';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleted(String name) {
|
||||
return 'Saiu da comunidade \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerar Senha Segura';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerar a chave secreta para \"$name\"? Todos os membros precisarão escanear o novo código QR para continuar a comunicação.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerar';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Senha secreta regenerada para \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Atualizar Segredo';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Segredo atualizado para \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scanar o novo código QR para atualizar o segredo para \"$name\"\n\n\n+++++';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannel => 'Adicionar Hashtag da Comunidade';
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannelDesc =>
|
||||
'Adicionar um canal de hashtag para esta comunidade';
|
||||
|
||||
@override
|
||||
String get community_selectCommunity => 'Selecione Comunidade';
|
||||
|
||||
@override
|
||||
String get community_regularHashtag => 'Hashtag Regular';
|
||||
|
||||
@override
|
||||
String get community_regularHashtagDesc =>
|
||||
'Hashtag público (qualquer pessoa pode participar)';
|
||||
|
||||
@override
|
||||
String get community_communityHashtag => 'Hashtag da Comunidade';
|
||||
|
||||
@override
|
||||
String get community_communityHashtagDesc =>
|
||||
'Apenas para membros da comunidade';
|
||||
|
||||
@override
|
||||
String community_forCommunity(String name) {
|
||||
return 'Para $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get listFilter_tooltip => 'Filtrar e ordenar';
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,9 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
@override
|
||||
String get common_cancel => 'Zrušiť';
|
||||
|
||||
@override
|
||||
String get common_ok => 'OK\nDobre';
|
||||
|
||||
@override
|
||||
String get common_connect => 'Pripojiť';
|
||||
|
||||
@@ -200,6 +203,20 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
@override
|
||||
String get settings_locationInvalid => 'Neplatná šírka alebo dĺžka.';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnable => 'Aktivovať GPS';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnableSubtitle =>
|
||||
'Povolí automatické aktualizovanie polohy pomocou GPS.';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalSec => 'Interval pre GPS (Sekundy)';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalInvalid =>
|
||||
'Interval musí byť aspoň 60 sekúnd a menej ako 86400 sekúnd.';
|
||||
|
||||
@override
|
||||
String get settings_latitude => 'Súradnica';
|
||||
|
||||
@@ -642,6 +659,9 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_manageRepeater => 'Spravovať opakované zoznamy';
|
||||
|
||||
@override
|
||||
String get contacts_manageRoom => 'Spravovať server miestnosti';
|
||||
|
||||
@override
|
||||
String get contacts_roomLogin => 'Prihlásenie do miestnosti';
|
||||
|
||||
@@ -824,6 +844,45 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
@override
|
||||
String get channels_sortUnread => 'Nezriadené';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannel => 'Vytvorte súkromný kanál';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannelDesc =>
|
||||
'Zabezpečené pomocou tajného kľúča.';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannel => 'Pripojiť sa k súkromnému kanálu';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannelDesc => 'Ručne zadajte tajný kľúč.';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannel => 'Pripojte sa k verejnému kanálu';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannelDesc =>
|
||||
'Któvek sátó na tutó kanalizovát.';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannel => 'Pripojte sa k Hashtag Kanálu';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannelDesc =>
|
||||
'Ktoekolikoľvek sa môže pridať do hashtag kanálov.';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCode => 'Skenujte QR kód';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCodeComingSoon => 'Čoskoro';
|
||||
|
||||
@override
|
||||
String get channels_enterHashtag => 'Zadajte hashtag';
|
||||
|
||||
@override
|
||||
String get channels_hashtagHint => 'napr. #tím';
|
||||
|
||||
@override
|
||||
String get chat_noMessages => 'Zatiaľ žiadne správy.';
|
||||
|
||||
@@ -1061,7 +1120,7 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle =>
|
||||
'Znovu nájsť vynútene pri nasledujacej pošlite';
|
||||
'Znovu nájsť vynútene pri nasledujúcej pošlite';
|
||||
|
||||
@override
|
||||
String get chat_pathCleared =>
|
||||
@@ -1141,6 +1200,24 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
return 'Nezriadené: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Otvoriť odkaz?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Chcete otvoriť tento odkaz v prehliadači?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Otvoriť';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Nepodarilo sa otvoriť odkaz: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Neplatný formát odkazu';
|
||||
|
||||
@override
|
||||
String get map_title => 'Mapa uzlov';
|
||||
|
||||
@@ -1468,6 +1545,10 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
return 'Prihlásenie zlyhalo: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get login_failedMessage =>
|
||||
'Prihlásenie zlyhalo. Heslo je nesprávne alebo je opakovač nedostupný.';
|
||||
|
||||
@override
|
||||
String get common_reload => 'Načítať';
|
||||
|
||||
@@ -1537,6 +1618,9 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_management => 'Správa opakérov';
|
||||
|
||||
@override
|
||||
String get room_management => 'Správa servera miestnosti';
|
||||
|
||||
@override
|
||||
String get repeater_managementTools => 'Nástroje na správu';
|
||||
|
||||
@@ -1560,6 +1644,12 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_cliSubtitle => 'Pošlite príkazy opakovaču';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Súsezný';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'Zobraziť susedné body bez skokov.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Nastavenia';
|
||||
|
||||
@@ -2237,6 +2327,34 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Obdielo dáta suseda';
|
||||
|
||||
@override
|
||||
String get neighbors_requestTimedOut => 'Súďia žiadajú o časové ukončenie.';
|
||||
|
||||
@override
|
||||
String neighbors_errorLoading(String error) {
|
||||
return 'Chyba pri načítaní susedov: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_repeatersNeighbours => 'Opakovadlá Súsezná';
|
||||
|
||||
@override
|
||||
String get neighbors_noData =>
|
||||
'Nie je dostupná žiadna informácia o susedoch.';
|
||||
|
||||
@override
|
||||
String neighbors_unknownContact(String pubkey) {
|
||||
return 'Neznáma $pubkey';
|
||||
}
|
||||
|
||||
@override
|
||||
String neighbors_heardAgo(String time) {
|
||||
return 'Počuli sme to: $time dozadu';
|
||||
}
|
||||
|
||||
@override
|
||||
String get channelPath_title => 'Cesta balíka';
|
||||
|
||||
@@ -2340,6 +2458,175 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
@override
|
||||
String get channelPath_unknownRepeater => 'Neznáme opakovače';
|
||||
|
||||
@override
|
||||
String get community_title => 'Komunita';
|
||||
|
||||
@override
|
||||
String get community_create => 'Vytvoriť komunitu';
|
||||
|
||||
@override
|
||||
String get community_createDesc =>
|
||||
'Vytvorte novú komunitu a zdieľajte cez QR kód.';
|
||||
|
||||
@override
|
||||
String get community_join => 'Pripojiť';
|
||||
|
||||
@override
|
||||
String get community_joinTitle => 'Pripojiť sa k spoločenstvu';
|
||||
|
||||
@override
|
||||
String community_joinConfirmation(String name) {
|
||||
return 'Chceš sa pridať do komunity \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_scanQr => 'Skontrolujte komunitný QR kód';
|
||||
|
||||
@override
|
||||
String get community_scanInstructions =>
|
||||
'Zamerte kameru na komunitný QR kód.';
|
||||
|
||||
@override
|
||||
String get community_showQr => 'Zobraziť QR kód';
|
||||
|
||||
@override
|
||||
String get community_publicChannel => 'Komunita verejná';
|
||||
|
||||
@override
|
||||
String get community_hashtagChannel => 'Komunitný Hashtag';
|
||||
|
||||
@override
|
||||
String get community_name => 'Komunita';
|
||||
|
||||
@override
|
||||
String get community_enterName => 'Zadajte názov komunity';
|
||||
|
||||
@override
|
||||
String community_created(String name) {
|
||||
return 'Komunita \"$name\" vytvorená';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_joined(String name) {
|
||||
return 'Pripojená komunita \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_qrTitle => 'Zdieľť komunitu';
|
||||
|
||||
@override
|
||||
String community_qrInstructions(String name) {
|
||||
return 'Skenejte tento QR kód, aby ste sa pripojili k $name.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_hashtagPrivacyHint =>
|
||||
'Hashtagové kanály komunity sú prístupné len členom komunity';
|
||||
|
||||
@override
|
||||
String get community_invalidQrCode => 'Neplatná QR kód komunity.';
|
||||
|
||||
@override
|
||||
String get community_alreadyMember => 'Už ste členom.';
|
||||
|
||||
@override
|
||||
String community_alreadyMemberMessage(String name) {
|
||||
return 'Vy ste už členom \"$name\".';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addPublicChannel => 'Pridať verejný komunikačný kanál';
|
||||
|
||||
@override
|
||||
String get community_addPublicChannelHint =>
|
||||
'Automaticky prida verejný kanál pre túto komunitu.';
|
||||
|
||||
@override
|
||||
String get community_noCommunities =>
|
||||
'Zatiaľ ste sa nepripojili k žiadnej komunite';
|
||||
|
||||
@override
|
||||
String get community_scanOrCreate =>
|
||||
'Skene QR kód alebo vytvor komunitu na začiatok.';
|
||||
|
||||
@override
|
||||
String get community_manageCommunities => 'Spravovať komunity';
|
||||
|
||||
@override
|
||||
String get community_delete => 'Nechajte komunitu';
|
||||
|
||||
@override
|
||||
String community_deleteConfirm(String name) {
|
||||
return 'Opustiť \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleteChannelsWarning(int count) {
|
||||
return 'Tým sa tiež vymaže $count kanál/kanálov a ich správy.';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleted(String name) {
|
||||
return 'Opustená komunita \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Zobraziť nový tajný kód';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Znovu vygenerovať tajný kľúč pre \"$name\"? Všetci členovia budú musieť skanovať nový QR kód, aby mohli nadviazať komunikáciu.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Znovu vygenerovať';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Záznam pre \"$name\" bol regenerovaný tajne';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Aktualizovať tajné heslo';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Zmena tajnej slova pre \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Skáňte nový QR kód na aktualizáciu tajného hesla pre \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannel => 'Pridať komunitný hashtag';
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannelDesc =>
|
||||
'Pridajte hashtagový kanál pre túto komunitu.';
|
||||
|
||||
@override
|
||||
String get community_selectCommunity => 'Vyberte komunitu';
|
||||
|
||||
@override
|
||||
String get community_regularHashtag => 'Zvyčajný hashtag';
|
||||
|
||||
@override
|
||||
String get community_regularHashtagDesc =>
|
||||
'Veľký hashtag (ktočokoľvek sa môže pridať)';
|
||||
|
||||
@override
|
||||
String get community_communityHashtag => 'Komunitný Hashtag';
|
||||
|
||||
@override
|
||||
String get community_communityHashtagDesc => 'Špecifické pre členov komunity';
|
||||
|
||||
@override
|
||||
String community_forCommunity(String name) {
|
||||
return 'Pre $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get listFilter_tooltip => 'Filtrovať a triediť';
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
@override
|
||||
String get common_cancel => 'Prekliči';
|
||||
|
||||
@override
|
||||
String get common_ok => 'V redu';
|
||||
|
||||
@override
|
||||
String get common_connect => 'Poveži se';
|
||||
|
||||
@@ -200,6 +203,20 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get settings_locationInvalid =>
|
||||
'Neveljna zemeljska širina ali dolžina.';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnable => 'Omogoči GPS';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnableSubtitle =>
|
||||
'Omogoči samodejno posodabljanje lokacije z GPS-jem.';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalSec => 'Interval za GPS (Sekunde)';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalInvalid =>
|
||||
'Intervallo mora biti vsaj 60 sekund in manj kot 86400 sekund.';
|
||||
|
||||
@override
|
||||
String get settings_latitude => 'Širina';
|
||||
|
||||
@@ -435,7 +452,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get appSettings_enableNotificationsSubtitle =>
|
||||
'Prejmujte obvestila o sporočilih in oglasih';
|
||||
'Prejmite obvestila o sporočilih in oglasih';
|
||||
|
||||
@override
|
||||
String get appSettings_notificationPermissionDenied =>
|
||||
@@ -631,7 +648,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contacts_noContactsFound =>
|
||||
'Niti ena osebe ali skupine ni najdena.';
|
||||
'Niti ena oseba ali skupine ni najdena.';
|
||||
|
||||
@override
|
||||
String get contacts_deleteContact => 'Izbrisati Kontakt';
|
||||
@@ -644,6 +661,9 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_manageRepeater => 'Upravljajte Ponovitve';
|
||||
|
||||
@override
|
||||
String get contacts_manageRoom => 'Upravljajte strežnik sobe';
|
||||
|
||||
@override
|
||||
String get contacts_roomLogin => 'Vnos v sobo';
|
||||
|
||||
@@ -680,7 +700,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get contacts_noContactsMatchFilter =>
|
||||
'Niti ena osebe ne ustreza vašemu kriteriju.';
|
||||
'Niti ena oseba ne ustreza vašemu kriteriju.';
|
||||
|
||||
@override
|
||||
String get contacts_noMembers => 'Nič članov.';
|
||||
@@ -824,6 +844,45 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
@override
|
||||
String get channels_sortUnread => 'Nerešeno';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannel => 'Ustvari zasebno kanal.';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannelDesc =>
|
||||
'Varno zaklenjeno s skrivnim ključem.';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannel => 'Pridružite se zasebni skupini';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannelDesc => 'Ročno vnesite zaporni ključ.';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannel => 'Pridružite se javnemu kanalu';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannelDesc =>
|
||||
'Kdor karkoli je, lahko se pridruži tej skupini.';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannel => 'Pridružite se Kanalu z Hashtagom';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannelDesc =>
|
||||
'Kdor karkoli, lahko se pridruži hashtag kanalom.';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCode => 'Skeniraj QR kodo';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCodeComingSoon => 'Prihajajoča';
|
||||
|
||||
@override
|
||||
String get channels_enterHashtag => 'Vnesite hashtag';
|
||||
|
||||
@override
|
||||
String get channels_hashtagHint => 'npr. #ekipa';
|
||||
|
||||
@override
|
||||
String get chat_noMessages => 'Še ni sporočil.';
|
||||
|
||||
@@ -1138,6 +1197,24 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
return 'Nerešeno: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Odpreti povezavo?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Ali želite odpreti to povezavo v brskalniku?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Odpri';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Povezave ni bilo mogoče odpreti: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Neveljavna oblika povezave';
|
||||
|
||||
@override
|
||||
String get map_title => 'Mapa omrežja';
|
||||
|
||||
@@ -1147,7 +1224,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get map_nodesNeedGps =>
|
||||
'Omrežje morajo deliti svoje GPS koordinate,\nda se prikazajo na zemljeobrazniku.';
|
||||
'Omrežje morajo deliti svoje GPS koordinate,\nda se prikazao na zemljeobrazniku.';
|
||||
|
||||
@override
|
||||
String map_nodesCount(int count) {
|
||||
@@ -1469,6 +1546,10 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
return 'Prijava je bila neuspešna: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get login_failedMessage =>
|
||||
'Prijava je bila neuspešna. Geslo je napačno ali pa je repetitor nedosegljiv.';
|
||||
|
||||
@override
|
||||
String get common_reload => 'Ponovno naloži';
|
||||
|
||||
@@ -1537,6 +1618,9 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_management => 'Upravljanje ponovitve';
|
||||
|
||||
@override
|
||||
String get room_management => 'Upravljanje stremlišča';
|
||||
|
||||
@override
|
||||
String get repeater_managementTools => 'Upravne orodje';
|
||||
|
||||
@@ -1561,6 +1645,12 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get repeater_cliSubtitle =>
|
||||
'Pošlji ukazne povelje na ponovitveno enoto.';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Sosedi';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'Pogledati nič sosednjih hopjev.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Nastavitve';
|
||||
|
||||
@@ -2242,6 +2332,34 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Prejeto podatke o sosedih';
|
||||
|
||||
@override
|
||||
String get neighbors_requestTimedOut =>
|
||||
'Sosedi zahtevajo izklop po dogovoru.';
|
||||
|
||||
@override
|
||||
String neighbors_errorLoading(String error) {
|
||||
return 'Napaka pri obnašanju sosedov: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_repeatersNeighbours => 'Ponovitve Sosedi';
|
||||
|
||||
@override
|
||||
String get neighbors_noData => 'Niso na voljo podatki o sosedih.';
|
||||
|
||||
@override
|
||||
String neighbors_unknownContact(String pubkey) {
|
||||
return 'Nepoznano $pubkey';
|
||||
}
|
||||
|
||||
@override
|
||||
String neighbors_heardAgo(String time) {
|
||||
return 'Udeleženec je prejel sporočilo $time nazaj.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get channelPath_title => 'Pot do paketa';
|
||||
|
||||
@@ -2345,6 +2463,175 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
@override
|
||||
String get channelPath_unknownRepeater => 'Nepoznati ponovitelj';
|
||||
|
||||
@override
|
||||
String get community_title => 'Skupnost';
|
||||
|
||||
@override
|
||||
String get community_create => 'Ustvari skupnost';
|
||||
|
||||
@override
|
||||
String get community_createDesc =>
|
||||
'Ustvari novo skupnost in jo deli preko QR kode.';
|
||||
|
||||
@override
|
||||
String get community_join => 'Pridružiti se';
|
||||
|
||||
@override
|
||||
String get community_joinTitle => 'Pridružite se skupnosti';
|
||||
|
||||
@override
|
||||
String community_joinConfirmation(String name) {
|
||||
return 'Želiš se pridružiti skupnosti \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_scanQr => 'Skeniraj QR kode skupnosti';
|
||||
|
||||
@override
|
||||
String get community_scanInstructions =>
|
||||
'Nasmerite kamero s skupnostnim QR kodom.';
|
||||
|
||||
@override
|
||||
String get community_showQr => 'Pokaži QR kodo';
|
||||
|
||||
@override
|
||||
String get community_publicChannel => 'Skupnostna javna';
|
||||
|
||||
@override
|
||||
String get community_hashtagChannel => 'Skupnostni hashtag';
|
||||
|
||||
@override
|
||||
String get community_name => 'Komunitarne ime';
|
||||
|
||||
@override
|
||||
String get community_enterName => 'Vnesite ime skupnosti';
|
||||
|
||||
@override
|
||||
String community_created(String name) {
|
||||
return 'Skupnost \"$name\" je bila ustvarila.';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_joined(String name) {
|
||||
return 'Prilojen k skupnosti \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_qrTitle => 'Delite skupnost';
|
||||
|
||||
@override
|
||||
String community_qrInstructions(String name) {
|
||||
return 'Skenirajte to QR kodo za vključitev $name.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_hashtagPrivacyHint =>
|
||||
'Hashtag kanali skupnosti so dostopni samo članom skupnosti';
|
||||
|
||||
@override
|
||||
String get community_invalidQrCode => 'Neveljaven QR koden skupnosti';
|
||||
|
||||
@override
|
||||
String get community_alreadyMember => 'Že član';
|
||||
|
||||
@override
|
||||
String community_alreadyMemberMessage(String name) {
|
||||
return 'Kljub temu ste že član/ka $name.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addPublicChannel => 'Dodaj Objavni Kanal Komunitarja';
|
||||
|
||||
@override
|
||||
String get community_addPublicChannelHint =>
|
||||
'Samodejno dodaj javni kanal za to skupnost.';
|
||||
|
||||
@override
|
||||
String get community_noCommunities => 'Še nobena skupnost se ni pridružila.';
|
||||
|
||||
@override
|
||||
String get community_scanOrCreate =>
|
||||
'Skenirajte QR kodo ali ustvarite skupnost za začetek.';
|
||||
|
||||
@override
|
||||
String get community_manageCommunities => 'Upravljajte skupnosti';
|
||||
|
||||
@override
|
||||
String get community_delete => 'Opusti skupnost';
|
||||
|
||||
@override
|
||||
String community_deleteConfirm(String name) {
|
||||
return 'Zapustiti \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleteChannelsWarning(int count) {
|
||||
return 'To bo izbrisalo tudi $count kanal/kanalov in njihova sporočila.';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleted(String name) {
|
||||
return 'Zapustil skupnost \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Preberi nov tajni kôd';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Preberite novo tajno geslo za \"$name\"? Vsi članici morajo prebrati novo QR kodo, da lahko nadaljujejo s komunikacijo.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Preberi znova';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Tajna za \"$name\" ponovno ustvarjena';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Ažurniraj tajno';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Skrivnostno spremembo za \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Skeniraj nov kôd QR za posodabljanje tajne za $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannel => 'Dodaj Oznako Obštnine';
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannelDesc =>
|
||||
'Dodajte hashtag kanal za to skupnost.';
|
||||
|
||||
@override
|
||||
String get community_selectCommunity => 'Izberi skupnost';
|
||||
|
||||
@override
|
||||
String get community_regularHashtag => 'Oznaka s hashtagom';
|
||||
|
||||
@override
|
||||
String get community_regularHashtagDesc =>
|
||||
'javna oznaka (kateri koli lahko sodelujejo)';
|
||||
|
||||
@override
|
||||
String get community_communityHashtag => 'Skupnostni hashtag';
|
||||
|
||||
@override
|
||||
String get community_communityHashtagDesc =>
|
||||
'Izključeno za uporabnike skupnosti';
|
||||
|
||||
@override
|
||||
String community_forCommunity(String name) {
|
||||
return 'Za $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get listFilter_tooltip => 'Filtri in vrstiči';
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
@override
|
||||
String get common_cancel => 'Avbryt';
|
||||
|
||||
@override
|
||||
String get common_ok => 'Okej';
|
||||
|
||||
@override
|
||||
String get common_connect => 'Anslut';
|
||||
|
||||
@@ -199,6 +202,20 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
@override
|
||||
String get settings_locationInvalid => 'Ogiltig latitud eller longitud.';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnable => 'Aktivera GPS';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnableSubtitle =>
|
||||
'Aktivera automatiska uppdateringar av platsen med hjälp av GPS.';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalSec => 'Interval för GPS (Sekunder)';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalInvalid =>
|
||||
'Intervalet måste vara minst 60 sekunder och mindre än 86400 sekunder.';
|
||||
|
||||
@override
|
||||
String get settings_latitude => 'Latitud';
|
||||
|
||||
@@ -638,6 +655,9 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_manageRepeater => 'Hantera Upprepare';
|
||||
|
||||
@override
|
||||
String get contacts_manageRoom => 'Hantera Rumserver';
|
||||
|
||||
@override
|
||||
String get contacts_roomLogin => 'Rum Inloggning';
|
||||
|
||||
@@ -817,6 +837,46 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
@override
|
||||
String get channels_sortUnread => 'Oläst';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannel => 'Skapa en privat kanal';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannelDesc =>
|
||||
'Skyddat med en hemlig nyckel.';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannel => 'Gå med i en Privat Kanal';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannelDesc =>
|
||||
'Ange en hemlig nyckel manuellt.';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannel => 'Gå med i den Offentliga Kanalen';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannelDesc =>
|
||||
'Vem som helst kan gå med i denna kanal.';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannel => 'Gå med i en Hashtagkanal';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannelDesc =>
|
||||
'Väldigt enkelt att gå med i hashtag-kanaler.';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCode => 'Skanna en QR-kod';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCodeComingSoon => 'Kommer snart';
|
||||
|
||||
@override
|
||||
String get channels_enterHashtag => 'Ange hashtag';
|
||||
|
||||
@override
|
||||
String get channels_hashtagHint => 't.ex. #team';
|
||||
|
||||
@override
|
||||
String get chat_noMessages => 'Inga meddelanden ännu';
|
||||
|
||||
@@ -1132,6 +1192,24 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
return 'Olästa: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Öppna länk?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Vill du öppna den här länken i din webbläsare?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Öppna';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Kunde inte öppna länken: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Ogiltigt länkformat';
|
||||
|
||||
@override
|
||||
String get map_title => 'Nodkarta';
|
||||
|
||||
@@ -1457,6 +1535,10 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
return 'Inloggning misslyckades: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get login_failedMessage =>
|
||||
'Inloggning misslyckades. Antingen är lösenordet fel eller så går det inte att nå repeatern.';
|
||||
|
||||
@override
|
||||
String get common_reload => 'Ladda om';
|
||||
|
||||
@@ -1525,6 +1607,9 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_management => 'Återuppspelarens Hantering';
|
||||
|
||||
@override
|
||||
String get room_management => 'Rumserverhantering';
|
||||
|
||||
@override
|
||||
String get repeater_managementTools => 'Administrationsverktyg';
|
||||
|
||||
@@ -1548,6 +1633,12 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_cliSubtitle => 'Skicka kommandon till repetitorn';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => 'Grannar';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => 'Visa noll hoppgrannar.';
|
||||
|
||||
@override
|
||||
String get repeater_settings => 'Inställningar';
|
||||
|
||||
@@ -2225,6 +2316,33 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => 'Mottagna grannars data';
|
||||
|
||||
@override
|
||||
String get neighbors_requestTimedOut => 'Grannar begär tidsinställd utskick.';
|
||||
|
||||
@override
|
||||
String neighbors_errorLoading(String error) {
|
||||
return 'Fel vid inläsning av grannar: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_repeatersNeighbours => 'Upprepar grannar';
|
||||
|
||||
@override
|
||||
String get neighbors_noData => 'Inga grannuppgifter finns tillgängliga.';
|
||||
|
||||
@override
|
||||
String neighbors_unknownContact(String pubkey) {
|
||||
return 'Okänd $pubkey';
|
||||
}
|
||||
|
||||
@override
|
||||
String neighbors_heardAgo(String time) {
|
||||
return 'Hördes: $time sedan';
|
||||
}
|
||||
|
||||
@override
|
||||
String get channelPath_title => 'Paketväg';
|
||||
|
||||
@@ -2328,6 +2446,175 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
@override
|
||||
String get channelPath_unknownRepeater => 'Okänt Upprepare';
|
||||
|
||||
@override
|
||||
String get community_title => 'Gemenskap';
|
||||
|
||||
@override
|
||||
String get community_create => 'Skapa Gemenskap';
|
||||
|
||||
@override
|
||||
String get community_createDesc =>
|
||||
'Skapa en ny gemenskap och dela via QR-kod.';
|
||||
|
||||
@override
|
||||
String get community_join => 'Gå med';
|
||||
|
||||
@override
|
||||
String get community_joinTitle => 'Gå med i gemenskapen';
|
||||
|
||||
@override
|
||||
String community_joinConfirmation(String name) {
|
||||
return 'Vill du gå med i communityn \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_scanQr => 'Skanna Gemenskapens QR';
|
||||
|
||||
@override
|
||||
String get community_scanInstructions =>
|
||||
'Rikta kameran mot en QR-kod i communityn';
|
||||
|
||||
@override
|
||||
String get community_showQr => 'Visa QR-kod';
|
||||
|
||||
@override
|
||||
String get community_publicChannel => 'Föreningens Offentliga';
|
||||
|
||||
@override
|
||||
String get community_hashtagChannel => 'Community Hashtag';
|
||||
|
||||
@override
|
||||
String get community_name => 'Gemenskapens namn';
|
||||
|
||||
@override
|
||||
String get community_enterName => 'Ange communities namn';
|
||||
|
||||
@override
|
||||
String community_created(String name) {
|
||||
return 'Community \"$name\" har skapats';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_joined(String name) {
|
||||
return 'Medlem i communityn \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_qrTitle => 'Dela Gemenskap';
|
||||
|
||||
@override
|
||||
String community_qrInstructions(String name) {
|
||||
return 'Skanna denna QR-kod för att gå med i \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_hashtagPrivacyHint =>
|
||||
'Community-hashtagkanaler kan endast nås av medlemmar i communityn';
|
||||
|
||||
@override
|
||||
String get community_invalidQrCode => 'Ogiltig community QR-kod';
|
||||
|
||||
@override
|
||||
String get community_alreadyMember => 'Är redan medlem';
|
||||
|
||||
@override
|
||||
String community_alreadyMemberMessage(String name) {
|
||||
return 'Du är redan medlem av \"$name\".';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addPublicChannel =>
|
||||
'Lägg till Gemenskapskanal (Offentlig)';
|
||||
|
||||
@override
|
||||
String get community_addPublicChannelHint =>
|
||||
'Lägg automatiskt till den offentliga kanalen för denna community';
|
||||
|
||||
@override
|
||||
String get community_noCommunities => 'Inga gemenskaper har anslutats ännu';
|
||||
|
||||
@override
|
||||
String get community_scanOrCreate =>
|
||||
'Skanna en QR-kod eller skapa en community för att komma igång';
|
||||
|
||||
@override
|
||||
String get community_manageCommunities => 'Hantera Gemenskaper';
|
||||
|
||||
@override
|
||||
String get community_delete => 'Lämna Gemenskap';
|
||||
|
||||
@override
|
||||
String community_deleteConfirm(String name) {
|
||||
return 'Lämna \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleteChannelsWarning(int count) {
|
||||
return 'Detta kommer också att radera $count kanal/kanaler och deras meddelanden.';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleted(String name) {
|
||||
return 'Lämnade community \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerera hemlig kod';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerera den hemliga nyckeln för \"$name\"? Alla medlemmar måste scanna den nya QR-koden för att fortsätta kommunicera.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerera';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Lösenord återskapad för \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Uppdatera hemlighet';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Hemlighet uppdaterad för \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Skanna den nya QR-koden för att uppdatera hemligheten för \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannel => 'Lägg till Gemenskapens Hashtag';
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannelDesc =>
|
||||
'Lägg till en hashtag-kanal för denna community';
|
||||
|
||||
@override
|
||||
String get community_selectCommunity => 'Välj Gemenskap';
|
||||
|
||||
@override
|
||||
String get community_regularHashtag => 'Vanlig Hash Tag';
|
||||
|
||||
@override
|
||||
String get community_regularHashtagDesc =>
|
||||
'Offentlig hashtag (alla kan gå med)';
|
||||
|
||||
@override
|
||||
String get community_communityHashtag => 'Community Hashtag';
|
||||
|
||||
@override
|
||||
String get community_communityHashtagDesc => 'Endast för medlemmar';
|
||||
|
||||
@override
|
||||
String community_forCommunity(String name) {
|
||||
return 'För $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get listFilter_tooltip => 'Filtrera och sortera';
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get common_cancel => '取消';
|
||||
|
||||
@override
|
||||
String get common_ok => '好的';
|
||||
|
||||
@override
|
||||
String get common_connect => '连接';
|
||||
|
||||
@@ -196,6 +199,18 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get settings_locationInvalid => '无效的纬度或经度。';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnable => '启用GPS';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnableSubtitle => '启用GPS自动更新位置。';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalSec => 'GPS 间隔(秒)';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalInvalid => '时间间隔必须至少为60秒,且小于86400秒。';
|
||||
|
||||
@override
|
||||
String get settings_latitude => '纬度';
|
||||
|
||||
@@ -611,6 +626,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get contacts_manageRepeater => '管理重复项';
|
||||
|
||||
@override
|
||||
String get contacts_manageRoom => '管理房间服务器';
|
||||
|
||||
@override
|
||||
String get contacts_roomLogin => '房间登录';
|
||||
|
||||
@@ -789,6 +807,42 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get channels_sortUnread => '未读';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannel => '创建私聊频道';
|
||||
|
||||
@override
|
||||
String get channels_createPrivateChannelDesc => '使用密钥保护。';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannel => '加入私密频道';
|
||||
|
||||
@override
|
||||
String get channels_joinPrivateChannelDesc => '手动输入密钥。';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannel => '加入公共频道';
|
||||
|
||||
@override
|
||||
String get channels_joinPublicChannelDesc => '任何人都可以加入这个频道。';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannel => '加入标签频道';
|
||||
|
||||
@override
|
||||
String get channels_joinHashtagChannelDesc => '任何人都可以加入话题频道。';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCode => '扫描二维码';
|
||||
|
||||
@override
|
||||
String get channels_scanQrCodeComingSoon => '即将到来';
|
||||
|
||||
@override
|
||||
String get channels_enterHashtag => '输入标签';
|
||||
|
||||
@override
|
||||
String get channels_hashtagHint => '例如 #团队';
|
||||
|
||||
@override
|
||||
String get chat_noMessages => '目前还没有消息';
|
||||
|
||||
@@ -1094,6 +1148,23 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
return '未读:$count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => '打开链接?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation => '您想在浏览器中打开此链接吗?';
|
||||
|
||||
@override
|
||||
String get chat_open => '打开';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return '无法打开链接:$url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => '链接格式无效';
|
||||
|
||||
@override
|
||||
String get map_title => '节点地图';
|
||||
|
||||
@@ -1411,6 +1482,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
return '登录失败:$error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get login_failedMessage => '登录失败。密码不正确或中继器不可达。';
|
||||
|
||||
@override
|
||||
String get common_reload => '重新加载';
|
||||
|
||||
@@ -1474,6 +1548,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_management => '重复器管理';
|
||||
|
||||
@override
|
||||
String get room_management => '房间服务器管理';
|
||||
|
||||
@override
|
||||
String get repeater_managementTools => '管理工具';
|
||||
|
||||
@@ -1495,6 +1572,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get repeater_cliSubtitle => '发送命令到重复器';
|
||||
|
||||
@override
|
||||
String get repeater_neighbours => '邻居';
|
||||
|
||||
@override
|
||||
String get repeater_neighboursSubtitle => '查看零跳邻居。';
|
||||
|
||||
@override
|
||||
String get repeater_settings => '设置';
|
||||
|
||||
@@ -2125,6 +2208,33 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
return '$celsius°C / $fahrenheit°F';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_receivedData => '收到邻居数据';
|
||||
|
||||
@override
|
||||
String get neighbors_requestTimedOut => '邻居请求超时处理。';
|
||||
|
||||
@override
|
||||
String neighbors_errorLoading(String error) {
|
||||
return '加载邻居时出错:$error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_repeatersNeighbours => '重复器邻居';
|
||||
|
||||
@override
|
||||
String get neighbors_noData => '没有可用的邻居数据。';
|
||||
|
||||
@override
|
||||
String neighbors_unknownContact(String pubkey) {
|
||||
return '未知$pubkey';
|
||||
}
|
||||
|
||||
@override
|
||||
String neighbors_heardAgo(String time) {
|
||||
return '听到的时间:$time前';
|
||||
}
|
||||
|
||||
@override
|
||||
String get channelPath_title => '数据包路径';
|
||||
|
||||
@@ -2225,6 +2335,167 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get channelPath_unknownRepeater => '未知重复器';
|
||||
|
||||
@override
|
||||
String get community_title => '社区';
|
||||
|
||||
@override
|
||||
String get community_create => '创建社区';
|
||||
|
||||
@override
|
||||
String get community_createDesc => '创建新的社区并可通过二维码分享。';
|
||||
|
||||
@override
|
||||
String get community_join => '加入';
|
||||
|
||||
@override
|
||||
String get community_joinTitle => '加入社区';
|
||||
|
||||
@override
|
||||
String community_joinConfirmation(String name) {
|
||||
return '您想加入社区 \"$name\" 吗?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_scanQr => '扫描社区二维码';
|
||||
|
||||
@override
|
||||
String get community_scanInstructions => '将相机对准社区二维码';
|
||||
|
||||
@override
|
||||
String get community_showQr => '显示二维码';
|
||||
|
||||
@override
|
||||
String get community_publicChannel => '社区公开';
|
||||
|
||||
@override
|
||||
String get community_hashtagChannel => '社区标签';
|
||||
|
||||
@override
|
||||
String get community_name => '社区名称';
|
||||
|
||||
@override
|
||||
String get community_enterName => '请输入社区名称';
|
||||
|
||||
@override
|
||||
String community_created(String name) {
|
||||
return '社区“$name”已创建';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_joined(String name) {
|
||||
return '加入社区 \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_qrTitle => '分享社区';
|
||||
|
||||
@override
|
||||
String community_qrInstructions(String name) {
|
||||
return '扫描此二维码加入$name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_hashtagPrivacyHint => '社区标签频道仅社区成员可加入';
|
||||
|
||||
@override
|
||||
String get community_invalidQrCode => '无效的社区二维码';
|
||||
|
||||
@override
|
||||
String get community_alreadyMember => '已经是会员了';
|
||||
|
||||
@override
|
||||
String community_alreadyMemberMessage(String name) {
|
||||
return '您已经是 \"$name\" 的会员。';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addPublicChannel => '添加社区公共频道';
|
||||
|
||||
@override
|
||||
String get community_addPublicChannelHint => '自动添加该社区的公共频道';
|
||||
|
||||
@override
|
||||
String get community_noCommunities => '尚未加入任何社区';
|
||||
|
||||
@override
|
||||
String get community_scanOrCreate => '扫描二维码或创建社区开始';
|
||||
|
||||
@override
|
||||
String get community_manageCommunities => '管理社群';
|
||||
|
||||
@override
|
||||
String get community_delete => '退出社区';
|
||||
|
||||
@override
|
||||
String community_deleteConfirm(String name) {
|
||||
return '退出 \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleteChannelsWarning(int count) {
|
||||
return '这也将删除 $count 个频道及其消息。';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_deleted(String name) {
|
||||
return '已退出社区 \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => '重新生成密钥';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return '重新生成“$name”的秘密密钥?所有成员将需要扫描新的二维码才能继续沟通。';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => '重新生成';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return '密码已重置为“$name”';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => '更新密钥';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return '密码已更新为“$name”';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return '扫描新的二维码更新\"$name\"的密码';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannel => '添加社区标签';
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannelDesc => '添加一个话题频道给此社区';
|
||||
|
||||
@override
|
||||
String get community_selectCommunity => '选择社区';
|
||||
|
||||
@override
|
||||
String get community_regularHashtag => '常规话题标签';
|
||||
|
||||
@override
|
||||
String get community_regularHashtagDesc => '公共话题(任何人都可以加入)';
|
||||
|
||||
@override
|
||||
String get community_communityHashtag => '社区标签';
|
||||
|
||||
@override
|
||||
String get community_communityHashtagDesc => '仅限社区成员使用';
|
||||
|
||||
@override
|
||||
String community_forCommunity(String name) {
|
||||
return '对于 $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get listFilter_tooltip => '筛选和排序';
|
||||
|
||||
|
||||
+288
-90
@@ -2,7 +2,7 @@
|
||||
"@@locale": "nl",
|
||||
"appTitle": "MeshCore Open",
|
||||
"nav_contacts": "Contacten",
|
||||
"nav_channels": "Kanaal",
|
||||
"nav_channels": "Kanalen",
|
||||
"nav_map": "Kaart",
|
||||
"common_cancel": "Annuleren",
|
||||
"common_connect": "Verbinden",
|
||||
@@ -10,12 +10,12 @@
|
||||
"common_save": "Opslaan",
|
||||
"common_delete": "Verwijderen",
|
||||
"common_close": "Sluiten",
|
||||
"common_edit": "Bewerk",
|
||||
"common_edit": "Bewerken",
|
||||
"common_add": "Toevoegen",
|
||||
"common_settings": "Instellingen",
|
||||
"common_disconnect": "Verbinden verbreken",
|
||||
"common_disconnect": "Verbinding verbreken",
|
||||
"common_connected": "Verbonden",
|
||||
"common_disconnected": "Ontkoppeld",
|
||||
"common_disconnected": "Verbinding verbroken",
|
||||
"common_create": "Maak",
|
||||
"common_continue": "Doorgaan",
|
||||
"common_share": "Delen",
|
||||
@@ -23,10 +23,10 @@
|
||||
"common_retry": "Nogmaals proberen",
|
||||
"common_hide": "Verbergen",
|
||||
"common_remove": "Verwijderen",
|
||||
"common_enable": "Aktivatie",
|
||||
"common_enable": "Activeren",
|
||||
"common_disable": "Uitschakelen",
|
||||
"common_reboot": "Herstarten",
|
||||
"common_loading": "Laad...",
|
||||
"common_loading": "Laden...",
|
||||
"common_notAvailable": "—",
|
||||
"common_voltageValue": "{volts} V",
|
||||
"@common_voltageValue": {
|
||||
@@ -78,7 +78,7 @@
|
||||
"settings_nodeSettings": "Node Instellingen",
|
||||
"settings_nodeName": "Node Naam",
|
||||
"settings_nodeNameNotSet": "Niet ingesteld",
|
||||
"settings_nodeNameHint": "Voer knooppuntnaam in",
|
||||
"settings_nodeNameHint": "Voer nodenaam in",
|
||||
"settings_nodeNameUpdated": "Naam bijgewerkt",
|
||||
"settings_radioSettings": "Radio Instellingen",
|
||||
"settings_radioSettingsSubtitle": "Frequentie, vermogen, spredfactor",
|
||||
@@ -129,7 +129,7 @@
|
||||
"settings_infoBattery": "Batterij",
|
||||
"settings_infoPublicKey": "Openbare Sleutel",
|
||||
"settings_infoContactsCount": "Aantal Contacten",
|
||||
"settings_infoChannelCount": "Kanaal Aantal",
|
||||
"settings_infoChannelCount": "Aantal Kanalen",
|
||||
"settings_presets": "Presets",
|
||||
"settings_preset915Mhz": "915 MHz",
|
||||
"settings_preset868Mhz": "868 MHz",
|
||||
@@ -143,8 +143,8 @@
|
||||
"settings_txPower": "TX Vermogen (dBm)",
|
||||
"settings_txPowerHelper": "0 - 22",
|
||||
"settings_txPowerInvalid": "Ongeldige TX-vermogen (0-22 dBm)",
|
||||
"settings_longRange": "Lang Bereik",
|
||||
"settings_fastSpeed": "Snelle Snelheid",
|
||||
"settings_longRange": "Lange Afstand",
|
||||
"settings_fastSpeed": "Hoge Snelheid",
|
||||
"settings_error": "Fout: {message}",
|
||||
"@settings_error": {
|
||||
"placeholders": {
|
||||
@@ -157,7 +157,7 @@
|
||||
"appSettings_appearance": "Uiterlijk",
|
||||
"appSettings_theme": "Thema",
|
||||
"appSettings_themeSystem": "Standaardinstelling",
|
||||
"appSettings_themeLight": "Helder",
|
||||
"appSettings_themeLight": "Licht",
|
||||
"appSettings_themeDark": "Donker",
|
||||
"appSettings_language": "Taal",
|
||||
"appSettings_languageSystem": "Standaardinstelling",
|
||||
@@ -185,16 +185,16 @@
|
||||
"appSettings_channelMessageNotifications": "Kanaal Bericht Meldingen",
|
||||
"appSettings_channelMessageNotificationsSubtitle": "Toon notificatie bij het ontvangen van kanaalberichten",
|
||||
"appSettings_advertisementNotifications": "Advertentie-meldingen",
|
||||
"appSettings_advertisementNotificationsSubtitle": "Toon notificatie wanneer nieuwe knooppunten worden ontdekt",
|
||||
"appSettings_advertisementNotificationsSubtitle": "Toon notificatie wanneer nieuwe nodes worden ontdekt",
|
||||
"appSettings_messaging": "Berichten",
|
||||
"appSettings_clearPathOnMaxRetry": "Duidelijke Pad op Max Retry",
|
||||
"appSettings_clearPathOnMaxRetry": "Wis Pad op Max Retry",
|
||||
"appSettings_clearPathOnMaxRetrySubtitle": "Reset contactpad na 5 mislukte verzendpogingen",
|
||||
"appSettings_pathsWillBeCleared": "De paden worden na 5 mislukte pogingen leeggehaald.",
|
||||
"appSettings_pathsWillNotBeCleared": "Padoms worden niet automatisch verwijderd",
|
||||
"appSettings_autoRouteRotation": "Automatische Route Rotatie",
|
||||
"appSettings_autoRouteRotationSubtitle": "Wissel tussen de beste paden en floodmodus over.",
|
||||
"appSettings_autoRouteRotationEnabled": "Automatische routeplanning rotatie ingeschakeld",
|
||||
"appSettings_autoRouteRotationDisabled": "Automatische routeplanning rotatie is uitgeschakeld",
|
||||
"appSettings_autoRouteRotation": "Route Automatisch Roteren",
|
||||
"appSettings_autoRouteRotationSubtitle": "Verwissel tussen beste pad en floodmodus.",
|
||||
"appSettings_autoRouteRotationEnabled": "Automatische route rotatie ingeschakeld",
|
||||
"appSettings_autoRouteRotationDisabled": "Automatische route rotatie is uitgeschakeld",
|
||||
"appSettings_battery": "Batterij",
|
||||
"appSettings_batteryChemistry": "Batterijchemie",
|
||||
"appSettings_batteryChemistryPerDevice": "Instellen per apparaat ({deviceName})",
|
||||
@@ -210,15 +210,15 @@
|
||||
"appSettings_batteryLifepo4": "LiFePO4 (2,6-3,65V)",
|
||||
"appSettings_batteryLipo": "LiPo (3,0-4,2V)",
|
||||
"appSettings_mapDisplay": "Kaartweergave",
|
||||
"appSettings_showRepeaters": "Toon Herhalingen",
|
||||
"appSettings_showRepeatersSubtitle": "Toon herhalende knoopjes op de kaart",
|
||||
"appSettings_showRepeaters": "Toon Repeaters",
|
||||
"appSettings_showRepeatersSubtitle": "Toon repeaternodes op de kaart",
|
||||
"appSettings_showChatNodes": "Chat Nodes tonen",
|
||||
"appSettings_showChatNodesSubtitle": "Chatnodes weergeven op de kaart",
|
||||
"appSettings_showOtherNodes": "Toon Andere Knopen",
|
||||
"appSettings_showOtherNodesSubtitle": "Toon andere knooptypes op de kaart",
|
||||
"appSettings_showOtherNodes": "Toon Andere Nodes",
|
||||
"appSettings_showOtherNodesSubtitle": "Toon andere nodetypes op de kaart",
|
||||
"appSettings_timeFilter": "Filter op tijd",
|
||||
"appSettings_timeFilterShowAll": "Alle knooppunten tonen",
|
||||
"appSettings_timeFilterShowLast": "Toon knopen van de laatste {hours} uur",
|
||||
"appSettings_timeFilterShowAll": "Alle nodes tonen",
|
||||
"appSettings_timeFilterShowLast": "Toon nodes van de laatste {hours} uur",
|
||||
"@appSettings_timeFilterShowLast": {
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
@@ -227,8 +227,8 @@
|
||||
}
|
||||
},
|
||||
"appSettings_mapTimeFilter": "Filter tijd op kaart",
|
||||
"appSettings_showNodesDiscoveredWithin": "Toon knooppunten ontdekt binnen:",
|
||||
"appSettings_allTime": "Alle tijd",
|
||||
"appSettings_showNodesDiscoveredWithin": "Toon nodes ontdekt binnen:",
|
||||
"appSettings_allTime": "Altijd",
|
||||
"appSettings_lastHour": "Laat uur",
|
||||
"appSettings_last6Hours": "laatste 6 uur",
|
||||
"appSettings_last24Hours": "De laatste 24 uur",
|
||||
@@ -266,7 +266,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts_manageRepeater": "Beheer Herhaling",
|
||||
"contacts_manageRepeater": "Beheer Repeater",
|
||||
"contacts_roomLogin": "Ruimte Inloggen",
|
||||
"contacts_openChat": "Open Chat",
|
||||
"contacts_editGroup": "Groep bewerken",
|
||||
@@ -294,7 +294,7 @@
|
||||
"contacts_noContactsMatchFilter": "Geen contacten matchen met uw filter",
|
||||
"contacts_noMembers": "Geen leden",
|
||||
"contacts_lastSeenNow": "Laatste keer gezien nu",
|
||||
"contacts_lastSeenMinsAgo": "Laast gezien {minutes} minuten geleden",
|
||||
"contacts_lastSeenMinsAgo": "Laatst gezien {minutes} minuten geleden",
|
||||
"@contacts_lastSeenMinsAgo": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
@@ -539,7 +539,7 @@
|
||||
"chat_pathManagement": "Beheer van Paden",
|
||||
"chat_routingMode": "Routeerwijze",
|
||||
"chat_autoUseSavedPath": "Automatisch (gebruik opgeslagen pad)",
|
||||
"chat_forceFloodMode": "Dwing Overstromingsmodus",
|
||||
"chat_forceFloodMode": "Dwing Floodsmodus",
|
||||
"chat_recentAckPaths": "Recente ACK Paden (tik om te gebruiken):",
|
||||
"chat_pathHistoryFull": "De voorgeschiedenis is vol. Verwijder vermeldingen om er nieuwe aan toe te voegen.",
|
||||
"chat_hopSingular": "Hop",
|
||||
@@ -562,7 +562,7 @@
|
||||
"chat_clearPathSubtitle": "Dwing herontdekking bij volgende verzending",
|
||||
"chat_pathCleared": "Pad is vrijgegeven. Volgende bericht herontdekt route.",
|
||||
"chat_floodModeSubtitle": "Gebruik de route-schakelaar in de app-balk",
|
||||
"chat_floodModeEnabled": "Overstromingsmodus is ingeschakeld. Schakel dit uit via het route-icoon in de app-balk.",
|
||||
"chat_floodModeEnabled": "Floodmodus is ingeschakeld. Schakel dit uit via het route-icoon in de app-balk.",
|
||||
"chat_fullPath": "Volledige Pad",
|
||||
"chat_pathDetailsNotAvailable": "De paddetails zijn nog niet beschikbaar. Probeer een bericht te sturen om te vernieuwen.",
|
||||
"chat_pathSetHops": "Pad ingesteld: {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}",
|
||||
@@ -583,9 +583,9 @@
|
||||
"chat_path": "Pad",
|
||||
"chat_publicKey": "Openbare Sleutel",
|
||||
"chat_compressOutgoingMessages": "Verzenden van uitgaande berichten comprimeren",
|
||||
"chat_floodForced": "Overstroming (gedwongen)",
|
||||
"chat_directForced": "Direct (gedwongen)",
|
||||
"chat_hopsForced": "{count} sprongen (gedwongen)",
|
||||
"chat_floodForced": "Flood (afgedwongen)",
|
||||
"chat_directForced": "Direct (afgedwongen)",
|
||||
"chat_hopsForced": "{count} hops (afgedwongen)",
|
||||
"@chat_hopsForced": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
@@ -593,7 +593,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_floodAuto": "Overstroming (auto)",
|
||||
"chat_floodAuto": "Flood (auto)",
|
||||
"chat_direct": "Direct",
|
||||
"chat_poiShared": "Gedeelde POI",
|
||||
"chat_unread": "Nieuw: {count}",
|
||||
@@ -604,8 +604,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Link openen?",
|
||||
"chat_openLinkConfirmation": "Wilt u deze link in uw browser openen?",
|
||||
"chat_open": "Openen",
|
||||
"chat_couldNotOpenLink": "Kan link niet openen: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Ongeldig linkformaat",
|
||||
"map_title": "Node Map",
|
||||
"map_noNodesWithLocation": "Geen knopen met locatiegegevens",
|
||||
"map_noNodesWithLocation": "Geen nodes met locatiegegevens",
|
||||
"map_nodesNeedGps": "Nodes moeten hun GPS-coördinaten delen\nom op de kaart te verschijnen",
|
||||
"map_nodesCount": "Nodes: {count}",
|
||||
"@map_nodesCount": {
|
||||
@@ -624,7 +636,7 @@
|
||||
}
|
||||
},
|
||||
"map_chat": "Chat",
|
||||
"map_repeater": "Herhaling",
|
||||
"map_repeater": "Repeater",
|
||||
"map_room": "Ruimte",
|
||||
"map_sensor": "Sensor",
|
||||
"map_pinDm": "Verzenden als bericht (DM)",
|
||||
@@ -652,11 +664,11 @@
|
||||
}
|
||||
},
|
||||
"map_connectToShareMarkers": "Verbind met een apparaat om markers te delen",
|
||||
"map_filterNodes": "Filter Knopen",
|
||||
"map_nodeTypes": "Node Types",
|
||||
"map_chatNodes": "Chat Nodes",
|
||||
"map_repeaters": "Herhalingen",
|
||||
"map_otherNodes": "Andere knooppunten",
|
||||
"map_filterNodes": "Filter Nodes",
|
||||
"map_nodeTypes": "Nodetypes",
|
||||
"map_chatNodes": "Chatnodes",
|
||||
"map_repeaters": "Repeaters",
|
||||
"map_otherNodes": "Andere Nodes",
|
||||
"map_keyPrefix": "Prefix sleutel",
|
||||
"map_filterByKeyPrefix": "Filteren op sleutelvoorgemeld",
|
||||
"map_publicKeyPrefix": "Openbare sleutelvoorgemeld",
|
||||
@@ -665,7 +677,7 @@
|
||||
"map_lastSeenTime": "Laatste Bekeken Tijd",
|
||||
"map_sharedPin": "Gedeelde pin",
|
||||
"map_joinRoom": "Sluit Kamer",
|
||||
"map_manageRepeater": "Beheer Herhaling",
|
||||
"map_manageRepeater": "Beheer Repeater",
|
||||
"mapCache_title": "Offline Kaarten Cache",
|
||||
"mapCache_selectAreaFirst": "Select een gebied om eerst in de cache op te slaan",
|
||||
"mapCache_noTilesToDownload": "Geen tiles te downloaden voor dit gebied.",
|
||||
@@ -788,18 +800,18 @@
|
||||
"time_allTime": "Alle tijd",
|
||||
"dialog_disconnect": "Verbinden verbreken",
|
||||
"dialog_disconnectConfirm": "Ben je er zeker van dat je verbinding met dit apparaat wilt verbreken?",
|
||||
"login_repeaterLogin": "Herhaalders Inloggen",
|
||||
"login_repeaterLogin": "Inloggen Repeater",
|
||||
"login_roomLogin": "Ruimte Inloggen",
|
||||
"login_password": "Wachtwoord",
|
||||
"login_enterPassword": "Wachtwoord invoeren",
|
||||
"login_savePassword": "Wachtwoord opslaan",
|
||||
"login_savePasswordSubtitle": "Het wachtwoord wordt veilig op dit apparaat opgeslagen.",
|
||||
"login_repeaterDescription": "Voer het wachtwoord van de herhaling 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_routing": "Routing",
|
||||
"login_routingMode": "Routeerwijze",
|
||||
"login_autoUseSavedPath": "Automatisch (gebruik opgeslagen pad)",
|
||||
"login_forceFloodMode": "Dwing Overstromingsmodus",
|
||||
"login_forceFloodMode": "Dwing Floodmodus Af",
|
||||
"login_managePaths": "Padbeheer",
|
||||
"login_login": "Inloggen",
|
||||
"login_attempt": "Poging {current}/{max}",
|
||||
@@ -821,6 +833,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"login_failedMessage": "Inloggen mislukt. Het wachtwoord is onjuist of de repeater is niet bereikbaar.",
|
||||
"common_reload": "Opnieuw laden",
|
||||
"common_clear": "Schoonmaken",
|
||||
"path_currentPath": "Huidige pad: {path}",
|
||||
@@ -846,7 +859,7 @@
|
||||
"path_labelHexPrefixes": "Pad (hex-voorkeursletters)",
|
||||
"path_helperMaxHops": "Maximaal 64 sprongen. Elke prefix is 2 hexadecimale tekens (1 byte)",
|
||||
"path_selectFromContacts": "Of select contacten:",
|
||||
"path_noRepeatersFound": "Geen herhalingen of zaalservers gevonden.",
|
||||
"path_noRepeatersFound": "Geen repeaters of roomservers gevonden.",
|
||||
"path_customPathsRequire": "Aangepaste paden vereisen tussentse overstappen die berichten kunnen doorgeven.",
|
||||
"path_invalidHexPrefixes": "Ongeldige hex-voorkeursletters: {prefixes}",
|
||||
"@path_invalidHexPrefixes": {
|
||||
@@ -858,20 +871,20 @@
|
||||
},
|
||||
"path_tooLong": "Pad is te lang. Maximaal 64 sprongen zijn toegestaan.",
|
||||
"path_setPath": "Stel Pad in",
|
||||
"repeater_management": "Beheer Herhalingen",
|
||||
"repeater_management": "Beheer Repeaters",
|
||||
"repeater_managementTools": "Beheerinstrumenten",
|
||||
"repeater_status": "Status",
|
||||
"repeater_statusSubtitle": "Status, statistieken en buren bekijken",
|
||||
"repeater_telemetry": "Telemetry",
|
||||
"repeater_telemetrySubtitle": "Bekijk telemetrie van sensoren en systeemgegevens",
|
||||
"repeater_cli": "CLI",
|
||||
"repeater_cliSubtitle": "Verzend commando's naar de herhaaldere",
|
||||
"repeater_cliSubtitle": "Verzend commando's naar de repeater",
|
||||
"repeater_settings": "Instellingen",
|
||||
"repeater_settingsSubtitle": "Configureer repeaterparameters",
|
||||
"repeater_statusTitle": "Status herhalen",
|
||||
"repeater_statusTitle": "Status repeater",
|
||||
"repeater_routingMode": "Routeerwijze",
|
||||
"repeater_autoUseSavedPath": "Automatisch (gebruik opgeslagen pad)",
|
||||
"repeater_forceFloodMode": "Dwing Overloopmodus",
|
||||
"repeater_forceFloodMode": "Dwing Floodmodus Af",
|
||||
"repeater_pathManagement": "Beheer van paden",
|
||||
"repeater_refresh": "Vernieuwen",
|
||||
"repeater_statusRequestTimeout": "Statusverzoek is uitgevallen.",
|
||||
@@ -883,22 +896,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_systemInformation": "Systeem Informatie",
|
||||
"repeater_systemInformation": "Systeeminformatie",
|
||||
"repeater_battery": "Batterij",
|
||||
"repeater_clockAtLogin": "Tijd (bij aanmelden)",
|
||||
"repeater_uptime": "Beschikbaarheid",
|
||||
"repeater_queueLength": "Wachttijd",
|
||||
"repeater_debugFlags": "Debug Flags",
|
||||
"repeater_radioStatistics": "Radio Statistieken",
|
||||
"repeater_debugFlags": "Debugvlaggen",
|
||||
"repeater_radioStatistics": "Radiostatistieken",
|
||||
"repeater_lastRssi": "Laatste RSSI",
|
||||
"repeater_lastSnr": "Laatste SNR",
|
||||
"repeater_noiseFloor": "Ruishoordniveau",
|
||||
"repeater_noiseFloor": "Ruisvloer",
|
||||
"repeater_txAirtime": "TX Airtime",
|
||||
"repeater_rxAirtime": "RX Airtime",
|
||||
"repeater_packetStatistics": "Pakket Statistieken",
|
||||
"repeater_packetStatistics": "Pakketstatistieken",
|
||||
"repeater_sent": "Verzonden",
|
||||
"repeater_received": "Ontvangen",
|
||||
"repeater_duplicates": "Dubbele",
|
||||
"repeater_duplicates": "Duplicaat",
|
||||
"repeater_daysHoursMinsSecs": "{days} dagen {hours} uur {minutes} minuten {seconds} seconden",
|
||||
"@repeater_daysHoursMinsSecs": {
|
||||
"placeholders": {
|
||||
@@ -916,7 +929,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_packetTxTotal": "Totaal: {total}, Overstroming: {flood}, Direct: {direct}",
|
||||
"repeater_packetTxTotal": "Totaal: {total}, Flood: {flood}, Direct: {direct}",
|
||||
"@repeater_packetTxTotal": {
|
||||
"placeholders": {
|
||||
"total": {
|
||||
@@ -930,7 +943,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_packetRxTotal": "Totaal: {total}, Overstroming: {flood}, Direct: {direct}",
|
||||
"repeater_packetRxTotal": "Totaal: {total}, Flood: {flood}, Direct: {direct}",
|
||||
"@repeater_packetRxTotal": {
|
||||
"placeholders": {
|
||||
"total": {
|
||||
@@ -944,7 +957,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_duplicatesFloodDirect": "Overstroming: {flood}, Direct: {direct}",
|
||||
"repeater_duplicatesFloodDirect": "Flood: {flood}, Direct: {direct}",
|
||||
"@repeater_duplicatesFloodDirect": {
|
||||
"placeholders": {
|
||||
"flood": {
|
||||
@@ -963,10 +976,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_settingsTitle": "Herstelinstellingen",
|
||||
"repeater_settingsTitle": "Repeater Instellingen",
|
||||
"repeater_basicSettings": "Basisinstellingen",
|
||||
"repeater_repeaterName": "Herhaalnaam",
|
||||
"repeater_repeaterNameHelper": "Weergave naam voor deze herhaling",
|
||||
"repeater_repeaterName": "Repeaternaam",
|
||||
"repeater_repeaterNameHelper": "Weergave naam voor deze repeater",
|
||||
"repeater_adminPassword": "Admin wachtwoord",
|
||||
"repeater_adminPasswordHelper": "Volledige toegangspaswoord",
|
||||
"repeater_guestPassword": "Wachtwoord Gast",
|
||||
@@ -977,7 +990,7 @@
|
||||
"repeater_txPower": "TX Power",
|
||||
"repeater_txPowerHelper": "1-30 dBm",
|
||||
"repeater_bandwidth": "Bandbreedte",
|
||||
"repeater_spreadingFactor": "Spreadsnelheid",
|
||||
"repeater_spreadingFactor": "Spreidingsfactor",
|
||||
"repeater_codingRate": "Codeertarief",
|
||||
"repeater_locationSettings": "Locatie Instellingen",
|
||||
"repeater_latitude": "Breedtegraad",
|
||||
@@ -985,11 +998,11 @@
|
||||
"repeater_longitude": "Lengtegraad",
|
||||
"repeater_longitudeHelper": "Graadseconden (bijv. -122.4194)",
|
||||
"repeater_features": "Kenmerken",
|
||||
"repeater_packetForwarding": "Pakketdoorstrooming",
|
||||
"repeater_packetForwardingSubtitle": "Herstel activeren om pakketten door te sturen",
|
||||
"repeater_packetForwarding": "Pakketdoorvoering",
|
||||
"repeater_packetForwardingSubtitle": "Repeater instellen om pakketten door te sturen",
|
||||
"repeater_guestAccess": "Toegang voor Gasten",
|
||||
"repeater_guestAccessSubtitle": "Toegestane leesbeheer toegang voor gasten.",
|
||||
"repeater_privacyMode": "Privacy Mode",
|
||||
"repeater_privacyMode": "Privacy Modus",
|
||||
"repeater_privacyModeSubtitle": "Naam/locatie verbergen in advertenties",
|
||||
"repeater_advertisementSettings": "Advertentie Instellingen",
|
||||
"repeater_localAdvertInterval": "Lokale Advertentie Interval",
|
||||
@@ -1001,7 +1014,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_floodAdvertInterval": "Advertentie Interval bij overstroming",
|
||||
"repeater_floodAdvertInterval": "Flood Advertentie Interval",
|
||||
"repeater_floodAdvertIntervalHours": "{hours} uur",
|
||||
"@repeater_floodAdvertIntervalHours": {
|
||||
"placeholders": {
|
||||
@@ -1012,15 +1025,15 @@
|
||||
},
|
||||
"repeater_encryptedAdvertInterval": "Versleutelde Advertentie Interval",
|
||||
"repeater_dangerZone": "Gevaarzone",
|
||||
"repeater_rebootRepeater": "Herstart Herhaalder",
|
||||
"repeater_rebootRepeaterSubtitle": "De herstart van het herhalerapparaat",
|
||||
"repeater_rebootRepeater": "Herstart Repeater",
|
||||
"repeater_rebootRepeaterSubtitle": "Herstart het Repeaterapparaat",
|
||||
"repeater_rebootRepeaterConfirm": "Ben je er zeker van dat je deze repeater opnieuw wilt opstarten?",
|
||||
"repeater_regenerateIdentityKey": "Identiteit sleutel opnieuw genereren",
|
||||
"repeater_regenerateIdentityKeySubtitle": "Nieuwe publieke/private sleutelpaar genereren",
|
||||
"repeater_regenerateIdentityKeyConfirm": "Dit genereert een nieuwe identiteit voor de herhaling. Doorgaan?",
|
||||
"repeater_regenerateIdentityKeyConfirm": "Dit genereert een nieuwe identiteit voor de repeater. Doorgaan?",
|
||||
"repeater_eraseFileSystem": "Verwijder Besturingssysteem",
|
||||
"repeater_eraseFileSystemSubtitle": "Formateer het herhalende bestandsysteem",
|
||||
"repeater_eraseFileSystemConfirm": "WAARSCHUWING: Dit zal alle gegevens op de herhaling wissen. Dit kan niet worden teruggedraaid!",
|
||||
"repeater_eraseFileSystemSubtitle": "Formateer het bestandsysteem van de repeater",
|
||||
"repeater_eraseFileSystemConfirm": "WAARSCHUWING: Dit zal alle gegevens op de repeater wissen. Dit kan niet worden teruggedraaid!",
|
||||
"repeater_eraseSerialOnly": "Verwijderen is alleen beschikbaar via de seriële console.",
|
||||
"repeater_commandSent": "Commando verzonden: {command}",
|
||||
"@repeater_commandSent": {
|
||||
@@ -1049,7 +1062,7 @@
|
||||
}
|
||||
},
|
||||
"repeater_refreshBasicSettings": "Basisinstellingen vernieuwen",
|
||||
"repeater_refreshRadioSettings": "Radiozenders Instellingen Bijwerken",
|
||||
"repeater_refreshRadioSettings": "Radiozender Instellingen Verversen",
|
||||
"repeater_refreshTxPower": "Nieuw laden TX-vermogen",
|
||||
"repeater_refreshLocationSettings": "Instellingen Locatie Vernieuwen",
|
||||
"repeater_refreshPacketForwarding": "Vernieuwen Pakket Doorversturing",
|
||||
@@ -1072,7 +1085,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_cliTitle": "Herhaling CLI",
|
||||
"repeater_cliTitle": "Repeater CLI",
|
||||
"repeater_debugNextCommand": "Debug Volgende Commando",
|
||||
"repeater_commandHelp": "Help",
|
||||
"repeater_clearHistory": "Verwijder Geschiedenis",
|
||||
@@ -1106,14 +1119,14 @@
|
||||
"repeater_cliHelpClearStats": "Reset verschillende statistiek-tellers naar nul.",
|
||||
"repeater_cliHelpSetAf": "Stelt de luchtvaartfactor in.",
|
||||
"repeater_cliHelpSetTx": "Stelt LoRa zendvermogen in dBm. (om te wijzigen)",
|
||||
"repeater_cliHelpSetRepeat": "Activeert of deactiveert de herhalerrol voor dit knoop.",
|
||||
"repeater_cliHelpSetRepeat": "Activeert of deactiveert de repeater rol van deze node.",
|
||||
"repeater_cliHelpSetAllowReadOnly": "(Kamervisie) Als 'aan', dan wordt inloggen met een blanco wachtwoord toegestaan, maar kan niet naar de kamervisie Posten. (alleen lezen mogelijk).",
|
||||
"repeater_cliHelpSetFloodMax": "Stelt het maximale aantal hops van een inkomend overlastpakket in (indien >= max, wordt het pakket niet doorgestuurd)",
|
||||
"repeater_cliHelpSetFloodMax": "Stelt het maximale aantal hops van een inkomend floodpakket in (indien >= max, wordt het pakket niet doorgestuurd)",
|
||||
"repeater_cliHelpSetIntThresh": "Stelt de Interferentiewaarde (in dB) in. Standaardwaarde is 14. Stel in op 0 om het detecteren van kanaalinterferentie uit te schakelen.",
|
||||
"repeater_cliHelpSetAgcResetInterval": "Stelt het interval in om de Auto Gain Controller te resetten. Stel in op 0 om dit uit te schakelen.",
|
||||
"repeater_cliHelpSetMultiAcks": "Activeert of deactiveert de functie 'dubbele ACKs'.",
|
||||
"repeater_cliHelpSetMultiAcks": "Activeert of deactiveert de functie 'duplicate ACKs'.",
|
||||
"repeater_cliHelpSetAdvertInterval": "Stelt het timerinterval in minuten in om een lokale (zero-hop) advertentiepakket te versturen. Stel in op 0 om uit te schakelen.",
|
||||
"repeater_cliHelpSetFloodAdvertInterval": "Stelt het timerinterval in uren in om een overstromingsadvertentiepakket te versturen. Stel in op 0 om dit uit te schakelen.",
|
||||
"repeater_cliHelpSetFloodAdvertInterval": "Stelt het timerinterval in uren in om een floodadvertentiepakket te versturen. Stel in op 0 om dit uit te schakelen.",
|
||||
"repeater_cliHelpSetGuestPassword": "Stelt/past de gastenwacht aan of wijzigt deze. (voor herstelcontacten kunnen gastelogins de \"Get Stats\" verzoek verzenden)",
|
||||
"repeater_cliHelpSetName": "Stelt de advertentietitel in.",
|
||||
"repeater_cliHelpSetLat": "Stelt de breedtegraad van de advertentiekaart in. (graadrijssysteem)",
|
||||
@@ -1136,7 +1149,7 @@
|
||||
"repeater_cliHelpLogErase": "Verwijdert de pakketlogs uit het bestandssysteem.",
|
||||
"repeater_cliHelpNeighbors": "Toont een lijst met andere repeater nodes die via nul-hop advertenties zijn gehoord. Elke regel is id-prefix-hex:timestamp:snr-times-4",
|
||||
"repeater_cliHelpNeighborRemove": "Verwijdert de eerste overeenkomende vermelding (via pubkey prefix (hex)) uit de lijst van buren.",
|
||||
"repeater_cliHelpRegion": "(reeks alleen) Lijst alle gedefinieerde regio's en huidige overstromingsrechten.",
|
||||
"repeater_cliHelpRegion": "(Alleen Serieel) Lijst alle gedefinieerde regio's en huidige floodrechten.",
|
||||
"repeater_cliHelpRegionLoad": "LET OP: dit is een speciale multi-command aanroep. Elke volgende opdracht is een regiortaak (uitgelijnd met spaties om de ouderhiërarchie aan te duiden, met minimaal één spatie). Beëindigd door een lege regel/opdracht te sturen.",
|
||||
"repeater_cliHelpRegionGet": "Zoekt naar regio met gegeven naam voorvoegsel (of \"\" voor de globale scope). Antwoordt met \"-> regio-naam (ouder-naam) 'F'\"",
|
||||
"repeater_cliHelpRegionPut": "Voegt of wijzigt een regio-definitie met de gegeven naam.",
|
||||
@@ -1148,8 +1161,8 @@
|
||||
"repeater_cliHelpRegionSave": "Bewaar de lijst/kaart van de regio's naar de opslag.",
|
||||
"repeater_cliHelpGps": "Geeft de status van de GPS. Wanneer de GPS uit staat, antwoordt het alleen met \"uit\", als het aan staat, antwoordt het met \"aan\", status, fix, sat count.",
|
||||
"repeater_cliHelpGpsOnOff": "Schakel de GPS-standby aan/uit.",
|
||||
"repeater_cliHelpGpsSync": "Synchroniseer knooptime met GPS-klok.",
|
||||
"repeater_cliHelpGpsSetLoc": "Stel de positie van het knoop vast naar GPS-coördinaten en sla de voorkeuren op.",
|
||||
"repeater_cliHelpGpsSync": "Synchroniseer node met GPS-klok.",
|
||||
"repeater_cliHelpGpsSetLoc": "Stel de positie van de node vast als GPS-coördinaten en sla de voorkeuren op.",
|
||||
"repeater_cliHelpGpsAdvert": "Geeft de locatie advertentieconfiguratie van de node:\n- none: locatie niet in advertenties opnemen\n- share: gps locatie delen (van SensorManager)\n- prefs: locatie adverteren die in de voorkeuren is opgeslagen",
|
||||
"repeater_cliHelpGpsAdvertSet": "Stelt advertentie locatie configuratie in.",
|
||||
"repeater_commandsListTitle": "Commandenlijst",
|
||||
@@ -1158,8 +1171,8 @@
|
||||
"repeater_settingsCategory": "Instellingen",
|
||||
"repeater_bridge": "Bruggen",
|
||||
"repeater_logging": "Logging",
|
||||
"repeater_neighborsRepeaterOnly": "Buren (Alleen herhaald)",
|
||||
"repeater_regionManagementRepeaterOnly": "Regiobeheer (Alleen voor Repeater)",
|
||||
"repeater_neighborsRepeaterOnly": "Buren (Alleen repeaters)",
|
||||
"repeater_regionManagementRepeaterOnly": "Regiobeheer (Alleen Repeater)",
|
||||
"repeater_regionNote": "Regio-commando's zijn geïntroduceerd om regio-definities en permissies te beheren.",
|
||||
"repeater_gpsManagement": "Beheer GPS",
|
||||
"repeater_gpsNote": "De GPS-commando is geïntroduceerd om locatiegerelateerde onderwerpen te beheren.",
|
||||
@@ -1228,12 +1241,12 @@
|
||||
"channelPath_title": "Pakketpad",
|
||||
"channelPath_viewMap": "Kaart bekijken",
|
||||
"channelPath_otherObservedPaths": "Overige Waargenomen Paden",
|
||||
"channelPath_repeaterHops": "Herhalingstapjes",
|
||||
"channelPath_repeaterHops": "Repeater Hops",
|
||||
"channelPath_noHopDetails": "De details van de pakket zijn niet verstrekt.",
|
||||
"channelPath_messageDetails": "Details Bericht",
|
||||
"channelPath_senderLabel": "Afzender",
|
||||
"channelPath_timeLabel": "Tijd",
|
||||
"channelPath_repeatsLabel": "Herhalen",
|
||||
"channelPath_repeatsLabel": "Repeats",
|
||||
"channelPath_pathLabel": "Pad {index}",
|
||||
"channelPath_observedLabel": "Waargenomen",
|
||||
"channelPath_observedPathTitle": "Waargenomen pad {index} • {hops}",
|
||||
@@ -1271,7 +1284,7 @@
|
||||
}
|
||||
},
|
||||
"channelPath_unknownPath": "Onbekend",
|
||||
"channelPath_floodPath": "Overstroming",
|
||||
"channelPath_floodPath": "Flood",
|
||||
"channelPath_directPath": "Direct",
|
||||
"channelPath_observedZeroOf": "0 van {total} sprongen",
|
||||
"@channelPath_observedZeroOf": {
|
||||
@@ -1293,7 +1306,7 @@
|
||||
}
|
||||
},
|
||||
"channelPath_mapTitle": "Padkaart",
|
||||
"channelPath_noRepeaterLocations": "Geen herhaler locaties beschikbaar voor deze route.",
|
||||
"channelPath_noRepeaterLocations": "Geen repeaters beschikbaar voor deze route.",
|
||||
"channelPath_primaryPath": "Pad {index} (Hoofdtype)",
|
||||
"@channelPath_primaryPath": {
|
||||
"placeholders": {
|
||||
@@ -1323,7 +1336,7 @@
|
||||
}
|
||||
},
|
||||
"channelPath_noHopDetailsAvailable": "Geen details beschikbaar voor dit pakket.",
|
||||
"channelPath_unknownRepeater": "Onbekend Herhaalaar",
|
||||
"channelPath_unknownRepeater": "Onbekend Repeater",
|
||||
"listFilter_tooltip": "Filteren en sorteren",
|
||||
"listFilter_sortBy": "Sorteren door",
|
||||
"listFilter_latestMessages": "Recente berichten",
|
||||
@@ -1332,8 +1345,193 @@
|
||||
"listFilter_filters": "Filters",
|
||||
"listFilter_all": "Alles",
|
||||
"listFilter_users": "Gebruikers",
|
||||
"listFilter_repeaters": "Herhalingen",
|
||||
"listFilter_roomServers": "Kamervirtualisatie",
|
||||
"listFilter_repeaters": "Repeaters",
|
||||
"listFilter_roomServers": "Roomservers",
|
||||
"listFilter_unreadOnly": "Alleen ongelezen",
|
||||
"listFilter_newGroup": "Nieuwe groep"
|
||||
"listFilter_newGroup": "Nieuwe groep",
|
||||
"@neighbors_errorLoading": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_neighbours": "Buren",
|
||||
"repeater_neighboursSubtitle": "Bekijk nul hops buren.",
|
||||
"neighbors_receivedData": "Ontvangen Buurdata",
|
||||
"neighbors_requestTimedOut": "Buren vragen om tijdelijk uitgeschakeld.",
|
||||
"neighbors_errorLoading": "Fout bij het laden van buren: {error}",
|
||||
"neighbors_repeatersNeighbours": "Herhalingen Buren",
|
||||
"neighbors_noData": "Geen gegevens van buren beschikbaar.",
|
||||
"channels_createPrivateChannelDesc": "Beveiligd met een geheime sleutel.",
|
||||
"channels_createPrivateChannel": "Maak een Privé Kanaal",
|
||||
"channels_joinPrivateChannel": "Sluit een Privé Kanaal aan",
|
||||
"channels_joinPrivateChannelDesc": "Handmatig een geheime sleutel invoeren.",
|
||||
"channels_joinPublicChannel": "Sluit het Open Kanaal",
|
||||
"channels_joinPublicChannelDesc": "Iedereen kan dit kanaal aanmelden.",
|
||||
"channels_joinHashtagChannel": "Sluit een Hashtag Kanaal",
|
||||
"channels_joinHashtagChannelDesc": "Iedereen kan lid worden van hashtag-kanalen.",
|
||||
"channels_scanQrCode": "Scan een QR-code",
|
||||
"channels_scanQrCodeComingSoon": "Komt later",
|
||||
"channels_enterHashtag": "Voer hashtag in",
|
||||
"channels_hashtagHint": "bijv. #team",
|
||||
"@neighbors_unknownContact": {
|
||||
"placeholders": {
|
||||
"pubkey": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@neighbors_heardAgo": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"neighbors_unknownContact": "Onbekende {pubkey}",
|
||||
"neighbors_heardAgo": "Horen: {time} geleden",
|
||||
"settings_locationGPSEnable": "GPS inschakelen",
|
||||
"settings_locationGPSEnableSubtitle": "Activeer automatisch locatieupdates via GPS.",
|
||||
"settings_locationIntervalSec": "Interval voor GPS (Seconden)",
|
||||
"settings_locationIntervalInvalid": "De intervallen moeten minstens 60 seconden zijn en minder dan 86400 seconden.",
|
||||
"contacts_manageRoom": "Beheer Ruimte Server",
|
||||
"room_management": "Beheer Server Kamer",
|
||||
"@community_joinConfirmation": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_created": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_joined": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_qrInstructions": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_alreadyMemberMessage": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleteConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleted": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_forCommunity": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_title": "Gemeenschap",
|
||||
"common_ok": "OK",
|
||||
"community_createDesc": "Maak een nieuwe community en deel deze via QR-code.",
|
||||
"community_create": "Maak Gemeenschap",
|
||||
"community_join": "Sluit aan",
|
||||
"community_joinTitle": "Worden lid van de community",
|
||||
"community_joinConfirmation": "Wil je je aansluiten bij de community \"{name}\"?",
|
||||
"community_scanQr": "Scan Gemeenschap QR",
|
||||
"community_scanInstructions": "Richt de camera op een gemeenschappelijke QR-code",
|
||||
"community_showQr": "Toon QR-code",
|
||||
"community_publicChannel": "Gemeenschap Openbaar",
|
||||
"community_hashtagChannel": "Gemeenschappelijk Hashtag",
|
||||
"community_name": "Gemeenschapnaam",
|
||||
"community_enterName": "Voer de gemeenschapsnaam in",
|
||||
"community_created": "Gemeenschap \"{name}\" is aangemaakt",
|
||||
"community_joined": "Gevonden in de community \"{name}\"",
|
||||
"community_qrTitle": "Deel Gemeenschap",
|
||||
"community_qrInstructions": "Scan deze QR-code om je aan te sluiten bij {name}",
|
||||
"community_hashtagPrivacyHint": "Community hashtag-kanalen zijn alleen toegankelijk voor leden van de community",
|
||||
"community_invalidQrCode": "Ongeldige community QR-code",
|
||||
"community_alreadyMember": "Alleen al lid",
|
||||
"community_alreadyMemberMessage": "U bent al lid van \"{name}\".",
|
||||
"community_addPublicChannel": "Voeg een Openbaar Gemeenschapskanaal toe",
|
||||
"community_addPublicChannelHint": "Automatisch de publieke kanaal toevoegen voor deze community",
|
||||
"community_noCommunities": "Nog geen gemeenschappen zijn bijgesloten.",
|
||||
"community_scanOrCreate": "Scan een QR-code of een community aanmaken om te beginnen",
|
||||
"community_manageCommunities": "Beheer Gemeenschappen",
|
||||
"community_delete": "Laat Gemeenschap",
|
||||
"community_deleteConfirm": "\"{name}\" verlaten?",
|
||||
"community_deleteChannelsWarning": "Dit verwijdert ook {count} kanaal/kanalen en hun berichten.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Community \"{name}\" verlaten",
|
||||
"community_addHashtagChannel": "Voeg Community Hashtag toe",
|
||||
"community_addHashtagChannelDesc": "Voeg een hashtag-kanaal toe aan deze community",
|
||||
"community_selectCommunity": "Selecteer Gemeenschap",
|
||||
"community_regularHashtag": "Gewone Hashtag",
|
||||
"community_regularHashtagDesc": "Open hashtag (iedereen kan deelnemen)",
|
||||
"community_communityHashtag": "Gemeenschappelijk Hashtag",
|
||||
"community_communityHashtagDesc": "Alleen zichtbaar voor leden van de community",
|
||||
"community_forCommunity": "Voor {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_secretRegenerated": "Geheim hersteld voor \"{name}\"",
|
||||
"community_regenerateSecret": "Regeneer Geheimwoord",
|
||||
"community_regenerateSecretConfirm": "Regeneere de geheime sleutel voor \"{name}\"? Alle leden moeten de nieuwe QR-code scannen om verder te communiceren.",
|
||||
"community_regenerate": "Regeneer",
|
||||
"community_updateSecret": "Bijwerken Geheime",
|
||||
"community_secretUpdated": "Geheim gewijzigd voor \"{name}\"",
|
||||
"community_scanToUpdateSecret": "Scan de nieuwe QR-code om het geheim voor \"{name}\" bij te werken"
|
||||
}
|
||||
|
||||
+199
-1
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Otworzyć link?",
|
||||
"chat_openLinkConfirmation": "Czy chcesz otworzyć ten link w przeglądarce?",
|
||||
"chat_open": "Otwórz",
|
||||
"chat_couldNotOpenLink": "Nie można otworzyć linku: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Nieprawidłowy format linku",
|
||||
"map_title": "Mapa węzłów",
|
||||
"map_noNodesWithLocation": "Brak węzłów z danymi lokalizacyjnymi",
|
||||
"map_nodesNeedGps": "Węzły muszą udostępniać swoje współrzędne GPS,\naby pojawić się na mapie.",
|
||||
@@ -821,6 +833,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"login_failedMessage": "Logowanie nie powiodło się. Hasło jest nieprawidłowe albo repeater jest nieosiągalny.",
|
||||
"common_reload": "Ponownie załadować",
|
||||
"common_clear": "Wyczyść",
|
||||
"path_currentPath": "Aktualny ścieżka: {path}",
|
||||
@@ -1335,5 +1348,190 @@
|
||||
"listFilter_repeaters": "Powtarzacze",
|
||||
"listFilter_roomServers": "Serwery pokoju",
|
||||
"listFilter_unreadOnly": "Tylko nieprzeczytane",
|
||||
"listFilter_newGroup": "Nowa grupa"
|
||||
"listFilter_newGroup": "Nowa grupa",
|
||||
"@neighbors_errorLoading": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_neighbours": "Sąsiedzi",
|
||||
"repeater_neighboursSubtitle": "Wyświetl sąsiedztwo zerowych hopów.",
|
||||
"neighbors_receivedData": "Otrzymano dane sąsiedztwa",
|
||||
"neighbors_requestTimedOut": "Sąsiedzi proszą o wyłączenie timingu.",
|
||||
"neighbors_errorLoading": "Błąd podczas ładowania sąsiadów: {error}",
|
||||
"neighbors_repeatersNeighbours": "Powtarzacze Sąsiedzi",
|
||||
"neighbors_noData": "Brak danych dotyczących sąsiadów.",
|
||||
"channels_joinPrivateChannelDesc": "Ręcznie wprowadź klucz tajny.",
|
||||
"channels_createPrivateChannel": "Utwórz Prywatny Kanał",
|
||||
"channels_createPrivateChannelDesc": "Zabezpieczone kluczem szyfrowym.",
|
||||
"channels_joinPrivateChannel": "Dołącz do Prywatnego Kanału",
|
||||
"channels_joinPublicChannel": "Dołącz do kanału publicznego.",
|
||||
"channels_joinPublicChannelDesc": "Każdy może dołączyć do tego kanału.",
|
||||
"channels_joinHashtagChannel": "Dołącz do kanału oznaczanego hashtagiem",
|
||||
"channels_joinHashtagChannelDesc": "Każdy może dołączyć do kanałów z hashtagami.",
|
||||
"channels_scanQrCode": "Skanuj kod QR",
|
||||
"channels_scanQrCodeComingSoon": "Wkrótce",
|
||||
"channels_enterHashtag": "Wprowadź hashtag",
|
||||
"channels_hashtagHint": "np. #zespół",
|
||||
"@neighbors_unknownContact": {
|
||||
"placeholders": {
|
||||
"pubkey": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@neighbors_heardAgo": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"neighbors_heardAgo": "Usłyszano: {time} temu",
|
||||
"neighbors_unknownContact": "Nieznana {pubkey}",
|
||||
"settings_locationGPSEnable": "Włącz GPS",
|
||||
"settings_locationGPSEnableSubtitle": "Włącza automatyczne aktualizowanie pozycji za pomocą GPS.",
|
||||
"settings_locationIntervalSec": "Interwał dla GPS (Sekundy)",
|
||||
"settings_locationIntervalInvalid": "Interwał musi wynosić co najmniej 60 sekund i mniej niż 86400 sekund.",
|
||||
"contacts_manageRoom": "Zarządzaj Serwerem Pokoju",
|
||||
"room_management": "Zarządzanie Serwerem Pokoju",
|
||||
"@community_joinConfirmation": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_created": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_joined": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_qrInstructions": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_alreadyMemberMessage": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleteConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleted": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_forCommunity": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_createDesc": "Utwórz nową społeczność i udostępnij za pomocą kodu QR.",
|
||||
"community_title": "Społeczność",
|
||||
"community_create": "Utwórz Społeczność",
|
||||
"common_ok": "OK",
|
||||
"community_join": "Dołącz",
|
||||
"community_joinTitle": "Dołącz do społeczności",
|
||||
"community_joinConfirmation": "Czy chcesz dołączyć do społeczności \"{name}\"?",
|
||||
"community_scanQr": "Skanuj QR kod społeczności",
|
||||
"community_scanInstructions": "Skieruj kamerę w kierunku kodu QR społeczności.",
|
||||
"community_showQr": "Pokaż kod QR",
|
||||
"community_publicChannel": "Społeczność Publiczna",
|
||||
"community_hashtagChannel": "Hashtag Społeczności",
|
||||
"community_name": "Nazwa Społeczności",
|
||||
"community_enterName": "Wprowadź nazwę społeczności",
|
||||
"community_created": "Społeczność \"{name}\" została utworzona",
|
||||
"community_joined": "Dołączył do społeczności \"{name}\"",
|
||||
"community_qrTitle": "Dziel się Społecznością",
|
||||
"community_qrInstructions": "Skanuj ten kod QR, aby dołączyć {name}",
|
||||
"community_hashtagPrivacyHint": "Kanały hashtagowe społeczności są dostępne tylko dla członków społeczności",
|
||||
"community_invalidQrCode": "Nieprawidłowy kod QR społeczności.",
|
||||
"community_alreadyMember": "Już jesteś członkiem.",
|
||||
"community_alreadyMemberMessage": "Jesteś już członkiem \"{name}\".",
|
||||
"community_addPublicChannel": "Dodaj Kanał Publiczny Społeczności",
|
||||
"community_addPublicChannelHint": "Automatycznie dodaj kanał publiczny dla tej społeczności.",
|
||||
"community_noCommunities": "Nie dołączono jeszcze żadnych społeczności.",
|
||||
"community_scanOrCreate": "Skanuj kod QR lub utwórz społeczność, aby zacząć.",
|
||||
"community_manageCommunities": "Zarządzaj Grupami",
|
||||
"community_delete": "Opuszczenie Społeczności",
|
||||
"community_deleteConfirm": "Opuścić \"{name}\"?",
|
||||
"community_deleteChannelsWarning": "Spowoduje to również usunięcie {count} kanału/kanałów i ich wiadomości.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Opuszczono społeczność \"{name}\"",
|
||||
"community_addHashtagChannel": "Dodaj hashtag społeczności",
|
||||
"community_addHashtagChannelDesc": "Dodaj kanał z hashtagiem dla tej społeczności",
|
||||
"community_selectCommunity": "Wybierz społeczność",
|
||||
"community_regularHashtag": "Hashtag regular",
|
||||
"community_regularHashtagDesc": "Publiczny hashtag (każdy może dołączyć)",
|
||||
"community_communityHashtag": "Hashtag Społeczności",
|
||||
"community_communityHashtagDesc": "Dostępne tylko dla członków społeczności",
|
||||
"community_forCommunity": "Dla {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerate": "Zregeneruj",
|
||||
"community_secretRegenerated": "Hasło ponownie wygenerowane dla \"{name}\"",
|
||||
"community_regenerateSecret": "Zregeneruj sekret",
|
||||
"community_regenerateSecretConfirm": "Regeneruj tajny klucz dla \"{name}\"? Wszyscy członkowie będą musieli zeskanować nowy kod QR, aby kontynuować komunikację.",
|
||||
"community_scanToUpdateSecret": "Skanuj nowy kod QR, aby zaktualizować sekret dla \"{name}\"",
|
||||
"community_secretUpdated": "Hasło zaktualizowane dla \"{name}\"",
|
||||
"community_updateSecret": "Zaktualizuj tajny klucz"
|
||||
}
|
||||
|
||||
+199
-1
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Abrir link?",
|
||||
"chat_openLinkConfirmation": "Deseja abrir este link no seu navegador?",
|
||||
"chat_open": "Abrir",
|
||||
"chat_couldNotOpenLink": "Não foi possível abrir o link: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Formato de link inválido",
|
||||
"map_title": "Mapa de Nós",
|
||||
"map_noNodesWithLocation": "Não existem nós com dados de localização.",
|
||||
"map_nodesNeedGps": "Os nós precisam partilhar as suas coordenadas GPS\npara aparecerem no mapa",
|
||||
@@ -821,6 +833,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"login_failedMessage": "Falha no login. A senha está incorreta ou o repetidor está inacessível.",
|
||||
"common_reload": "Recarregar",
|
||||
"common_clear": "Limpar",
|
||||
"path_currentPath": "Caminho atual: {path}",
|
||||
@@ -1335,5 +1348,190 @@
|
||||
"listFilter_repeaters": "Repetidores",
|
||||
"listFilter_roomServers": "Servidores de sala",
|
||||
"listFilter_unreadOnly": "Apenas não lido",
|
||||
"listFilter_newGroup": "Novo grupo"
|
||||
"listFilter_newGroup": "Novo grupo",
|
||||
"@neighbors_errorLoading": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_neighbours": "Vizinhos",
|
||||
"neighbors_receivedData": "Dados dos Vizinhos Recebidos",
|
||||
"repeater_neighboursSubtitle": "Visualizar vizinhos de salto zero.",
|
||||
"neighbors_requestTimedOut": "Vizinhos solicitam tempo limite esgotado.",
|
||||
"neighbors_errorLoading": "Erro ao carregar vizinhos: {error}",
|
||||
"neighbors_repeatersNeighbours": "Repetidores Vizinhos",
|
||||
"neighbors_noData": "Não estão disponíveis dados de vizinhos.",
|
||||
"channels_createPrivateChannelDesc": "Protegido com uma chave secreta.",
|
||||
"channels_joinPrivateChannelDesc": "Inserir uma chave secreta manualmente.",
|
||||
"channels_createPrivateChannel": "Criar um Canal Privado",
|
||||
"channels_joinPrivateChannel": "Junte-se a um Canal Privado",
|
||||
"channels_joinPublicChannel": "Junte-se ao Canal Público",
|
||||
"channels_joinPublicChannelDesc": "Qualquer pessoa pode entrar neste canal.",
|
||||
"channels_joinHashtagChannel": "Junte-se a um Canal com Hashtag",
|
||||
"channels_joinHashtagChannelDesc": "Qualquer pessoa pode participar de canais com hashtag.",
|
||||
"channels_scanQrCode": "Digitalizar um Código QR",
|
||||
"channels_scanQrCodeComingSoon": "Em breve",
|
||||
"channels_enterHashtag": "Insira hashtag",
|
||||
"channels_hashtagHint": "ex. #equipe",
|
||||
"@neighbors_unknownContact": {
|
||||
"placeholders": {
|
||||
"pubkey": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@neighbors_heardAgo": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"neighbors_heardAgo": "Ouvido: {time} atrás",
|
||||
"neighbors_unknownContact": "{pubkey} Desconhecido",
|
||||
"settings_locationGPSEnable": "Ativar GPS",
|
||||
"settings_locationGPSEnableSubtitle": "Habilita a atualização automática da localização via GPS.",
|
||||
"settings_locationIntervalInvalid": "O intervalo deve ser de pelo menos 60 segundos e inferior a 86400 segundos.",
|
||||
"settings_locationIntervalSec": "Intervalo para GPS (Segundos)",
|
||||
"contacts_manageRoom": "Gerenciar Servidor de Sala",
|
||||
"room_management": "Gerenciamento de Servidor de Sala",
|
||||
"@community_joinConfirmation": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_created": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_joined": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_qrInstructions": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_alreadyMemberMessage": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleteConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleted": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_forCommunity": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_title": "Comunidade",
|
||||
"community_createDesc": "Crie uma nova comunidade e compartilhe via código QR.",
|
||||
"common_ok": "OK",
|
||||
"community_create": "Criar Comunidade",
|
||||
"community_join": "Junte-se",
|
||||
"community_joinTitle": "Junte-se à Comunidade",
|
||||
"community_joinConfirmation": "Você gostaria de se juntar à comunidade \"{name}\"?",
|
||||
"community_scanQr": "Digitalizar a QR Code da Comunidade",
|
||||
"community_scanInstructions": "Aponte a câmera para um código QR da comunidade",
|
||||
"community_showQr": "Mostrar Código QR",
|
||||
"community_publicChannel": "Comunidade Pública",
|
||||
"community_hashtagChannel": "Hashtag da Comunidade",
|
||||
"community_name": "Nome da Comunidade",
|
||||
"community_enterName": "Insira o nome da comunidade",
|
||||
"community_created": "Comunidade \"{name}\" criada",
|
||||
"community_joined": "Juntou-se à comunidade \"{name}\"",
|
||||
"community_qrTitle": "Partilhar Comunidade",
|
||||
"community_qrInstructions": "Escanear este código QR para juntar-se a {name}",
|
||||
"community_hashtagPrivacyHint": "Os canais de hashtag da comunidade só podem ser acessados por membros da comunidade",
|
||||
"community_invalidQrCode": "Código QR da comunidade inválido",
|
||||
"community_alreadyMember": "Já é Membro",
|
||||
"community_alreadyMemberMessage": "Você já é membro de \"{name}\".",
|
||||
"community_addPublicChannel": "Adicionar Canal Público da Comunidade",
|
||||
"community_addPublicChannelHint": "Adicionar automaticamente o canal público para esta comunidade",
|
||||
"community_noCommunities": "Ainda não foram adicionadas comunidades.",
|
||||
"community_scanOrCreate": "Escaneie um código QR ou crie uma comunidade para começar.",
|
||||
"community_manageCommunities": "Gerenciar Comunidades",
|
||||
"community_delete": "Deixar Comunidade",
|
||||
"community_deleteConfirm": "Sair de \"{name}\"?",
|
||||
"community_deleteChannelsWarning": "Isso também excluirá {count} canal/canais e suas mensagens.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Saiu da comunidade \"{name}\"",
|
||||
"community_addHashtagChannel": "Adicionar Hashtag da Comunidade",
|
||||
"community_addHashtagChannelDesc": "Adicionar um canal de hashtag para esta comunidade",
|
||||
"community_selectCommunity": "Selecione Comunidade",
|
||||
"community_regularHashtag": "Hashtag Regular",
|
||||
"community_regularHashtagDesc": "Hashtag público (qualquer pessoa pode participar)",
|
||||
"community_communityHashtag": "Hashtag da Comunidade",
|
||||
"community_communityHashtagDesc": "Apenas para membros da comunidade",
|
||||
"community_forCommunity": "Para {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerateSecretConfirm": "Regenerar a chave secreta para \"{name}\"? Todos os membros precisarão escanear o novo código QR para continuar a comunicação.",
|
||||
"community_regenerateSecret": "Regenerar Senha Segura",
|
||||
"community_secretRegenerated": "Senha secreta regenerada para \"{name}\"",
|
||||
"community_regenerate": "Regenerar",
|
||||
"community_secretUpdated": "Segredo atualizado para \"{name}\"",
|
||||
"community_scanToUpdateSecret": "Scanar o novo código QR para atualizar o segredo para \"{name}\"\n\n\n+++++",
|
||||
"community_updateSecret": "Atualizar Segredo"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,778 @@
|
||||
{
|
||||
"@@locale": "ru",
|
||||
"appTitle": "MeshCore Open",
|
||||
"nav_contacts": "Контакты",
|
||||
"nav_channels": "Каналы",
|
||||
"nav_map": "Карта",
|
||||
"common_cancel": "Отмена",
|
||||
"common_ok": "OK",
|
||||
"common_connect": "Коннект",
|
||||
"common_unknownDevice": "Неизвестное устройство",
|
||||
"common_save": "Сохранить",
|
||||
"common_delete": "Удалить",
|
||||
"common_close": "Закрыть",
|
||||
"common_edit": "Изменить",
|
||||
"common_add": "Добавить",
|
||||
"common_settings": "Настройки",
|
||||
"common_disconnect": "Отключить",
|
||||
"common_connected": "Подключено",
|
||||
"common_disconnected": "Отключено",
|
||||
"common_create": "Создать",
|
||||
"common_continue": "Продолжить",
|
||||
"common_share": "Поделиться",
|
||||
"common_copy": "Копировать",
|
||||
"common_retry": "Повторить",
|
||||
"common_hide": "Скрыть",
|
||||
"common_remove": "Убрать",
|
||||
"common_enable": "Включить",
|
||||
"common_disable": "Выключить",
|
||||
"common_reboot": "Перезагрузить",
|
||||
"common_loading": "Загрузка...",
|
||||
"common_notAvailable": "—",
|
||||
"common_voltageValue": "{volts} В",
|
||||
"common_percentValue": "{percent}%",
|
||||
"scanner_title": "MeshCore Open",
|
||||
"scanner_scanning": "Поиск устройств...",
|
||||
"scanner_connecting": "Подключение...",
|
||||
"scanner_disconnecting": "Отключение...",
|
||||
"scanner_notConnected": "Не подключено",
|
||||
"scanner_connectedTo": "Подключено к {deviceName}",
|
||||
"scanner_searchingDevices": "Поиск устройств MeshCore...",
|
||||
"scanner_tapToScan": "Нажмите для поиска MeshCore устройств",
|
||||
"scanner_connectionFailed": "Подключение не удалось: {error}",
|
||||
"scanner_stop": "Стоп",
|
||||
"scanner_scan": "Сканирование",
|
||||
"device_quickSwitch": "Быстрое переключение",
|
||||
"device_meshcore": "MeshCore",
|
||||
"settings_title": "Настройки",
|
||||
"settings_deviceInfo": "Информация об устройстве",
|
||||
"settings_appSettings": "Настройки приложения",
|
||||
"settings_appSettingsSubtitle": "Уведомления, сообщения и настройки карты",
|
||||
"settings_nodeSettings": "Настройки ноды",
|
||||
"settings_nodeName": "Имя ноды",
|
||||
"settings_nodeNameNotSet": "Не установлено",
|
||||
"settings_nodeNameHint": "Введите имя ноды",
|
||||
"settings_nodeNameUpdated": "Имя обновлено",
|
||||
"settings_radioSettings": "Настройки радио",
|
||||
"settings_radioSettingsSubtitle": "Частота, мощность и коэффициент распространения",
|
||||
"settings_radioSettingsUpdated": "Настройки радио обновлены",
|
||||
"settings_location": "Позиция",
|
||||
"settings_locationSubtitle": "Координаты GPS",
|
||||
"settings_locationUpdated": "Позиция и настройки GPS обновлены",
|
||||
"settings_locationBothRequired": "Введите широту и долготу.",
|
||||
"settings_locationInvalid": "Неверная широта или долгота.",
|
||||
"settings_locationGPSEnable": "Включить GPS",
|
||||
"settings_locationGPSEnableSubtitle": "Включение GPS для автоматического обновления позиции.",
|
||||
"settings_locationIntervalSec": "Интервал для позиционирования GPS (секунды)",
|
||||
"settings_locationIntervalInvalid": "Интервал должен составлять не менее 60 секунд и не более 86400 секунд.",
|
||||
"settings_latitude": "Широта",
|
||||
"settings_longitude": "Долгота",
|
||||
"settings_privacyMode": "Режим конфиденциальности",
|
||||
"settings_privacyModeSubtitle": "Скрыть имя/позицию в анонсировании",
|
||||
"settings_privacyModeToggle": "Включите режим конфиденциальности, чтобы скрыть свое имя и местоположение в анонсировании.",
|
||||
"settings_privacyModeEnabled": "Режим конфиденциальности включен",
|
||||
"settings_privacyModeDisabled": "Режим конфиденциальности выключен",
|
||||
"settings_actions": "Действия",
|
||||
"settings_sendAdvertisement": "Отправить анонсирование",
|
||||
"settings_sendAdvertisementSubtitle": "Отправить анонсирование о присутствии сейчас",
|
||||
"settings_advertisementSent": "Анонсирование отправлено",
|
||||
"settings_syncTime": "Синхронизация времени",
|
||||
"settings_syncTimeSubtitle": "Синхронизировать время с телефоном",
|
||||
"settings_timeSynchronized": "Время синхронизировано",
|
||||
"settings_refreshContacts": "Обновить контакты",
|
||||
"settings_refreshContactsSubtitle": "Перезагрузить список контактов с устройства",
|
||||
"settings_rebootDevice": "Перезагрузить устройство",
|
||||
"settings_rebootDeviceSubtitle": "Перезапустить устройство MeshCore",
|
||||
"settings_rebootDeviceConfirm": "Вы уверены, что хотите перезагрузить устройство? Вы будете отключены.",
|
||||
"settings_debug": "Отладка",
|
||||
"settings_bleDebugLog": "Журнал отладки BLE",
|
||||
"settings_bleDebugLogSubtitle": "Команды BLE, ответы и сырые данные",
|
||||
"settings_appDebugLog": "Журнал отладки приложения",
|
||||
"settings_appDebugLogSubtitle": "Сообщения отладки приложения",
|
||||
"settings_about": "О программе",
|
||||
"settings_aboutVersion": "MeshCore Open v{version}",
|
||||
"settings_aboutLegalese": "2026 MeshCore Open Source Project",
|
||||
"settings_aboutDescription": "Открытое клиентское приложение на Flutter для устройств MeshCore с LoRa-сетями.",
|
||||
"settings_infoName": "Имя",
|
||||
"settings_infoId": "ID",
|
||||
"settings_infoStatus": "Статус",
|
||||
"settings_infoBattery": "Батарея",
|
||||
"settings_infoPublicKey": "Публичный ключ",
|
||||
"settings_infoContactsCount": "Количество контактов",
|
||||
"settings_infoChannelCount": "Количество каналов",
|
||||
"settings_presets": "Пресеты",
|
||||
"settings_preset915Mhz": "915 МГц",
|
||||
"settings_preset868Mhz": "868 МГц",
|
||||
"settings_preset433Mhz": "433 МГц",
|
||||
"settings_frequency": "Частота (МГц)",
|
||||
"settings_frequencyHelper": "300.0 – 2500.0",
|
||||
"settings_frequencyInvalid": "Недопустимая частота (300–2500 МГц)",
|
||||
"settings_bandwidth": "Полоса пропускания",
|
||||
"settings_spreadingFactor": "Коэффициент расширения",
|
||||
"settings_codingRate": "Коэффициент кодирования",
|
||||
"settings_txPower": "Мощность передачи (дБм)",
|
||||
"settings_txPowerHelper": "0 – 22",
|
||||
"settings_txPowerInvalid": "Недопустимая мощность передачи (0–22 дБм)",
|
||||
"settings_longRange": "Дальний радиус",
|
||||
"settings_fastSpeed": "Высокая скорость",
|
||||
"settings_error": "Ошибка: {message}",
|
||||
"appSettings_title": "Настройки приложения",
|
||||
"appSettings_appearance": "Внешний вид",
|
||||
"appSettings_theme": "Тема",
|
||||
"appSettings_themeSystem": "Как в системе",
|
||||
"appSettings_themeLight": "Светлая",
|
||||
"appSettings_themeDark": "Тёмная",
|
||||
"appSettings_language": "Язык",
|
||||
"appSettings_languageSystem": "Как в системе",
|
||||
"appSettings_languageEn": "Английский",
|
||||
"appSettings_languageFr": "Французский",
|
||||
"appSettings_languageEs": "Испанский",
|
||||
"appSettings_languageDe": "Немецкий",
|
||||
"appSettings_languagePl": "Польский",
|
||||
"appSettings_languageSl": "Словенский",
|
||||
"appSettings_languagePt": "Португальский",
|
||||
"appSettings_languageIt": "Итальянский",
|
||||
"appSettings_languageZh": "Китайский",
|
||||
"appSettings_languageSv": "Шведский",
|
||||
"appSettings_languageNl": "Нидерландский",
|
||||
"appSettings_languageSk": "Словацкий",
|
||||
"appSettings_languageBg": "Болгарский",
|
||||
"appSettings_languageRu": "Русский",
|
||||
"appSettings_notifications": "Уведомления",
|
||||
"appSettings_enableNotifications": "Включить уведомления",
|
||||
"appSettings_enableNotificationsSubtitle": "Получать уведомления о сообщениях и оповещениях",
|
||||
"appSettings_notificationPermissionDenied": "Разрешение на уведомления отклонено",
|
||||
"appSettings_notificationsEnabled": "Уведомления включены",
|
||||
"appSettings_notificationsDisabled": "Уведомления отключены",
|
||||
"appSettings_messageNotifications": "Уведомления о сообщениях",
|
||||
"appSettings_messageNotificationsSubtitle": "Показывать уведомление при получении новых сообщений",
|
||||
"appSettings_channelMessageNotifications": "Уведомления о сообщениях в каналах",
|
||||
"appSettings_channelMessageNotificationsSubtitle": "Показывать уведомление при получении сообщений в каналах",
|
||||
"appSettings_advertisementNotifications": "Уведомления об анонсированиях",
|
||||
"appSettings_advertisementNotificationsSubtitle": "Показывать уведомление при обнаружении новых нод",
|
||||
"appSettings_messaging": "Обмен сообщениями",
|
||||
"appSettings_clearPathOnMaxRetry": "Сбросить маршрут после максимального числа попыток",
|
||||
"appSettings_clearPathOnMaxRetrySubtitle": "Сбросить маршрут контакта после 5 неудачных попыток отправки",
|
||||
"appSettings_pathsWillBeCleared": "Маршруты будут сброшены после 5 неудачных попыток",
|
||||
"appSettings_pathsWillNotBeCleared": "Маршруты не будут автоматически сбрасываться",
|
||||
"appSettings_autoRouteRotation": "Автоматическое переключение маршрутов",
|
||||
"appSettings_autoRouteRotationSubtitle": "Циклически переключаться между лучшими маршрутами и режимом рассылки",
|
||||
"appSettings_autoRouteRotationEnabled": "Автоматическое переключение маршрутов включено",
|
||||
"appSettings_autoRouteRotationDisabled": "Автоматическое переключение маршрутов отключено",
|
||||
"appSettings_battery": "Батарея",
|
||||
"appSettings_batteryChemistry": "Химия батареи",
|
||||
"appSettings_batteryChemistryPerDevice": "Установить для устройства ({deviceName})",
|
||||
"appSettings_batteryChemistryConnectFirst": "Подключитесь к устройству, чтобы выбрать",
|
||||
"appSettings_batteryNmc": "18650 NMC (3.0–4.2 В)",
|
||||
"appSettings_batteryLifepo4": "LiFePO4 (2.6–3.65 В)",
|
||||
"appSettings_batteryLipo": "LiPo (3.0–4.2 В)",
|
||||
"appSettings_mapDisplay": "Отображение карты",
|
||||
"appSettings_showRepeaters": "Показывать репитеры",
|
||||
"appSettings_showRepeatersSubtitle": "Отображать репитеры на карте",
|
||||
"appSettings_showChatNodes": "Показывать чат-ноды",
|
||||
"appSettings_showChatNodesSubtitle": "Отображать чат-ноды на карте",
|
||||
"appSettings_showOtherNodes": "Показывать другие ноды",
|
||||
"appSettings_showOtherNodesSubtitle": "Отображать другие типы нод на карте",
|
||||
"appSettings_timeFilter": "Фильтр по времени",
|
||||
"appSettings_timeFilterShowAll": "Показывать все ноды",
|
||||
"appSettings_timeFilterShowLast": "Показывать ноды за последние {hours} ч",
|
||||
"appSettings_mapTimeFilter": "Временной фильтр карты",
|
||||
"appSettings_showNodesDiscoveredWithin": "Показывать ноды, обнаруженные за:",
|
||||
"appSettings_allTime": "Всё время",
|
||||
"appSettings_lastHour": "Последний час",
|
||||
"appSettings_last6Hours": "Последние 6 часов",
|
||||
"appSettings_last24Hours": "Последние 24 часа",
|
||||
"appSettings_lastWeek": "Последнюю неделю",
|
||||
"appSettings_offlineMapCache": "Кэш офлайн-карты",
|
||||
"appSettings_noAreaSelected": "Область не выбрана",
|
||||
"appSettings_areaSelectedZoom": "Область выбрана (масштаб {minZoom}–{maxZoom})",
|
||||
"appSettings_debugCard": "Отладка",
|
||||
"appSettings_appDebugLogging": "Журнал отладки приложения",
|
||||
"appSettings_appDebugLoggingSubtitle": "Записывать отладочные сообщения приложения для диагностики",
|
||||
"appSettings_appDebugLoggingEnabled": "Журнал отладки приложения включён",
|
||||
"appSettings_appDebugLoggingDisabled": "Журнал отладки приложения отключён",
|
||||
"contacts_title": "Контакты",
|
||||
"contacts_noContacts": "Контактов пока нет",
|
||||
"contacts_contactsWillAppear": "Контакты появятся, когда устройства начнут рассылать оповещения",
|
||||
"contacts_searchContacts": "Поиск контактов...",
|
||||
"contacts_noUnreadContacts": "Нет непрочитанных контактов",
|
||||
"contacts_noContactsFound": "Контакты или группы не найдены",
|
||||
"contacts_deleteContact": "Удалить контакт",
|
||||
"contacts_removeConfirm": "Удалить {contactName} из контактов?",
|
||||
"contacts_manageRepeater": "Управление репитером",
|
||||
"contacts_manageRoom": "Управление сервером комнат",
|
||||
"contacts_roomLogin": "Вход на сервер комнат",
|
||||
"contacts_openChat": "Открыть чат",
|
||||
"contacts_editGroup": "Изменить группу",
|
||||
"contacts_deleteGroup": "Удалить группу",
|
||||
"contacts_deleteGroupConfirm": "Удалить \"{groupName}\"?",
|
||||
"contacts_newGroup": "Новая группа",
|
||||
"contacts_groupName": "Имя группы",
|
||||
"contacts_groupNameRequired": "Имя группы обязательно",
|
||||
"contacts_groupAlreadyExists": "Группа \"{name}\" уже существует",
|
||||
"contacts_filterContacts": "Фильтр контактов...",
|
||||
"contacts_noContactsMatchFilter": "Нет контактов, соответствующих фильтру",
|
||||
"contacts_noMembers": "Нет участников",
|
||||
"contacts_lastSeenNow": "Видели только что",
|
||||
"contacts_lastSeenMinsAgo": "Видели {minutes} мин назад",
|
||||
"contacts_lastSeenHourAgo": "Видели 1 час назад",
|
||||
"contacts_lastSeenHoursAgo": "Видели {hours} ч назад",
|
||||
"contacts_lastSeenDayAgo": "Видели 1 день назад",
|
||||
"contacts_lastSeenDaysAgo": "Видели {days} дн. назад",
|
||||
"channels_title": "Каналы",
|
||||
"channels_noChannelsConfigured": "Каналы не настроены",
|
||||
"channels_addPublicChannel": "Добавить публичный канал",
|
||||
"channels_searchChannels": "Поиск каналов...",
|
||||
"channels_noChannelsFound": "Каналы не найдены",
|
||||
"channels_channelIndex": "Канал {index}",
|
||||
"channels_hashtagChannel": "Хэштег-канал",
|
||||
"channels_public": "Публичный",
|
||||
"channels_private": "Приватный",
|
||||
"channels_publicChannel": "Публичный канал",
|
||||
"channels_privateChannel": "Приватный канал",
|
||||
"channels_editChannel": "Изменить канал",
|
||||
"channels_deleteChannel": "Удалить канал",
|
||||
"channels_deleteChannelConfirm": "Удалить \"{name}\"? Это действие нельзя отменить.",
|
||||
"channels_channelDeleted": "Канал \"{name}\" удалён",
|
||||
"channels_addChannel": "Добавить канал",
|
||||
"channels_channelIndexLabel": "Индекс канала",
|
||||
"channels_channelName": "Имя канала",
|
||||
"channels_usePublicChannel": "Использовать публичный канал",
|
||||
"channels_standardPublicPsk": "Стандартный публичный PSK",
|
||||
"channels_pskHex": "PSK (Hex)",
|
||||
"channels_generateRandomPsk": "Сгенерировать случайный PSK",
|
||||
"channels_enterChannelName": "Введите имя канала",
|
||||
"channels_pskMustBe32Hex": "PSK должен содержать 32 шестнадцатеричных символа",
|
||||
"channels_channelAdded": "Канал \"{name}\" добавлен",
|
||||
"channels_editChannelTitle": "Изменить канал {index}",
|
||||
"channels_smazCompression": "Сжатие SMAZ",
|
||||
"channels_channelUpdated": "Канал \"{name}\" обновлён",
|
||||
"channels_publicChannelAdded": "Публичный канал добавлен",
|
||||
"channels_sortBy": "Сортировка",
|
||||
"channels_sortManual": "Вручную",
|
||||
"channels_sortAZ": "По алфавиту",
|
||||
"channels_sortLatestMessages": "По последним сообщениям",
|
||||
"channels_sortUnread": "По непрочитанным",
|
||||
"channels_createPrivateChannel": "Создать приватный канал",
|
||||
"channels_createPrivateChannelDesc": "Защищён секретным ключом.",
|
||||
"channels_joinPrivateChannel": "Присоединиться к приватному каналу",
|
||||
"channels_joinPrivateChannelDesc": "Введите секретный ключ вручную.",
|
||||
"channels_joinPublicChannel": "Присоединиться к публичному каналу",
|
||||
"channels_joinPublicChannelDesc": "К этому каналу может присоединиться любой.",
|
||||
"channels_joinHashtagChannel": "Присоединиться к хэштег-каналу",
|
||||
"channels_joinHashtagChannelDesc": "К хэштег-каналам может присоединиться любой.",
|
||||
"channels_scanQrCode": "Сканировать QR-код",
|
||||
"channels_scanQrCodeComingSoon": "Скоро будет",
|
||||
"channels_enterHashtag": "Введите хэштег",
|
||||
"channels_hashtagHint": "например, #команда",
|
||||
"chat_noMessages": "Сообщений пока нет",
|
||||
"chat_sendMessageToStart": "Отправьте сообщение, чтобы начать",
|
||||
"chat_originalMessageNotFound": "Исходное сообщение не найдено",
|
||||
"chat_replyingTo": "Ответ для {name}",
|
||||
"chat_replyTo": "Ответить {name}",
|
||||
"chat_location": "Местоположение",
|
||||
"chat_sendMessageTo": "Отправить сообщение {contactName}",
|
||||
"chat_typeMessage": "Напишите сообщение...",
|
||||
"chat_messageTooLong": "Сообщение слишком длинное (макс. {maxBytes} байт).",
|
||||
"chat_messageCopied": "Сообщение скопировано",
|
||||
"chat_messageDeleted": "Сообщение удалено",
|
||||
"chat_retryingMessage": "Повтор отправки сообщения",
|
||||
"chat_retryCount": "Попытка {current}/{max}",
|
||||
"chat_sendGif": "Отправить GIF",
|
||||
"chat_reply": "Ответить",
|
||||
"chat_addReaction": "Добавить реакцию",
|
||||
"chat_me": "Я",
|
||||
"emojiCategorySmileys": "Смайлы",
|
||||
"emojiCategoryGestures": "Жесты",
|
||||
"emojiCategoryHearts": "Сердечки",
|
||||
"emojiCategoryObjects": "Предметы",
|
||||
"gifPicker_title": "Выберите GIF",
|
||||
"gifPicker_searchHint": "Поиск GIF...",
|
||||
"gifPicker_poweredBy": "Работает на GIPHY",
|
||||
"gifPicker_noGifsFound": "GIF не найдены",
|
||||
"gifPicker_failedLoad": "Не удалось загрузить GIF",
|
||||
"gifPicker_failedSearch": "Не удалось выполнить поиск GIF",
|
||||
"gifPicker_noInternet": "Нет подключения к интернету",
|
||||
"debugLog_appTitle": "Журнал отладки приложения",
|
||||
"debugLog_bleTitle": "Журнал отладки BLE",
|
||||
"debugLog_copyLog": "Копировать журнал",
|
||||
"debugLog_clearLog": "Очистить журнал",
|
||||
"debugLog_copied": "Журнал отладки скопирован",
|
||||
"debugLog_bleCopied": "Журнал BLE скопирован",
|
||||
"debugLog_noEntries": "Журнал отладки пока пуст",
|
||||
"debugLog_enableInSettings": "Включите запись журнала отладки в настройках",
|
||||
"debugLog_frames": "Фреймы",
|
||||
"debugLog_rawLogRx": "Сырой журнал приёма",
|
||||
"debugLog_noBleActivity": "Активность BLE пока отсутствует",
|
||||
"debugFrame_length": "Длина фрейма: {count} байт",
|
||||
"debugFrame_command": "Команда: 0x{value}",
|
||||
"debugFrame_textMessageHeader": "Фрейм текстового сообщения:",
|
||||
"debugFrame_destinationPubKey": "- Публичный ключ получателя: {pubKey}",
|
||||
"debugFrame_timestamp": "- Временная метка: {timestamp}",
|
||||
"debugFrame_flags": "- Флаги: 0x{value}",
|
||||
"debugFrame_textType": "- Тип текста: {type} ({label})",
|
||||
"debugFrame_textTypeCli": "CLI",
|
||||
"debugFrame_textTypePlain": "Обычный",
|
||||
"debugFrame_text": "- Текст: \"{text}\"",
|
||||
"debugFrame_hexDump": "Шестнадцатеричный дамп:",
|
||||
"chat_pathManagement": "Управление маршрутами",
|
||||
"chat_routingMode": "Режим маршрутизации",
|
||||
"chat_autoUseSavedPath": "Авто (использовать сохранённый маршрут)",
|
||||
"chat_forceFloodMode": "Принудительный режим рассылки",
|
||||
"chat_recentAckPaths": "Недавние подтверждённые маршруты (нажмите, чтобы использовать):",
|
||||
"chat_pathHistoryFull": "История маршрутов заполнена. Удалите записи, чтобы добавить новые.",
|
||||
"chat_hopSingular": "хоп",
|
||||
"chat_hopPlural": "хопов",
|
||||
"chat_hopsCount": "{count} {count, plural, one{хоп} few{хопа} many{хопов} other{хопов}}",
|
||||
"chat_successes": "успешно",
|
||||
"chat_removePath": "Удалить маршрут",
|
||||
"chat_noPathHistoryYet": "История маршрутов пока пуста.\nОтправьте сообщение, чтобы обнаружить маршруты.",
|
||||
"chat_pathActions": "Действия с маршрутом:",
|
||||
"chat_setCustomPath": "Указать маршрут вручную",
|
||||
"chat_setCustomPathSubtitle": "Вручную задать маршрут передачи",
|
||||
"chat_clearPath": "Очистить маршрут",
|
||||
"chat_clearPathSubtitle": "Принудительно обновить маршрут при следующей отправке",
|
||||
"chat_pathCleared": "Маршрут очищен. Следующее сообщение обновит маршрут.",
|
||||
"chat_floodModeSubtitle": "Используйте переключатель маршрутизации в панели приложения",
|
||||
"chat_floodModeEnabled": "Режим рассылки включён. Отключите через значок маршрутизации в панели приложения.",
|
||||
"chat_fullPath": "Полный маршрут",
|
||||
"chat_pathDetailsNotAvailable": "Детали маршрута ещё недоступны. Попробуйте отправить сообщение для обновления.",
|
||||
"chat_pathSetHops": "Маршрут установлен: {hopCount} {hopCount, plural, one{хоп} few{хопа} many{хопов} other{хопов}} — {status}",
|
||||
"chat_pathSavedLocally": "Сохранено локально. Подключитесь для синхронизации.",
|
||||
"chat_pathDeviceConfirmed": "Подтверждено устройством.",
|
||||
"chat_pathDeviceNotConfirmed": "Ещё не подтверждено устройством.",
|
||||
"chat_type": "Тип",
|
||||
"chat_path": "Маршрут",
|
||||
"chat_publicKey": "Публичный ключ",
|
||||
"chat_compressOutgoingMessages": "Сжимать исходящие сообщения",
|
||||
"chat_floodForced": "Рассылка (принудительно)",
|
||||
"chat_directForced": "Прямой (принудительно)",
|
||||
"chat_hopsForced": "{count} хоп(ов) (принудительно)",
|
||||
"chat_floodAuto": "Рассылка (авто)",
|
||||
"chat_direct": "Прямой",
|
||||
"chat_poiShared": "Точка интереса отправлена",
|
||||
"chat_unread": "Непрочитанных: {count}",
|
||||
"map_title": "Карта нод",
|
||||
"map_noNodesWithLocation": "Нет нод с данными о местоположении",
|
||||
"map_nodesNeedGps": "Ноды должны передавать свои GPS-координаты, чтобы отображаться на карте",
|
||||
"map_nodesCount": "Нод: {count}",
|
||||
"map_pinsCount": "Меток: {count}",
|
||||
"map_chat": "Чат",
|
||||
"map_repeater": "Репитер",
|
||||
"map_room": "Комната",
|
||||
"map_sensor": "Сенсор",
|
||||
"map_pinDm": "Метка (ЛС)",
|
||||
"map_pinPrivate": "Метка (Приватная)",
|
||||
"map_pinPublic": "Метка (Публичная)",
|
||||
"map_lastSeen": "Последнее появление",
|
||||
"map_disconnectConfirm": "Вы уверены, что хотите отключиться от этого устройства?",
|
||||
"map_from": "От",
|
||||
"map_source": "Источник",
|
||||
"map_flags": "Флаги",
|
||||
"map_shareMarkerHere": "Поделиться меткой здесь",
|
||||
"map_pinLabel": "Метка",
|
||||
"map_label": "Подпись",
|
||||
"map_pointOfInterest": "Точка интереса",
|
||||
"map_sendToContact": "Отправить контакту",
|
||||
"map_sendToChannel": "Отправить в канал",
|
||||
"map_noChannelsAvailable": "Нет доступных каналов",
|
||||
"map_publicLocationShare": "Публичная передача местоположения",
|
||||
"map_publicLocationShareConfirm": "Вы собираетесь поделиться местоположением в {channelLabel}. Этот канал публичный, и любой, у кого есть PSK, сможет его увидеть.",
|
||||
"map_connectToShareMarkers": "Подключитесь к устройству, чтобы делиться метками",
|
||||
"map_filterNodes": "Фильтр нод",
|
||||
"map_nodeTypes": "Типы нод",
|
||||
"map_chatNodes": "Чат-ноды",
|
||||
"map_repeaters": "Репитеры",
|
||||
"map_otherNodes": "Другие ноды",
|
||||
"map_keyPrefix": "Префикс ключа",
|
||||
"map_filterByKeyPrefix": "Фильтр по префиксу ключа",
|
||||
"map_publicKeyPrefix": "Префикс публичного ключа",
|
||||
"map_markers": "Метки",
|
||||
"map_showSharedMarkers": "Показывать общие метки",
|
||||
"map_lastSeenTime": "Время последнего появления",
|
||||
"map_sharedPin": "Общая метка",
|
||||
"map_joinRoom": "Присоединиться к комнате",
|
||||
"map_manageRepeater": "Управление репитером",
|
||||
"mapCache_title": "Кэш офлайн-карты",
|
||||
"mapCache_selectAreaFirst": "Сначала выберите область для кэширования",
|
||||
"mapCache_noTilesToDownload": "Нет плиток для загрузки в этой области",
|
||||
"mapCache_downloadTilesTitle": "Загрузить плитки",
|
||||
"mapCache_downloadTilesPrompt": "Загрузить {count} плиток для офлайн-использования?",
|
||||
"mapCache_downloadAction": "Загрузить",
|
||||
"mapCache_cachedTiles": "Закэшировано {count} плиток",
|
||||
"mapCache_cachedTilesWithFailed": "Закэшировано {downloaded} плиток ({failed} не загружено)",
|
||||
"mapCache_clearOfflineCacheTitle": "Очистить офлайн-кэш",
|
||||
"mapCache_clearOfflineCachePrompt": "Удалить все закэшированные плитки карты?",
|
||||
"mapCache_offlineCacheCleared": "Офлайн-кэш очищен",
|
||||
"mapCache_noAreaSelected": "Область не выбрана",
|
||||
"mapCache_cacheArea": "Область кэширования",
|
||||
"mapCache_useCurrentView": "Использовать текущий вид",
|
||||
"mapCache_zoomRange": "Диапазон масштаба",
|
||||
"mapCache_estimatedTiles": "Оценочное количество плиток: {count}",
|
||||
"mapCache_downloadedTiles": "Загружено {completed} из {total}",
|
||||
"mapCache_downloadTilesButton": "Загрузить плитки",
|
||||
"mapCache_clearCacheButton": "Очистить кэш",
|
||||
"mapCache_failedDownloads": "Неудачных загрузок: {count}",
|
||||
"mapCache_boundsLabel": "С {north}, Ю {south}, В {east}, З {west}",
|
||||
"time_justNow": "Только что",
|
||||
"time_minutesAgo": "{minutes} мин назад",
|
||||
"time_hoursAgo": "{hours} ч назад",
|
||||
"time_daysAgo": "{days} дн. назад",
|
||||
"time_hour": "час",
|
||||
"time_hours": "часов",
|
||||
"time_day": "день",
|
||||
"time_days": "дней",
|
||||
"time_week": "неделя",
|
||||
"time_weeks": "недель",
|
||||
"time_month": "месяц",
|
||||
"time_months": "месяцев",
|
||||
"time_minutes": "минут",
|
||||
"time_allTime": "Всё время",
|
||||
"dialog_disconnect": "Отключиться",
|
||||
"dialog_disconnectConfirm": "Вы уверены, что хотите отключиться от этого устройства?",
|
||||
"login_repeaterLogin": "Вход в репитер",
|
||||
"login_roomLogin": "Вход на сервер комнат",
|
||||
"login_password": "Пароль",
|
||||
"login_enterPassword": "Введите пароль",
|
||||
"login_savePassword": "Сохранить пароль",
|
||||
"login_savePasswordSubtitle": "Пароль будет надёжно сохранён на этом устройстве",
|
||||
"login_repeaterDescription": "Введите пароль репитера для доступа к настройкам и статусу.",
|
||||
"login_roomDescription": "Введите пароль комнаты для доступа к настройкам и статусу.",
|
||||
"login_routing": "Маршрутизация",
|
||||
"login_routingMode": "Режим маршрутизации",
|
||||
"login_autoUseSavedPath": "Авто (использовать сохранённый маршрут)",
|
||||
"login_forceFloodMode": "Принудительный режим рассылки",
|
||||
"login_managePaths": "Управление маршрутами",
|
||||
"login_login": "Войти",
|
||||
"login_attempt": "Попытка {current}/{max}",
|
||||
"login_failed": "Ошибка входа: {error}",
|
||||
"login_failedMessage": "Не удалось войти. Либо пароль неверен, либо репитер недоступен.",
|
||||
"common_reload": "Обновить",
|
||||
"common_clear": "Очистить",
|
||||
"path_currentPath": "Текущий маршрут: {path}",
|
||||
"path_usingHopsPath": "Используется маршрут из {count} {count, plural, one{хоп} few{хопа} many{хопов} other{хопов}}",
|
||||
"path_enterCustomPath": "Введите маршрут вручную",
|
||||
"path_currentPathLabel": "Текущий маршрут",
|
||||
"path_hexPrefixInstructions": "Введите 2-символьные шестнадцатеричные префиксы для каждого хопа, разделённые запятыми.",
|
||||
"path_hexPrefixExample": "Пример: A1,F2,3C (каждый узел использует первый байт своего публичного ключа)",
|
||||
"path_labelHexPrefixes": "Маршрут (шестнадцатеричные префиксы)",
|
||||
"path_helperMaxHops": "Максимум 64 хопа. Каждый префикс — 2 шестнадцатеричных символа (1 байт)",
|
||||
"path_selectFromContacts": "Или выберите из контактов:",
|
||||
"path_noRepeatersFound": "Репитеры или серверы комнат не найдены.",
|
||||
"path_customPathsRequire": "Пользовательские маршруты требуют промежуточных узлов, способных ретранслировать сообщения.",
|
||||
"path_invalidHexPrefixes": "Недопустимые шестнадцатеричные префиксы: {prefixes}",
|
||||
"path_tooLong": "Маршрут слишком длинный. Максимум 64 хопа.",
|
||||
"path_setPath": "Установить маршрут",
|
||||
"repeater_management": "Управление репитером",
|
||||
"room_management": "Управление сервером комнат",
|
||||
"repeater_managementTools": "Инструменты управления",
|
||||
"repeater_status": "Статус",
|
||||
"repeater_statusSubtitle": "Просмотр статуса, статистики и соседей репитера",
|
||||
"repeater_telemetry": "Телеметрия",
|
||||
"repeater_telemetrySubtitle": "Просмотр телеметрии датчиков и системной статистики",
|
||||
"repeater_cli": "CLI",
|
||||
"repeater_cliSubtitle": "Отправка команд репитеру",
|
||||
"repeater_neighbours": "Соседи",
|
||||
"repeater_neighboursSubtitle": "Просмотр соседей на нулевом хопе.",
|
||||
"repeater_settings": "Настройки",
|
||||
"repeater_settingsSubtitle": "Настройка параметров репитера",
|
||||
"repeater_statusTitle": "Статус репитера",
|
||||
"repeater_routingMode": "Режим маршрутизации",
|
||||
"repeater_autoUseSavedPath": "Авто (использовать сохранённый маршрут)",
|
||||
"repeater_forceFloodMode": "Принудительный режим рассылки",
|
||||
"repeater_pathManagement": "Управление маршрутами",
|
||||
"repeater_refresh": "Обновить",
|
||||
"repeater_statusRequestTimeout": "Время ожидания статуса истекло.",
|
||||
"repeater_errorLoadingStatus": "Ошибка загрузки статуса: {error}",
|
||||
"repeater_systemInformation": "Системная информация",
|
||||
"repeater_battery": "Батарея",
|
||||
"repeater_clockAtLogin": "Время (при входе)",
|
||||
"repeater_uptime": "Время работы",
|
||||
"repeater_queueLength": "Длина очереди",
|
||||
"repeater_debugFlags": "Флаги отладки",
|
||||
"repeater_radioStatistics": "Радиостатистика",
|
||||
"repeater_lastRssi": "Последний RSSI",
|
||||
"repeater_lastSnr": "Последний SNR",
|
||||
"repeater_noiseFloor": "Уровень шума",
|
||||
"repeater_txAirtime": "Время эфира (передача)",
|
||||
"repeater_rxAirtime": "Время эфира (приём)",
|
||||
"repeater_packetStatistics": "Статистика пакетов",
|
||||
"repeater_sent": "Отправлено",
|
||||
"repeater_received": "Получено",
|
||||
"repeater_duplicates": "Дубликаты",
|
||||
"repeater_daysHoursMinsSecs": "{days} дн. {hours}ч {minutes}м {seconds}с",
|
||||
"repeater_packetTxTotal": "Всего: {total}, Рассылка: {flood}, Прямые: {direct}",
|
||||
"repeater_packetRxTotal": "Всего: {total}, Рассылка: {flood}, Прямые: {direct}",
|
||||
"repeater_duplicatesFloodDirect": "Рассылка: {flood}, Прямые: {direct}",
|
||||
"repeater_duplicatesTotal": "Всего: {total}",
|
||||
"repeater_settingsTitle": "Настройки репитера",
|
||||
"repeater_basicSettings": "Основные настройки",
|
||||
"repeater_repeaterName": "Имя репитера",
|
||||
"repeater_repeaterNameHelper": "Отображаемое имя этого репитера",
|
||||
"repeater_adminPassword": "Пароль администратора",
|
||||
"repeater_adminPasswordHelper": "Пароль с полным доступом",
|
||||
"repeater_guestPassword": "Гостевой пароль",
|
||||
"repeater_guestPasswordHelper": "Пароль для доступа только для чтения",
|
||||
"repeater_radioSettings": "Настройки радио",
|
||||
"repeater_frequencyMhz": "Частота (МГц)",
|
||||
"repeater_frequencyHelper": "300–2500 МГц",
|
||||
"repeater_txPower": "Мощность передачи",
|
||||
"repeater_txPowerHelper": "1–30 дБм",
|
||||
"repeater_bandwidth": "Полоса пропускания",
|
||||
"repeater_spreadingFactor": "Коэффициент расширения",
|
||||
"repeater_codingRate": "Коэффициент кодирования",
|
||||
"repeater_locationSettings": "Настройки местоположения",
|
||||
"repeater_latitude": "Широта",
|
||||
"repeater_latitudeHelper": "В десятичных градусах (напр., 37.7749)",
|
||||
"repeater_longitude": "Долгота",
|
||||
"repeater_longitudeHelper": "В десятичных градусах (напр., -122.4194)",
|
||||
"repeater_features": "Функции",
|
||||
"repeater_packetForwarding": "Пересылка пакетов",
|
||||
"repeater_packetForwardingSubtitle": "Разрешить репитеру пересылать пакеты",
|
||||
"repeater_guestAccess": "Гостевой доступ",
|
||||
"repeater_guestAccessSubtitle": "Разрешить гостевой доступ только для чтения",
|
||||
"repeater_privacyMode": "Режим конфиденциальности",
|
||||
"repeater_privacyModeSubtitle": "Скрывать имя/местоположение в оповещениях",
|
||||
"repeater_advertisementSettings": "Настройки анонсирования",
|
||||
"repeater_localAdvertInterval": "Интервал локальных анонсирований",
|
||||
"repeater_localAdvertIntervalMinutes": "{minutes} минут",
|
||||
"repeater_floodAdvertInterval": "Интервал анонсирований рассылкой (flood)",
|
||||
"repeater_floodAdvertIntervalHours": "{hours} часов",
|
||||
"repeater_encryptedAdvertInterval": "Интервал зашифрованных анонсирований",
|
||||
"repeater_dangerZone": "Опасная зона",
|
||||
"repeater_rebootRepeater": "Перезагрузить репитер",
|
||||
"repeater_rebootRepeaterSubtitle": "Перезапустить устройство репитера",
|
||||
"repeater_rebootRepeaterConfirm": "Вы уверены, что хотите перезагрузить этот репитер?",
|
||||
"repeater_regenerateIdentityKey": "Пересоздать ключ идентификации",
|
||||
"repeater_regenerateIdentityKeySubtitle": "Сгенерировать новую пару публичного/приватного ключей",
|
||||
"repeater_regenerateIdentityKeyConfirm": "Это создаст новую идентичность для репитера. Продолжить?",
|
||||
"repeater_eraseFileSystem": "Стереть файловую систему",
|
||||
"repeater_eraseFileSystemSubtitle": "Отформатировать файловую систему репитера",
|
||||
"repeater_eraseFileSystemConfirm": "ВНИМАНИЕ: это удалит все данные на репитере. Действие нельзя отменить!",
|
||||
"repeater_eraseSerialOnly": "Очистка доступна только через последовательную консоль.",
|
||||
"repeater_commandSent": "Команда отправлена: {command}",
|
||||
"repeater_errorSendingCommand": "Ошибка отправки команды: {error}",
|
||||
"repeater_confirm": "Подтвердить",
|
||||
"repeater_settingsSaved": "Настройки успешно сохранены",
|
||||
"repeater_errorSavingSettings": "Ошибка сохранения настроек: {error}",
|
||||
"repeater_refreshBasicSettings": "Обновить основные настройки",
|
||||
"repeater_refreshRadioSettings": "Обновить настройки радио",
|
||||
"repeater_refreshTxPower": "Обновить мощность передачи",
|
||||
"repeater_refreshLocationSettings": "Обновить настройки местоположения",
|
||||
"repeater_refreshPacketForwarding": "Обновить пересылку пакетов",
|
||||
"repeater_refreshGuestAccess": "Обновить гостевой доступ",
|
||||
"repeater_refreshPrivacyMode": "Обновить режим конфиденциальности",
|
||||
"repeater_refreshAdvertisementSettings": "Обновить настройки анонсирований",
|
||||
"repeater_refreshed": "{label} обновлён",
|
||||
"repeater_errorRefreshing": "Ошибка обновления {label}",
|
||||
"repeater_cliTitle": "CLI репитера",
|
||||
"repeater_debugNextCommand": "Отладка следующей команды",
|
||||
"repeater_commandHelp": "Справка по командам",
|
||||
"repeater_clearHistory": "Очистить историю",
|
||||
"repeater_noCommandsSent": "Команды ещё не отправлялись",
|
||||
"repeater_typeCommandOrUseQuick": "Введите команду ниже или используйте быстрые команды",
|
||||
"repeater_enterCommandHint": "Введите команду...",
|
||||
"repeater_previousCommand": "Предыдущая команда",
|
||||
"repeater_nextCommand": "Следующая команда",
|
||||
"repeater_enterCommandFirst": "Сначала введите команду",
|
||||
"repeater_cliCommandFrameTitle": "Фрейм CLI-команды",
|
||||
"repeater_cliCommandError": "Ошибка: {error}",
|
||||
"repeater_cliQuickGetName": "Получить имя",
|
||||
"repeater_cliQuickGetRadio": "Получить радио",
|
||||
"repeater_cliQuickGetTx": "Получить TX",
|
||||
"repeater_cliQuickNeighbors": "Соседи",
|
||||
"repeater_cliQuickVersion": "Версия",
|
||||
"repeater_cliQuickAdvertise": "Анонсировать",
|
||||
"repeater_cliQuickClock": "Время",
|
||||
"repeater_cliHelpAdvert": "Отправляет пакет анонсирования",
|
||||
"repeater_cliHelpReboot": "Перезагружает устройство. (обычно вы получите «Тайм-аут» — это нормально)",
|
||||
"repeater_cliHelpClock": "Показывает текущее время по часам устройства.",
|
||||
"repeater_cliHelpPassword": "Устанавливает новый пароль администратора для устройства.",
|
||||
"repeater_cliHelpVersion": "Показывает версию устройства и дату сборки прошивки.",
|
||||
"repeater_cliHelpClearStats": "Сбрасывает различные счётчики статистики в ноль.",
|
||||
"repeater_cliHelpSetAf": "Устанавливает коэффициент времени в эфире.",
|
||||
"repeater_cliHelpSetTx": "Устанавливает мощность передачи LoRa в дБм. (требуется перезагрузка)",
|
||||
"repeater_cliHelpSetRepeat": "Включает или отключает роль репитера для этой ноды.",
|
||||
"repeater_cliHelpSetAllowReadOnly": "(Сервер комнат) Если «on», то вход без пароля разрешён, но публиковать в комнату нельзя (только чтение)",
|
||||
"repeater_cliHelpSetFloodMax": "Устанавливает максимальное число хопов для входящих пакетов в режиме рассылки (если >= макс., пакет не пересылается)",
|
||||
"repeater_cliHelpSetIntThresh": "Устанавливает порог интерференции (в дБ). По умолчанию 14. Установите 0, чтобы отключить обнаружение помех.",
|
||||
"repeater_cliHelpSetAgcResetInterval": "Устанавливает интервал сброса автоматической регулировки усиления. Установите 0, чтобы отключить.",
|
||||
"repeater_cliHelpSetMultiAcks": "Включает или отключает функцию «двойных ACK».",
|
||||
"repeater_cliHelpSetAdvertInterval": "Устанавливает интервал (в минутах) отправки локального (нулевой хоп) анонсирования. Установите 0, чтобы отключить.",
|
||||
"repeater_cliHelpSetFloodAdvertInterval": "Устанавливает интервал (в часах) отправки анонсирований рассылкой. Установите 0, чтобы отключить.",
|
||||
"repeater_cliHelpSetGuestPassword": "Устанавливает/обновляет гостевой пароль. (для репитеров гости могут отправлять запрос «Get Stats»)",
|
||||
"repeater_cliHelpSetName": "Устанавливает имя в оповещениях.",
|
||||
"repeater_cliHelpSetLat": "Устанавливает широту для карты в оповещениях. (десятичные градусы)",
|
||||
"repeater_cliHelpSetLon": "Устанавливает долготу для карты в оповещениях. (десятичные градусы)",
|
||||
"repeater_cliHelpSetRadio": "Устанавливает полностью новые параметры радио и сохраняет их в настройки. Требуется команда «reboot» для применения.",
|
||||
"repeater_cliHelpSetRxDelay": "Устанавливает (экспериментально) базовую задержку (>1 для эффекта) для принятых пакетов на основе качества сигнала. Установите 0, чтобы отключить.",
|
||||
"repeater_cliHelpSetTxDelay": "Устанавливает множитель времени в эфире для пакета в режиме рассылки и применяет случайную задержку перед пересылкой (чтобы уменьшить коллизии).",
|
||||
"repeater_cliHelpSetDirectTxDelay": "То же, что txdelay, но для случайной задержки пересылки пакетов в прямом режиме.",
|
||||
"repeater_cliHelpSetBridgeEnabled": "Включить/выключить мост.",
|
||||
"repeater_cliHelpSetBridgeDelay": "Установить задержку перед ретрансляцией пакетов.",
|
||||
"repeater_cliHelpSetBridgeSource": "Выбрать, будет ли мост ретранслировать полученные или отправленные пакеты.",
|
||||
"repeater_cliHelpSetBridgeBaud": "Установить скорость последовательного соединения для мостов RS232.",
|
||||
"repeater_cliHelpSetBridgeSecret": "Установить секрет моста для мостов ESP-NOW.",
|
||||
"repeater_cliHelpSetAdcMultiplier": "Устанавливает пользовательский коэффициент коррекции напряжения батареи (поддерживается только на некоторых платах).",
|
||||
"repeater_cliHelpTempRadio": "Устанавливает временные параметры радио на заданное число минут, затем возвращает исходные. (НЕ сохраняется в настройки).",
|
||||
"repeater_cliHelpSetPerm": "Изменяет ACL. Удаляет запись (по префиксу публичного ключа), если «permissions» равен нулю. Добавляет новую запись, если указан полный ключ и он отсутствует в ACL. Обновляет запись по совпадению префикса. Биты прав зависят от роли прошивки, но младшие 2 бита: 0 (Гость), 1 (Только чтение), 2 (Чтение/запись), 3 (Админ)",
|
||||
"repeater_cliHelpGetBridgeType": "Получает тип моста: none, rs232, espnow",
|
||||
"repeater_cliHelpLogStart": "Начинает запись пакетов в файловую систему.",
|
||||
"repeater_cliHelpLogStop": "Останавливает запись пакетов в файловую систему.",
|
||||
"repeater_cliHelpLogErase": "Удаляет журналы пакетов из файловой системы.",
|
||||
"repeater_cliHelpNeighbors": "Показывает список других репитеров, услышанных через оповещения нулевого хопа. Каждая строка: префикс-id-в-hex:временная-метка:snr×4",
|
||||
"repeater_cliHelpNeighborRemove": "Удаляет первую подходящую запись (по префиксу публичного ключа в hex) из списка соседей.",
|
||||
"repeater_cliHelpRegion": "(только через последовательный порт) Показывает все определённые регионы и текущие права на рассылку.",
|
||||
"repeater_cliHelpRegionLoad": "ПРИМЕЧАНИЕ: это специальная многострочная команда. Каждая следующая строка — имя региона (с отступом пробелами для указания иерархии, минимум один пробел). Завершается пустой строкой.",
|
||||
"repeater_cliHelpRegionGet": "Ищет регион по префиксу имени (или «*» для глобальной области). Отвечает: «-> имя-региона (родитель) 'F'»",
|
||||
"repeater_cliHelpRegionPut": "Добавляет или обновляет определение региона с заданным именем.",
|
||||
"repeater_cliHelpRegionRemove": "Удаляет определение региона с заданным именем. (должно точно совпадать и не иметь дочерних регионов)",
|
||||
"repeater_cliHelpRegionAllowf": "Разрешает рассылку («F»lood) для заданного региона. («*» для глобальной/устаревшей области)",
|
||||
"repeater_cliHelpRegionDenyf": "Запрещает рассылку («F»lood) для заданного региона. (НЕ рекомендуется для глобальной области!)",
|
||||
"repeater_cliHelpRegionHome": "Показывает текущий «домашний» регион. (Пока не используется, зарезервировано на будущее)",
|
||||
"repeater_cliHelpRegionHomeSet": "Устанавливает «домашний» регион.",
|
||||
"repeater_cliHelpRegionSave": "Сохраняет список/карту регионов в память.",
|
||||
"repeater_cliHelpGps": "Показывает статус GPS. Если GPS выключен — отвечает только «off». Если включён — показывает статус, фиксацию, количество спутников.",
|
||||
"repeater_cliHelpGpsOnOff": "Переключает состояние питания GPS.",
|
||||
"repeater_cliHelpGpsSync": "Синхронизирует время ноды с часами GPS.",
|
||||
"repeater_cliHelpGpsSetLoc": "Устанавливает позицию ноды по координатам GPS и сохраняет в настройки.",
|
||||
"repeater_cliHelpGpsAdvert": "Показывает конфигурацию передачи местоположения в анонсированиях:\n- none: не включать местоположение\n- share: передавать GPS-координаты (из SensorManager)\n- prefs: передавать координаты из настроек",
|
||||
"repeater_cliHelpGpsAdvertSet": "Устанавливает конфигурацию передачи местоположения.",
|
||||
"repeater_commandsListTitle": "Список команд",
|
||||
"repeater_commandsListNote": "ПРИМЕЧАНИЕ: для большинства команд «set ...» существуют соответствующие команды «get ...».",
|
||||
"repeater_general": "Общие",
|
||||
"repeater_settingsCategory": "Настройки",
|
||||
"repeater_bridge": "Мост",
|
||||
"repeater_logging": "Журналирование",
|
||||
"repeater_neighborsRepeaterOnly": "Соседи (только для репитеров)",
|
||||
"repeater_regionManagementRepeaterOnly": "Управление регионами (только для репитеров)",
|
||||
"repeater_regionNote": "Команды регионов введены для управления определениями регионов и правами доступа.",
|
||||
"repeater_gpsManagement": "Управление GPS",
|
||||
"repeater_gpsNote": "Команда gps введена для управления параметрами, связанными с местоположением.",
|
||||
"telemetry_receivedData": "Полученные телеметрические данные",
|
||||
"telemetry_requestTimeout": "Время ожидания телеметрии истекло.",
|
||||
"telemetry_errorLoading": "Ошибка загрузки телеметрии: {error}",
|
||||
"telemetry_noData": "Данные телеметрии недоступны.",
|
||||
"telemetry_channelTitle": "Канал {channel}",
|
||||
"telemetry_batteryLabel": "Батарея",
|
||||
"telemetry_voltageLabel": "Напряжение",
|
||||
"telemetry_mcuTemperatureLabel": "Температура МК",
|
||||
"telemetry_temperatureLabel": "Температура",
|
||||
"telemetry_currentLabel": "Ток",
|
||||
"telemetry_batteryValue": "{percent}% / {volts}В",
|
||||
"telemetry_voltageValue": "{volts}В",
|
||||
"telemetry_currentValue": "{amps}А",
|
||||
"telemetry_temperatureValue": "{celsius}°C / {fahrenheit}°F",
|
||||
"neighbors_receivedData": "Полученные данные о соседях",
|
||||
"neighbors_requestTimedOut": "Время ожидания данных о соседях истекло.",
|
||||
"neighbors_errorLoading": "Ошибка загрузки соседей: {error}",
|
||||
"neighbors_repeatersNeighbours": "Соседи репитеров",
|
||||
"neighbors_noData": "Данные о соседях недоступны.",
|
||||
"neighbors_unknownContact": "Неизвестный {pubkey}",
|
||||
"neighbors_heardA ago": "Слышали: {time} назад",
|
||||
"channelPath_title": "Путь пакета",
|
||||
"channelPath_viewMap": "Посмотреть на карте",
|
||||
"channelPath_otherObservedPaths": "Другие наблюдаемые пути",
|
||||
"channelPath_repeaterHops": "Хопы через репитеры",
|
||||
"channelPath_noHopDetails": "Детали хопов для этого пакета не предоставлены.",
|
||||
"channelPath_messageDetails": "Детали сообщения",
|
||||
"channelPath_senderLabel": "Отправитель",
|
||||
"channelPath_timeLabel": "Время",
|
||||
"channelPath_repeatsLabel": "Повторы",
|
||||
"channelPath_pathLabel": "Путь {index}",
|
||||
"channelPath_observedLabel": "Наблюдаемый",
|
||||
"channelPath_observedPathTitle": "Наблюдаемый путь {index} • {hops}",
|
||||
"channelPath_noLocationData": "Нет данных о местоположении",
|
||||
"channelPath_timeWithDate": "{day}/{month} {time}",
|
||||
"channelPath_timeOnly": "{time}",
|
||||
"channelPath_unknownPath": "Неизвестный",
|
||||
"channelPath_floodPath": "Рассылка",
|
||||
"channelPath_directPath": "Прямой",
|
||||
"channelPath_observedZeroOf": "0 из {total} хопов",
|
||||
"channelPath_observedSomeOf": "{observed} из {total} хопов",
|
||||
"channelPath_mapTitle": "Карта пути",
|
||||
"channelPath_noRepeaterLocations": "Нет данных о местоположении репитеров для этого пути.",
|
||||
"channelPath_primaryPath": "Путь {index} (Основной)",
|
||||
"channelPath_pathLabelTitle": "Путь",
|
||||
"channelPath_observedPathHeader": "Наблюдаемый путь",
|
||||
"channelPath_selectedPathLabel": "{label} • {prefixes}",
|
||||
"channelPath_noHopDetailsAvailable": "Детали хопов для этого пакета недоступны.",
|
||||
"channelPath_unknownRepeater": "Неизвестный репитер",
|
||||
"community_title": "Сообщество",
|
||||
"community_create": "Создать сообщество",
|
||||
"community_createDesc": "Создать новое сообщество и поделиться через QR-код.",
|
||||
"community_join": "Присоединиться",
|
||||
"community_joinTitle": "Присоединиться к сообществу",
|
||||
"community_joinConfirmation": "Вы хотите присоединиться к сообществу \"{name}\"?",
|
||||
"community_scanQr": "Сканировать QR-код сообщества",
|
||||
"community_scanInstructions": "Наведите камеру на QR-код сообщества",
|
||||
"community_showQr": "Показать QR-код",
|
||||
"community_publicChannel": "Публичный канал сообщества",
|
||||
"community_hashtagChannel": "Хэштег-канал сообщества",
|
||||
"community_name": "Имя сообщества",
|
||||
"community_enterName": "Введите имя сообщества",
|
||||
"community_created": "Сообщество \"{name}\" создано",
|
||||
"community_joined": "Присоединились к сообществу \"{name}\"",
|
||||
"community_qrTitle": "Поделиться сообществом",
|
||||
"community_qrInstructions": "Отсканируйте этот QR-код, чтобы присоединиться к \"{name}\"",
|
||||
"community_hashtagPrivacyHint": "Хэштег-каналы сообщества доступны только его участникам",
|
||||
"community_invalidQrCode": "Недопустимый QR-код сообщества",
|
||||
"community_alreadyMember": "Уже участник",
|
||||
"community_alreadyMemberMessage": "Вы уже участник сообщества \"{name}\".",
|
||||
"community_addPublicChannel": "Добавить публичный канал сообщества",
|
||||
"community_addPublicChannelHint": "Автоматически добавить публичный канал для этого сообщества",
|
||||
"community_noCommunities": "Вы ещё не присоединились ни к одному сообществу",
|
||||
"community_scanOrCreate": "Отсканируйте QR-код или создайте сообщество, чтобы начать",
|
||||
"community_manageCommunities": "Управление сообществами",
|
||||
"community_delete": "Покинуть сообщество",
|
||||
"community_deleteConfirm": "Покинуть \"{name}\"?",
|
||||
"community_deleteChannelsWarning": "Это также удалит {count} канал(ов) и их сообщения.",
|
||||
"community_deleted": "Покинули сообщество \"{name}\"",
|
||||
"community_regenerateSecret": "Пересоздать секрет",
|
||||
"community_regenerateSecretConfirm": "Пересоздать секретный ключ для \"{name}\"? Все участники должны будут отсканировать новый QR-код для продолжения общения.",
|
||||
"community_regenerate": "Пересоздать",
|
||||
"community_secretRegenerated": "Секрет пересоздан для \"{name}\"",
|
||||
"community_updateSecret": "Обновить секрет",
|
||||
"community_secretUpdated": "Секрет обновлён для \"{name}\"",
|
||||
"community_scanToUpdateSecret": "Отсканируйте новый QR-код, чтобы обновить секрет для \"{name}\"",
|
||||
"community_addHashtagChannel": "Добавить хэштег-канал сообщества",
|
||||
"community_addHashtagChannelDesc": "Добавить хэштег-канал для этого сообщества",
|
||||
"community_selectCommunity": "Выбрать сообщество",
|
||||
"community_regularHashtag": "Обычный хэштег",
|
||||
"community_regularHashtagDesc": "Публичный хэштег (любой может присоединиться)",
|
||||
"community_communityHashtag": "Хэштег сообщества",
|
||||
"community_communityHashtagDesc": "Доступен только участникам сообщества",
|
||||
"community_forCommunity": "Для {name}",
|
||||
"listFilter_tooltip": "Фильтр и сортировка",
|
||||
"listFilter_sortBy": "Сортировка по",
|
||||
"listFilter_latestMessages": "Последние сообщения",
|
||||
"listFilter_heardRecently": "Слышали недавно",
|
||||
"listFilter_az": "По алфавиту",
|
||||
"listFilter_filters": "Фильтры",
|
||||
"listFilter_all": "Все",
|
||||
"listFilter_users": "Пользователи",
|
||||
"listFilter_repeaters": "Репитеры",
|
||||
"listFilter_roomServers": "Серверы комнат",
|
||||
"listFilter_unreadOnly": "Только непрочитанные",
|
||||
"listFilter_newGroup": "Новая группа",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@neighbors_heardAgo": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_open": "Открыть",
|
||||
"chat_couldNotOpenLink": "Не удалось открыть ссылку: {url}",
|
||||
"chat_openLink": "Открыть ссылку?",
|
||||
"chat_openLinkConfirmation": "Хотите открыть эту ссылку в вашем браузере?",
|
||||
"neighbors_heardAgo": "Слушал(а): {time} назад",
|
||||
"chat_invalidLink": "Неправильный формат ссылки"
|
||||
}
|
||||
+200
-2
@@ -559,7 +559,7 @@
|
||||
"chat_setCustomPath": "Nastaviť vlastnú cestu",
|
||||
"chat_setCustomPathSubtitle": "Ručne zadajte trasu.",
|
||||
"chat_clearPath": "Vyčistiš cestu",
|
||||
"chat_clearPathSubtitle": "Znovu nájsť vynútene pri nasledujacej pošlite",
|
||||
"chat_clearPathSubtitle": "Znovu nájsť vynútene pri nasledujúcej pošlite",
|
||||
"chat_pathCleared": "Cesta vyčistená. Nasledujúce prepočetné získa trasu znova.",
|
||||
"chat_floodModeSubtitle": "Použite prepínanie trasy v navigačnom paneli.",
|
||||
"chat_floodModeEnabled": "Odosporňovacia prevádzka je zapnutá. Vypnite ju znova cez ikonu routovania v navigačnom páse.",
|
||||
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Otvoriť odkaz?",
|
||||
"chat_openLinkConfirmation": "Chcete otvoriť tento odkaz v prehliadači?",
|
||||
"chat_open": "Otvoriť",
|
||||
"chat_couldNotOpenLink": "Nepodarilo sa otvoriť odkaz: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Neplatný formát odkazu",
|
||||
"map_title": "Mapa uzlov",
|
||||
"map_noNodesWithLocation": "Žiadne uzly s údajmi o polohe",
|
||||
"map_nodesNeedGps": "Uholníky musia zdieľať svoje GPS súradnice, aby sa zobrazili na mape.",
|
||||
@@ -821,6 +833,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"login_failedMessage": "Prihlásenie zlyhalo. Heslo je nesprávne alebo je opakovač nedostupný.",
|
||||
"common_reload": "Načítať",
|
||||
"common_clear": "Zmazať",
|
||||
"path_currentPath": "Aktívna cesta: {path}",
|
||||
@@ -1335,5 +1348,190 @@
|
||||
"listFilter_repeaters": "Opakovadlá",
|
||||
"listFilter_roomServers": "Servéry miestnosti",
|
||||
"listFilter_unreadOnly": "Nezaregistrované len",
|
||||
"listFilter_newGroup": "Nová skupina"
|
||||
"listFilter_newGroup": "Nová skupina",
|
||||
"@neighbors_errorLoading": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_neighboursSubtitle": "Zobraziť susedné body bez skokov.",
|
||||
"neighbors_requestTimedOut": "Súďia žiadajú o časové ukončenie.",
|
||||
"neighbors_receivedData": "Obdielo dáta suseda",
|
||||
"repeater_neighbours": "Súsezný",
|
||||
"neighbors_errorLoading": "Chyba pri načítaní susedov: {error}",
|
||||
"neighbors_repeatersNeighbours": "Opakovadlá Súsezná",
|
||||
"neighbors_noData": "Nie je dostupná žiadna informácia o susedoch.",
|
||||
"channels_createPrivateChannel": "Vytvorte súkromný kanál",
|
||||
"channels_joinPrivateChannel": "Pripojiť sa k súkromnému kanálu",
|
||||
"channels_joinPrivateChannelDesc": "Ručne zadajte tajný kľúč.",
|
||||
"channels_createPrivateChannelDesc": "Zabezpečené pomocou tajného kľúča.",
|
||||
"channels_joinPublicChannel": "Pripojte sa k verejnému kanálu",
|
||||
"channels_joinPublicChannelDesc": "Któvek sátó na tutó kanalizovát.",
|
||||
"channels_joinHashtagChannel": "Pripojte sa k Hashtag Kanálu",
|
||||
"channels_joinHashtagChannelDesc": "Ktoekolikoľvek sa môže pridať do hashtag kanálov.",
|
||||
"channels_scanQrCode": "Skenujte QR kód",
|
||||
"channels_scanQrCodeComingSoon": "Čoskoro",
|
||||
"channels_enterHashtag": "Zadajte hashtag",
|
||||
"channels_hashtagHint": "napr. #tím",
|
||||
"@neighbors_unknownContact": {
|
||||
"placeholders": {
|
||||
"pubkey": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@neighbors_heardAgo": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"neighbors_heardAgo": "Počuli sme to: {time} dozadu",
|
||||
"neighbors_unknownContact": "Neznáma {pubkey}",
|
||||
"settings_locationGPSEnable": "Aktivovať GPS",
|
||||
"settings_locationGPSEnableSubtitle": "Povolí automatické aktualizovanie polohy pomocou GPS.",
|
||||
"settings_locationIntervalSec": "Interval pre GPS (Sekundy)",
|
||||
"settings_locationIntervalInvalid": "Interval musí byť aspoň 60 sekúnd a menej ako 86400 sekúnd.",
|
||||
"contacts_manageRoom": "Spravovať server miestnosti",
|
||||
"room_management": "Správa servera miestnosti",
|
||||
"@community_joinConfirmation": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_created": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_joined": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_qrInstructions": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_alreadyMemberMessage": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleteConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleted": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_forCommunity": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_create": "Vytvoriť komunitu",
|
||||
"community_title": "Komunita",
|
||||
"community_createDesc": "Vytvorte novú komunitu a zdieľajte cez QR kód.",
|
||||
"community_join": "Pripojiť",
|
||||
"community_joinTitle": "Pripojiť sa k spoločenstvu",
|
||||
"community_joinConfirmation": "Chceš sa pridať do komunity \"{name}\"?",
|
||||
"community_scanQr": "Skontrolujte komunitný QR kód",
|
||||
"community_scanInstructions": "Zamerte kameru na komunitný QR kód.",
|
||||
"community_showQr": "Zobraziť QR kód",
|
||||
"common_ok": "OK\nDobre",
|
||||
"community_publicChannel": "Komunita verejná",
|
||||
"community_hashtagChannel": "Komunitný Hashtag",
|
||||
"community_name": "Komunita",
|
||||
"community_enterName": "Zadajte názov komunity",
|
||||
"community_created": "Komunita \"{name}\" vytvorená",
|
||||
"community_joined": "Pripojená komunita \"{name}\"",
|
||||
"community_qrTitle": "Zdieľť komunitu",
|
||||
"community_qrInstructions": "Skenejte tento QR kód, aby ste sa pripojili k {name}.",
|
||||
"community_hashtagPrivacyHint": "Hashtagové kanály komunity sú prístupné len členom komunity",
|
||||
"community_invalidQrCode": "Neplatná QR kód komunity.",
|
||||
"community_alreadyMember": "Už ste členom.",
|
||||
"community_alreadyMemberMessage": "Vy ste už členom \"{name}\".",
|
||||
"community_addPublicChannel": "Pridať verejný komunikačný kanál",
|
||||
"community_addPublicChannelHint": "Automaticky prida verejný kanál pre túto komunitu.",
|
||||
"community_noCommunities": "Zatiaľ ste sa nepripojili k žiadnej komunite",
|
||||
"community_scanOrCreate": "Skene QR kód alebo vytvor komunitu na začiatok.",
|
||||
"community_manageCommunities": "Spravovať komunity",
|
||||
"community_delete": "Nechajte komunitu",
|
||||
"community_deleteConfirm": "Opustiť \"{name}\"?",
|
||||
"community_deleteChannelsWarning": "Tým sa tiež vymaže {count} kanál/kanálov a ich správy.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Opustená komunita \"{name}\"",
|
||||
"community_addHashtagChannel": "Pridať komunitný hashtag",
|
||||
"community_addHashtagChannelDesc": "Pridajte hashtagový kanál pre túto komunitu.",
|
||||
"community_selectCommunity": "Vyberte komunitu",
|
||||
"community_regularHashtag": "Zvyčajný hashtag",
|
||||
"community_regularHashtagDesc": "Veľký hashtag (ktočokoľvek sa môže pridať)",
|
||||
"community_communityHashtag": "Komunitný Hashtag",
|
||||
"community_communityHashtagDesc": "Špecifické pre členov komunity",
|
||||
"community_forCommunity": "Pre {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_secretRegenerated": "Záznam pre \"{name}\" bol regenerovaný tajne",
|
||||
"community_regenerateSecretConfirm": "Znovu vygenerovať tajný kľúč pre \"{name}\"? Všetci členovia budú musieť skanovať nový QR kód, aby mohli nadviazať komunikáciu.",
|
||||
"community_regenerate": "Znovu vygenerovať",
|
||||
"community_regenerateSecret": "Zobraziť nový tajný kód",
|
||||
"community_scanToUpdateSecret": "Skáňte nový QR kód na aktualizáciu tajného hesla pre \"{name}\"",
|
||||
"community_updateSecret": "Aktualizovať tajné heslo",
|
||||
"community_secretUpdated": "Zmena tajnej slova pre \"{name}\""
|
||||
}
|
||||
|
||||
+203
-5
@@ -176,7 +176,7 @@
|
||||
"appSettings_languageBg": "Български",
|
||||
"appSettings_notifications": "Obveščanja",
|
||||
"appSettings_enableNotifications": "Omogoči obveščanje",
|
||||
"appSettings_enableNotificationsSubtitle": "Prejmujte obvestila o sporočilih in oglasih",
|
||||
"appSettings_enableNotificationsSubtitle": "Prejmite obvestila o sporočilih in oglasih",
|
||||
"appSettings_notificationPermissionDenied": "Odobritev obvestila zavrnjena",
|
||||
"appSettings_notificationsEnabled": "Obvestila omogočena",
|
||||
"appSettings_notificationsDisabled": "Obvestila so izklopljena",
|
||||
@@ -256,7 +256,7 @@
|
||||
"contacts_contactsWillAppear": "Kontakti se bodo prikazali, ko naprave oglasijo.",
|
||||
"contacts_searchContacts": "Iskanje kontaktov...",
|
||||
"contacts_noUnreadContacts": "Nerešeno kontaktov.",
|
||||
"contacts_noContactsFound": "Niti ena osebe ali skupine ni najdena.",
|
||||
"contacts_noContactsFound": "Niti ena oseba ali skupine ni najdena.",
|
||||
"contacts_deleteContact": "Izbrisati Kontakt",
|
||||
"contacts_removeConfirm": "Izbrisati {contactName} iz kontaktov?",
|
||||
"@contacts_removeConfirm": {
|
||||
@@ -291,7 +291,7 @@
|
||||
}
|
||||
},
|
||||
"contacts_filterContacts": "Filtri kontakt\\,...",
|
||||
"contacts_noContactsMatchFilter": "Niti ena osebe ne ustreza vašemu kriteriju.",
|
||||
"contacts_noContactsMatchFilter": "Niti ena oseba ne ustreza vašemu kriteriju.",
|
||||
"contacts_noMembers": "Nič članov.",
|
||||
"contacts_lastSeenNow": "Datum zadnjega vpisa zdaj",
|
||||
"contacts_lastSeenMinsAgo": "Zadnjič videti {minutes} minut nazaj",
|
||||
@@ -604,9 +604,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Odpreti povezavo?",
|
||||
"chat_openLinkConfirmation": "Ali želite odpreti to povezavo v brskalniku?",
|
||||
"chat_open": "Odpri",
|
||||
"chat_couldNotOpenLink": "Povezave ni bilo mogoče odpreti: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Neveljavna oblika povezave",
|
||||
"map_title": "Mapa omrežja",
|
||||
"map_noNodesWithLocation": "Nihče od notranjih elementov nima podatkov o lokaciji.",
|
||||
"map_nodesNeedGps": "Omrežje morajo deliti svoje GPS koordinate,\nda se prikazajo na zemljeobrazniku.",
|
||||
"map_nodesNeedGps": "Omrežje morajo deliti svoje GPS koordinate,\nda se prikazao na zemljeobrazniku.",
|
||||
"map_nodesCount": "Omize: {count}",
|
||||
"@map_nodesCount": {
|
||||
"placeholders": {
|
||||
@@ -821,6 +833,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"login_failedMessage": "Prijava je bila neuspešna. Geslo je napačno ali pa je repetitor nedosegljiv.",
|
||||
"common_reload": "Ponovno naloži",
|
||||
"common_clear": "Ponoviti",
|
||||
"path_currentPath": "Trenutna pot: {path}",
|
||||
@@ -1335,5 +1348,190 @@
|
||||
"listFilter_repeaters": "Ponovitve",
|
||||
"listFilter_roomServers": "Smeti za prostore",
|
||||
"listFilter_unreadOnly": "Nezbrani samo",
|
||||
"listFilter_newGroup": "Nova skupina"
|
||||
"listFilter_newGroup": "Nova skupina",
|
||||
"@neighbors_errorLoading": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_neighboursSubtitle": "Pogledati nič sosednjih hopjev.",
|
||||
"repeater_neighbours": "Sosedi",
|
||||
"neighbors_receivedData": "Prejeto podatke o sosedih",
|
||||
"neighbors_requestTimedOut": "Sosedi zahtevajo izklop po dogovoru.",
|
||||
"neighbors_errorLoading": "Napaka pri obnašanju sosedov: {error}",
|
||||
"neighbors_repeatersNeighbours": "Ponovitve Sosedi",
|
||||
"neighbors_noData": "Niso na voljo podatki o sosedih.",
|
||||
"channels_joinPrivateChannel": "Pridružite se zasebni skupini",
|
||||
"channels_createPrivateChannelDesc": "Varno zaklenjeno s skrivnim ključem.",
|
||||
"channels_joinPrivateChannelDesc": "Ročno vnesite zaporni ključ.",
|
||||
"channels_createPrivateChannel": "Ustvari zasebno kanal.",
|
||||
"channels_joinPublicChannel": "Pridružite se javnemu kanalu",
|
||||
"channels_joinPublicChannelDesc": "Kdor karkoli je, lahko se pridruži tej skupini.",
|
||||
"channels_joinHashtagChannel": "Pridružite se Kanalu z Hashtagom",
|
||||
"channels_joinHashtagChannelDesc": "Kdor karkoli, lahko se pridruži hashtag kanalom.",
|
||||
"channels_scanQrCode": "Skeniraj QR kodo",
|
||||
"channels_scanQrCodeComingSoon": "Prihajajoča",
|
||||
"channels_enterHashtag": "Vnesite hashtag",
|
||||
"channels_hashtagHint": "npr. #ekipa",
|
||||
"@neighbors_unknownContact": {
|
||||
"placeholders": {
|
||||
"pubkey": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@neighbors_heardAgo": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"neighbors_unknownContact": "Nepoznano {pubkey}",
|
||||
"neighbors_heardAgo": "Udeleženec je prejel sporočilo {time} nazaj.",
|
||||
"settings_locationGPSEnable": "Omogoči GPS",
|
||||
"settings_locationGPSEnableSubtitle": "Omogoči samodejno posodabljanje lokacije z GPS-jem.",
|
||||
"settings_locationIntervalSec": "Interval za GPS (Sekunde)",
|
||||
"settings_locationIntervalInvalid": "Intervallo mora biti vsaj 60 sekund in manj kot 86400 sekund.",
|
||||
"contacts_manageRoom": "Upravljajte strežnik sobe",
|
||||
"room_management": "Upravljanje stremlišča",
|
||||
"@community_joinConfirmation": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_created": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_joined": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_qrInstructions": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_alreadyMemberMessage": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleteConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleted": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_forCommunity": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_createDesc": "Ustvari novo skupnost in jo deli preko QR kode.",
|
||||
"community_title": "Skupnost",
|
||||
"common_ok": "V redu",
|
||||
"community_create": "Ustvari skupnost",
|
||||
"community_joinTitle": "Pridružite se skupnosti",
|
||||
"community_joinConfirmation": "Želiš se pridružiti skupnosti \"{name}\"?",
|
||||
"community_scanQr": "Skeniraj QR kode skupnosti",
|
||||
"community_scanInstructions": "Nasmerite kamero s skupnostnim QR kodom.",
|
||||
"community_showQr": "Pokaži QR kodo",
|
||||
"community_publicChannel": "Skupnostna javna",
|
||||
"community_hashtagChannel": "Skupnostni hashtag",
|
||||
"community_name": "Komunitarne ime",
|
||||
"community_enterName": "Vnesite ime skupnosti",
|
||||
"community_join": "Pridružiti se",
|
||||
"community_created": "Skupnost \"{name}\" je bila ustvarila.",
|
||||
"community_joined": "Prilojen k skupnosti \"{name}\"",
|
||||
"community_qrTitle": "Delite skupnost",
|
||||
"community_qrInstructions": "Skenirajte to QR kodo za vključitev {name}.",
|
||||
"community_hashtagPrivacyHint": "Hashtag kanali skupnosti so dostopni samo članom skupnosti",
|
||||
"community_invalidQrCode": "Neveljaven QR koden skupnosti",
|
||||
"community_alreadyMember": "Že član",
|
||||
"community_alreadyMemberMessage": "Kljub temu ste že član/ka {name}.",
|
||||
"community_addPublicChannel": "Dodaj Objavni Kanal Komunitarja",
|
||||
"community_addPublicChannelHint": "Samodejno dodaj javni kanal za to skupnost.",
|
||||
"community_noCommunities": "Še nobena skupnost se ni pridružila.",
|
||||
"community_scanOrCreate": "Skenirajte QR kodo ali ustvarite skupnost za začetek.",
|
||||
"community_manageCommunities": "Upravljajte skupnosti",
|
||||
"community_delete": "Opusti skupnost",
|
||||
"community_deleteConfirm": "Zapustiti \"{name}\"?",
|
||||
"community_deleteChannelsWarning": "To bo izbrisalo tudi {count} kanal/kanalov in njihova sporočila.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Zapustil skupnost \"{name}\"",
|
||||
"community_addHashtagChannel": "Dodaj Oznako Obštnine",
|
||||
"community_addHashtagChannelDesc": "Dodajte hashtag kanal za to skupnost.",
|
||||
"community_selectCommunity": "Izberi skupnost",
|
||||
"community_regularHashtag": "Oznaka s hashtagom",
|
||||
"community_regularHashtagDesc": "javna oznaka (kateri koli lahko sodelujejo)",
|
||||
"community_communityHashtag": "Skupnostni hashtag",
|
||||
"community_communityHashtagDesc": "Izključeno za uporabnike skupnosti",
|
||||
"community_forCommunity": "Za {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_secretRegenerated": "Tajna za \"{name}\" ponovno ustvarjena",
|
||||
"community_regenerateSecret": "Preberi nov tajni kôd",
|
||||
"community_regenerateSecretConfirm": "Preberite novo tajno geslo za \"{name}\"? Vsi članici morajo prebrati novo QR kodo, da lahko nadaljujejo s komunikacijo.",
|
||||
"community_regenerate": "Preberi znova",
|
||||
"community_scanToUpdateSecret": "Skeniraj nov kôd QR za posodabljanje tajne za {name}",
|
||||
"community_updateSecret": "Ažurniraj tajno",
|
||||
"community_secretUpdated": "Skrivnostno spremembo za \"{name}\""
|
||||
}
|
||||
|
||||
+199
-1
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Öppna länk?",
|
||||
"chat_openLinkConfirmation": "Vill du öppna den här länken i din webbläsare?",
|
||||
"chat_open": "Öppna",
|
||||
"chat_couldNotOpenLink": "Kunde inte öppna länken: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Ogiltigt länkformat",
|
||||
"map_title": "Nodkarta",
|
||||
"map_noNodesWithLocation": "Inga noder med platsinformation",
|
||||
"map_nodesNeedGps": "Noder måste dela sina GPS-koordinater\nför att visas på kartan",
|
||||
@@ -821,6 +833,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"login_failedMessage": "Inloggning misslyckades. Antingen är lösenordet fel eller så går det inte att nå repeatern.",
|
||||
"common_reload": "Ladda om",
|
||||
"common_clear": "Rensa",
|
||||
"path_currentPath": "Nuvarande sökväg: {path}",
|
||||
@@ -1335,5 +1348,190 @@
|
||||
"listFilter_repeaters": "Upprepare",
|
||||
"listFilter_roomServers": "Rumservrar",
|
||||
"listFilter_unreadOnly": "Endast oinlästa",
|
||||
"listFilter_newGroup": "Ny grupp"
|
||||
"listFilter_newGroup": "Ny grupp",
|
||||
"@neighbors_errorLoading": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_neighbours": "Grannar",
|
||||
"repeater_neighboursSubtitle": "Visa noll hoppgrannar.",
|
||||
"neighbors_receivedData": "Mottagna grannars data",
|
||||
"neighbors_requestTimedOut": "Grannar begär tidsinställd utskick.",
|
||||
"neighbors_errorLoading": "Fel vid inläsning av grannar: {error}",
|
||||
"neighbors_repeatersNeighbours": "Upprepar grannar",
|
||||
"neighbors_noData": "Inga grannuppgifter finns tillgängliga.",
|
||||
"channels_createPrivateChannel": "Skapa en privat kanal",
|
||||
"channels_joinPrivateChannel": "Gå med i en Privat Kanal",
|
||||
"channels_joinPrivateChannelDesc": "Ange en hemlig nyckel manuellt.",
|
||||
"channels_createPrivateChannelDesc": "Skyddat med en hemlig nyckel.",
|
||||
"channels_joinPublicChannel": "Gå med i den Offentliga Kanalen",
|
||||
"channels_joinPublicChannelDesc": "Vem som helst kan gå med i denna kanal.",
|
||||
"channels_joinHashtagChannel": "Gå med i en Hashtagkanal",
|
||||
"channels_joinHashtagChannelDesc": "Väldigt enkelt att gå med i hashtag-kanaler.",
|
||||
"channels_scanQrCode": "Skanna en QR-kod",
|
||||
"channels_scanQrCodeComingSoon": "Kommer snart",
|
||||
"channels_enterHashtag": "Ange hashtag",
|
||||
"channels_hashtagHint": "t.ex. #team",
|
||||
"@neighbors_unknownContact": {
|
||||
"placeholders": {
|
||||
"pubkey": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@neighbors_heardAgo": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"neighbors_heardAgo": "Hördes: {time} sedan",
|
||||
"neighbors_unknownContact": "Okänd {pubkey}",
|
||||
"settings_locationGPSEnable": "Aktivera GPS",
|
||||
"settings_locationGPSEnableSubtitle": "Aktivera automatiska uppdateringar av platsen med hjälp av GPS.",
|
||||
"settings_locationIntervalSec": "Interval för GPS (Sekunder)",
|
||||
"settings_locationIntervalInvalid": "Intervalet måste vara minst 60 sekunder och mindre än 86400 sekunder.",
|
||||
"contacts_manageRoom": "Hantera Rumserver",
|
||||
"room_management": "Rumserverhantering",
|
||||
"@community_joinConfirmation": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_created": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_joined": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_qrInstructions": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_alreadyMemberMessage": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleteConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleted": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_forCommunity": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_create": "Skapa Gemenskap",
|
||||
"community_createDesc": "Skapa en ny gemenskap och dela via QR-kod.",
|
||||
"common_ok": "Okej",
|
||||
"community_title": "Gemenskap",
|
||||
"community_join": "Gå med",
|
||||
"community_joinTitle": "Gå med i gemenskapen",
|
||||
"community_joinConfirmation": "Vill du gå med i communityn \"{name}\"?",
|
||||
"community_scanQr": "Skanna Gemenskapens QR",
|
||||
"community_scanInstructions": "Rikta kameran mot en QR-kod i communityn",
|
||||
"community_showQr": "Visa QR-kod",
|
||||
"community_publicChannel": "Föreningens Offentliga",
|
||||
"community_name": "Gemenskapens namn",
|
||||
"community_enterName": "Ange communities namn",
|
||||
"community_created": "Community \"{name}\" har skapats",
|
||||
"community_joined": "Medlem i communityn \"{name}\"",
|
||||
"community_qrTitle": "Dela Gemenskap",
|
||||
"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_hashtagChannel": "Community Hashtag",
|
||||
"community_invalidQrCode": "Ogiltig community QR-kod",
|
||||
"community_alreadyMember": "Är redan medlem",
|
||||
"community_alreadyMemberMessage": "Du är redan medlem av \"{name}\".",
|
||||
"community_addPublicChannel": "Lägg till Gemenskapskanal (Offentlig)",
|
||||
"community_addPublicChannelHint": "Lägg automatiskt till den offentliga kanalen för denna community",
|
||||
"community_noCommunities": "Inga gemenskaper har anslutats ännu",
|
||||
"community_scanOrCreate": "Skanna en QR-kod eller skapa en community för att komma igång",
|
||||
"community_manageCommunities": "Hantera Gemenskaper",
|
||||
"community_delete": "Lämna Gemenskap",
|
||||
"community_deleteConfirm": "Lämna \"{name}\"?",
|
||||
"community_deleteChannelsWarning": "Detta kommer också att radera {count} kanal/kanaler och deras meddelanden.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Lämnade community \"{name}\"",
|
||||
"community_addHashtagChannel": "Lägg till Gemenskapens Hashtag",
|
||||
"community_addHashtagChannelDesc": "Lägg till en hashtag-kanal för denna community",
|
||||
"community_selectCommunity": "Välj Gemenskap",
|
||||
"community_regularHashtag": "Vanlig Hash Tag",
|
||||
"community_regularHashtagDesc": "Offentlig hashtag (alla kan gå med)",
|
||||
"community_communityHashtagDesc": "Endast för medlemmar",
|
||||
"community_forCommunity": "För {name}",
|
||||
"community_communityHashtag": "Community Hashtag",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerate": "Regenerera",
|
||||
"community_regenerateSecretConfirm": "Regenerera den hemliga nyckeln för \"{name}\"? Alla medlemmar måste scanna den nya QR-koden för att fortsätta kommunicera.",
|
||||
"community_secretRegenerated": "Lösenord återskapad för \"{name}\"",
|
||||
"community_regenerateSecret": "Regenerera hemlig kod",
|
||||
"community_scanToUpdateSecret": "Skanna den nya QR-koden för att uppdatera hemligheten för \"{name}\"",
|
||||
"community_secretUpdated": "Hemlighet uppdaterad för \"{name}\"",
|
||||
"community_updateSecret": "Uppdatera hemlighet"
|
||||
}
|
||||
|
||||
+1538
File diff suppressed because it is too large
Load Diff
+199
-1
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "打开链接?",
|
||||
"chat_openLinkConfirmation": "您想在浏览器中打开此链接吗?",
|
||||
"chat_open": "打开",
|
||||
"chat_couldNotOpenLink": "无法打开链接:{url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "链接格式无效",
|
||||
"map_title": "节点地图",
|
||||
"map_noNodesWithLocation": "没有具有位置数据的节点",
|
||||
"map_nodesNeedGps": "节点需要共享它们的 GPS 坐标\n才能在地图上显示",
|
||||
@@ -821,6 +833,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"login_failedMessage": "登录失败。密码不正确或中继器不可达。",
|
||||
"common_reload": "重新加载",
|
||||
"common_clear": "清除",
|
||||
"path_currentPath": "当前路径:{path}",
|
||||
@@ -1335,5 +1348,190 @@
|
||||
"listFilter_repeaters": "重复器",
|
||||
"listFilter_roomServers": "房间服务器",
|
||||
"listFilter_unreadOnly": "未读消息",
|
||||
"listFilter_newGroup": "新组"
|
||||
"listFilter_newGroup": "新组",
|
||||
"@neighbors_errorLoading": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_neighboursSubtitle": "查看零跳邻居。",
|
||||
"repeater_neighbours": "邻居",
|
||||
"neighbors_receivedData": "收到邻居数据",
|
||||
"neighbors_requestTimedOut": "邻居请求超时处理。",
|
||||
"neighbors_errorLoading": "加载邻居时出错:{error}",
|
||||
"neighbors_repeatersNeighbours": "重复器邻居",
|
||||
"neighbors_noData": "没有可用的邻居数据。",
|
||||
"channels_joinPrivateChannel": "加入私密频道",
|
||||
"channels_createPrivateChannelDesc": "使用密钥保护。",
|
||||
"channels_joinPrivateChannelDesc": "手动输入密钥。",
|
||||
"channels_createPrivateChannel": "创建私聊频道",
|
||||
"channels_joinPublicChannel": "加入公共频道",
|
||||
"channels_joinPublicChannelDesc": "任何人都可以加入这个频道。",
|
||||
"channels_joinHashtagChannel": "加入标签频道",
|
||||
"channels_joinHashtagChannelDesc": "任何人都可以加入话题频道。",
|
||||
"channels_scanQrCode": "扫描二维码",
|
||||
"channels_scanQrCodeComingSoon": "即将到来",
|
||||
"channels_enterHashtag": "输入标签",
|
||||
"channels_hashtagHint": "例如 #团队",
|
||||
"@neighbors_unknownContact": {
|
||||
"placeholders": {
|
||||
"pubkey": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@neighbors_heardAgo": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"neighbors_heardAgo": "听到的时间:{time}前",
|
||||
"neighbors_unknownContact": "未知{pubkey}",
|
||||
"settings_locationGPSEnable": "启用GPS",
|
||||
"settings_locationGPSEnableSubtitle": "启用GPS自动更新位置。",
|
||||
"settings_locationIntervalSec": "GPS 间隔(秒)",
|
||||
"settings_locationIntervalInvalid": "时间间隔必须至少为60秒,且小于86400秒。",
|
||||
"contacts_manageRoom": "管理房间服务器",
|
||||
"room_management": "房间服务器管理",
|
||||
"@community_joinConfirmation": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_created": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_joined": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_qrInstructions": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_alreadyMemberMessage": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleteConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_deleted": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_forCommunity": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_create": "创建社区",
|
||||
"community_title": "社区",
|
||||
"community_createDesc": "创建新的社区并可通过二维码分享。",
|
||||
"common_ok": "好的",
|
||||
"community_join": "加入",
|
||||
"community_joinTitle": "加入社区",
|
||||
"community_joinConfirmation": "您想加入社区 \"{name}\" 吗?",
|
||||
"community_scanQr": "扫描社区二维码",
|
||||
"community_scanInstructions": "将相机对准社区二维码",
|
||||
"community_showQr": "显示二维码",
|
||||
"community_publicChannel": "社区公开",
|
||||
"community_hashtagChannel": "社区标签",
|
||||
"community_name": "社区名称",
|
||||
"community_enterName": "请输入社区名称",
|
||||
"community_created": "社区“{name}”已创建",
|
||||
"community_joined": "加入社区 \"{name}\"",
|
||||
"community_qrTitle": "分享社区",
|
||||
"community_qrInstructions": "扫描此二维码加入{name}",
|
||||
"community_hashtagPrivacyHint": "社区标签频道仅社区成员可加入",
|
||||
"community_invalidQrCode": "无效的社区二维码",
|
||||
"community_alreadyMember": "已经是会员了",
|
||||
"community_alreadyMemberMessage": "您已经是 \"{name}\" 的会员。",
|
||||
"community_addPublicChannel": "添加社区公共频道",
|
||||
"community_addPublicChannelHint": "自动添加该社区的公共频道",
|
||||
"community_noCommunities": "尚未加入任何社区",
|
||||
"community_scanOrCreate": "扫描二维码或创建社区开始",
|
||||
"community_manageCommunities": "管理社群",
|
||||
"community_delete": "退出社区",
|
||||
"community_deleteConfirm": "退出 \"{name}\"?",
|
||||
"community_deleteChannelsWarning": "这也将删除 {count} 个频道及其消息。",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "已退出社区 \"{name}\"",
|
||||
"community_addHashtagChannel": "添加社区标签",
|
||||
"community_addHashtagChannelDesc": "添加一个话题频道给此社区",
|
||||
"community_selectCommunity": "选择社区",
|
||||
"community_regularHashtag": "常规话题标签",
|
||||
"community_regularHashtagDesc": "公共话题(任何人都可以加入)",
|
||||
"community_communityHashtag": "社区标签",
|
||||
"community_communityHashtagDesc": "仅限社区成员使用",
|
||||
"community_forCommunity": "对于 {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerateSecret": "重新生成密钥",
|
||||
"community_secretRegenerated": "密码已重置为“{name}”",
|
||||
"community_regenerate": "重新生成",
|
||||
"community_regenerateSecretConfirm": "重新生成“{name}”的秘密密钥?所有成员将需要扫描新的二维码才能继续沟通。",
|
||||
"community_scanToUpdateSecret": "扫描新的二维码更新\"{name}\"的密码",
|
||||
"community_updateSecret": "更新密钥",
|
||||
"community_secretUpdated": "密码已更新为“{name}”"
|
||||
}
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ void main() async {
|
||||
final storage = StorageService();
|
||||
final connector = MeshCoreConnector();
|
||||
final pathHistoryService = PathHistoryService(storage);
|
||||
final retryService = MessageRetryService(storage);
|
||||
final retryService = MessageRetryService();
|
||||
final appSettingsService = AppSettingsService();
|
||||
final bleDebugLogService = BleDebugLogService();
|
||||
final appDebugLogService = AppDebugLogService();
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:crypto/crypto.dart' as crypto;
|
||||
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
|
||||
class Channel {
|
||||
@@ -61,6 +64,44 @@ class Channel {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// Derive PSK from hashtag name using SHA256.
|
||||
/// The hashtag is normalized to include '#' prefix.
|
||||
/// Returns first 16 bytes of SHA256 hash as PSK.
|
||||
static Uint8List derivePskFromHashtag(String hashtag) {
|
||||
final name = hashtag.startsWith('#') ? hashtag : '#$hashtag';
|
||||
final hash = crypto.sha256.convert(utf8.encode(name)).bytes;
|
||||
return Uint8List.fromList(hash.sublist(0, 16));
|
||||
}
|
||||
|
||||
/// Derive PSK for community public channel using HMAC-SHA256.
|
||||
/// PSK = HMAC-SHA256(K, "channel:v1:__public__")[:16]
|
||||
///
|
||||
/// This creates a channel that is "public" only to members who have
|
||||
/// the community secret. Outsiders see only opaque IDs.
|
||||
static Uint8List deriveCommunityPublicPsk(Uint8List secret) {
|
||||
final hmac = crypto.Hmac(crypto.sha256, secret);
|
||||
final digest = hmac.convert(utf8.encode('channel:v1:__public__'));
|
||||
return Uint8List.fromList(digest.bytes.sublist(0, 16));
|
||||
}
|
||||
|
||||
/// Derive PSK for community hashtag channel using HMAC-SHA256.
|
||||
/// PSK = HMAC-SHA256(K, "channel:v1:" + normalized_name)[:16]
|
||||
///
|
||||
/// Community hashtag channels are deterministic for all members
|
||||
/// (same name => same id) but impossible to enumerate/guess without K.
|
||||
static Uint8List deriveCommunityHashtagPsk(Uint8List secret, String hashtag) {
|
||||
final normalized = _normalizeCommunityHashtag(hashtag);
|
||||
final hmac = crypto.Hmac(crypto.sha256, secret);
|
||||
final digest = hmac.convert(utf8.encode('channel:v1:$normalized'));
|
||||
return Uint8List.fromList(digest.bytes.sublist(0, 16));
|
||||
}
|
||||
|
||||
/// Normalize a hashtag name for consistent community PSK derivation.
|
||||
/// Strips leading #, converts to lowercase, trims whitespace.
|
||||
static String _normalizeCommunityHashtag(String hashtag) {
|
||||
return hashtag.replaceFirst(RegExp(r'^#'), '').toLowerCase().trim();
|
||||
}
|
||||
|
||||
static String formatPskHex(Uint8List psk) {
|
||||
return _bytesToHex(psk);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:crypto/crypto.dart' as crypto;
|
||||
|
||||
/// Represents a community with a shared secret for deriving channel PSKs.
|
||||
///
|
||||
/// A Community is a namespace with a shared secret K (32 random bytes),
|
||||
/// distributed via QR code. Members can create Community Public Channels
|
||||
/// and Community Hashtag Channels that are opaque to outsiders.
|
||||
class Community {
|
||||
/// Unique identifier for local storage
|
||||
final String id;
|
||||
|
||||
/// Display name for the community
|
||||
final String name;
|
||||
|
||||
/// The 32-byte shared secret (K)
|
||||
final Uint8List secret;
|
||||
|
||||
/// Timestamp when the community was created/joined
|
||||
final DateTime createdAt;
|
||||
|
||||
/// List of hashtag channel names (without #) that have been added
|
||||
final List<String> hashtagChannels;
|
||||
|
||||
Community({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.secret,
|
||||
required this.createdAt,
|
||||
List<String>? hashtagChannels,
|
||||
}) : hashtagChannels = hashtagChannels ?? [];
|
||||
|
||||
/// Generate a new community with a random 32-byte secret
|
||||
factory Community.create({
|
||||
required String id,
|
||||
required String name,
|
||||
}) {
|
||||
final random = Random.secure();
|
||||
final secret = Uint8List(32);
|
||||
for (int i = 0; i < 32; i++) {
|
||||
secret[i] = random.nextInt(256);
|
||||
}
|
||||
return Community(
|
||||
id: id,
|
||||
name: name,
|
||||
secret: secret,
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Parse a community from QR code JSON data
|
||||
factory Community.fromQrData(String id, String qrData) {
|
||||
final json = jsonDecode(qrData) as Map<String, dynamic>;
|
||||
if (json['type'] != 'meshcore_community') {
|
||||
throw const FormatException('Invalid QR code type');
|
||||
}
|
||||
if (json['v'] != 1) {
|
||||
throw const FormatException('Unsupported QR code version');
|
||||
}
|
||||
|
||||
final name = json['name'] as String;
|
||||
final secretBase64 = json['k'] as String;
|
||||
final secret = base64Url.decode(secretBase64);
|
||||
|
||||
if (secret.length != 32) {
|
||||
throw const FormatException('Invalid secret length');
|
||||
}
|
||||
|
||||
return Community(
|
||||
id: id,
|
||||
name: name,
|
||||
secret: Uint8List.fromList(secret),
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Parse a community from storage JSON
|
||||
factory Community.fromJson(Map<String, dynamic> json) {
|
||||
return Community(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
secret: base64Decode(json['secret'] as String),
|
||||
createdAt: DateTime.fromMillisecondsSinceEpoch(json['created_at'] as int),
|
||||
hashtagChannels: (json['hashtag_channels'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert to JSON for storage
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'secret': base64Encode(secret),
|
||||
'created_at': createdAt.millisecondsSinceEpoch,
|
||||
'hashtag_channels': hashtagChannels,
|
||||
};
|
||||
}
|
||||
|
||||
/// Generate QR code JSON payload for sharing
|
||||
String toQrJson() {
|
||||
return jsonEncode({
|
||||
'v': 1,
|
||||
'type': 'meshcore_community',
|
||||
'name': name,
|
||||
'k': base64Url.encode(secret),
|
||||
});
|
||||
}
|
||||
|
||||
/// Derive the public Community ID from the secret.
|
||||
/// This is safe to display/log since it's one-way derived.
|
||||
/// CID = SHA256("community:v1" || K)
|
||||
String get communityId {
|
||||
final data = utf8.encode('community:v1') + secret;
|
||||
final hash = crypto.sha256.convert(data).bytes;
|
||||
return _bytesToHex(Uint8List.fromList(hash));
|
||||
}
|
||||
|
||||
/// Short version of community ID for display (first 8 chars)
|
||||
String get shortCommunityId => communityId.substring(0, 8);
|
||||
|
||||
/// Derive PSK for community public channel.
|
||||
/// PSK = HMAC-SHA256(K, "channel:v1:__public__")[:16]
|
||||
Uint8List deriveCommunityPublicPsk() {
|
||||
final hmac = crypto.Hmac(crypto.sha256, secret);
|
||||
final digest = hmac.convert(utf8.encode('channel:v1:__public__'));
|
||||
return Uint8List.fromList(digest.bytes.sublist(0, 16));
|
||||
}
|
||||
|
||||
/// Derive PSK for community hashtag channel.
|
||||
/// PSK = HMAC-SHA256(K, "channel:v1:" + normalized_name)[:16]
|
||||
Uint8List deriveCommunityHashtagPsk(String hashtag) {
|
||||
final normalized = _normalizeCommunityHashtag(hashtag);
|
||||
final hmac = crypto.Hmac(crypto.sha256, secret);
|
||||
final digest = hmac.convert(utf8.encode('channel:v1:$normalized'));
|
||||
return Uint8List.fromList(digest.bytes.sublist(0, 16));
|
||||
}
|
||||
|
||||
/// Check if QR data is valid community data
|
||||
static bool isValidQrData(String data) {
|
||||
try {
|
||||
final json = jsonDecode(data) as Map<String, dynamic>;
|
||||
if (json['type'] != 'meshcore_community') return false;
|
||||
if (json['v'] != 1) return false;
|
||||
if (json['name'] == null || (json['name'] as String).isEmpty) {
|
||||
return false;
|
||||
}
|
||||
if (json['k'] == null) return false;
|
||||
final secret = base64Url.decode(json['k'] as String);
|
||||
return secret.length == 32;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalize a hashtag name for consistent PSK derivation.
|
||||
/// Strips leading #, converts to lowercase, trims whitespace.
|
||||
static String _normalizeCommunityHashtag(String hashtag) {
|
||||
return hashtag.replaceFirst(RegExp(r'^#'), '').toLowerCase().trim();
|
||||
}
|
||||
|
||||
/// Add a hashtag channel to this community's list
|
||||
Community addHashtagChannel(String hashtag) {
|
||||
final normalized = _normalizeCommunityHashtag(hashtag);
|
||||
if (hashtagChannels.contains(normalized)) {
|
||||
return this;
|
||||
}
|
||||
return Community(
|
||||
id: id,
|
||||
name: name,
|
||||
secret: secret,
|
||||
createdAt: createdAt,
|
||||
hashtagChannels: [...hashtagChannels, normalized],
|
||||
);
|
||||
}
|
||||
|
||||
/// Remove a hashtag channel from this community's list
|
||||
Community removeHashtagChannel(String hashtag) {
|
||||
final normalized = _normalizeCommunityHashtag(hashtag);
|
||||
return Community(
|
||||
id: id,
|
||||
name: name,
|
||||
secret: secret,
|
||||
createdAt: createdAt,
|
||||
hashtagChannels: hashtagChannels.where((h) => h != normalized).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a copy of this community with a new secret
|
||||
Community withNewSecret(Uint8List newSecret) {
|
||||
return Community(
|
||||
id: id,
|
||||
name: name,
|
||||
secret: newSecret,
|
||||
createdAt: createdAt,
|
||||
hashtagChannels: hashtagChannels,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a copy of this community with a regenerated random secret
|
||||
Community withRegeneratedSecret() {
|
||||
final random = Random.secure();
|
||||
final newSecret = Uint8List(32);
|
||||
for (int i = 0; i < 32; i++) {
|
||||
newSecret[i] = random.nextInt(256);
|
||||
}
|
||||
return withNewSecret(newSecret);
|
||||
}
|
||||
|
||||
/// Extract secret from QR data (for updating existing community)
|
||||
static Uint8List? extractSecretFromQrData(String qrData) {
|
||||
try {
|
||||
final json = jsonDecode(qrData) as Map<String, dynamic>;
|
||||
if (json['type'] != 'meshcore_community') return null;
|
||||
if (json['v'] != 1) return null;
|
||||
final secretBase64 = json['k'] as String;
|
||||
final secret = base64Url.decode(secretBase64);
|
||||
if (secret.length != 32) return null;
|
||||
return Uint8List.fromList(secret);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static String _bytesToHex(Uint8List bytes) {
|
||||
return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is Community &&
|
||||
runtimeType == other.runtimeType &&
|
||||
id == other.id;
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
}
|
||||
@@ -3,11 +3,14 @@ import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../helpers/chat_scroll_controller.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../helpers/link_handler.dart';
|
||||
import '../helpers/utf8_length_limiter.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/channel.dart';
|
||||
@@ -15,6 +18,7 @@ import '../models/channel_message.dart';
|
||||
import '../utils/emoji_utils.dart';
|
||||
import '../widgets/emoji_picker.dart';
|
||||
import '../widgets/gif_message.dart';
|
||||
import '../widgets/jump_to_bottom_button.dart';
|
||||
import '../widgets/gif_picker.dart';
|
||||
import 'channel_message_path_screen.dart';
|
||||
import 'map_screen.dart';
|
||||
@@ -33,42 +37,51 @@ class ChannelChatScreen extends StatefulWidget {
|
||||
|
||||
class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
final TextEditingController _textController = TextEditingController();
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
final ChatScrollController _scrollController = ChatScrollController();
|
||||
final FocusNode _textFieldFocusNode = FocusNode();
|
||||
ChannelMessage? _replyingToMessage;
|
||||
final Map<String, GlobalKey> _messageKeys = {};
|
||||
bool _isLoadingOlder = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_textFieldFocusNode.addListener(_onTextFieldFocusChange);
|
||||
_scrollController.onScrollNearTop = _loadOlderMessages;
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
context.read<MeshCoreConnector>().setActiveChannel(widget.channel.index);
|
||||
|
||||
// Scroll to bottom when opening channel chat - use SchedulerBinding for next frame
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onTextFieldFocusChange() {
|
||||
if (_textFieldFocusNode.hasFocus && mounted) {
|
||||
_scrollController.handleKeyboardOpen();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadOlderMessages() async {
|
||||
if (_isLoadingOlder) return;
|
||||
setState(() => _isLoadingOlder = true);
|
||||
|
||||
final connector = context.read<MeshCoreConnector>();
|
||||
await connector.loadOlderChannelMessages(widget.channel.index);
|
||||
|
||||
if (mounted) {
|
||||
setState(() => _isLoadingOlder = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
context.read<MeshCoreConnector>().setActiveChannel(null);
|
||||
_textFieldFocusNode.removeListener(_onTextFieldFocusChange);
|
||||
_textFieldFocusNode.dispose();
|
||||
_textController.dispose();
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _scrollToBottom() {
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.animateTo(
|
||||
_scrollController.position.maxScrollExtent,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _setReplyingTo(ChannelMessage message) {
|
||||
setState(() {
|
||||
_replyingToMessage = message;
|
||||
@@ -153,10 +166,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
builder: (context, connector, child) {
|
||||
final messages = connector.getChannelMessages(widget.channel);
|
||||
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollToBottom();
|
||||
});
|
||||
|
||||
if (messages.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
@@ -190,20 +199,51 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.all(8),
|
||||
itemCount: messages.length,
|
||||
itemBuilder: (context, index) {
|
||||
final message = messages[index];
|
||||
if (!_messageKeys.containsKey(message.messageId)) {
|
||||
_messageKeys[message.messageId] = GlobalKey();
|
||||
}
|
||||
return Container(
|
||||
key: _messageKeys[message.messageId]!,
|
||||
child: _buildMessageBubble(message),
|
||||
);
|
||||
},
|
||||
// Reverse messages so newest appear at bottom with reverse: true
|
||||
final reversedMessages = messages.reversed.toList();
|
||||
final itemCount = reversedMessages.length + (_isLoadingOlder ? 1 : 0);
|
||||
|
||||
// Auto-scroll to bottom if user is already at bottom
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollController.scrollToBottomIfAtBottom();
|
||||
});
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
ListView.builder(
|
||||
reverse: true, // List grows from bottom up
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.all(8),
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) {
|
||||
// Loading indicator now appears at end (bottom) of reversed list
|
||||
if (_isLoadingOlder && index == itemCount - 1) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
final messageIndex = index;
|
||||
final message = reversedMessages[messageIndex];
|
||||
if (!_messageKeys.containsKey(message.messageId)) {
|
||||
_messageKeys[message.messageId] = GlobalKey();
|
||||
}
|
||||
return Container(
|
||||
key: _messageKeys[message.messageId]!,
|
||||
child: _buildMessageBubble(message),
|
||||
);
|
||||
},
|
||||
),
|
||||
JumpToBottomButton(
|
||||
scrollController: _scrollController,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -241,7 +281,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
onTap: () => _showMessagePathInfo(message),
|
||||
onLongPress: () => _showMessageActions(message),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.all(4)
|
||||
: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.65,
|
||||
),
|
||||
@@ -255,15 +297,20 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!isOutgoing) ...[
|
||||
Text(
|
||||
message.senderName,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
Padding(
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.only(left: 8, top: 4, bottom: 4)
|
||||
: EdgeInsets.zero,
|
||||
child: Text(
|
||||
message.senderName,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (gifId == null) const SizedBox(height: 4),
|
||||
],
|
||||
if (message.replyToMessageId != null) ...[
|
||||
_buildReplyPreview(message),
|
||||
@@ -272,60 +319,84 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
if (poi != null)
|
||||
_buildPoiMessage(context, poi, isOutgoing)
|
||||
else if (gifId != null)
|
||||
GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
fallbackTextColor: isOutgoing
|
||||
? Theme.of(context).colorScheme.onPrimaryContainer.withValues(alpha: 0.7)
|
||||
: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor: Colors.transparent,
|
||||
fallbackTextColor: isOutgoing
|
||||
? Theme.of(context).colorScheme.onPrimaryContainer.withValues(alpha: 0.7)
|
||||
: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
)
|
||||
else
|
||||
Text(
|
||||
message.text,
|
||||
Linkify(
|
||||
text: message.text,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
linkStyle: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.green,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
options: const LinkifyOptions(
|
||||
humanize: false,
|
||||
defaultToHttps: false,
|
||||
),
|
||||
linkifiers: const [UrlLinkifier()],
|
||||
onOpen: (link) => LinkHandler.handleLinkTap(context, link.url),
|
||||
),
|
||||
if (displayPath.isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'via ${_formatPathPrefixes(displayPath)}',
|
||||
style: TextStyle(fontSize: 11, color: Colors.grey[600]),
|
||||
Padding(
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.symmetric(horizontal: 8)
|
||||
: EdgeInsets.zero,
|
||||
child: Text(
|
||||
'via ${_formatPathPrefixes(displayPath)}',
|
||||
style: TextStyle(fontSize: 11, color: Colors.grey[600]),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
_formatTime(message.timestamp),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
if (message.repeatCount > 0) ...[
|
||||
const SizedBox(width: 6),
|
||||
Icon(Icons.repeat, size: 12, color: Colors.grey[600]),
|
||||
const SizedBox(width: 2),
|
||||
Padding(
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.only(left: 8, right: 8, bottom: 4)
|
||||
: EdgeInsets.zero,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'${message.repeatCount}',
|
||||
style: TextStyle(fontSize: 11, color: Colors.grey[600]),
|
||||
_formatTime(message.timestamp),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
if (message.repeatCount > 0) ...[
|
||||
const SizedBox(width: 6),
|
||||
Icon(Icons.repeat, size: 12, color: Colors.grey[600]),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
'${message.repeatCount}',
|
||||
style: TextStyle(fontSize: 11, color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
if (isOutgoing) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
message.status == ChannelMessageStatus.sent
|
||||
? Icons.check
|
||||
: message.status == ChannelMessageStatus.pending
|
||||
? Icons.schedule
|
||||
: Icons.error_outline,
|
||||
size: 14,
|
||||
color: message.status == ChannelMessageStatus.failed
|
||||
? Colors.red
|
||||
: Colors.grey[600],
|
||||
),
|
||||
],
|
||||
],
|
||||
if (isOutgoing) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
message.status == ChannelMessageStatus.sent
|
||||
? Icons.check
|
||||
: message.status == ChannelMessageStatus.pending
|
||||
? Icons.schedule
|
||||
: Icons.error_outline,
|
||||
size: 14,
|
||||
color: message.status == ChannelMessageStatus.failed
|
||||
? Colors.red
|
||||
: Colors.grey[600],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -364,8 +435,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor: colorScheme.surfaceContainerHighest,
|
||||
fallbackTextColor: previewTextColor,
|
||||
width: 120,
|
||||
height: 80,
|
||||
maxSize: 80,
|
||||
),
|
||||
);
|
||||
} else if (poi != null) {
|
||||
@@ -690,14 +760,16 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
fallbackTextColor:
|
||||
Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
width: 160,
|
||||
height: 110,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
fallbackTextColor:
|
||||
Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
maxSize: 160,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -711,9 +783,11 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
|
||||
return TextField(
|
||||
controller: _textController,
|
||||
focusNode: _textFieldFocusNode,
|
||||
inputFormatters: [
|
||||
Utf8LengthLimitingTextInputFormatter(maxBytes),
|
||||
],
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
decoration: InputDecoration(
|
||||
hintText: context.l10n.chat_typeMessage,
|
||||
border: OutlineInputBorder(
|
||||
|
||||
+1132
-217
File diff suppressed because it is too large
Load Diff
+153
-75
@@ -5,11 +5,14 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../helpers/chat_scroll_controller.dart';
|
||||
import '../helpers/link_handler.dart';
|
||||
import '../helpers/utf8_length_limiter.dart';
|
||||
import '../models/channel_message.dart';
|
||||
import '../models/contact.dart';
|
||||
@@ -20,6 +23,7 @@ import 'map_screen.dart';
|
||||
import '../utils/emoji_utils.dart';
|
||||
import '../widgets/emoji_picker.dart';
|
||||
import '../widgets/gif_message.dart';
|
||||
import '../widgets/jump_to_bottom_button.dart';
|
||||
import '../widgets/gif_picker.dart';
|
||||
import '../widgets/path_selection_dialog.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
@@ -36,25 +40,44 @@ class ChatScreen extends StatefulWidget {
|
||||
|
||||
class _ChatScreenState extends State<ChatScreen> {
|
||||
final _textController = TextEditingController();
|
||||
final _scrollController = ScrollController();
|
||||
final _scrollController = ChatScrollController();
|
||||
final _textFieldFocusNode = FocusNode();
|
||||
bool _isLoadingOlder = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_textFieldFocusNode.addListener(_onTextFieldFocusChange);
|
||||
_scrollController.onScrollNearTop = _loadOlderMessages;
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
context.read<MeshCoreConnector>().setActiveContact(widget.contact.publicKeyHex);
|
||||
|
||||
// Scroll to bottom when opening chat use SchedulerBinding for next frame
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onTextFieldFocusChange() {
|
||||
if (_textFieldFocusNode.hasFocus && mounted) {
|
||||
_scrollController.handleKeyboardOpen();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadOlderMessages() async {
|
||||
if (_isLoadingOlder) return;
|
||||
setState(() => _isLoadingOlder = true);
|
||||
|
||||
final connector = context.read<MeshCoreConnector>();
|
||||
await connector.loadOlderMessages(widget.contact.publicKeyHex);
|
||||
|
||||
if (mounted) {
|
||||
setState(() => _isLoadingOlder = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
context.read<MeshCoreConnector>().setActiveContact(null);
|
||||
_textFieldFocusNode.removeListener(_onTextFieldFocusChange);
|
||||
_textFieldFocusNode.dispose();
|
||||
_textController.dispose();
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
@@ -167,9 +190,16 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: messages.isEmpty
|
||||
? _buildEmptyState()
|
||||
: _buildMessageList(messages, connector),
|
||||
child: Stack(
|
||||
children: [
|
||||
messages.isEmpty
|
||||
? _buildEmptyState()
|
||||
: _buildMessageList(messages, connector),
|
||||
JumpToBottomButton(
|
||||
scrollController: _scrollController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildInputBar(connector),
|
||||
],
|
||||
@@ -201,13 +231,37 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
}
|
||||
|
||||
Widget _buildMessageList(List<Message> messages, MeshCoreConnector connector) {
|
||||
// Reverse messages so newest appear at bottom with reverse: true
|
||||
final reversedMessages = messages.reversed.toList();
|
||||
final itemCount = reversedMessages.length + (_isLoadingOlder ? 1 : 0);
|
||||
|
||||
// Auto-scroll to bottom if user is already at bottom
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollController.scrollToBottomIfAtBottom();
|
||||
});
|
||||
|
||||
return ListView.builder(
|
||||
reverse: true, // List grows from bottom up
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16),
|
||||
itemCount: messages.length,
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) {
|
||||
// Loading indicator now appears at end (bottom) of reversed list
|
||||
if (_isLoadingOlder && index == itemCount - 1) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
final messageIndex = index;
|
||||
Contact contact = widget.contact;
|
||||
final message = messages[index];
|
||||
final message = reversedMessages[messageIndex];
|
||||
String fourByteHex = '';
|
||||
if (widget.contact.type == advTypeRoom) {
|
||||
contact = _resolveContactFrom4Bytes(
|
||||
@@ -256,13 +310,15 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor: colorScheme.surfaceContainerHighest,
|
||||
fallbackTextColor:
|
||||
colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
width: 160,
|
||||
height: 110,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor: colorScheme.surfaceContainerHighest,
|
||||
fallbackTextColor:
|
||||
colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
maxSize: 160,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -276,9 +332,11 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
|
||||
return TextField(
|
||||
controller: _textController,
|
||||
focusNode: _textFieldFocusNode,
|
||||
inputFormatters: [
|
||||
Utf8LengthLimitingTextInputFormatter(maxBytes),
|
||||
],
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
decoration: InputDecoration(
|
||||
hintText: context.l10n.chat_typeMessage,
|
||||
border: const OutlineInputBorder(),
|
||||
@@ -336,16 +394,6 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
text,
|
||||
);
|
||||
_textController.clear();
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.animateTo(
|
||||
_scrollController.position.maxScrollExtent,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -957,7 +1005,9 @@ class _MessageBubble extends StatelessWidget {
|
||||
],
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.all(4)
|
||||
: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.65,
|
||||
),
|
||||
@@ -969,75 +1019,103 @@ class _MessageBubble extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!isOutgoing) ...[
|
||||
Text(
|
||||
senderName,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.primary,
|
||||
Padding(
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.only(left: 8, top: 4, bottom: 4)
|
||||
: EdgeInsets.zero,
|
||||
child: Text(
|
||||
senderName,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (gifId == null) const SizedBox(height: 4),
|
||||
],
|
||||
if (poi != null)
|
||||
_buildPoiMessage(context, poi, textColor, metaColor)
|
||||
else if (gifId != null)
|
||||
GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor: bubbleColor,
|
||||
fallbackTextColor: textColor.withValues(alpha: 0.7),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor: Colors.transparent,
|
||||
fallbackTextColor: textColor.withValues(alpha: 0.7),
|
||||
),
|
||||
)
|
||||
else
|
||||
Text(
|
||||
messageText,
|
||||
Linkify(
|
||||
text: messageText,
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
),
|
||||
linkStyle: const TextStyle(
|
||||
color: Colors.green,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
options: const LinkifyOptions(
|
||||
humanize: false,
|
||||
defaultToHttps: false,
|
||||
),
|
||||
linkifiers: const [UrlLinkifier()],
|
||||
onOpen: (link) => LinkHandler.handleLinkTap(context, link.url),
|
||||
),
|
||||
if (isOutgoing && message.retryCount > 0) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
context.l10n.chat_retryCount(message.retryCount, 4),
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: metaColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
Padding(
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.symmetric(horizontal: 8)
|
||||
: EdgeInsets.zero,
|
||||
child: Text(
|
||||
context.l10n.chat_retryCount(message.retryCount, 4),
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: metaColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 4),
|
||||
Wrap(
|
||||
spacing: 4,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
_formatTime(message.timestamp),
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: metaColor,
|
||||
),
|
||||
),
|
||||
if (isOutgoing) ...[
|
||||
const SizedBox(width: 4),
|
||||
_buildStatusIcon(metaColor),
|
||||
],
|
||||
if (message.tripTimeMs != null &&
|
||||
message.status == MessageStatus.delivered) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.speed,
|
||||
size: 10,
|
||||
color: isOutgoing ? metaColor : Colors.green[700],
|
||||
),
|
||||
Padding(
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.only(left: 8, right: 8, bottom: 4)
|
||||
: EdgeInsets.zero,
|
||||
child: Wrap(
|
||||
spacing: 4,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'${(message.tripTimeMs! / 1000).toStringAsFixed(1)}s',
|
||||
_formatTime(message.timestamp),
|
||||
style: TextStyle(
|
||||
fontSize: 9,
|
||||
color: isOutgoing ? metaColor : Colors.green[700],
|
||||
fontSize: 10,
|
||||
color: metaColor,
|
||||
),
|
||||
),
|
||||
if (isOutgoing) ...[
|
||||
const SizedBox(width: 4),
|
||||
_buildStatusIcon(metaColor),
|
||||
],
|
||||
if (message.tripTimeMs != null &&
|
||||
message.status == MessageStatus.delivered) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.speed,
|
||||
size: 10,
|
||||
color: isOutgoing ? metaColor : Colors.green[700],
|
||||
),
|
||||
Text(
|
||||
'${(message.tripTimeMs! / 1000).toStringAsFixed(1)}s',
|
||||
style: TextStyle(
|
||||
fontSize: 9,
|
||||
color: isOutgoing ? metaColor : Colors.green[700],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/community.dart';
|
||||
import '../storage/community_store.dart';
|
||||
import '../widgets/qr_scanner_widget.dart';
|
||||
|
||||
/// Screen for scanning community QR codes to join communities.
|
||||
///
|
||||
/// After successful scan, the user can:
|
||||
/// 1. Join the community (saves to local storage)
|
||||
/// 2. Optionally add the Community Public Channel to the device
|
||||
class CommunityQrScannerScreen extends StatefulWidget {
|
||||
const CommunityQrScannerScreen({super.key});
|
||||
|
||||
@override
|
||||
State<CommunityQrScannerScreen> createState() =>
|
||||
_CommunityQrScannerScreenState();
|
||||
}
|
||||
|
||||
class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
|
||||
final CommunityStore _communityStore = CommunityStore();
|
||||
bool _isProcessing = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.l10n.community_scanQr),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: _isProcessing
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: QrScannerWidget(
|
||||
onScanned: (data) => _handleScannedData(context, data),
|
||||
validator: Community.isValidQrData,
|
||||
onValidationFailed: (_) => _showInvalidQrError(context),
|
||||
instructions: context.l10n.community_scanInstructions,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleScannedData(BuildContext context, String data) async {
|
||||
if (_isProcessing) return;
|
||||
|
||||
setState(() {
|
||||
_isProcessing = true;
|
||||
});
|
||||
|
||||
try {
|
||||
// Parse the community data
|
||||
final community = Community.fromQrData(const Uuid().v4(), data);
|
||||
|
||||
// Check if this community already exists
|
||||
final existing = await _communityStore.findByCommunityId(
|
||||
community.communityId,
|
||||
);
|
||||
|
||||
if (existing != null) {
|
||||
if (context.mounted) {
|
||||
_showAlreadyMemberDialog(context, existing);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Show confirmation dialog
|
||||
if (context.mounted) {
|
||||
await _showJoinConfirmationDialog(context, community);
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.community_invalidQrCode),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isProcessing = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showInvalidQrError(BuildContext context) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.community_invalidQrCode),
|
||||
backgroundColor: Colors.orange,
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showAlreadyMemberDialog(BuildContext context, Community community) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: Text(context.l10n.community_alreadyMember),
|
||||
content: Text(
|
||||
context.l10n.community_alreadyMemberMessage(community.name),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(dialogContext);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(context.l10n.common_ok),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showJoinConfirmationDialog(
|
||||
BuildContext context,
|
||||
Community community,
|
||||
) async {
|
||||
bool addPublicChannel = true;
|
||||
|
||||
final result = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) => StatefulBuilder(
|
||||
builder: (dialogContext, setDialogState) => AlertDialog(
|
||||
title: Text(context.l10n.community_joinTitle),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(context.l10n.community_joinConfirmation(community.name)),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.groups,
|
||||
color: Theme.of(dialogContext).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
community.name,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
'ID: ${community.shortCommunityId}...',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
CheckboxListTile(
|
||||
value: addPublicChannel,
|
||||
onChanged: (value) {
|
||||
setDialogState(() {
|
||||
addPublicChannel = value ?? true;
|
||||
});
|
||||
},
|
||||
title: Text(context.l10n.community_addPublicChannel),
|
||||
subtitle: Text(context.l10n.community_addPublicChannelHint),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(dialogContext, false),
|
||||
child: Text(context.l10n.common_cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(dialogContext, true),
|
||||
child: Text(context.l10n.community_join),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (result == true && context.mounted) {
|
||||
await _joinCommunity(context, community, addPublicChannel);
|
||||
} else if (context.mounted) {
|
||||
// User cancelled - go back
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _joinCommunity(
|
||||
BuildContext context,
|
||||
Community community,
|
||||
bool addPublicChannel,
|
||||
) async {
|
||||
// Save community to local storage
|
||||
await _communityStore.addCommunity(community);
|
||||
|
||||
// Optionally add the community public channel to the device
|
||||
if (addPublicChannel && context.mounted) {
|
||||
final connector = context.read<MeshCoreConnector>();
|
||||
final nextIndex = _findNextAvailableChannelIndex(connector);
|
||||
|
||||
if (nextIndex != null) {
|
||||
final psk = community.deriveCommunityPublicPsk();
|
||||
final channelName = '${community.name} Public';
|
||||
connector.setChannel(nextIndex, channelName, psk);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.community_joined(community.name)),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
|
||||
// Return to previous screen
|
||||
Navigator.pop(context, community);
|
||||
}
|
||||
}
|
||||
|
||||
int? _findNextAvailableChannelIndex(MeshCoreConnector connector) {
|
||||
final usedIndices = connector.channels.map((c) => c.index).toSet();
|
||||
for (int i = 0; i < connector.maxChannels; i++) {
|
||||
if (!usedIndices.contains(i)) return i;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -27,13 +27,15 @@ import 'map_screen.dart';
|
||||
import 'repeater_hub_screen.dart';
|
||||
import 'settings_screen.dart';
|
||||
|
||||
enum RoomLoginDestination {
|
||||
chat,
|
||||
management,
|
||||
}
|
||||
|
||||
class ContactsScreen extends StatefulWidget {
|
||||
final bool hideBackButton;
|
||||
|
||||
const ContactsScreen({
|
||||
super.key,
|
||||
this.hideBackButton = false,
|
||||
});
|
||||
const ContactsScreen({super.key, this.hideBackButton = false});
|
||||
|
||||
@override
|
||||
State<ContactsScreen> createState() => _ContactsScreenState();
|
||||
@@ -114,7 +116,8 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
top: false,
|
||||
child: QuickSwitchBar(
|
||||
selectedIndex: 0,
|
||||
onDestinationSelected: (index) => _handleQuickSwitch(index, context),
|
||||
onDestinationSelected: (index) =>
|
||||
_handleQuickSwitch(index, context),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -168,8 +171,9 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
}
|
||||
|
||||
final filteredAndSorted = _filterAndSortContacts(contacts, connector);
|
||||
final filteredGroups =
|
||||
_showUnreadOnly ? const <ContactGroup>[] : _filterAndSortGroups(_groups, contacts);
|
||||
final filteredGroups = _showUnreadOnly
|
||||
? const <ContactGroup>[]
|
||||
: _filterAndSortGroups(_groups, contacts);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
@@ -199,7 +203,10 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
_searchDebounce?.cancel();
|
||||
@@ -238,14 +245,18 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
final group = filteredGroups[index];
|
||||
return _buildGroupTile(context, group, contacts);
|
||||
}
|
||||
final contact = filteredAndSorted[index - filteredGroups.length];
|
||||
final unreadCount = connector.getUnreadCountForContact(contact);
|
||||
final contact =
|
||||
filteredAndSorted[index - filteredGroups.length];
|
||||
final unreadCount = connector.getUnreadCountForContact(
|
||||
contact,
|
||||
);
|
||||
return _ContactTile(
|
||||
contact: contact,
|
||||
lastSeen: _resolveLastSeen(contact),
|
||||
unreadCount: unreadCount,
|
||||
onTap: () => _openChat(context, contact),
|
||||
onLongPress: () => _showContactOptions(context, connector, contact),
|
||||
onLongPress: () =>
|
||||
_showContactOptions(context, connector, contact),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -255,40 +266,61 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
);
|
||||
}
|
||||
|
||||
List<ContactGroup> _filterAndSortGroups(List<ContactGroup> groups, List<Contact> contacts) {
|
||||
List<ContactGroup> _filterAndSortGroups(
|
||||
List<ContactGroup> groups,
|
||||
List<Contact> contacts,
|
||||
) {
|
||||
final query = _searchQuery.trim().toLowerCase();
|
||||
final contactsByKey = <String, Contact>{};
|
||||
for (final contact in contacts) {
|
||||
contactsByKey[contact.publicKeyHex] = contact;
|
||||
}
|
||||
|
||||
final filtered = groups.where((group) {
|
||||
if (query.isEmpty) return true;
|
||||
if (group.name.toLowerCase().contains(query)) return true;
|
||||
for (final key in group.memberKeys) {
|
||||
final contact = contactsByKey[key];
|
||||
if (contact != null && matchesContactQuery(contact, query)) return true;
|
||||
}
|
||||
return false;
|
||||
}).where((group) {
|
||||
if (_typeFilter == ContactTypeFilter.all) return true;
|
||||
for (final key in group.memberKeys) {
|
||||
final contact = contactsByKey[key];
|
||||
if (contact != null && _matchesTypeFilter(contact)) return true;
|
||||
}
|
||||
return false;
|
||||
}).toList();
|
||||
final filtered = groups
|
||||
.where((group) {
|
||||
if (query.isEmpty) return true;
|
||||
if (group.name.toLowerCase().contains(query)) return true;
|
||||
for (final key in group.memberKeys) {
|
||||
final contact = contactsByKey[key];
|
||||
if (contact != null && matchesContactQuery(contact, query)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.where((group) {
|
||||
if (_typeFilter == ContactTypeFilter.all) return true;
|
||||
for (final key in group.memberKeys) {
|
||||
final contact = contactsByKey[key];
|
||||
if (contact != null && _matchesTypeFilter(contact)) return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.toList();
|
||||
|
||||
filtered.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
|
||||
filtered.sort(
|
||||
(a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()),
|
||||
);
|
||||
return filtered;
|
||||
}
|
||||
|
||||
List<Contact> _filterAndSortContacts(List<Contact> contacts, MeshCoreConnector connector) {
|
||||
List<Contact> _filterAndSortContacts(
|
||||
List<Contact> contacts,
|
||||
MeshCoreConnector connector,
|
||||
) {
|
||||
var filtered = contacts.where((contact) {
|
||||
if (_searchQuery.isEmpty) return true;
|
||||
return matchesContactQuery(contact, _searchQuery);
|
||||
}).toList();
|
||||
|
||||
// Filter out own node from the list
|
||||
if (connector.selfPublicKey != null) {
|
||||
final selfPubKeyHex = pubKeyToHex(connector.selfPublicKey!);
|
||||
filtered = filtered.where((contact) {
|
||||
return contact.publicKeyHex != selfPubKeyHex;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
if (_typeFilter != ContactTypeFilter.all) {
|
||||
filtered = filtered.where(_matchesTypeFilter).toList();
|
||||
}
|
||||
@@ -301,19 +333,27 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
|
||||
switch (_sortOption) {
|
||||
case ContactSortOption.lastSeen:
|
||||
filtered.sort((a, b) => _resolveLastSeen(b).compareTo(_resolveLastSeen(a)));
|
||||
filtered.sort(
|
||||
(a, b) => _resolveLastSeen(b).compareTo(_resolveLastSeen(a)),
|
||||
);
|
||||
break;
|
||||
case ContactSortOption.recentMessages:
|
||||
filtered.sort((a, b) {
|
||||
final aMessages = connector.getMessages(a);
|
||||
final bMessages = connector.getMessages(b);
|
||||
final aLastMsg = aMessages.isEmpty ? DateTime(1970) : aMessages.last.timestamp;
|
||||
final bLastMsg = bMessages.isEmpty ? DateTime(1970) : bMessages.last.timestamp;
|
||||
final aLastMsg = aMessages.isEmpty
|
||||
? DateTime(1970)
|
||||
: aMessages.last.timestamp;
|
||||
final bLastMsg = bMessages.isEmpty
|
||||
? DateTime(1970)
|
||||
: bMessages.last.timestamp;
|
||||
return bLastMsg.compareTo(aLastMsg);
|
||||
});
|
||||
break;
|
||||
case ContactSortOption.name:
|
||||
filtered.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
|
||||
filtered.sort(
|
||||
(a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -340,7 +380,11 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
: contact.lastSeen;
|
||||
}
|
||||
|
||||
Widget _buildGroupTile(BuildContext context, ContactGroup group, List<Contact> contacts) {
|
||||
Widget _buildGroupTile(
|
||||
BuildContext context,
|
||||
ContactGroup group,
|
||||
List<Contact> contacts,
|
||||
) {
|
||||
final memberContacts = _resolveGroupContacts(group, contacts);
|
||||
final subtitle = _formatGroupMembers(context, memberContacts);
|
||||
return ListTile(
|
||||
@@ -359,7 +403,10 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
);
|
||||
}
|
||||
|
||||
List<Contact> _resolveGroupContacts(ContactGroup group, List<Contact> contacts) {
|
||||
List<Contact> _resolveGroupContacts(
|
||||
ContactGroup group,
|
||||
List<Contact> contacts,
|
||||
) {
|
||||
final byKey = <String, Contact>{};
|
||||
for (final contact in contacts) {
|
||||
byKey[contact.publicKeyHex] = contact;
|
||||
@@ -371,7 +418,9 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
resolved.add(contact);
|
||||
}
|
||||
}
|
||||
resolved.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
|
||||
resolved.sort(
|
||||
(a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()),
|
||||
);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
@@ -387,7 +436,7 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
if (contact.type == advTypeRepeater) {
|
||||
_showRepeaterLogin(context, contact);
|
||||
} else if (contact.type == advTypeRoom) {
|
||||
_showRoomLogin(context, contact);
|
||||
_showRoomLogin(context, contact, RoomLoginDestination.chat);
|
||||
} else {
|
||||
context.read<MeshCoreConnector>().markContactRead(contact.publicKeyHex);
|
||||
Navigator.push(
|
||||
@@ -403,17 +452,13 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
case 1:
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
buildQuickSwitchRoute(
|
||||
const ChannelsScreen(hideBackButton: true),
|
||||
),
|
||||
buildQuickSwitchRoute(const ChannelsScreen(hideBackButton: true)),
|
||||
);
|
||||
break;
|
||||
case 2:
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
buildQuickSwitchRoute(
|
||||
const MapScreen(hideBackButton: true),
|
||||
),
|
||||
buildQuickSwitchRoute(const MapScreen(hideBackButton: true)),
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -429,10 +474,8 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => RepeaterHubScreen(
|
||||
repeater: repeater,
|
||||
password: password,
|
||||
),
|
||||
builder: (context) =>
|
||||
RepeaterHubScreen(repeater: repeater, password: password),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -440,18 +483,23 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
);
|
||||
}
|
||||
|
||||
void _showRoomLogin(BuildContext context, Contact room) {
|
||||
void _showRoomLogin(
|
||||
BuildContext context,
|
||||
Contact room,
|
||||
RoomLoginDestination destination,
|
||||
) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => RoomLoginDialog(
|
||||
room: room,
|
||||
onLogin: (password) {
|
||||
// Navigate to chat screen after successful login
|
||||
context.read<MeshCoreConnector>().markContactRead(room.publicKeyHex);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChatScreen(contact: room),
|
||||
builder: (context) => destination == RoomLoginDestination.management
|
||||
? RepeaterHubScreen(repeater: room, password: password)
|
||||
: ChatScreen(contact: room),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -459,7 +507,11 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
);
|
||||
}
|
||||
|
||||
void _showGroupOptions(BuildContext context, ContactGroup group, List<Contact> contacts) {
|
||||
void _showGroupOptions(
|
||||
BuildContext context,
|
||||
ContactGroup group,
|
||||
List<Contact> contacts,
|
||||
) {
|
||||
final members = _resolveGroupContacts(group, contacts);
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
@@ -478,7 +530,10 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete, color: Colors.red),
|
||||
title: Text(context.l10n.contacts_deleteGroup, style: const TextStyle(color: Colors.red)),
|
||||
title: Text(
|
||||
context.l10n.contacts_deleteGroup,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
_confirmDeleteGroup(context, group);
|
||||
@@ -522,7 +577,10 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
});
|
||||
await _saveGroups();
|
||||
},
|
||||
child: Text(context.l10n.common_delete, style: const TextStyle(color: Colors.red)),
|
||||
child: Text(
|
||||
context.l10n.common_delete,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -548,10 +606,16 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
final filteredContacts = filterQuery.isEmpty
|
||||
? sortedContacts
|
||||
: sortedContacts
|
||||
.where((contact) => matchesContactQuery(contact, filterQuery))
|
||||
.toList();
|
||||
.where(
|
||||
(contact) => matchesContactQuery(contact, filterQuery),
|
||||
)
|
||||
.toList();
|
||||
return AlertDialog(
|
||||
title: Text(isEditing ? context.l10n.contacts_editGroup : context.l10n.contacts_newGroup),
|
||||
title: Text(
|
||||
isEditing
|
||||
? context.l10n.contacts_editGroup
|
||||
: context.l10n.contacts_newGroup,
|
||||
),
|
||||
content: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Column(
|
||||
@@ -582,12 +646,18 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
SizedBox(
|
||||
height: 240,
|
||||
child: filteredContacts.isEmpty
|
||||
? Center(child: Text(context.l10n.contacts_noContactsMatchFilter))
|
||||
? Center(
|
||||
child: Text(
|
||||
context.l10n.contacts_noContactsMatchFilter,
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: filteredContacts.length,
|
||||
itemBuilder: (context, index) {
|
||||
final contact = filteredContacts[index];
|
||||
final isSelected = selectedKeys.contains(contact.publicKeyHex);
|
||||
final isSelected = selectedKeys.contains(
|
||||
contact.publicKeyHex,
|
||||
);
|
||||
return CheckboxListTile(
|
||||
value: isSelected,
|
||||
title: Text(contact.name),
|
||||
@@ -618,7 +688,9 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
final name = nameController.text.trim();
|
||||
if (name.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.contacts_groupNameRequired)),
|
||||
SnackBar(
|
||||
content: Text(context.l10n.contacts_groupNameRequired),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -628,13 +700,19 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
});
|
||||
if (exists) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.contacts_groupAlreadyExists(name))),
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.contacts_groupAlreadyExists(name),
|
||||
),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
if (isEditing) {
|
||||
final index = _groups.indexWhere((g) => g.name == group.name);
|
||||
final index = _groups.indexWhere(
|
||||
(g) => g.name == group.name,
|
||||
);
|
||||
if (index != -1) {
|
||||
_groups[index] = ContactGroup(
|
||||
name: name,
|
||||
@@ -642,7 +720,12 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
_groups.add(ContactGroup(name: name, memberKeys: selectedKeys.toList()));
|
||||
_groups.add(
|
||||
ContactGroup(
|
||||
name: name,
|
||||
memberKeys: selectedKeys.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
await _saveGroups();
|
||||
@@ -650,7 +733,11 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
Navigator.pop(dialogContext);
|
||||
}
|
||||
},
|
||||
child: Text(isEditing ? context.l10n.common_save : context.l10n.common_create),
|
||||
child: Text(
|
||||
isEditing
|
||||
? context.l10n.common_save
|
||||
: context.l10n.common_create,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -682,16 +769,24 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
_showRepeaterLogin(context, contact);
|
||||
},
|
||||
)
|
||||
else if (isRoom)
|
||||
else if (isRoom) ...[
|
||||
ListTile(
|
||||
leading: const Icon(Icons.room, color: Colors.blue),
|
||||
title: Text(context.l10n.contacts_roomLogin),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
_showRoomLogin(context, contact);
|
||||
_showRoomLogin(context, contact, RoomLoginDestination.chat);
|
||||
},
|
||||
)
|
||||
else
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.room_preferences, color: Colors.orange),
|
||||
title: Text(context.l10n.room_management),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
_showRoomLogin(context, contact, RoomLoginDestination.management);
|
||||
},
|
||||
),
|
||||
] else
|
||||
ListTile(
|
||||
leading: const Icon(Icons.chat),
|
||||
title: Text(context.l10n.contacts_openChat),
|
||||
@@ -702,7 +797,10 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete, color: Colors.red),
|
||||
title: Text(context.l10n.contacts_deleteContact, style: const TextStyle(color: Colors.red)),
|
||||
title: Text(
|
||||
context.l10n.contacts_deleteContact,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
_confirmDelete(context, connector, contact);
|
||||
@@ -734,7 +832,10 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
Navigator.pop(dialogContext);
|
||||
connector.removeContact(contact);
|
||||
},
|
||||
child: Text(context.l10n.common_delete, style: const TextStyle(color: Colors.red)),
|
||||
child: Text(
|
||||
context.l10n.common_delete,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -759,28 +860,41 @@ class _ContactTile extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final shotPublicKey =
|
||||
"<${contact.publicKeyHex.substring(0, 8)}...${contact.publicKeyHex.substring(contact.publicKeyHex.length - 8)}>";
|
||||
return ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: _getTypeColor(contact.type),
|
||||
child: _buildContactAvatar(contact),
|
||||
),
|
||||
title: Text(contact.name),
|
||||
subtitle: Text('${contact.typeLabel} • ${contact.pathLabel}'),
|
||||
trailing: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (unreadCount > 0) ...[
|
||||
UnreadBadge(count: unreadCount),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
Text(
|
||||
_formatLastSeen(context, lastSeen),
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||
subtitle: Text(
|
||||
'${contact.typeLabel} • ${contact.pathLabel} $shotPublicKey',
|
||||
),
|
||||
// Clamp text scaling in trailing section to prevent overflow while
|
||||
// maintaining accessibility. Primary content (title/subtitle) scales normally.
|
||||
trailing: MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaler: TextScaler.linear(
|
||||
MediaQuery.textScalerOf(context).scale(1.0).clamp(1.0, 1.3),
|
||||
),
|
||||
if (contact.hasLocation)
|
||||
Icon(Icons.location_on, size: 14, color: Colors.grey[400]),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (unreadCount > 0) ...[
|
||||
UnreadBadge(count: unreadCount),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
Text(
|
||||
_formatLastSeen(context, lastSeen),
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||
),
|
||||
if (contact.hasLocation)
|
||||
Icon(Icons.location_on, size: 14, color: Colors.grey[400]),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
@@ -790,10 +904,7 @@ class _ContactTile extends StatelessWidget {
|
||||
Widget _buildContactAvatar(Contact contact) {
|
||||
final emoji = firstEmoji(contact.name);
|
||||
if (emoji != null) {
|
||||
return Text(
|
||||
emoji,
|
||||
style: const TextStyle(fontSize: 18),
|
||||
);
|
||||
return Text(emoji, style: const TextStyle(fontSize: 18));
|
||||
}
|
||||
return Icon(_getTypeIcon(contact.type), color: Colors.white, size: 20);
|
||||
}
|
||||
@@ -832,13 +943,21 @@ class _ContactTile extends StatelessWidget {
|
||||
final now = DateTime.now();
|
||||
final diff = now.difference(lastSeen);
|
||||
|
||||
if (diff.isNegative || diff.inMinutes < 5) return context.l10n.contacts_lastSeenNow;
|
||||
if (diff.inMinutes < 60) return context.l10n.contacts_lastSeenMinsAgo(diff.inMinutes);
|
||||
if (diff.isNegative || diff.inMinutes < 5) {
|
||||
return context.l10n.contacts_lastSeenNow;
|
||||
}
|
||||
if (diff.inMinutes < 60) {
|
||||
return context.l10n.contacts_lastSeenMinsAgo(diff.inMinutes);
|
||||
}
|
||||
if (diff.inHours < 24) {
|
||||
final hours = diff.inHours;
|
||||
return hours == 1 ? context.l10n.contacts_lastSeenHourAgo : context.l10n.contacts_lastSeenHoursAgo(hours);
|
||||
return hours == 1
|
||||
? context.l10n.contacts_lastSeenHourAgo
|
||||
: context.l10n.contacts_lastSeenHoursAgo(hours);
|
||||
}
|
||||
final days = diff.inDays;
|
||||
return days == 1 ? context.l10n.contacts_lastSeenDayAgo : context.l10n.contacts_lastSeenDaysAgo(days);
|
||||
return days == 1
|
||||
? context.l10n.contacts_lastSeenDayAgo
|
||||
: context.l10n.contacts_lastSeenDaysAgo(days);
|
||||
}
|
||||
}
|
||||
|
||||
+292
-130
@@ -1,3 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
@@ -47,6 +49,8 @@ class _MapScreenState extends State<MapScreen> {
|
||||
final Set<String> _hiddenMarkerIds = {};
|
||||
Set<String> _removedMarkerIds = {};
|
||||
bool _isSelectingPoi = false;
|
||||
bool _hasInitializedMap = false;
|
||||
bool _removedMarkersLoaded = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -67,9 +71,39 @@ class _MapScreenState extends State<MapScreen> {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_removedMarkerIds = ids;
|
||||
_removedMarkersLoaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
double _standardDeviation(List<double> values) {
|
||||
if (values.length <= 1) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
final mean = values.reduce((a, b) => a + b) / values.length;
|
||||
|
||||
double sumSquaredDiff = 0.0;
|
||||
for (final value in values) {
|
||||
final diff = value - mean;
|
||||
sumSquaredDiff += diff * diff;
|
||||
}
|
||||
|
||||
// Sample standard deviation (n-1) — most appropriate here
|
||||
final variance = sumSquaredDiff / (values.length - 1);
|
||||
|
||||
return sqrt(variance);
|
||||
}
|
||||
|
||||
// Calculate zoom level based on the spread of points (std deviation in degrees)
|
||||
double _zoomFromStdDev(double latStdDev, double lonStdDev) {
|
||||
final maxSpread = max(latStdDev, lonStdDev);
|
||||
if (maxSpread <= 0) return 13.0;
|
||||
// Approzimate: each zoom level halves the visible area
|
||||
// ~0.01 degrees spread -> zoom 13, ~0.1 -> zoom 10, ~1.0 -> zoom 7
|
||||
final zoom = 10.0 - log(maxSpread * 10 + 1) / ln10 * 3;
|
||||
return zoom.clamp(4.0, 15.0);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer2<MeshCoreConnector, AppSettingsService>(
|
||||
@@ -80,10 +114,12 @@ class _MapScreenState extends State<MapScreen> {
|
||||
final highlightPosition = widget.highlightPosition;
|
||||
final sharedMarkers = settings.mapShowMarkers
|
||||
? _collectSharedMarkers(connector)
|
||||
.where((marker) =>
|
||||
!_hiddenMarkerIds.contains(marker.id) &&
|
||||
!_removedMarkerIds.contains(marker.id))
|
||||
.toList()
|
||||
.where(
|
||||
(marker) =>
|
||||
!_hiddenMarkerIds.contains(marker.id) &&
|
||||
!_removedMarkerIds.contains(marker.id),
|
||||
)
|
||||
.toList()
|
||||
: <_SharedMarker>[];
|
||||
|
||||
// Filter by time
|
||||
@@ -91,16 +127,18 @@ class _MapScreenState extends State<MapScreen> {
|
||||
final filteredByTime = settings.mapTimeFilterHours == 0
|
||||
? contacts
|
||||
: contacts.where((c) {
|
||||
final hoursSinceLastSeen =
|
||||
now.difference(c.lastSeen).inHours;
|
||||
final hoursSinceLastSeen = now.difference(c.lastSeen).inHours;
|
||||
return hoursSinceLastSeen <= settings.mapTimeFilterHours;
|
||||
}).toList();
|
||||
|
||||
// Filter by key prefix
|
||||
final keyPrefix = settings.mapKeyPrefix.trim();
|
||||
final filteredByKeyPrefix = (settings.mapKeyPrefixEnabled && keyPrefix.isNotEmpty)
|
||||
final filteredByKeyPrefix =
|
||||
(settings.mapKeyPrefixEnabled && keyPrefix.isNotEmpty)
|
||||
? filteredByTime.where((c) {
|
||||
return c.publicKeyHex.toLowerCase().startsWith(keyPrefix.toLowerCase());
|
||||
return c.publicKeyHex.toLowerCase().startsWith(
|
||||
keyPrefix.toLowerCase(),
|
||||
);
|
||||
}).toList()
|
||||
: filteredByTime;
|
||||
|
||||
@@ -109,30 +147,91 @@ class _MapScreenState extends State<MapScreen> {
|
||||
.where((c) => c.hasLocation)
|
||||
.toList();
|
||||
|
||||
// Calculate center of all nodes, or default to (0, 0)
|
||||
// Calculate center and zoom of all nodes, or default to (0, 0)
|
||||
LatLng center = const LatLng(0, 0);
|
||||
final hasMapContent = contactsWithLocation.isNotEmpty ||
|
||||
double initialZoom = 10.0;
|
||||
final hasMapContent =
|
||||
contactsWithLocation.isNotEmpty ||
|
||||
sharedMarkers.isNotEmpty ||
|
||||
_isSelectingPoi ||
|
||||
highlightPosition != null;
|
||||
if (contactsWithLocation.isNotEmpty || sharedMarkers.isNotEmpty) {
|
||||
double avgLat = contactsWithLocation
|
||||
.map((c) => c.latitude!)
|
||||
.fold<double>(0, (sum, lat) => sum + lat);
|
||||
double avgLon = contactsWithLocation
|
||||
.map((c) => c.longitude!)
|
||||
.fold<double>(0, (sum, lon) => sum + lon);
|
||||
for (final marker in sharedMarkers) {
|
||||
avgLat += marker.position.latitude;
|
||||
avgLon += marker.position.longitude;
|
||||
}
|
||||
final total = contactsWithLocation.length + sharedMarkers.length;
|
||||
if (total > 0) {
|
||||
center = LatLng(avgLat / total, avgLon / total);
|
||||
final allPoints = [
|
||||
...contactsWithLocation.map(
|
||||
(c) => LatLng(c.latitude!, c.longitude!),
|
||||
),
|
||||
...sharedMarkers.map((m) => m.position),
|
||||
];
|
||||
if (allPoints.length >= 3) {
|
||||
final latValues = allPoints.map((p) => p.latitude).toList();
|
||||
final lonValues = allPoints.map((p) => p.longitude).toList();
|
||||
|
||||
final meanLat =
|
||||
latValues.reduce((a, b) => a + b) / latValues.length;
|
||||
final meanLon =
|
||||
lonValues.reduce((a, b) => a + b) / lonValues.length;
|
||||
final latStdDev = _standardDeviation(latValues);
|
||||
final lonStdDev = _standardDeviation(lonValues);
|
||||
|
||||
final filteredPoints = allPoints
|
||||
.where(
|
||||
(p) =>
|
||||
(p.latitude - meanLat).abs() <= latStdDev * 2 &&
|
||||
(p.longitude - meanLon).abs() <= lonStdDev * 2,
|
||||
)
|
||||
.toList();
|
||||
|
||||
if (filteredPoints.isNotEmpty) {
|
||||
final filteredLatValues = filteredPoints
|
||||
.map((p) => p.latitude)
|
||||
.toList();
|
||||
final filteredLonValues = filteredPoints
|
||||
.map((p) => p.longitude)
|
||||
.toList();
|
||||
final avgLat = filteredLatValues.reduce((a, b) => a + b);
|
||||
final avgLon = filteredLonValues.reduce((a, b) => a + b);
|
||||
center = LatLng(
|
||||
avgLat / filteredPoints.length,
|
||||
avgLon / filteredPoints.length,
|
||||
);
|
||||
// Use std deviation of filtered points for zoom
|
||||
final filteredLatStdDev = _standardDeviation(filteredLatValues);
|
||||
final filteredLonStdDev = _standardDeviation(filteredLonValues);
|
||||
initialZoom = _zoomFromStdDev(
|
||||
filteredLatStdDev,
|
||||
filteredLonStdDev,
|
||||
);
|
||||
} else {
|
||||
center = LatLng(meanLat, meanLon);
|
||||
initialZoom = _zoomFromStdDev(latStdDev, lonStdDev);
|
||||
}
|
||||
} else {
|
||||
double avgLat = 0.0;
|
||||
double avgLon = 0.0;
|
||||
for (final point in allPoints) {
|
||||
avgLat += point.latitude;
|
||||
avgLon += point.longitude;
|
||||
}
|
||||
center = LatLng(
|
||||
avgLat / allPoints.length,
|
||||
avgLon / allPoints.length,
|
||||
);
|
||||
initialZoom = 12.0;
|
||||
}
|
||||
}
|
||||
if (highlightPosition != null) {
|
||||
center = highlightPosition;
|
||||
initialZoom = widget.highlightZoom;
|
||||
}
|
||||
|
||||
// Re center map after removed markers have loaded
|
||||
if (!_hasInitializedMap && _removedMarkersLoaded && hasMapContent) {
|
||||
_hasInitializedMap = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
_mapController.move(center, initialZoom);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final allowBack = !connector.isConnected;
|
||||
@@ -156,7 +255,9 @@ class _MapScreenState extends State<MapScreen> {
|
||||
tooltip: context.l10n.common_settings,
|
||||
onPressed: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const SettingsScreen()),
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const SettingsScreen(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -169,9 +270,12 @@ class _MapScreenState extends State<MapScreen> {
|
||||
mapController: _mapController,
|
||||
options: MapOptions(
|
||||
initialCenter: center,
|
||||
initialZoom: 13.0,
|
||||
initialZoom: initialZoom,
|
||||
minZoom: 2.0,
|
||||
maxZoom: 18.0,
|
||||
interactionOptions: InteractionOptions(
|
||||
flags: ~InteractiveFlag.rotate
|
||||
),
|
||||
onTap: (_, latLng) {
|
||||
if (_isSelectingPoi) {
|
||||
setState(() {
|
||||
@@ -234,18 +338,23 @@ class _MapScreenState extends State<MapScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
_buildLegend(contactsWithLocation.length, sharedMarkers.length),
|
||||
_buildLegend(
|
||||
contactsWithLocation.length,
|
||||
sharedMarkers.length,
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: SafeArea(
|
||||
top: false,
|
||||
child: QuickSwitchBar(
|
||||
selectedIndex: 2,
|
||||
onDestinationSelected: (index) => _handleQuickSwitch(index, context),
|
||||
onDestinationSelected: (index) =>
|
||||
_handleQuickSwitch(index, context),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => _showFilterDialog(context, settingsService),
|
||||
tooltip: context.l10n.map_filterNodes,
|
||||
child: const Icon(Icons.filter_list),
|
||||
),
|
||||
),
|
||||
@@ -259,27 +368,17 @@ class _MapScreenState extends State<MapScreen> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.location_off,
|
||||
size: 64,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
Icon(Icons.location_off, size: 64, color: Colors.grey[400]),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
context.l10n.map_noNodesWithLocation,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
style: TextStyle(fontSize: 18, color: Colors.grey[600]),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
context.l10n.map_nodesNeedGps,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[500],
|
||||
),
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[500]),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -293,7 +392,9 @@ class _MapScreenState extends State<MapScreen> {
|
||||
if (!contact.hasLocation) continue;
|
||||
|
||||
// Apply node type filters
|
||||
if (contact.type == advTypeRepeater && !settings.mapShowRepeaters) continue;
|
||||
if (contact.type == advTypeRepeater && !settings.mapShowRepeaters) {
|
||||
continue;
|
||||
}
|
||||
if (contact.type == advTypeChat && !settings.mapShowChatNodes) continue;
|
||||
if (contact.type != advTypeChat &&
|
||||
contact.type != advTypeRepeater &&
|
||||
@@ -396,13 +497,37 @@ class _MapScreenState extends State<MapScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildLegendItem(Icons.person, context.l10n.map_chat, Colors.blue),
|
||||
_buildLegendItem(Icons.router, context.l10n.map_repeater, Colors.green),
|
||||
_buildLegendItem(Icons.meeting_room, context.l10n.map_room, Colors.purple),
|
||||
_buildLegendItem(Icons.sensors, context.l10n.map_sensor, Colors.orange),
|
||||
_buildLegendItem(
|
||||
Icons.person,
|
||||
context.l10n.map_chat,
|
||||
Colors.blue,
|
||||
),
|
||||
_buildLegendItem(
|
||||
Icons.router,
|
||||
context.l10n.map_repeater,
|
||||
Colors.green,
|
||||
),
|
||||
_buildLegendItem(
|
||||
Icons.meeting_room,
|
||||
context.l10n.map_room,
|
||||
Colors.purple,
|
||||
),
|
||||
_buildLegendItem(
|
||||
Icons.sensors,
|
||||
context.l10n.map_sensor,
|
||||
Colors.orange,
|
||||
),
|
||||
_buildLegendItem(Icons.flag, context.l10n.map_pinDm, Colors.blue),
|
||||
_buildLegendItem(Icons.flag, context.l10n.map_pinPrivate, Colors.purple),
|
||||
_buildLegendItem(Icons.flag, context.l10n.map_pinPublic, Colors.orange),
|
||||
_buildLegendItem(
|
||||
Icons.flag,
|
||||
context.l10n.map_pinPrivate,
|
||||
Colors.purple,
|
||||
),
|
||||
_buildLegendItem(
|
||||
Icons.flag,
|
||||
context.l10n.map_pinPublic,
|
||||
Colors.orange,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -418,10 +543,7 @@ class _MapScreenState extends State<MapScreen> {
|
||||
children: [
|
||||
Icon(icon, size: 16, color: color),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
Text(label, style: const TextStyle(fontSize: 12)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -475,7 +597,9 @@ class _MapScreenState extends State<MapScreen> {
|
||||
label: payload.label,
|
||||
flags: payload.flags,
|
||||
fromName: message.senderName,
|
||||
sourceLabel: channel.name.isEmpty ? 'Channel ${channel.index}' : channel.name,
|
||||
sourceLabel: channel.name.isEmpty
|
||||
? 'Channel ${channel.index}'
|
||||
: channel.name,
|
||||
isChannel: true,
|
||||
isPublicChannel: isPublic,
|
||||
),
|
||||
@@ -541,11 +665,7 @@ class _MapScreenState extends State<MapScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.flag,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
child: const Icon(Icons.flag, color: Colors.white, size: 20),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -563,10 +683,8 @@ class _MapScreenState extends State<MapScreen> {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => RepeaterHubScreen(
|
||||
repeater: repeater,
|
||||
password: password,
|
||||
),
|
||||
builder: (context) =>
|
||||
RepeaterHubScreen(repeater: repeater, password: password),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -584,9 +702,7 @@ class _MapScreenState extends State<MapScreen> {
|
||||
context.read<MeshCoreConnector>().markContactRead(room.publicKeyHex);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChatScreen(contact: room),
|
||||
),
|
||||
MaterialPageRoute(builder: (context) => ChatScreen(contact: room)),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -613,9 +729,14 @@ class _MapScreenState extends State<MapScreen> {
|
||||
children: [
|
||||
_buildInfoRow('Type', contact.typeLabel),
|
||||
_buildInfoRow('Path', contact.pathLabel),
|
||||
_buildInfoRow('Location',
|
||||
'${contact.latitude!.toStringAsFixed(6)}, ${contact.longitude!.toStringAsFixed(6)}'),
|
||||
_buildInfoRow(context.l10n.map_lastSeen, _formatLastSeen(contact.lastSeen)),
|
||||
_buildInfoRow(
|
||||
'Location',
|
||||
'${contact.latitude!.toStringAsFixed(6)}, ${contact.longitude!.toStringAsFixed(6)}',
|
||||
),
|
||||
_buildInfoRow(
|
||||
context.l10n.map_lastSeen,
|
||||
_formatLastSeen(contact.lastSeen),
|
||||
),
|
||||
_buildInfoRow('Public Key', contact.publicKeyHex),
|
||||
],
|
||||
),
|
||||
@@ -624,7 +745,8 @@ class _MapScreenState extends State<MapScreen> {
|
||||
onPressed: () => Navigator.pop(dialogContext),
|
||||
child: Text(context.l10n.common_close),
|
||||
),
|
||||
if (contact.type == advTypeChat) // Only show chat button for chat nodes
|
||||
if (contact.type ==
|
||||
advTypeChat) // Only show chat button for chat nodes
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(dialogContext);
|
||||
@@ -637,22 +759,22 @@ class _MapScreenState extends State<MapScreen> {
|
||||
},
|
||||
child: Text(context.l10n.contacts_openChat),
|
||||
),
|
||||
if (contact.type == advTypeRepeater)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(dialogContext);
|
||||
_showRepeaterLogin(context, contact);
|
||||
},
|
||||
child: Text(context.l10n.map_manageRepeater),
|
||||
),
|
||||
if (contact.type == advTypeRoom)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(dialogContext);
|
||||
_showRoomLogin(context, contact);
|
||||
},
|
||||
child: Text(context.l10n.map_joinRoom),
|
||||
),
|
||||
if (contact.type == advTypeRepeater)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(dialogContext);
|
||||
_showRepeaterLogin(context, contact);
|
||||
},
|
||||
child: Text(context.l10n.map_manageRepeater),
|
||||
),
|
||||
if (contact.type == advTypeRoom)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(dialogContext);
|
||||
_showRoomLogin(context, contact);
|
||||
},
|
||||
child: Text(context.l10n.map_joinRoom),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -664,17 +786,13 @@ class _MapScreenState extends State<MapScreen> {
|
||||
case 0:
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
buildQuickSwitchRoute(
|
||||
const ContactsScreen(hideBackButton: true),
|
||||
),
|
||||
buildQuickSwitchRoute(const ContactsScreen(hideBackButton: true)),
|
||||
);
|
||||
break;
|
||||
case 1:
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
buildQuickSwitchRoute(
|
||||
const ChannelsScreen(hideBackButton: true),
|
||||
),
|
||||
buildQuickSwitchRoute(const ChannelsScreen(hideBackButton: true)),
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -722,7 +840,8 @@ class _MapScreenState extends State<MapScreen> {
|
||||
'Location',
|
||||
'${marker.position.latitude.toStringAsFixed(6)}, ${marker.position.longitude.toStringAsFixed(6)}',
|
||||
),
|
||||
if (marker.flags.isNotEmpty) _buildInfoRow(context.l10n.map_flags, marker.flags),
|
||||
if (marker.flags.isNotEmpty)
|
||||
_buildInfoRow(context.l10n.map_flags, marker.flags),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
@@ -772,10 +891,7 @@ class _MapScreenState extends State<MapScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
Text(value, style: const TextStyle(fontSize: 14)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -860,7 +976,10 @@ class _MapScreenState extends State<MapScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<String?> _promptForLabel(BuildContext context, String defaultLabel) async {
|
||||
Future<String?> _promptForLabel(
|
||||
BuildContext context,
|
||||
String defaultLabel,
|
||||
) async {
|
||||
final controller = TextEditingController(text: defaultLabel);
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
@@ -881,7 +1000,10 @@ class _MapScreenState extends State<MapScreen> {
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final label = controller.text.trim().replaceAll('|', '/');
|
||||
Navigator.pop(dialogContext, label.isEmpty ? defaultLabel : label);
|
||||
Navigator.pop(
|
||||
dialogContext,
|
||||
label.isEmpty ? defaultLabel : label,
|
||||
);
|
||||
},
|
||||
child: Text(context.l10n.common_continue),
|
||||
),
|
||||
@@ -913,8 +1035,11 @@ class _MapScreenState extends State<MapScreen> {
|
||||
return Consumer<MeshCoreConnector>(
|
||||
builder: (consumerContext, liveConnector, child) {
|
||||
final allContacts = liveConnector.contacts
|
||||
.where((contact) =>
|
||||
contact.type != advTypeRepeater && contact.type != advTypeRoom)
|
||||
.where(
|
||||
(contact) =>
|
||||
contact.type != advTypeRepeater &&
|
||||
contact.type != advTypeRoom,
|
||||
)
|
||||
.toList();
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
@@ -924,7 +1049,10 @@ class _MapScreenState extends State<MapScreen> {
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
|
||||
child: Text(context.l10n.map_sendToContact, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
child: Text(
|
||||
context.l10n.map_sendToContact,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 4, 16, 8),
|
||||
@@ -935,7 +1063,10 @@ class _MapScreenState extends State<MapScreen> {
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setSheetState(() {
|
||||
@@ -945,50 +1076,73 @@ class _MapScreenState extends State<MapScreen> {
|
||||
),
|
||||
),
|
||||
...allContacts
|
||||
.where((contact) =>
|
||||
query.isEmpty || matchesContactQuery(contact, query))
|
||||
.where(
|
||||
(contact) =>
|
||||
query.isEmpty ||
|
||||
matchesContactQuery(contact, query),
|
||||
)
|
||||
.map((contact) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.person),
|
||||
title: Text(contact.name),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
liveConnector.sendMessage(contact, markerText);
|
||||
},
|
||||
);
|
||||
}),
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.person),
|
||||
title: Text(contact.name),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
liveConnector.sendMessage(contact, markerText);
|
||||
},
|
||||
);
|
||||
}),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
|
||||
child: Text(context.l10n.map_sendToChannel, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
child: Text(
|
||||
context.l10n.map_sendToChannel,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
if (liveConnector.isLoadingChannels)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
child: LinearProgressIndicator(),
|
||||
)
|
||||
else if (liveConnector.channels.where((c) => !c.isEmpty).isEmpty)
|
||||
else if (liveConnector.channels
|
||||
.where((c) => !c.isEmpty)
|
||||
.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Text(context.l10n.map_noChannelsAvailable),
|
||||
)
|
||||
else
|
||||
...liveConnector.channels.where((c) => !c.isEmpty).map((channel) {
|
||||
...liveConnector.channels.where((c) => !c.isEmpty).map((
|
||||
channel,
|
||||
) {
|
||||
final isPublic = _isPublicChannel(channel);
|
||||
final label = channel.name.isEmpty ? 'Channel ${channel.index}' : channel.name;
|
||||
final label = channel.name.isEmpty
|
||||
? 'Channel ${channel.index}'
|
||||
: channel.name;
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
isPublic ? Icons.public : Icons.tag,
|
||||
color: isPublic ? Colors.orange : Colors.blue,
|
||||
),
|
||||
title: Text(label),
|
||||
subtitle: isPublic ? Text(context.l10n.channels_publicChannel) : null,
|
||||
subtitle: isPublic
|
||||
? Text(context.l10n.channels_publicChannel)
|
||||
: null,
|
||||
onTap: () async {
|
||||
Navigator.pop(sheetContext);
|
||||
final canSend = isPublic
|
||||
? await _confirmPublicShare(context, label)
|
||||
: true;
|
||||
if (canSend) {
|
||||
liveConnector.sendChannelMessage(channel, markerText);
|
||||
liveConnector.sendChannelMessage(
|
||||
channel,
|
||||
markerText,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -1008,12 +1162,17 @@ class _MapScreenState extends State<MapScreen> {
|
||||
return channel.isPublicChannel;
|
||||
}
|
||||
|
||||
Future<bool> _confirmPublicShare(BuildContext context, String channelLabel) async {
|
||||
Future<bool> _confirmPublicShare(
|
||||
BuildContext context,
|
||||
String channelLabel,
|
||||
) async {
|
||||
final result = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: Text(context.l10n.map_publicLocationShare),
|
||||
content: Text(context.l10n.map_publicLocationShareConfirm(channelLabel)),
|
||||
content: Text(
|
||||
context.l10n.map_publicLocationShareConfirm(channelLabel),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(dialogContext, false),
|
||||
@@ -1029,7 +1188,10 @@ class _MapScreenState extends State<MapScreen> {
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
void _showFilterDialog(BuildContext context, AppSettingsService settingsService) {
|
||||
void _showFilterDialog(
|
||||
BuildContext context,
|
||||
AppSettingsService settingsService,
|
||||
) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
@@ -1133,10 +1295,7 @@ class _MapScreenState extends State<MapScreen> {
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_getTimeFilterLabel(settings.mapTimeFilterHours),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[700]),
|
||||
),
|
||||
Slider(
|
||||
value: _hoursToSliderValue(settings.mapTimeFilterHours),
|
||||
@@ -1176,11 +1335,14 @@ class _MapScreenState extends State<MapScreen> {
|
||||
|
||||
if (hours <= 24) {
|
||||
return (hours / 24) * 40;
|
||||
} else if (hours <= 168) { // 7 days
|
||||
} else if (hours <= 168) {
|
||||
// 7 days
|
||||
return 40 + ((hours - 24) / (168 - 24)) * 20;
|
||||
} else if (hours <= 720) { // 30 days
|
||||
} else if (hours <= 720) {
|
||||
// 30 days
|
||||
return 60 + ((hours - 168) / (720 - 168)) * 20;
|
||||
} else if (hours <= 4380) { // 6 months
|
||||
} else if (hours <= 4380) {
|
||||
// 6 months
|
||||
return 80 + ((hours - 720) / (4380 - 720)) * 19;
|
||||
} else {
|
||||
return 100;
|
||||
|
||||
@@ -0,0 +1,456 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../models/path_selection.dart';
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../services/repeater_command_service.dart';
|
||||
import '../widgets/path_management_dialog.dart';
|
||||
import '../widgets/snr_indicator.dart';
|
||||
|
||||
class NeighboursScreen extends StatefulWidget {
|
||||
final Contact repeater;
|
||||
final String password;
|
||||
|
||||
const NeighboursScreen({
|
||||
super.key,
|
||||
required this.repeater,
|
||||
required this.password,
|
||||
});
|
||||
|
||||
@override
|
||||
State<NeighboursScreen> createState() => _NeighboursScreenState();
|
||||
}
|
||||
|
||||
class _NeighboursScreenState extends State<NeighboursScreen> {
|
||||
static const int _reqNeighboursKeyLen = 4;
|
||||
static const int _statusPayloadOffset = 8;
|
||||
static const int _statusStatsSize = 52;
|
||||
static const int _statusResponseBytes =
|
||||
_statusPayloadOffset + _statusStatsSize;
|
||||
Uint8List _tagData = Uint8List(4);
|
||||
int _neighbourCount = 0;
|
||||
|
||||
bool _isLoading = false;
|
||||
bool _isLoaded = false;
|
||||
bool _hasData = false;
|
||||
Timer? _statusTimeout;
|
||||
StreamSubscription<Uint8List>? _frameSubscription;
|
||||
RepeaterCommandService? _commandService;
|
||||
PathSelection? _pendingStatusSelection;
|
||||
List<Map<String, dynamic>>? _parsedNeighbours;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
_commandService = RepeaterCommandService(connector);
|
||||
_setupMessageListener();
|
||||
_loadNeighbours();
|
||||
_hasData = false;
|
||||
}
|
||||
|
||||
void _setupMessageListener() {
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
|
||||
// Listen for incoming text messages from the repeater
|
||||
_frameSubscription = connector.receivedFrames.listen((frame) {
|
||||
if (frame.isEmpty) return;
|
||||
|
||||
if (frame[0] == respCodeSent) {
|
||||
_tagData = frame.sublist(2, 6);
|
||||
//_timeEstment = frame.buffer.asByteData().getUint32(6, Endian.little);
|
||||
}
|
||||
|
||||
// Check if it's a binary response
|
||||
if (frame[0] == pushCodeBinaryResponse &&
|
||||
listEquals(frame.sublist(2, 6), _tagData)) {
|
||||
_handleNeighboursResponse(connector, frame.sublist(6));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
String fmtDuration(double seconds) {
|
||||
if (seconds < 60) {
|
||||
return '${seconds.toStringAsFixed(1)}s';
|
||||
}
|
||||
|
||||
final int m = (seconds ~/ 60).toInt();
|
||||
final double s = seconds - (60 * m);
|
||||
|
||||
if (m < 60) {
|
||||
return '${m}m ${s.toStringAsFixed(0)}s';
|
||||
}
|
||||
|
||||
final int h = m ~/ 60;
|
||||
final int m2 = m % 60;
|
||||
|
||||
return '${h}h ${m2}m';
|
||||
}
|
||||
|
||||
static List<Map<String, dynamic>> parseNeighboursData(
|
||||
BufferReader buffer,
|
||||
int resultsCount,
|
||||
) {
|
||||
final Map<int, Map<String, dynamic>> neighbours = {};
|
||||
for (var i = 0; i < resultsCount; i++) {
|
||||
final neighbourData = neighbours.putIfAbsent(
|
||||
i,
|
||||
() => {
|
||||
'contact': null,
|
||||
'publicKey': <Uint8List>{},
|
||||
'lastHeard': <int>{},
|
||||
'snr': <double>{},
|
||||
},
|
||||
);
|
||||
neighbourData['publicKey'] = buffer.readBytes(_reqNeighboursKeyLen);
|
||||
neighbourData['lastHeard'] = buffer.readUInt32LE();
|
||||
neighbourData['snr'] = buffer.readInt8() / 4.0;
|
||||
}
|
||||
|
||||
return neighbours.values.toList();
|
||||
}
|
||||
|
||||
void _handleNeighboursResponse(MeshCoreConnector connector, Uint8List frame) {
|
||||
final buffer = BufferReader(frame);
|
||||
final neighbourCount = buffer.readUInt16LE();
|
||||
final parsedNeighbours = parseNeighboursData(buffer, buffer.readUInt16LE());
|
||||
connector.contacts.where((c) => c.type == advTypeRepeater).forEach((
|
||||
repeater,
|
||||
) {
|
||||
for (var neighbourData in parsedNeighbours) {
|
||||
final publicKey = neighbourData['publicKey'];
|
||||
if (listEquals(
|
||||
repeater.publicKey.sublist(0, _reqNeighboursKeyLen),
|
||||
publicKey,
|
||||
)) {
|
||||
neighbourData['contact'] = repeater;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setState(() {
|
||||
_parsedNeighbours = parsedNeighbours;
|
||||
_neighbourCount = neighbourCount;
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.neighbors_receivedData),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
_statusTimeout?.cancel();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_isLoaded = true;
|
||||
_hasData = true;
|
||||
});
|
||||
}
|
||||
|
||||
Contact _resolveRepeater(MeshCoreConnector connector) {
|
||||
return connector.contacts.firstWhere(
|
||||
(c) => c.publicKeyHex == widget.repeater.publicKeyHex,
|
||||
orElse: () => widget.repeater,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadNeighbours() async {
|
||||
if (_commandService == null) return;
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_isLoaded = false;
|
||||
});
|
||||
try {
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
final repeater = _resolveRepeater(connector);
|
||||
final selection = await connector.preparePathForContactSend(repeater);
|
||||
_pendingStatusSelection = selection;
|
||||
|
||||
//[version][number of requested neighbours][offset_16bit][order by][len of public key]
|
||||
final frame = buildSendBinaryReq(
|
||||
repeater.publicKey,
|
||||
payload: Uint8List.fromList([
|
||||
reqTypeGetNeighbours,
|
||||
0x00,
|
||||
0x0F,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
_reqNeighboursKeyLen,
|
||||
]),
|
||||
);
|
||||
await connector.sendFrame(frame);
|
||||
|
||||
final pathLengthValue = selection.useFlood ? -1 : selection.hopCount;
|
||||
final messageBytes = frame.length >= _statusResponseBytes
|
||||
? frame.length
|
||||
: _statusResponseBytes;
|
||||
final timeoutMs = connector.calculateTimeout(
|
||||
pathLength: pathLengthValue,
|
||||
messageBytes: messageBytes,
|
||||
);
|
||||
_statusTimeout?.cancel();
|
||||
_statusTimeout = Timer(Duration(milliseconds: timeoutMs), () {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_isLoaded = false;
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.neighbors_requestTimedOut),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
_recordStatusResult(false);
|
||||
});
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_isLoaded = false;
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.neighbors_errorLoading(e.toString())),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _recordStatusResult(bool success) {
|
||||
final selection = _pendingStatusSelection;
|
||||
if (selection == null) return;
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
final repeater = _resolveRepeater(connector);
|
||||
connector.recordRepeaterPathResult(repeater, selection, success, null);
|
||||
_pendingStatusSelection = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_frameSubscription?.cancel();
|
||||
_commandService?.dispose();
|
||||
_statusTimeout?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final connector = context.watch<MeshCoreConnector>();
|
||||
final repeater = _resolveRepeater(connector);
|
||||
final isFloodMode = repeater.pathOverride == -1;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
l10n.neighbors_repeatersNeighbours,
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
repeater.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
PopupMenuButton<String>(
|
||||
icon: Icon(isFloodMode ? Icons.waves : Icons.route),
|
||||
tooltip: l10n.repeater_routingMode,
|
||||
onSelected: (mode) async {
|
||||
if (mode == 'flood') {
|
||||
await connector.setPathOverride(repeater, pathLen: -1);
|
||||
} else {
|
||||
await connector.setPathOverride(repeater, pathLen: null);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 'auto',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.auto_mode,
|
||||
size: 20,
|
||||
color: !isFloodMode
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_autoUseSavedPath,
|
||||
style: TextStyle(
|
||||
fontWeight: !isFloodMode
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'flood',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.waves,
|
||||
size: 20,
|
||||
color: isFloodMode
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_forceFloodMode,
|
||||
style: TextStyle(
|
||||
fontWeight: isFloodMode
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.timeline),
|
||||
tooltip: l10n.repeater_pathManagement,
|
||||
onPressed: () =>
|
||||
PathManagementDialog.show(context, contact: repeater),
|
||||
),
|
||||
IconButton(
|
||||
icon: _isLoading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.refresh),
|
||||
onPressed: _isLoading ? null : _loadNeighbours,
|
||||
tooltip: l10n.repeater_refresh,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: _loadNeighbours,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
if (!_isLoaded &&
|
||||
!_hasData &&
|
||||
(_parsedNeighbours == null || _parsedNeighbours!.isEmpty))
|
||||
Center(
|
||||
child: Text(
|
||||
l10n.neighbors_noData,
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
if (_isLoaded ||
|
||||
_hasData &&
|
||||
!(_parsedNeighbours == null ||
|
||||
_parsedNeighbours!.isEmpty))
|
||||
_buildNeighboursInfoCard(
|
||||
"${l10n.repeater_neighbours} - $_neighbourCount",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNeighboursInfoCard(String title) {
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
color: Theme.of(context).textTheme.headlineSmall?.color,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
for (final entry in _parsedNeighbours!.asMap().entries)
|
||||
_buildInfoRow(
|
||||
entry.value['contact'] != null
|
||||
? entry.value['contact'].name
|
||||
: context.l10n.neighbors_unknownContact(
|
||||
"<${pubKeyToHex(entry.value['publicKey'])}>",
|
||||
),
|
||||
context.l10n.neighbors_heardAgo(
|
||||
fmtDuration(entry.value['lastHeard'] + 0.0),
|
||||
),
|
||||
entry.value['snr'],
|
||||
connector.currentSf!,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(
|
||||
String label,
|
||||
String value,
|
||||
double snr,
|
||||
int spreadingFactor,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 3),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
label,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: Text(value),
|
||||
trailing: SNRIcon(
|
||||
snr: snr,
|
||||
snrLevels: getSNRfromSF(spreadingFactor),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:meshcore_open/connector/meshcore_protocol.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/contact.dart';
|
||||
import 'repeater_status_screen.dart';
|
||||
import 'repeater_cli_screen.dart';
|
||||
import 'repeater_settings_screen.dart';
|
||||
import 'telemetry_screen.dart';
|
||||
import 'neighbours_screen.dart';
|
||||
|
||||
class RepeaterHubScreen extends StatelessWidget {
|
||||
final Contact repeater;
|
||||
@@ -25,10 +27,17 @@ class RepeaterHubScreen extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(l10n.repeater_management),
|
||||
Text(
|
||||
repeater.type == advTypeRepeater
|
||||
? l10n.repeater_management
|
||||
: l10n.room_management,
|
||||
),
|
||||
Text(
|
||||
repeater.name,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.normal),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -39,130 +48,167 @@ class RepeaterHubScreen extends StatelessWidget {
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
// Repeater info card
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 40,
|
||||
backgroundColor: Colors.orange,
|
||||
child: const Icon(Icons.cell_tower, size: 40, color: Colors.white),
|
||||
// Repeater info card
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 40,
|
||||
backgroundColor: Colors.orange,
|
||||
child: const Icon(
|
||||
Icons.cell_tower,
|
||||
size: 40,
|
||||
color: Colors.white,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
repeater.name,
|
||||
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
repeater.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
repeater.pathLabel,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||
),
|
||||
if (repeater.hasLocation) ...[
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.location_on, size: 14, color: Colors.grey[600]),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${repeater.latitude?.toStringAsFixed(4)}, ${repeater.longitude?.toStringAsFixed(4)}',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'<${repeater.publicKeyHex.substring(0, 8)}...${repeater.publicKeyHex.substring(repeater.publicKeyHex.length - 8)}>',
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
repeater.pathLabel,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||
),
|
||||
if (repeater.hasLocation) ...[
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.location_on,
|
||||
size: 14,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${repeater.latitude?.toStringAsFixed(4)}, ${repeater.longitude?.toStringAsFixed(4)}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
l10n.repeater_managementTools,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Status button
|
||||
_buildManagementCard(
|
||||
context,
|
||||
icon: Icons.analytics,
|
||||
title: l10n.repeater_status,
|
||||
subtitle: l10n.repeater_statusSubtitle,
|
||||
color: Colors.blue,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => RepeaterStatusScreen(
|
||||
repeater: repeater,
|
||||
password: password,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
l10n.repeater_managementTools,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Status button
|
||||
_buildManagementCard(
|
||||
context,
|
||||
icon: Icons.analytics,
|
||||
title: l10n.repeater_status,
|
||||
subtitle: l10n.repeater_statusSubtitle,
|
||||
color: Colors.blue,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => RepeaterStatusScreen(
|
||||
repeater: repeater,
|
||||
password: password,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Telemetry button
|
||||
_buildManagementCard(
|
||||
context,
|
||||
icon: Icons.bar_chart_sharp,
|
||||
title: l10n.repeater_telemetry,
|
||||
subtitle: l10n.repeater_telemetrySubtitle,
|
||||
color: Colors.teal,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => TelemetryScreen(
|
||||
repeater: repeater,
|
||||
password: password,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Telemetry button
|
||||
_buildManagementCard(
|
||||
context,
|
||||
icon: Icons.bar_chart_sharp,
|
||||
title: l10n.repeater_telemetry,
|
||||
subtitle: l10n.repeater_telemetrySubtitle,
|
||||
color: Colors.teal,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
TelemetryScreen(repeater: repeater, password: password),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// CLI button
|
||||
_buildManagementCard(
|
||||
context,
|
||||
icon: Icons.terminal,
|
||||
title: l10n.repeater_cli,
|
||||
subtitle: l10n.repeater_cliSubtitle,
|
||||
color: Colors.green,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => RepeaterCliScreen(
|
||||
repeater: repeater,
|
||||
password: password,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// CLI button
|
||||
_buildManagementCard(
|
||||
context,
|
||||
icon: Icons.terminal,
|
||||
title: l10n.repeater_cli,
|
||||
subtitle: l10n.repeater_cliSubtitle,
|
||||
color: Colors.green,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => RepeaterCliScreen(
|
||||
repeater: repeater,
|
||||
password: password,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Neighbors button
|
||||
_buildManagementCard(
|
||||
context,
|
||||
icon: Icons.group,
|
||||
title: l10n.repeater_neighbours,
|
||||
subtitle: l10n.repeater_neighboursSubtitle,
|
||||
color: Colors.orange,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => NeighboursScreen(
|
||||
repeater: repeater,
|
||||
password: password,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Settings button
|
||||
_buildManagementCard(
|
||||
context,
|
||||
icon: Icons.settings,
|
||||
title: l10n.repeater_settings,
|
||||
subtitle: l10n.repeater_settingsSubtitle,
|
||||
color: Colors.orange,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => RepeaterSettingsScreen(
|
||||
repeater: repeater,
|
||||
password: password,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Settings button
|
||||
_buildManagementCard(
|
||||
context,
|
||||
icon: Icons.settings,
|
||||
title: l10n.repeater_settings,
|
||||
subtitle: l10n.repeater_settingsSubtitle,
|
||||
color: Colors.deepOrange,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => RepeaterSettingsScreen(
|
||||
repeater: repeater,
|
||||
password: password,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -209,10 +255,7 @@ class RepeaterHubScreen extends StatelessWidget {
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -895,7 +895,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
DropdownButtonFormField<int>(
|
||||
value: _bandwidth,
|
||||
initialValue: _bandwidth,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.repeater_bandwidth,
|
||||
border: const OutlineInputBorder(),
|
||||
@@ -917,7 +917,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
DropdownButtonFormField<int>(
|
||||
value: _spreadingFactor,
|
||||
initialValue: _spreadingFactor,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.repeater_spreadingFactor,
|
||||
border: const OutlineInputBorder(),
|
||||
@@ -939,7 +939,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
DropdownButtonFormField<int>(
|
||||
value: _codingRate,
|
||||
initialValue: _codingRate,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.repeater_codingRate,
|
||||
border: const OutlineInputBorder(),
|
||||
|
||||
@@ -8,9 +8,47 @@ import '../widgets/device_tile.dart';
|
||||
import 'contacts_screen.dart';
|
||||
|
||||
/// Screen for scanning and connecting to MeshCore devices
|
||||
class ScannerScreen extends StatelessWidget {
|
||||
class ScannerScreen extends StatefulWidget {
|
||||
const ScannerScreen({super.key});
|
||||
|
||||
@override
|
||||
State<ScannerScreen> createState() => _ScannerScreenState();
|
||||
}
|
||||
|
||||
class _ScannerScreenState extends State<ScannerScreen> {
|
||||
bool _changedNavigation = false;
|
||||
late final VoidCallback _connectionListener;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
|
||||
_connectionListener = () {
|
||||
if (connector.state == MeshCoreConnectionState.disconnected) {
|
||||
_changedNavigation = false;
|
||||
} else if (connector.state == MeshCoreConnectionState.connected && !_changedNavigation) {
|
||||
_changedNavigation = true;
|
||||
if (mounted) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ContactsScreen(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
connector.addListener(_connectionListener);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
connector.removeListener(_connectionListener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -161,15 +199,6 @@ final l10n = context.l10n;
|
||||
? result.device.platformName
|
||||
: result.advertisementData.advName;
|
||||
await connector.connect(result.device, displayName: name);
|
||||
|
||||
if (context.mounted && connector.isConnected) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ContactsScreen(),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
||||
+227
-122
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:meshcore_open/widgets/elements_ui.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
@@ -38,10 +39,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.settings_title),
|
||||
centerTitle: true,
|
||||
),
|
||||
appBar: AppBar(title: Text(l10n.settings_title), centerTitle: true),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: Consumer<MeshCoreConnector>(
|
||||
@@ -68,7 +66,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDeviceInfoCard(BuildContext context, MeshCoreConnector connector) {
|
||||
Widget _buildDeviceInfoCard(
|
||||
BuildContext context,
|
||||
MeshCoreConnector connector,
|
||||
) {
|
||||
final l10n = context.l10n;
|
||||
return Card(
|
||||
child: Padding(
|
||||
@@ -83,21 +84,38 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
const SizedBox(height: 16),
|
||||
_buildInfoRow(l10n.settings_infoName, connector.deviceDisplayName),
|
||||
_buildInfoRow(l10n.settings_infoId, connector.deviceIdLabel),
|
||||
_buildInfoRow(l10n.settings_infoStatus, connector.isConnected ? l10n.common_connected : l10n.common_disconnected),
|
||||
_buildInfoRow(
|
||||
l10n.settings_infoStatus,
|
||||
connector.isConnected
|
||||
? l10n.common_connected
|
||||
: l10n.common_disconnected,
|
||||
),
|
||||
_buildBatteryInfoRow(context, connector),
|
||||
if (connector.selfName != null)
|
||||
_buildInfoRow(l10n.settings_nodeName, connector.selfName!),
|
||||
if (connector.selfPublicKey != null)
|
||||
_buildInfoRow(l10n.settings_infoPublicKey, '${pubKeyToHex(connector.selfPublicKey!).substring(0, 16)}...'),
|
||||
_buildInfoRow(l10n.settings_infoContactsCount, '${connector.contacts.length}'),
|
||||
_buildInfoRow(l10n.settings_infoChannelCount, '${connector.channels.length}'),
|
||||
_buildInfoRow(
|
||||
l10n.settings_infoPublicKey,
|
||||
'${pubKeyToHex(connector.selfPublicKey!).substring(0, 16)}...',
|
||||
),
|
||||
_buildInfoRow(
|
||||
l10n.settings_infoContactsCount,
|
||||
'${connector.contacts.length}',
|
||||
),
|
||||
_buildInfoRow(
|
||||
l10n.settings_infoChannelCount,
|
||||
'${connector.channels.length}',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBatteryInfoRow(BuildContext context, MeshCoreConnector connector) {
|
||||
Widget _buildBatteryInfoRow(
|
||||
BuildContext context,
|
||||
MeshCoreConnector connector,
|
||||
) {
|
||||
final l10n = context.l10n;
|
||||
final percent = connector.batteryPercent;
|
||||
final millivolts = connector.batteryMillivolts;
|
||||
@@ -167,7 +185,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNodeSettingsCard(BuildContext context, MeshCoreConnector connector) {
|
||||
Widget _buildNodeSettingsCard(
|
||||
BuildContext context,
|
||||
MeshCoreConnector connector,
|
||||
) {
|
||||
final l10n = context.l10n;
|
||||
return Card(
|
||||
child: Column(
|
||||
@@ -298,7 +319,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const BleDebugLogScreen()),
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const BleDebugLogScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -311,7 +334,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const AppDebugLogScreen()),
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const AppDebugLogScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -334,20 +359,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
if (leading != null) ...[
|
||||
leading,
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
if (leading != null) ...[leading, const SizedBox(width: 8)],
|
||||
Text(label, style: TextStyle(color: Colors.grey[600])),
|
||||
],
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: valueColor,
|
||||
),
|
||||
style: TextStyle(fontWeight: FontWeight.w500, color: valueColor),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
@@ -413,75 +432,154 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
final l10n = context.l10n;
|
||||
final latController = TextEditingController();
|
||||
final lonController = TextEditingController();
|
||||
final intervalController = TextEditingController();
|
||||
latController.text = connector.selfLatitude?.toStringAsFixed(6) ?? '';
|
||||
lonController.text = connector.selfLongitude?.toStringAsFixed(6) ?? '';
|
||||
|
||||
// Safe access to custom vars - may be null before device responds
|
||||
final customVars = connector.currentCustomVars ?? {};
|
||||
final bool hasGPS = customVars.containsKey("gps");
|
||||
bool isGPSEnabled = customVars["gps"] == "1";
|
||||
|
||||
// Read current interval or default to 900 (15 minutes)
|
||||
final currentInterval = int.tryParse(customVars["gps_interval"] ?? "") ?? 900;
|
||||
intervalController.text = currentInterval.toString();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(l10n.settings_location),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: latController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.settings_latitude,
|
||||
border: const OutlineInputBorder(),
|
||||
builder: (dialogContext) => StatefulBuilder(
|
||||
builder: (context, setDialogState) => AlertDialog(
|
||||
title: Text(l10n.settings_location),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: latController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.settings_latitude,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
signed: true,
|
||||
),
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: lonController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.settings_longitude,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
signed: true,
|
||||
),
|
||||
),
|
||||
if (hasGPS) ...[
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: intervalController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.settings_locationIntervalSec,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: false,
|
||||
signed: false,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FeatureToggleRow(
|
||||
title: l10n.settings_locationGPSEnable,
|
||||
subtitle: l10n.settings_locationGPSEnableSubtitle,
|
||||
value: isGPSEnabled,
|
||||
onChanged: (value) async {
|
||||
setDialogState(() => isGPSEnabled = value);
|
||||
if (value) {
|
||||
await connector.setCustomVar("gps:1");
|
||||
} else {
|
||||
await connector.setCustomVar("gps:0");
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(l10n.common_cancel),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: lonController,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.settings_longitude,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
|
||||
if (hasGPS) {
|
||||
final intervalText = intervalController.text.trim();
|
||||
if (intervalText.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final interval = int.tryParse(intervalText);
|
||||
if (interval == null || interval < 60 || interval >= 86400) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.settings_locationIntervalInvalid),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await connector.setCustomVar("gps_interval:$interval");
|
||||
await connector.refreshDeviceInfo();
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_locationUpdated)),
|
||||
);
|
||||
}
|
||||
|
||||
final latText = latController.text.trim();
|
||||
final lonText = lonController.text.trim();
|
||||
if (latText.isEmpty && lonText.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final currentLat = connector.selfLatitude;
|
||||
final currentLon = connector.selfLongitude;
|
||||
final lat = latText.isNotEmpty
|
||||
? double.tryParse(latText)
|
||||
: currentLat;
|
||||
final lon = lonText.isNotEmpty
|
||||
? double.tryParse(lonText)
|
||||
: currentLon;
|
||||
if (lat == null || lon == null) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_locationBothRequired)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (lat < -90 || lat > 90 || lon < -180 || lon > 180) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_locationInvalid)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await connector.setNodeLocation(lat: lat, lon: lon);
|
||||
await connector.refreshDeviceInfo();
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_locationUpdated)),
|
||||
);
|
||||
},
|
||||
child: Text(l10n.common_save),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(l10n.common_cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
final latText = latController.text.trim();
|
||||
final lonText = lonController.text.trim();
|
||||
if (latText.isEmpty && lonText.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final currentLat = connector.selfLatitude;
|
||||
final currentLon = connector.selfLongitude;
|
||||
final lat = latText.isNotEmpty ? double.tryParse(latText) : currentLat;
|
||||
final lon = lonText.isNotEmpty ? double.tryParse(lonText) : currentLon;
|
||||
if (lat == null || lon == null) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_locationBothRequired)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (lat < -90 || lat > 90 || lon < -180 || lon > 180) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_locationInvalid)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await connector.setNodeLocation(lat: lat, lon: lon);
|
||||
await connector.refreshDeviceInfo();
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_locationUpdated)),
|
||||
);
|
||||
},
|
||||
child: Text(l10n.common_save),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -530,17 +628,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
void _sendAdvert(BuildContext context, MeshCoreConnector connector) {
|
||||
final l10n = context.l10n;
|
||||
connector.sendSelfAdvert(flood: true);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_advertisementSent)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(l10n.settings_advertisementSent)));
|
||||
}
|
||||
|
||||
void _syncTime(BuildContext context, MeshCoreConnector connector) {
|
||||
final l10n = context.l10n;
|
||||
connector.syncTime();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_timeSynchronized)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(l10n.settings_timeSynchronized)));
|
||||
}
|
||||
|
||||
void _confirmReboot(BuildContext context, MeshCoreConnector connector) {
|
||||
@@ -560,7 +658,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Navigator.pop(context);
|
||||
connector.rebootDevice();
|
||||
},
|
||||
child: Text(l10n.common_reboot, style: const TextStyle(color: Colors.orange)),
|
||||
child: Text(
|
||||
l10n.common_reboot,
|
||||
style: const TextStyle(color: Colors.orange),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -572,7 +673,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
showAboutDialog(
|
||||
context: context,
|
||||
applicationName: l10n.appTitle,
|
||||
applicationVersion: _appVersion.isEmpty ? l10n.common_loading : _appVersion,
|
||||
applicationVersion: _appVersion.isEmpty
|
||||
? l10n.common_loading
|
||||
: _appVersion,
|
||||
applicationLegalese: l10n.settings_aboutLegalese,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
@@ -604,7 +707,8 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
|
||||
// Populate with current settings if available
|
||||
if (widget.connector.currentFreqHz != null) {
|
||||
_frequencyController.text = (widget.connector.currentFreqHz! / 1000.0).toStringAsFixed(3);
|
||||
_frequencyController.text = (widget.connector.currentFreqHz! / 1000.0)
|
||||
.toStringAsFixed(3);
|
||||
} else {
|
||||
_frequencyController.text = '915.0';
|
||||
}
|
||||
@@ -670,26 +774,31 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
final txPower = int.tryParse(_txPowerController.text);
|
||||
|
||||
if (freqMHz == null || freqMHz < 300 || freqMHz > 2500) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_frequencyInvalid)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(l10n.settings_frequencyInvalid)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (txPower == null || txPower < 0 || txPower > 22) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_txPowerInvalid)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(l10n.settings_txPowerInvalid)));
|
||||
return;
|
||||
}
|
||||
|
||||
final freqHz = (freqMHz * 1000).round();
|
||||
final bwHz = _bandwidth.hz;
|
||||
final sf = _spreadingFactor.value;
|
||||
final cr = _toDeviceCodingRate(_codingRate.value, widget.connector.currentCr);
|
||||
final cr = _toDeviceCodingRate(
|
||||
_codingRate.value,
|
||||
widget.connector.currentCr,
|
||||
);
|
||||
|
||||
try {
|
||||
await widget.connector.sendFrame(buildSetRadioParamsFrame(freqHz, bwHz, sf, cr));
|
||||
await widget.connector.sendFrame(
|
||||
buildSetRadioParamsFrame(freqHz, bwHz, sf, cr),
|
||||
);
|
||||
await widget.connector.sendFrame(buildSetRadioTxPowerFrame(txPower));
|
||||
await widget.connector.refreshDeviceInfo();
|
||||
|
||||
@@ -727,7 +836,10 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(l10n.settings_presets, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
Text(
|
||||
l10n.settings_presets,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
@@ -762,7 +874,9 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
border: const OutlineInputBorder(),
|
||||
helperText: l10n.settings_frequencyHelper,
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
DropdownButtonFormField<LoRaBandwidth>(
|
||||
@@ -772,10 +886,9 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
items: LoRaBandwidth.values
|
||||
.map((bw) => DropdownMenuItem(
|
||||
value: bw,
|
||||
child: Text(bw.label),
|
||||
))
|
||||
.map(
|
||||
(bw) => DropdownMenuItem(value: bw, child: Text(bw.label)),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value != null) setState(() => _bandwidth = value);
|
||||
@@ -789,10 +902,9 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
items: LoRaSpreadingFactor.values
|
||||
.map((sf) => DropdownMenuItem(
|
||||
value: sf,
|
||||
child: Text(sf.label),
|
||||
))
|
||||
.map(
|
||||
(sf) => DropdownMenuItem(value: sf, child: Text(sf.label)),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value != null) setState(() => _spreadingFactor = value);
|
||||
@@ -806,10 +918,9 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
items: LoRaCodingRate.values
|
||||
.map((cr) => DropdownMenuItem(
|
||||
value: cr,
|
||||
child: Text(cr.label),
|
||||
))
|
||||
.map(
|
||||
(cr) => DropdownMenuItem(value: cr, child: Text(cr.label)),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value != null) setState(() => _codingRate = value);
|
||||
@@ -833,10 +944,7 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(l10n.common_cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: _saveSettings,
|
||||
child: Text(l10n.common_save),
|
||||
),
|
||||
FilledButton(onPressed: _saveSettings, child: Text(l10n.common_save)),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -850,9 +958,6 @@ class _PresetChip extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ActionChip(
|
||||
label: Text(label),
|
||||
onPressed: onTap,
|
||||
);
|
||||
return ActionChip(label: Text(label), onPressed: onTap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -34,7 +31,6 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
static const int _statusResponseBytes =
|
||||
_statusPayloadOffset + _statusStatsSize;
|
||||
Uint8List _tagData = Uint8List(4);
|
||||
int _timeEstment = 0;
|
||||
|
||||
bool _isLoading = false;
|
||||
bool _isLoaded = false;
|
||||
@@ -64,18 +60,19 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
||||
|
||||
if (frame[0] == respCodeSent) {
|
||||
_tagData = frame.sublist(2, 6);
|
||||
_timeEstment = frame.buffer.asByteData().getUint32(6, Endian.little);
|
||||
}
|
||||
|
||||
// Check if it's a binary response
|
||||
if (frame[0] == pushCodeBinaryResponse &&
|
||||
listEquals(frame.sublist(2, 6), _tagData)) {
|
||||
_handleStatusResponse(context, frame.sublist(6));
|
||||
if (!mounted) return;
|
||||
_handleStatusResponse(frame.sublist(6));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _handleStatusResponse(BuildContext context, Uint8List frame) {
|
||||
void _handleStatusResponse(Uint8List frame) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_parsedTelemetry = CayenneLpp.parseByChannel(frame);
|
||||
});
|
||||
|
||||
@@ -16,7 +16,9 @@ class BleDebugLogEntry {
|
||||
|
||||
String get hexPreview {
|
||||
const maxBytes = 64;
|
||||
final bytes = payload.length > maxBytes ? payload.sublist(0, maxBytes) : payload;
|
||||
final bytes = payload.length > maxBytes
|
||||
? payload.sublist(0, maxBytes)
|
||||
: payload;
|
||||
final hex = bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ');
|
||||
return payload.length > maxBytes ? '$hex …' : hex;
|
||||
}
|
||||
@@ -26,14 +28,13 @@ class BleRawLogRxEntry {
|
||||
final DateTime timestamp;
|
||||
final Uint8List payload;
|
||||
|
||||
BleRawLogRxEntry({
|
||||
required this.timestamp,
|
||||
required this.payload,
|
||||
});
|
||||
BleRawLogRxEntry({required this.timestamp, required this.payload});
|
||||
|
||||
String get hexPreview {
|
||||
const maxBytes = 64;
|
||||
final bytes = payload.length > maxBytes ? payload.sublist(0, maxBytes) : payload;
|
||||
final bytes = payload.length > maxBytes
|
||||
? payload.sublist(0, maxBytes)
|
||||
: payload;
|
||||
final hex = bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ');
|
||||
return payload.length > maxBytes ? '$hex …' : hex;
|
||||
}
|
||||
@@ -45,7 +46,8 @@ class BleDebugLogService extends ChangeNotifier {
|
||||
final List<BleRawLogRxEntry> _rawLogRxEntries = [];
|
||||
|
||||
List<BleDebugLogEntry> get entries => List.unmodifiable(_entries);
|
||||
List<BleRawLogRxEntry> get rawLogRxEntries => List.unmodifiable(_rawLogRxEntries);
|
||||
List<BleRawLogRxEntry> get rawLogRxEntries =>
|
||||
List.unmodifiable(_rawLogRxEntries);
|
||||
|
||||
void logFrame(Uint8List frame, {required bool outgoing, String? note}) {
|
||||
if (frame.isEmpty) return;
|
||||
@@ -85,15 +87,32 @@ class BleDebugLogService extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String _describeFrame(int code, Uint8List frame, bool outgoing, String? note) {
|
||||
final label = _codeLabel(code);
|
||||
String _describeFrame(
|
||||
int code,
|
||||
Uint8List frame,
|
||||
bool outgoing,
|
||||
String? note,
|
||||
) {
|
||||
final label = _codeLabel(code, outgoing: outgoing);
|
||||
final prefix = outgoing ? 'TX' : 'RX';
|
||||
final extra = _frameDetail(code, frame);
|
||||
final noteText = note != null ? ' • $note' : '';
|
||||
return '$prefix $label$extra$noteText';
|
||||
}
|
||||
|
||||
String _codeLabel(int code) {
|
||||
String _codeLabel(int code, {required bool outgoing}) {
|
||||
if (outgoing) {
|
||||
return _commandLabel(code) ?? 'CODE_$code';
|
||||
}
|
||||
|
||||
final pushLabel = _pushLabel(code);
|
||||
if (pushLabel != null) return pushLabel;
|
||||
final responseLabel = _responseLabel(code);
|
||||
if (responseLabel != null) return responseLabel;
|
||||
return 'CODE_$code';
|
||||
}
|
||||
|
||||
String? _commandLabel(int code) {
|
||||
switch (code) {
|
||||
case cmdAppStart:
|
||||
return 'CMD_APP_START';
|
||||
@@ -135,6 +154,15 @@ class BleDebugLogService extends ChangeNotifier {
|
||||
return 'CMD_SET_CHANNEL';
|
||||
case cmdGetRadioSettings:
|
||||
return 'CMD_GET_RADIO_SETTINGS';
|
||||
case cmdSetCustomVar:
|
||||
return 'CMD_SET_CUSTOM_VAR';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String? _responseLabel(int code) {
|
||||
switch (code) {
|
||||
case respCodeOk:
|
||||
return 'RESP_CODE_OK';
|
||||
case respCodeErr:
|
||||
@@ -167,6 +195,13 @@ class BleDebugLogService extends ChangeNotifier {
|
||||
return 'RESP_CODE_CHANNEL_INFO';
|
||||
case respCodeRadioSettings:
|
||||
return 'RESP_CODE_RADIO_SETTINGS';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String? _pushLabel(int code) {
|
||||
switch (code) {
|
||||
case pushCodeAdvert:
|
||||
return 'PUSH_CODE_ADVERT';
|
||||
case pushCodePathUpdated:
|
||||
@@ -184,7 +219,7 @@ class BleDebugLogService extends ChangeNotifier {
|
||||
case pushCodeNewAdvert:
|
||||
return 'PUSH_CODE_NEW_ADVERT';
|
||||
default:
|
||||
return 'CODE_$code';
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:crypto/crypto.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../models/message.dart';
|
||||
import '../models/path_selection.dart';
|
||||
import 'storage_service.dart';
|
||||
import 'app_settings_service.dart';
|
||||
import 'app_debug_log_service.dart';
|
||||
|
||||
@@ -36,7 +35,6 @@ class MessageRetryService extends ChangeNotifier {
|
||||
static const int maxRetries = 5;
|
||||
static const int maxAckHistorySize = 100;
|
||||
|
||||
final StorageService _storage;
|
||||
final Map<String, Timer> _timeoutTimers = {};
|
||||
final Map<String, Message> _pendingMessages = {};
|
||||
final Map<String, Contact> _pendingContacts = {};
|
||||
@@ -59,7 +57,7 @@ class MessageRetryService extends ChangeNotifier {
|
||||
AppDebugLogService? _debugLogService;
|
||||
Function(String, PathSelection, bool, int?)? _recordPathResultCallback;
|
||||
|
||||
MessageRetryService(this._storage);
|
||||
MessageRetryService();
|
||||
|
||||
void initialize({
|
||||
required Function(Contact, String, int, int) sendMessageCallback,
|
||||
|
||||
@@ -206,7 +206,7 @@ class NotificationService {
|
||||
final preview = _truncateMessage(message, 30);
|
||||
final body = preview.isEmpty
|
||||
? 'Received new message'
|
||||
: 'Received new message: $preview';
|
||||
: preview;
|
||||
|
||||
await _notifications.show(
|
||||
channelIndex?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import '../models/community.dart';
|
||||
import 'prefs_manager.dart';
|
||||
|
||||
/// Persists communities to local storage using SharedPreferences.
|
||||
///
|
||||
/// Communities are stored as a JSON array under a single key.
|
||||
/// Each community contains its secret K, so this data should
|
||||
/// be considered sensitive (though device encryption handles security).
|
||||
class CommunityStore {
|
||||
static const String _communitiesKey = 'communities_v1';
|
||||
|
||||
/// Load all communities from storage
|
||||
Future<List<Community>> loadCommunities() async {
|
||||
final prefs = PrefsManager.instance;
|
||||
final jsonString = prefs.getString(_communitiesKey);
|
||||
if (jsonString == null || jsonString.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
final jsonList = jsonDecode(jsonString) as List<dynamic>;
|
||||
return jsonList
|
||||
.map((json) => Community.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
} catch (e) {
|
||||
// If JSON is corrupted, return empty list
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// Save all communities to storage
|
||||
Future<void> saveCommunities(List<Community> communities) async {
|
||||
final prefs = PrefsManager.instance;
|
||||
final jsonList = communities.map((c) => c.toJson()).toList();
|
||||
await prefs.setString(_communitiesKey, jsonEncode(jsonList));
|
||||
}
|
||||
|
||||
/// Add a new community
|
||||
Future<void> addCommunity(Community community) async {
|
||||
final communities = await loadCommunities();
|
||||
|
||||
// Check if community with same ID already exists
|
||||
final existingIndex = communities.indexWhere((c) => c.id == community.id);
|
||||
if (existingIndex >= 0) {
|
||||
// Replace existing
|
||||
communities[existingIndex] = community;
|
||||
} else {
|
||||
communities.add(community);
|
||||
}
|
||||
|
||||
await saveCommunities(communities);
|
||||
}
|
||||
|
||||
/// Update an existing community
|
||||
Future<void> updateCommunity(Community community) async {
|
||||
final communities = await loadCommunities();
|
||||
final index = communities.indexWhere((c) => c.id == community.id);
|
||||
if (index >= 0) {
|
||||
communities[index] = community;
|
||||
await saveCommunities(communities);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a community by ID
|
||||
Future<void> removeCommunity(String communityId) async {
|
||||
final communities = await loadCommunities();
|
||||
communities.removeWhere((c) => c.id == communityId);
|
||||
await saveCommunities(communities);
|
||||
}
|
||||
|
||||
/// Get a community by ID
|
||||
Future<Community?> getCommunity(String communityId) async {
|
||||
final communities = await loadCommunities();
|
||||
try {
|
||||
return communities.firstWhere((c) => c.id == communityId);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a community with the same secret already exists
|
||||
/// (to prevent duplicate imports from QR scanning)
|
||||
Future<Community?> findByCommunityId(String cid) async {
|
||||
final communities = await loadCommunities();
|
||||
try {
|
||||
return communities.firstWhere((c) => c.communityId == cid);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a hashtag channel to a community
|
||||
Future<void> addHashtagChannel(
|
||||
String communityId,
|
||||
String hashtag,
|
||||
) async {
|
||||
final community = await getCommunity(communityId);
|
||||
if (community != null) {
|
||||
final updated = community.addHashtagChannel(hashtag);
|
||||
await updateCommunity(updated);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a hashtag channel from a community
|
||||
Future<void> removeHashtagChannel(
|
||||
String communityId,
|
||||
String hashtag,
|
||||
) async {
|
||||
final community = await getCommunity(communityId);
|
||||
if (community != null) {
|
||||
final updated = community.removeHashtagChannel(hashtag);
|
||||
await updateCommunity(updated);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FeatureToggleRow extends StatefulWidget {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final bool value;
|
||||
final bool hasRefreshing;
|
||||
final bool isRefreshing;
|
||||
final ValueChanged<bool>? onChanged;
|
||||
final VoidCallback? onRefresh;
|
||||
final String? refreshTooltip;
|
||||
|
||||
const FeatureToggleRow({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.value,
|
||||
this.hasRefreshing = false,
|
||||
this.isRefreshing = false,
|
||||
this.onChanged,
|
||||
this.onRefresh,
|
||||
this.refreshTooltip,
|
||||
});
|
||||
|
||||
@override
|
||||
State<FeatureToggleRow> createState() => _FeatureToggleRow();
|
||||
}
|
||||
|
||||
class _FeatureToggleRow extends State<FeatureToggleRow> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SwitchListTile(
|
||||
title: Text(widget.title),
|
||||
subtitle: Text(widget.subtitle),
|
||||
value: widget.value,
|
||||
onChanged: widget.onChanged,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
if (widget.hasRefreshing)
|
||||
IconButton(
|
||||
icon: widget.isRefreshing
|
||||
? const SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.refresh, size: 20),
|
||||
onPressed: widget.isRefreshing ? null : widget.onRefresh,
|
||||
tooltip: widget.refreshTooltip,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,14 @@ class GifMessage extends StatefulWidget {
|
||||
final String url;
|
||||
final Color backgroundColor;
|
||||
final Color fallbackTextColor;
|
||||
final double width;
|
||||
final double height;
|
||||
final double maxSize;
|
||||
|
||||
const GifMessage({
|
||||
super.key,
|
||||
required this.url,
|
||||
required this.backgroundColor,
|
||||
required this.fallbackTextColor,
|
||||
this.width = 200,
|
||||
this.height = 140,
|
||||
this.maxSize = 200,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -122,6 +120,28 @@ class _GifMessageState extends State<GifMessage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Calculate display size based on image aspect ratio
|
||||
// Use 4:3 placeholder aspect ratio during loading to minimize layout shifts
|
||||
double displayWidth = widget.maxSize;
|
||||
double displayHeight = widget.maxSize * 0.75;
|
||||
|
||||
if (_image != null) {
|
||||
final imageWidth = _image!.width.toDouble();
|
||||
final imageHeight = _image!.height.toDouble();
|
||||
final aspectRatio = imageWidth / imageHeight;
|
||||
|
||||
// Fit within maxSize, calculating dimensions from aspect ratio
|
||||
if (aspectRatio >= 1) {
|
||||
// Wider than tall: constrain by width
|
||||
displayWidth = widget.maxSize;
|
||||
displayHeight = displayWidth / aspectRatio;
|
||||
} else {
|
||||
// Taller than wide: constrain by height
|
||||
displayHeight = widget.maxSize;
|
||||
displayWidth = displayHeight * aspectRatio;
|
||||
}
|
||||
}
|
||||
|
||||
Widget content;
|
||||
|
||||
if (_error != null) {
|
||||
@@ -151,33 +171,30 @@ class _GifMessageState extends State<GifMessage> {
|
||||
} else {
|
||||
content = RawImage(
|
||||
image: _image,
|
||||
fit: BoxFit.cover,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
fit: BoxFit.contain,
|
||||
width: displayWidth,
|
||||
height: displayHeight,
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: _togglePause,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Container(
|
||||
color: widget.backgroundColor,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
content,
|
||||
if (_isPaused && _image != null)
|
||||
Container(
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
child: const Center(
|
||||
child: Icon(Icons.pause, color: Colors.white70, size: 28),
|
||||
),
|
||||
child: Container(
|
||||
color: widget.backgroundColor,
|
||||
width: displayWidth,
|
||||
height: displayHeight,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
content,
|
||||
if (_isPaused && _image != null)
|
||||
Container(
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
child: const Center(
|
||||
child: Icon(Icons.pause, color: Colors.white70, size: 28),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../helpers/chat_scroll_controller.dart';
|
||||
|
||||
class JumpToBottomButton extends StatelessWidget {
|
||||
final ChatScrollController scrollController;
|
||||
|
||||
const JumpToBottomButton({
|
||||
super.key,
|
||||
required this.scrollController,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: scrollController.showJumpToBottom,
|
||||
builder: (context, show, _) {
|
||||
if (!show) return const SizedBox.shrink();
|
||||
return Positioned(
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
child: FloatingActionButton.small(
|
||||
onPressed: scrollController.jumpToBottom,
|
||||
child: const Icon(Icons.keyboard_arrow_down),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
|
||||
/// A reusable QR code display widget for sharing data.
|
||||
///
|
||||
/// Features:
|
||||
/// - Configurable size and colors
|
||||
/// - Optional logo/icon in center
|
||||
/// - Automatic theming (light/dark mode aware)
|
||||
/// - Title and instructions
|
||||
class QrCodeDisplay extends StatelessWidget {
|
||||
/// The data to encode in the QR code
|
||||
final String data;
|
||||
|
||||
/// Size of the QR code (width and height)
|
||||
final double size;
|
||||
|
||||
/// Optional widget to display in the center (e.g., app logo)
|
||||
final Widget? embeddedImage;
|
||||
|
||||
/// Size of the embedded image (if provided)
|
||||
final double embeddedImageSize;
|
||||
|
||||
/// Title displayed above the QR code
|
||||
final String? title;
|
||||
|
||||
/// Instructions displayed below the QR code
|
||||
final String? instructions;
|
||||
|
||||
/// Background color of the QR code (defaults to white)
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Foreground color of the QR code modules (defaults to black)
|
||||
final Color? foregroundColor;
|
||||
|
||||
/// Padding around the QR code
|
||||
final EdgeInsets padding;
|
||||
|
||||
/// Error correction level
|
||||
final int errorCorrectionLevel;
|
||||
|
||||
const QrCodeDisplay({
|
||||
super.key,
|
||||
required this.data,
|
||||
this.size = 200,
|
||||
this.embeddedImage,
|
||||
this.embeddedImageSize = 50,
|
||||
this.title,
|
||||
this.instructions,
|
||||
this.backgroundColor,
|
||||
this.foregroundColor,
|
||||
this.padding = const EdgeInsets.all(16),
|
||||
this.errorCorrectionLevel = QrErrorCorrectLevel.M,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final isDark = theme.brightness == Brightness.dark;
|
||||
|
||||
// Default colors based on theme
|
||||
final bgColor = backgroundColor ?? Colors.white;
|
||||
final fgColor = foregroundColor ?? Colors.black;
|
||||
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (title != null) ...[
|
||||
Text(
|
||||
title!,
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// QR code container with rounded corners
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: isDark
|
||||
? null
|
||||
: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: embeddedImage != null
|
||||
? _buildQrWithEmbeddedImage(fgColor, bgColor)
|
||||
: _buildSimpleQr(fgColor, bgColor),
|
||||
),
|
||||
|
||||
if (instructions != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
instructions!,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSimpleQr(Color fgColor, Color bgColor) {
|
||||
return QrImageView(
|
||||
data: data,
|
||||
version: QrVersions.auto,
|
||||
size: size,
|
||||
backgroundColor: bgColor,
|
||||
errorCorrectionLevel: errorCorrectionLevel,
|
||||
eyeStyle: QrEyeStyle(
|
||||
eyeShape: QrEyeShape.square,
|
||||
color: fgColor,
|
||||
),
|
||||
dataModuleStyle: QrDataModuleStyle(
|
||||
dataModuleShape: QrDataModuleShape.square,
|
||||
color: fgColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQrWithEmbeddedImage(Color fgColor, Color bgColor) {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
QrImageView(
|
||||
data: data,
|
||||
version: QrVersions.auto,
|
||||
size: size,
|
||||
backgroundColor: bgColor,
|
||||
// Use higher error correction when embedding image
|
||||
errorCorrectionLevel: QrErrorCorrectLevel.H,
|
||||
eyeStyle: QrEyeStyle(
|
||||
eyeShape: QrEyeShape.square,
|
||||
color: fgColor,
|
||||
),
|
||||
dataModuleStyle: QrDataModuleStyle(
|
||||
dataModuleShape: QrDataModuleShape.square,
|
||||
color: fgColor,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: embeddedImageSize,
|
||||
height: embeddedImageSize,
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: embeddedImage,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Dialog to display a QR code for sharing
|
||||
class QrCodeShareDialog extends StatelessWidget {
|
||||
final String data;
|
||||
final String? title;
|
||||
final String? instructions;
|
||||
final Widget? embeddedImage;
|
||||
|
||||
const QrCodeShareDialog({
|
||||
super.key,
|
||||
required this.data,
|
||||
this.title,
|
||||
this.instructions,
|
||||
this.embeddedImage,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
QrCodeDisplay(
|
||||
data: data,
|
||||
size: 250,
|
||||
title: title,
|
||||
instructions: instructions,
|
||||
embeddedImage: embeddedImage,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: FilledButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Done'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Show the dialog
|
||||
static Future<void> show({
|
||||
required BuildContext context,
|
||||
required String data,
|
||||
String? title,
|
||||
String? instructions,
|
||||
Widget? embeddedImage,
|
||||
}) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => QrCodeShareDialog(
|
||||
data: data,
|
||||
title: title,
|
||||
instructions: instructions,
|
||||
embeddedImage: embeddedImage,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
|
||||
/// A reusable QR code scanner widget that can be embedded anywhere.
|
||||
///
|
||||
/// Features:
|
||||
/// - Configurable scan window overlay
|
||||
/// - Flash toggle button
|
||||
/// - Camera switch button (front/back)
|
||||
/// - Customizable callbacks for scan results
|
||||
/// - Optional validation function for QR data
|
||||
/// - Automatic pause when not visible
|
||||
/// - Debouncing to prevent duplicate scans
|
||||
class QrScannerWidget extends StatefulWidget {
|
||||
/// Called when a valid QR code is scanned
|
||||
final void Function(String data) onScanned;
|
||||
|
||||
/// Optional validator - return true if the QR data is valid
|
||||
final bool Function(String data)? validator;
|
||||
|
||||
/// Optional error callback when validation fails
|
||||
final void Function(String data)? onValidationFailed;
|
||||
|
||||
/// Whether to show the flash toggle button
|
||||
final bool showFlashButton;
|
||||
|
||||
/// Whether to show the camera switch button
|
||||
final bool showCameraSwitchButton;
|
||||
|
||||
/// Custom overlay widget (defaults to scan window frame)
|
||||
final Widget? overlay;
|
||||
|
||||
/// Instructions text shown below the scan window
|
||||
final String? instructions;
|
||||
|
||||
/// Whether to continue scanning after first successful scan
|
||||
final bool continuousScanning;
|
||||
|
||||
/// Debounce duration to prevent duplicate scans
|
||||
final Duration debounceDuration;
|
||||
|
||||
const QrScannerWidget({
|
||||
super.key,
|
||||
required this.onScanned,
|
||||
this.validator,
|
||||
this.onValidationFailed,
|
||||
this.showFlashButton = true,
|
||||
this.showCameraSwitchButton = true,
|
||||
this.overlay,
|
||||
this.instructions,
|
||||
this.continuousScanning = false,
|
||||
this.debounceDuration = const Duration(milliseconds: 500),
|
||||
});
|
||||
|
||||
@override
|
||||
State<QrScannerWidget> createState() => _QrScannerWidgetState();
|
||||
}
|
||||
|
||||
class _QrScannerWidgetState extends State<QrScannerWidget>
|
||||
with WidgetsBindingObserver {
|
||||
late MobileScannerController _controller;
|
||||
bool _hasScanned = false;
|
||||
String? _lastScannedData;
|
||||
DateTime? _lastScanTime;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_controller = MobileScannerController(
|
||||
detectionSpeed: DetectionSpeed.normal,
|
||||
facing: CameraFacing.back,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
// Handle app lifecycle changes - pause/resume scanner
|
||||
if (!_controller.value.hasCameraPermission) return;
|
||||
|
||||
switch (state) {
|
||||
case AppLifecycleState.resumed:
|
||||
_controller.start();
|
||||
break;
|
||||
case AppLifecycleState.inactive:
|
||||
case AppLifecycleState.paused:
|
||||
case AppLifecycleState.detached:
|
||||
case AppLifecycleState.hidden:
|
||||
_controller.stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _handleDetection(BarcodeCapture capture) {
|
||||
// Prevent duplicate scans
|
||||
if (_hasScanned && !widget.continuousScanning) return;
|
||||
|
||||
final List<Barcode> barcodes = capture.barcodes;
|
||||
for (final barcode in barcodes) {
|
||||
final String? rawValue = barcode.rawValue;
|
||||
if (rawValue == null || rawValue.isEmpty) continue;
|
||||
|
||||
// Debounce - ignore if same data scanned too quickly
|
||||
final now = DateTime.now();
|
||||
if (_lastScannedData == rawValue &&
|
||||
_lastScanTime != null &&
|
||||
now.difference(_lastScanTime!) < widget.debounceDuration) {
|
||||
continue;
|
||||
}
|
||||
|
||||
_lastScannedData = rawValue;
|
||||
_lastScanTime = now;
|
||||
|
||||
// Validate if validator provided
|
||||
if (widget.validator != null && !widget.validator!(rawValue)) {
|
||||
widget.onValidationFailed?.call(rawValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mark as scanned to prevent duplicates
|
||||
if (!widget.continuousScanning) {
|
||||
setState(() {
|
||||
_hasScanned = true;
|
||||
});
|
||||
_controller.stop();
|
||||
}
|
||||
|
||||
// Notify callback
|
||||
widget.onScanned(rawValue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the scanner to allow scanning again
|
||||
void resetScanner() {
|
||||
setState(() {
|
||||
_hasScanned = false;
|
||||
_lastScannedData = null;
|
||||
_lastScanTime = null;
|
||||
});
|
||||
_controller.start();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
// Scanner view
|
||||
MobileScanner(
|
||||
controller: _controller,
|
||||
onDetect: _handleDetection,
|
||||
errorBuilder: (context, error, child) {
|
||||
return _buildErrorWidget(context, error);
|
||||
},
|
||||
),
|
||||
|
||||
// Overlay
|
||||
widget.overlay ?? _buildDefaultOverlay(context),
|
||||
|
||||
// Control buttons
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: _buildControls(context),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDefaultOverlay(BuildContext context) {
|
||||
return ColorFiltered(
|
||||
colorFilter: ColorFilter.mode(
|
||||
Colors.black.withValues(alpha: 0.5),
|
||||
BlendMode.srcOut,
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.black,
|
||||
backgroundBlendMode: BlendMode.dstOut,
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
height: 250,
|
||||
width: 250,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red, // This color is used for cutout
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
if (widget.instructions != null) ...[
|
||||
const SizedBox(height: 24),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 12,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.7),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
widget.instructions!,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildControls(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (widget.showFlashButton)
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _controller,
|
||||
builder: (context, state, child) {
|
||||
return IconButton.filled(
|
||||
onPressed: () => _controller.toggleTorch(),
|
||||
icon: Icon(
|
||||
state.torchState == TorchState.on
|
||||
? Icons.flash_on
|
||||
: Icons.flash_off,
|
||||
),
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.black54,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (widget.showFlashButton && widget.showCameraSwitchButton)
|
||||
const SizedBox(width: 24),
|
||||
if (widget.showCameraSwitchButton)
|
||||
IconButton.filled(
|
||||
onPressed: () => _controller.switchCamera(),
|
||||
icon: const Icon(Icons.cameraswitch),
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.black54,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildErrorWidget(BuildContext context, MobileScannerException error) {
|
||||
String message;
|
||||
IconData icon;
|
||||
|
||||
switch (error.errorCode) {
|
||||
case MobileScannerErrorCode.permissionDenied:
|
||||
message = 'Camera permission denied.\nPlease enable camera access in settings.';
|
||||
icon = Icons.no_photography;
|
||||
break;
|
||||
case MobileScannerErrorCode.unsupported:
|
||||
message = 'Camera not supported on this device.';
|
||||
icon = Icons.videocam_off;
|
||||
break;
|
||||
default:
|
||||
message = 'Failed to start camera.\n${error.errorDetails?.message ?? ''}';
|
||||
icon = Icons.error_outline;
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 64, color: Colors.grey),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600],
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A simpler scanner overlay with just corner brackets
|
||||
class ScannerCornerOverlay extends StatelessWidget {
|
||||
final double scanWindowSize;
|
||||
final Color borderColor;
|
||||
final double borderWidth;
|
||||
final double cornerLength;
|
||||
|
||||
const ScannerCornerOverlay({
|
||||
super.key,
|
||||
this.scanWindowSize = 250,
|
||||
this.borderColor = Colors.white,
|
||||
this.borderWidth = 3,
|
||||
this.cornerLength = 30,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: scanWindowSize,
|
||||
height: scanWindowSize,
|
||||
child: CustomPaint(
|
||||
painter: _CornerPainter(
|
||||
color: borderColor,
|
||||
strokeWidth: borderWidth,
|
||||
cornerLength: cornerLength,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CornerPainter extends CustomPainter {
|
||||
final Color color;
|
||||
final double strokeWidth;
|
||||
final double cornerLength;
|
||||
|
||||
_CornerPainter({
|
||||
required this.color,
|
||||
required this.strokeWidth,
|
||||
required this.cornerLength,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = color
|
||||
..strokeWidth = strokeWidth
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
||||
final path = Path();
|
||||
|
||||
// Top-left corner
|
||||
path.moveTo(0, cornerLength);
|
||||
path.lineTo(0, 0);
|
||||
path.lineTo(cornerLength, 0);
|
||||
|
||||
// Top-right corner
|
||||
path.moveTo(size.width - cornerLength, 0);
|
||||
path.lineTo(size.width, 0);
|
||||
path.lineTo(size.width, cornerLength);
|
||||
|
||||
// Bottom-right corner
|
||||
path.moveTo(size.width, size.height - cornerLength);
|
||||
path.lineTo(size.width, size.height);
|
||||
path.lineTo(size.width - cornerLength, size.height);
|
||||
|
||||
// Bottom-left corner
|
||||
path.moveTo(cornerLength, size.height);
|
||||
path.lineTo(0, size.height);
|
||||
path.lineTo(0, size.height - cornerLength);
|
||||
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
@@ -31,6 +31,7 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
|
||||
bool _savePassword = false;
|
||||
bool _isLoading = true;
|
||||
bool _obscurePassword = true;
|
||||
String? _loginError;
|
||||
late MeshCoreConnector _connector;
|
||||
int _currentAttempt = 0;
|
||||
static const int _maxAttempts = 5;
|
||||
@@ -79,6 +80,7 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
|
||||
setState(() {
|
||||
_isLoggingIn = true;
|
||||
_currentAttempt = 0;
|
||||
_loginError = null;
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -134,7 +136,7 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
|
||||
'Login failed for ${repeater.name}',
|
||||
tag: 'RepeaterLogin',
|
||||
);
|
||||
throw Exception('Wrong password or node is unreachable');
|
||||
break;
|
||||
}
|
||||
appLogger.warn(
|
||||
'Login attempt ${attempt + 1} timed out after ${timeoutSeconds}s',
|
||||
@@ -156,7 +158,13 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
|
||||
}
|
||||
|
||||
if (loginResult != true) {
|
||||
throw Exception('Wrong password or node is unreachable');
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoggingIn = false;
|
||||
_loginError = context.l10n.login_failedMessage;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If we got a response, login succeeded
|
||||
@@ -182,13 +190,8 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoggingIn = false;
|
||||
_loginError = context.l10n.login_failedMessage;
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.login_failed(e.toString())),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,15 +264,35 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.login_repeaterDescription,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (_loginError != null) ...[
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Icons.error, size: 18, color: Theme.of(context).colorScheme.error),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_loginError!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
TextField(
|
||||
controller: _passwordController,
|
||||
obscureText: _obscurePassword,
|
||||
@@ -291,6 +314,13 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
|
||||
},
|
||||
),
|
||||
),
|
||||
onChanged: (_) {
|
||||
if (_loginError != null && mounted) {
|
||||
setState(() {
|
||||
_loginError = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
onSubmitted: (_) => _handleLogin(),
|
||||
autofocus: _passwordController.text.isEmpty,
|
||||
),
|
||||
@@ -382,6 +412,7 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
List<double> getSNRfromSF(int spreadingFactor) {
|
||||
switch (spreadingFactor) {
|
||||
case 7:
|
||||
return [4.0, -2.0, -4.0, -6.0];
|
||||
case 8:
|
||||
return [4.0, -4.0, -6.0, -8.0];
|
||||
case 9:
|
||||
return [4.0, -6.0, -8.0, -10.0];
|
||||
case 10:
|
||||
return [4.0, -8.0, -10.0, -13.0];
|
||||
case 11:
|
||||
return [4.0, -10.0, -12.5, -15.0];
|
||||
case 12:
|
||||
return [4.0, -12.5, -15.0, -18.0];
|
||||
default:
|
||||
return []; // Or throw Exception('Invalid SF: $spreadingFactor');
|
||||
}
|
||||
}
|
||||
|
||||
class SNRIcon extends StatelessWidget {
|
||||
final double snr;
|
||||
final List<double> snrLevels;
|
||||
|
||||
const SNRIcon({
|
||||
super.key,
|
||||
required this.snr,
|
||||
this.snrLevels = const [4.0, -2.0, -4.0, -6.0],
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
IconData icon;
|
||||
Color color;
|
||||
|
||||
if (snr >= snrLevels[0]) {
|
||||
icon = Icons.signal_cellular_alt;
|
||||
color = Colors.green;
|
||||
} else if (snr >= snrLevels[1]) {
|
||||
icon = Icons.signal_cellular_alt;
|
||||
color = Colors.lightGreen;
|
||||
} else if (snr >= snrLevels[2]) {
|
||||
icon = Icons.signal_cellular_alt;
|
||||
color = Colors.yellow;
|
||||
} else if (snr >= snrLevels[3]) {
|
||||
icon = Icons.signal_cellular_alt_2_bar;
|
||||
color = Colors.orange;
|
||||
} else {
|
||||
icon = Icons.signal_cellular_alt_1_bar;
|
||||
color = Colors.red;
|
||||
}
|
||||
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, color: color),
|
||||
Text('$snr dB', style: TextStyle(fontSize: 10, color: color)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
||||
@@ -7,18 +7,22 @@ import Foundation
|
||||
|
||||
import flutter_blue_plus_darwin
|
||||
import flutter_local_notifications
|
||||
import mobile_scanner
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
import url_launcher_macos
|
||||
import wakelock_plus
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin"))
|
||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
platform :osx, '10.15'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_macos_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
|
||||
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_macos_build_settings(target)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,74 @@
|
||||
PODS:
|
||||
- flutter_blue_plus_darwin (0.0.2):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- FlutterMacOS
|
||||
- FlutterMacOS (1.0.0)
|
||||
- mobile_scanner (6.0.2):
|
||||
- FlutterMacOS
|
||||
- package_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- wakelock_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- flutter_blue_plus_darwin (from `Flutter/ephemeral/.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
|
||||
- flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos`)
|
||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
flutter_blue_plus_darwin:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_blue_plus_darwin/darwin
|
||||
flutter_local_notifications:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos
|
||||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
mobile_scanner:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos
|
||||
package_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
||||
path_provider_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||
shared_preferences_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||
sqflite_darwin:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
wakelock_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
|
||||
flutter_local_notifications: 13862b132e32eb858dea558a86d45d08daeacfe7
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
mobile_scanner: 0e365ed56cad24f28c0fd858ca04edefb40dfac3
|
||||
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
|
||||
wakelock_plus: 917609be14d812ddd9e9528876538b2263aaa03b
|
||||
|
||||
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
@@ -27,6 +27,8 @@
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||
99C5B380294D2DE19A818101 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B665683D805EE21638F484F2 /* Pods_RunnerTests.framework */; };
|
||||
D7DDCBD47F2955423D77927D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F985DDB6BE5BEB6B545DE9A /* Pods_Runner.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -60,11 +62,13 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
00F1FE94A1827B8A00BD3DB9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
0F985DDB6BE5BEB6B545DE9A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||
33CC10ED2044A3C60003C045 /* meshcore_open.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "meshcore_open.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10ED2044A3C60003C045 /* meshcore_open.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = meshcore_open.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
@@ -76,8 +80,14 @@
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
||||
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||
4172BCCDFD1E1404F7155426 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
96DE804777D5630B2C6952B5 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
B665683D805EE21638F484F2 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BEFF4DDC60AFB628205F8E82 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
D99E941424F19B7B9AA1B968 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
EA5A89F8C49904B995EFAA24 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -85,6 +95,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
99C5B380294D2DE19A818101 /* Pods_RunnerTests.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -92,6 +103,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D7DDCBD47F2955423D77927D /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -125,6 +137,7 @@
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||
33CC10EE2044A3C60003C045 /* Products */,
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||
73DBB8BFF247FD65EEC878CC /* Pods */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -172,9 +185,25 @@
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
73DBB8BFF247FD65EEC878CC /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BEFF4DDC60AFB628205F8E82 /* Pods-Runner.debug.xcconfig */,
|
||||
4172BCCDFD1E1404F7155426 /* Pods-Runner.release.xcconfig */,
|
||||
00F1FE94A1827B8A00BD3DB9 /* Pods-Runner.profile.xcconfig */,
|
||||
96DE804777D5630B2C6952B5 /* Pods-RunnerTests.debug.xcconfig */,
|
||||
EA5A89F8C49904B995EFAA24 /* Pods-RunnerTests.release.xcconfig */,
|
||||
D99E941424F19B7B9AA1B968 /* Pods-RunnerTests.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0F985DDB6BE5BEB6B545DE9A /* Pods_Runner.framework */,
|
||||
B665683D805EE21638F484F2 /* Pods_RunnerTests.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -186,6 +215,7 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
7DEC542F9A4811B2EEDCB8C1 /* [CP] Check Pods Manifest.lock */,
|
||||
331C80D1294CF70F00263BE5 /* Sources */,
|
||||
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||
331C80D3294CF70F00263BE5 /* Resources */,
|
||||
@@ -204,11 +234,13 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
79D67F01E273245A9C69C0B6 /* [CP] Check Pods Manifest.lock */,
|
||||
33CC10E92044A3C60003C045 /* Sources */,
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||
33CC10EB2044A3C60003C045 /* Resources */,
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||
306490712F2EAA29CA421662 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -291,6 +323,23 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
306490712F2EAA29CA421662 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
@@ -329,6 +378,50 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||
};
|
||||
79D67F01E273245A9C69C0B6 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
7DEC542F9A4811B2EEDCB8C1 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -380,6 +473,7 @@
|
||||
/* Begin XCBuildConfiguration section */
|
||||
331C80DB294CF71000263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 96DE804777D5630B2C6952B5 /* Pods-RunnerTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@@ -394,6 +488,7 @@
|
||||
};
|
||||
331C80DC294CF71000263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = EA5A89F8C49904B995EFAA24 /* Pods-RunnerTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@@ -408,6 +503,7 @@
|
||||
};
|
||||
331C80DD294CF71000263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D99E941424F19B7B9AA1B968 /* Pods-RunnerTests.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
|
||||
@@ -4,4 +4,7 @@
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
@@ -12,5 +12,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.device.bluetooth</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -30,5 +30,7 @@
|
||||
<string>NSApplication</string>
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>MeshCore needs Bluetooth to communicate with LoRa mesh devices</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>This app uses the camera to scan QR codes for joining communities.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -8,5 +8,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.device.bluetooth</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
+106
-2
@@ -262,6 +262,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.1"
|
||||
flutter_linkify:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_linkify
|
||||
sha256: "74669e06a8f358fee4512b4320c0b80e51cffc496607931de68d28f099254073"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -397,6 +405,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
linkify:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: linkify
|
||||
sha256: "4139ea77f4651ab9c315b577da2dd108d9aa0bd84b5d03d33323f1970c645832"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -453,6 +469,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
mobile_scanner:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: mobile_scanner
|
||||
sha256: "0b466a0a8a211b366c2e87f3345715faef9b6011c7147556ad22f37de6ba3173"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.11"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -605,6 +629,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.5+1"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: qr
|
||||
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
qr_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: qr_flutter
|
||||
sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -794,6 +834,70 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.2"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.28"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.6"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.5"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.5"
|
||||
uuid:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -883,5 +987,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.9.2 <4.0.0"
|
||||
flutter: ">=3.35.0"
|
||||
dart: ">=3.10.0 <4.0.0"
|
||||
flutter: ">=3.38.0"
|
||||
|
||||
+8
-1
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 0.3.0+3
|
||||
version: 5.0.0+5
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.2
|
||||
@@ -53,6 +53,10 @@ dependencies:
|
||||
wakelock_plus: ^1.2.8
|
||||
characters: ^1.4.0
|
||||
package_info_plus: ^8.0.0
|
||||
mobile_scanner: ^6.0.0 # QR/barcode scanning
|
||||
qr_flutter: ^4.1.0 # QR code generation
|
||||
url_launcher: ^6.3.0 # Launch URLs in system browser
|
||||
flutter_linkify: ^6.0.0 # Auto-detect and linkify URLs in text
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -78,6 +82,9 @@ flutter:
|
||||
# the material Icons class.
|
||||
uses-material-design: true
|
||||
|
||||
assets:
|
||||
- assets/images/
|
||||
|
||||
flutter_launcher_icons:
|
||||
android: true
|
||||
ios: true
|
||||
|
||||
+190
-19
@@ -10,6 +10,7 @@ Translates ARB/JSON localization values using a local Ollama model, while:
|
||||
- printing progress as it runs
|
||||
|
||||
Usage:
|
||||
# Translate all strings:
|
||||
python translate_arb_with_ollama.py \
|
||||
--in /home/zjs81/Desktop/meshcore-open/lib/l10n/app_en.arb \
|
||||
--out /home/zjs81/Desktop/meshcore-open/lib/l10n/app_es.arb \
|
||||
@@ -17,12 +18,28 @@ Usage:
|
||||
--model ministral-3:latest \
|
||||
--temperature 0 \
|
||||
--concurrency 4
|
||||
|
||||
# Translate only missing/untranslated strings:
|
||||
python translate_arb_with_ollama.py \
|
||||
--in /home/zjs81/Desktop/meshcore-open/lib/l10n/app_en.arb \
|
||||
--out /home/zjs81/Desktop/meshcore-open/lib/l10n/app_es.arb \
|
||||
--to-locale es \
|
||||
--missing-only \
|
||||
--model ministral-3:latest
|
||||
|
||||
# Translate all locales (missing strings only):
|
||||
python translate_arb_with_ollama.py \
|
||||
--in /home/zjs81/Desktop/meshcore-open/lib/l10n/app_en.arb \
|
||||
--l10n-dir /home/zjs81/Desktop/meshcore-open/lib/l10n \
|
||||
--missing-only \
|
||||
--model ministral-3:latest
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
@@ -448,11 +465,48 @@ def fmt_duration(seconds: float) -> str:
|
||||
return f"{h}h {m2}m"
|
||||
|
||||
|
||||
def find_missing_keys(source_data: Dict[str, Any], target_data: Dict[str, Any]) -> List[str]:
|
||||
"""Find keys that are in source but not in target (excluding metadata keys)."""
|
||||
missing = []
|
||||
for key in source_data:
|
||||
if key == "@@locale":
|
||||
continue
|
||||
if key.startswith("@"):
|
||||
continue
|
||||
if key not in target_data:
|
||||
missing.append(key)
|
||||
return missing
|
||||
|
||||
|
||||
def get_all_locale_files(l10n_dir: str, template_file: str) -> List[Tuple[str, str]]:
|
||||
"""Find all locale .arb files in the directory, excluding the template.
|
||||
|
||||
Returns list of (locale_code, file_path) tuples.
|
||||
"""
|
||||
locales = []
|
||||
template_basename = os.path.basename(template_file)
|
||||
|
||||
for filename in os.listdir(l10n_dir):
|
||||
if not filename.endswith('.arb'):
|
||||
continue
|
||||
if filename == template_basename:
|
||||
continue
|
||||
# Extract locale from filename like app_es.arb -> es
|
||||
if filename.startswith('app_') and filename.endswith('.arb'):
|
||||
locale = filename[4:-4] # Remove 'app_' prefix and '.arb' suffix
|
||||
filepath = os.path.join(l10n_dir, filename)
|
||||
locales.append((locale, filepath))
|
||||
|
||||
return sorted(locales)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--in", dest="in_path", required=True, help="Input .arb/.json file path")
|
||||
ap.add_argument("--out", dest="out_path", required=True, help="Output .arb/.json file path")
|
||||
ap.add_argument("--to-locale", required=True, help="Target locale code, e.g. es, fr, de")
|
||||
ap.add_argument("--in", dest="in_path", required=True, help="Input .arb/.json file path (source/template)")
|
||||
ap.add_argument("--out", dest="out_path", default=None, help="Output .arb/.json file path (required unless using --l10n-dir)")
|
||||
ap.add_argument("--to-locale", default=None, help="Target locale code, e.g. es, fr, de (required unless using --l10n-dir)")
|
||||
ap.add_argument("--l10n-dir", default=None, help="Directory containing locale .arb files. When set, translates all locales.")
|
||||
ap.add_argument("--missing-only", action="store_true", help="Only translate keys missing from target file")
|
||||
ap.add_argument("--target-lang", default=None, help="Target language name for the model, e.g. Spanish (defaults from locale)")
|
||||
ap.add_argument("--model", default="gemma3:4b", help="Ollama model name")
|
||||
ap.add_argument("--fallback-model", default=None, help="Larger model to use for low-confidence translations")
|
||||
@@ -504,19 +558,119 @@ def main() -> int:
|
||||
"vi": "Vietnamese",
|
||||
"id": "Indonesian",
|
||||
}
|
||||
target_lang = args.target_lang or locale_map.get(args.to_locale, args.to_locale)
|
||||
|
||||
# Read source/template file
|
||||
try:
|
||||
with open(args.in_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
source_data = json.load(f)
|
||||
except Exception as e:
|
||||
print(f"Failed to read input: {e}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
if not isinstance(data, dict):
|
||||
if not isinstance(source_data, dict):
|
||||
print("Input JSON must be an object at top-level.", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
# If --l10n-dir is provided, process all locale files
|
||||
if args.l10n_dir:
|
||||
locales = get_all_locale_files(args.l10n_dir, args.in_path)
|
||||
if not locales:
|
||||
print(f"No locale files found in {args.l10n_dir}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"Found {len(locales)} locale file(s) to process")
|
||||
|
||||
total_translated = 0
|
||||
for locale_code, locale_path in locales:
|
||||
target_lang = locale_map.get(locale_code, locale_code)
|
||||
|
||||
# Read existing target file
|
||||
try:
|
||||
with open(locale_path, "r", encoding="utf-8") as f:
|
||||
target_data = json.load(f)
|
||||
except Exception as e:
|
||||
print(f" [{locale_code}] Failed to read {locale_path}: {e}")
|
||||
continue
|
||||
|
||||
if args.missing_only:
|
||||
missing_keys = find_missing_keys(source_data, target_data)
|
||||
if not missing_keys:
|
||||
print(f" [{locale_code}] No missing keys")
|
||||
continue
|
||||
print(f" [{locale_code}] {len(missing_keys)} missing key(s): {', '.join(missing_keys[:5])}{'...' if len(missing_keys) > 5 else ''}")
|
||||
else:
|
||||
missing_keys = None
|
||||
|
||||
# Run translation for this locale
|
||||
result = translate_locale(
|
||||
source_data=source_data,
|
||||
target_data=target_data,
|
||||
target_locale=locale_code,
|
||||
target_lang=target_lang,
|
||||
out_path=locale_path,
|
||||
args=args,
|
||||
locale_map=locale_map,
|
||||
missing_keys=missing_keys,
|
||||
)
|
||||
total_translated += result
|
||||
|
||||
print(f"\nTotal: {total_translated} string(s) translated across {len(locales)} locale(s)")
|
||||
return 0
|
||||
|
||||
# Single locale mode - validate required args
|
||||
if not args.out_path:
|
||||
print("--out is required when not using --l10n-dir", file=sys.stderr)
|
||||
return 1
|
||||
if not args.to_locale:
|
||||
print("--to-locale is required when not using --l10n-dir", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
target_lang = args.target_lang or locale_map.get(args.to_locale, args.to_locale)
|
||||
|
||||
# Read existing target file if --missing-only and file exists
|
||||
target_data: Dict[str, Any] = {}
|
||||
missing_keys: Optional[List[str]] = None
|
||||
if args.missing_only:
|
||||
if os.path.exists(args.out_path):
|
||||
try:
|
||||
with open(args.out_path, "r", encoding="utf-8") as f:
|
||||
target_data = json.load(f)
|
||||
missing_keys = find_missing_keys(source_data, target_data)
|
||||
if not missing_keys:
|
||||
print(f"No missing keys in {args.out_path}")
|
||||
return 0
|
||||
print(f"Found {len(missing_keys)} missing key(s) to translate")
|
||||
except Exception as e:
|
||||
print(f"Failed to read target file: {e}", file=sys.stderr)
|
||||
return 2
|
||||
else:
|
||||
print(f"Target file {args.out_path} does not exist. Will translate all strings.")
|
||||
|
||||
result = translate_locale(
|
||||
source_data=source_data,
|
||||
target_data=target_data,
|
||||
target_locale=args.to_locale,
|
||||
target_lang=target_lang,
|
||||
out_path=args.out_path,
|
||||
args=args,
|
||||
locale_map=locale_map,
|
||||
missing_keys=missing_keys,
|
||||
)
|
||||
return 0 if result >= 0 else 1
|
||||
|
||||
|
||||
def translate_locale(
|
||||
source_data: Dict[str, Any],
|
||||
target_data: Dict[str, Any],
|
||||
target_locale: str,
|
||||
target_lang: str,
|
||||
out_path: str,
|
||||
args,
|
||||
locale_map: Dict[str, str],
|
||||
missing_keys: Optional[List[str]] = None,
|
||||
) -> int:
|
||||
"""Translate a single locale. Returns number of strings translated."""
|
||||
|
||||
cfg = OllamaConfig(
|
||||
host=args.host,
|
||||
model=args.model,
|
||||
@@ -540,17 +694,34 @@ def main() -> int:
|
||||
top_p=args.top_p,
|
||||
)
|
||||
|
||||
out_data: Dict[str, Any] = dict(data)
|
||||
out_data["@@locale"] = args.to_locale
|
||||
# Start with target data (preserves existing translations) or source data
|
||||
if target_data:
|
||||
out_data: Dict[str, Any] = dict(target_data)
|
||||
else:
|
||||
out_data: Dict[str, Any] = dict(source_data)
|
||||
out_data["@@locale"] = target_locale
|
||||
|
||||
items: List[Tuple[str, str]] = [(k, v) for k, v in data.items() if is_translatable_entry(k, v)]
|
||||
# Build list of items to translate
|
||||
if missing_keys is not None:
|
||||
# Only translate missing keys
|
||||
items: List[Tuple[str, str]] = [
|
||||
(k, source_data[k]) for k in missing_keys
|
||||
if is_translatable_entry(k, source_data.get(k))
|
||||
]
|
||||
# Also copy over any metadata keys for missing items
|
||||
for key in missing_keys:
|
||||
meta_key = f"@{key}"
|
||||
if meta_key in source_data:
|
||||
out_data[meta_key] = source_data[meta_key]
|
||||
else:
|
||||
items: List[Tuple[str, str]] = [(k, v) for k, v in source_data.items() if is_translatable_entry(k, v)]
|
||||
|
||||
# Apply manual translations first
|
||||
manual_count = 0
|
||||
items_to_translate: List[Tuple[str, str]] = []
|
||||
for k, v in items:
|
||||
if k in MANUAL_TRANSLATIONS and args.to_locale in MANUAL_TRANSLATIONS[k]:
|
||||
out_data[k] = MANUAL_TRANSLATIONS[k][args.to_locale]
|
||||
if k in MANUAL_TRANSLATIONS and target_locale in MANUAL_TRANSLATIONS[k]:
|
||||
out_data[k] = MANUAL_TRANSLATIONS[k][target_locale]
|
||||
manual_count += 1
|
||||
else:
|
||||
items_to_translate.append((k, v))
|
||||
@@ -560,8 +731,8 @@ def main() -> int:
|
||||
|
||||
total = len(items_to_translate)
|
||||
if total == 0 and manual_count == 0:
|
||||
print("No translatable string entries found (excluding @@locale and @metadata).", file=sys.stderr)
|
||||
return 1
|
||||
print("No translatable string entries found (excluding @@locale and @metadata).")
|
||||
return 0
|
||||
|
||||
if total == 0:
|
||||
print("All strings handled by manual translations.")
|
||||
@@ -705,19 +876,19 @@ def main() -> int:
|
||||
|
||||
if args.dry_run:
|
||||
print("Dry run: not writing output file.")
|
||||
return 0
|
||||
return translated_ok
|
||||
|
||||
try:
|
||||
with open(args.out_path, "w", encoding="utf-8") as f:
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
json.dump(out_data, f, ensure_ascii=False, indent=2)
|
||||
f.write("\n")
|
||||
except Exception as e:
|
||||
print(f"Failed to write output: {e}", file=sys.stderr)
|
||||
return 2
|
||||
return -1
|
||||
|
||||
print(f"Wrote: {args.out_path}")
|
||||
return 0
|
||||
print(f"Wrote: {out_path}")
|
||||
return translated_ok
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -7,8 +7,11 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <flutter_blue_plus_winrt/flutter_blue_plus_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FlutterBluePlusPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterBluePlusPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_blue_plus_winrt
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
Reference in New Issue
Block a user