From 723bf7293c0765510096dbf0a29a324493d62f0d Mon Sep 17 00:00:00 2001 From: ericz Date: Tue, 17 Mar 2026 21:56:42 +0100 Subject: [PATCH 01/36] location aware channel_message_path --- .gitignore | 3 +- lib/screens/channel_message_path_screen.dart | 93 +++++++++++--------- 2 files changed, 54 insertions(+), 42 deletions(-) diff --git a/.gitignore b/.gitignore index a312737..779856c 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,7 @@ secrets.dart .DS_Store .AppleDouble .LSOverride +macos/Flutter/GeneratedPluginRegistrant.swift # iOS **/ios/Pods/ @@ -85,4 +86,4 @@ keystore.properties .vscode/settings.json # Cloudflare Wrangler -.wrangler \ No newline at end of file +.wrangler diff --git a/lib/screens/channel_message_path_screen.dart b/lib/screens/channel_message_path_screen.dart index 747c2bf..e2c5b49 100644 --- a/lib/screens/channel_message_path_screen.dart +++ b/lib/screens/channel_message_path_screen.dart @@ -40,8 +40,7 @@ class ChannelMessagePathScreen extends StatelessWidget { final primaryPath = !channelMessage && !message.isOutgoing ? Uint8List.fromList(primaryPathTmp.reversed.toList()) : primaryPathTmp; - final contacts = connector.allContacts; - final hops = _buildPathHops(primaryPath, contacts, l10n); + final hops = _buildPathHops(primaryPath, connector, l10n); final hasHopDetails = primaryPath.isNotEmpty; final observedLabel = _formatObservedHops( primaryPath.length, @@ -365,8 +364,7 @@ class _ChannelMessagePathMapScreenState : selectedPathTmp; final selectedIndex = _indexForPath(selectedPath, observedPaths); - final contacts = connector.allContacts; - final hops = _buildPathHops(selectedPath, contacts, context.l10n); + final hops = _buildPathHops(selectedPath, connector, context.l10n); final points = []; @@ -787,17 +785,62 @@ class _ObservedPath { List<_PathHop> _buildPathHops( Uint8List pathBytes, - List contacts, + MeshCoreConnector connector, AppLocalizations l10n, ) { + if (pathBytes.isEmpty) return const []; + final candidatesByPrefix = >{}; + for (final contact in connector.allContacts) { + if (contact.publicKey.isEmpty) continue; + if (contact.type != advTypeRepeater && contact.type != advTypeRoom) { + continue; + } + final prefix = contact.publicKey.first; + candidatesByPrefix.putIfAbsent(prefix, () => []).add(contact); + } + for (final candidates in candidatesByPrefix.values) { + candidates.sort((a, b) => b.lastSeen.compareTo(a.lastSeen)); + } + final startPoint = + (connector.selfLatitude != null && connector.selfLongitude != null) + ? LatLng(connector.selfLatitude!, connector.selfLongitude!) + : null; + var previousPosition = startPoint; + final distance = Distance(); + final hops = <_PathHop>[]; for (var i = 0; i < pathBytes.length; i++) { - final prefix = pathBytes[i]; - final contact = _matchContactForPrefix(contacts, prefix); + final searchPoint = i == 0 ? startPoint : previousPosition; + final candidates = candidatesByPrefix[pathBytes[i]]; + Contact? contact; + if (candidates != null && candidates.isNotEmpty) { + var bestIndex = 0; + if (searchPoint != null) { + var bestDistance = double.infinity; + for (var j = 0; j < candidates.length; j++) { + final candidate = candidates[j]; + if (!candidate.hasLocation) continue; + final currentDistance = distance( + searchPoint, + LatLng(candidate.latitude!, candidate.longitude!), + ); + if (currentDistance < bestDistance) { + bestDistance = currentDistance; + bestIndex = j; + } + } + } + contact = candidates.removeAt(bestIndex); + if (candidates.isEmpty) { + candidatesByPrefix.remove(pathBytes[i]); + } + } + + previousPosition = _resolvePosition(contact); hops.add( _PathHop( index: i + 1, - prefix: prefix, + prefix: pathBytes[i], contact: contact, position: _resolvePosition(contact), l10n: l10n, @@ -807,44 +850,12 @@ List<_PathHop> _buildPathHops( return hops; } -Contact? _matchContactForPrefix(List contacts, int prefix) { - final matches = contacts - .where( - (contact) => - (contact.type == advTypeRepeater || contact.type == advTypeRoom) && - contact.publicKey.isNotEmpty && - contact.publicKey[0] == prefix, - ) - .toList(); - if (matches.isEmpty) return null; - - Contact? pickWhere(bool Function(Contact) predicate) { - for (final contact in matches) { - if (predicate(contact)) return contact; - } - return null; - } - - return pickWhere((c) => c.type == advTypeRepeater && _hasValidLocation(c)) ?? - pickWhere((c) => c.type == advTypeRepeater) ?? - pickWhere(_hasValidLocation) ?? - matches.first; -} - LatLng? _resolvePosition(Contact? contact) { if (contact == null) return null; - if (!_hasValidLocation(contact)) return null; + if (!contact.hasLocation) return null; return LatLng(contact.latitude!, contact.longitude!); } -bool _hasValidLocation(Contact contact) { - final lat = contact.latitude; - final lon = contact.longitude; - if (lat == null || lon == null) return false; - if (lat == 0 && lon == 0) return false; - return true; -} - String _formatPrefix(int prefix) { return prefix.toRadixString(16).padLeft(2, '0').toUpperCase(); } From d2df2b0beda0f3826bd6d712be77e474e87b8066 Mon Sep 17 00:00:00 2001 From: ericszimmermann Date: Tue, 17 Mar 2026 22:23:23 +0100 Subject: [PATCH 02/36] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- lib/screens/channel_message_path_screen.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/screens/channel_message_path_screen.dart b/lib/screens/channel_message_path_screen.dart index e2c5b49..d2f8d81 100644 --- a/lib/screens/channel_message_path_screen.dart +++ b/lib/screens/channel_message_path_screen.dart @@ -836,13 +836,16 @@ List<_PathHop> _buildPathHops( } } - previousPosition = _resolvePosition(contact); + final resolvedPosition = _resolvePosition(contact); + if (resolvedPosition != null) { + previousPosition = resolvedPosition; + } hops.add( _PathHop( index: i + 1, prefix: pathBytes[i], contact: contact, - position: _resolvePosition(contact), + position: resolvedPosition, l10n: l10n, ), ); From 11cb14a9256b53b43a937b55fe5810dbc2d10163 Mon Sep 17 00:00:00 2001 From: ericz Date: Tue, 17 Mar 2026 23:22:23 +0100 Subject: [PATCH 03/36] focus on hop if you click on one in the legend. --- lib/screens/channel_message_path_screen.dart | 29 +++++++++++++++++++ macos/Flutter/GeneratedPluginRegistrant.swift | 2 ++ 2 files changed, 31 insertions(+) diff --git a/lib/screens/channel_message_path_screen.dart b/lib/screens/channel_message_path_screen.dart index d2f8d81..0092f22 100644 --- a/lib/screens/channel_message_path_screen.dart +++ b/lib/screens/channel_message_path_screen.dart @@ -302,10 +302,12 @@ class _ChannelMessagePathMapScreenState extends State { static const double _labelZoomThreshold = 8.5; + final MapController _mapController = MapController(); Uint8List? _selectedPath; double _pathDistance = 0.0; bool _showNodeLabels = true; bool _didReceivePositionUpdate = false; + int? _focusedHopIndex; @override void initState() { @@ -336,6 +338,22 @@ class _ChannelMessagePathMapScreenState return totalDistance; } + void _focusHop(_PathHop hop) { + if (!hop.hasLocation) return; + final targetZoom = _didReceivePositionUpdate + ? max(_mapController.camera.zoom, 14.0) + : 14.0; + _mapController.move(hop.position!, targetZoom); + } + + void _onHopTapped(_PathHop hop) { + _focusHop(hop); + if (!mounted) return; + setState(() { + _focusedHopIndex = hop.index; + }); + } + @override Widget build(BuildContext context) { return Consumer( @@ -419,6 +437,7 @@ class _ChannelMessagePathMapScreenState children: [ FlutterMap( key: mapKey, + mapController: _mapController, options: MapOptions( initialCenter: initialCenter, initialZoom: initialZoom, @@ -470,6 +489,7 @@ class _ChannelMessagePathMapScreenState ) { setState(() { _selectedPath = observedPaths[index].pathBytes; + _focusedHopIndex = null; }); }), if (points.isEmpty) @@ -725,8 +745,17 @@ class _ChannelMessagePathMapScreenState separatorBuilder: (_, _) => const Divider(height: 1), itemBuilder: (context, index) { final hop = hops[index]; + final isFocused = _focusedHopIndex == hop.index; return ListTile( dense: true, + enabled: hop.hasLocation, + selected: isFocused, + selectedTileColor: Theme.of( + context, + ).colorScheme.primary.withValues(alpha: 0.12), + onTap: hop.hasLocation + ? () => _onHopTapped(hop) + : null, leading: CircleAvatar( radius: 14, child: Text( diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index d2ea57e..4084d9b 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,6 +9,7 @@ import flutter_blue_plus_darwin import flutter_local_notifications import mobile_scanner import package_info_plus +import path_provider_foundation import share_plus import shared_preferences_foundation import sqflite_darwin @@ -20,6 +21,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 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")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) From 7b3c09973604c592bca8d3002edd537788333d2a Mon Sep 17 00:00:00 2001 From: ericz Date: Wed, 18 Mar 2026 06:52:08 +0100 Subject: [PATCH 04/36] reduce zoomlevel --- lib/screens/channel_message_path_screen.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/screens/channel_message_path_screen.dart b/lib/screens/channel_message_path_screen.dart index 0092f22..76273c4 100644 --- a/lib/screens/channel_message_path_screen.dart +++ b/lib/screens/channel_message_path_screen.dart @@ -341,8 +341,8 @@ class _ChannelMessagePathMapScreenState void _focusHop(_PathHop hop) { if (!hop.hasLocation) return; final targetZoom = _didReceivePositionUpdate - ? max(_mapController.camera.zoom, 14.0) - : 14.0; + ? max(_mapController.camera.zoom, 10.0) + : 12.0; _mapController.move(hop.position!, targetZoom); } From 87d11c2e6b3d0e1e21099ee9c39ba852c863e92d Mon Sep 17 00:00:00 2001 From: ericszimmermann Date: Wed, 18 Mar 2026 07:00:16 +0100 Subject: [PATCH 05/36] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- lib/screens/channel_message_path_screen.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/screens/channel_message_path_screen.dart b/lib/screens/channel_message_path_screen.dart index 76273c4..f64cb53 100644 --- a/lib/screens/channel_message_path_screen.dart +++ b/lib/screens/channel_message_path_screen.dart @@ -848,7 +848,11 @@ List<_PathHop> _buildPathHops( var bestDistance = double.infinity; for (var j = 0; j < candidates.length; j++) { final candidate = candidates[j]; - if (!candidate.hasLocation) continue; + if (!candidate.hasLocation || + candidate.latitude == null || + candidate.longitude == null) { + continue; + } final currentDistance = distance( searchPoint, LatLng(candidate.latitude!, candidate.longitude!), From b88e5e647ad9a0cda4a994210aa84ae6717148b0 Mon Sep 17 00:00:00 2001 From: ericszimmermann Date: Wed, 18 Mar 2026 08:06:22 +0100 Subject: [PATCH 06/36] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- lib/screens/channel_message_path_screen.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/screens/channel_message_path_screen.dart b/lib/screens/channel_message_path_screen.dart index f64cb53..ea07eae 100644 --- a/lib/screens/channel_message_path_screen.dart +++ b/lib/screens/channel_message_path_screen.dart @@ -889,7 +889,10 @@ List<_PathHop> _buildPathHops( LatLng? _resolvePosition(Contact? contact) { if (contact == null) return null; if (!contact.hasLocation) return null; - return LatLng(contact.latitude!, contact.longitude!); + final latitude = contact.latitude; + final longitude = contact.longitude; + if (latitude == null || longitude == null) return null; + return LatLng(latitude, longitude); } String _formatPrefix(int prefix) { From 1392c2d00f38b891a8c1dbd9b6b3c87aa2f3a9ff Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Thu, 19 Mar 2026 22:49:16 -0700 Subject: [PATCH 07/36] feat: Enhance privacy settings and telemetry (#308) * feat: Enhance privacy settings and telemetry - Implemented telemetry options for contacts, allowing users to enable or disable telemetry data sharing. - Introduced a clear chat option in the chat interface for better message management. - Updated the telemetry screen to handle telemetry data for contacts, including battery level. - Refactored contact settings to include telemetry options and improved UI for better user experience. * feat: Refactor repeater resolution logic across multiple screens --- lib/connector/meshcore_connector.dart | 80 ++++++- lib/connector/meshcore_protocol.dart | 24 +- lib/l10n/app_bg.arb | 37 ++- lib/l10n/app_de.arb | 35 ++- lib/l10n/app_en.arb | 33 ++- lib/l10n/app_es.arb | 35 ++- lib/l10n/app_fr.arb | 35 ++- lib/l10n/app_it.arb | 35 ++- lib/l10n/app_localizations.dart | 144 +++++++++++ lib/l10n/app_localizations_bg.dart | 82 +++++++ lib/l10n/app_localizations_de.dart | 79 ++++++ lib/l10n/app_localizations_en.dart | 76 ++++++ lib/l10n/app_localizations_es.dart | 81 +++++++ lib/l10n/app_localizations_fr.dart | 82 +++++++ lib/l10n/app_localizations_it.dart | 82 +++++++ lib/l10n/app_localizations_nl.dart | 78 ++++++ lib/l10n/app_localizations_pl.dart | 82 +++++++ lib/l10n/app_localizations_pt.dart | 81 +++++++ lib/l10n/app_localizations_ru.dart | 81 +++++++ lib/l10n/app_localizations_sk.dart | 78 ++++++ lib/l10n/app_localizations_sl.dart | 79 ++++++ lib/l10n/app_localizations_sv.dart | 77 ++++++ lib/l10n/app_localizations_uk.dart | 80 +++++++ lib/l10n/app_localizations_zh.dart | 74 ++++++ lib/l10n/app_nl.arb | 35 ++- lib/l10n/app_pl.arb | 35 ++- lib/l10n/app_pt.arb | 35 ++- lib/l10n/app_ru.arb | 35 ++- lib/l10n/app_sk.arb | 35 ++- lib/l10n/app_sl.arb | 35 ++- lib/l10n/app_sv.arb | 35 ++- lib/l10n/app_uk.arb | 35 ++- lib/l10n/app_zh.arb | 35 ++- lib/models/contact.dart | 3 + lib/screens/channel_chat_screen.dart | 27 +++ lib/screens/chat_screen.dart | 278 +++++++++++++++++----- lib/screens/contacts_screen.dart | 5 +- lib/screens/neighbors_screen.dart | 25 +- lib/screens/repeater_cli_screen.dart | 15 +- lib/screens/repeater_hub_screen.dart | 3 +- lib/screens/repeater_settings_screen.dart | 15 +- lib/screens/repeater_status_screen.dart | 15 +- lib/screens/settings_screen.dart | 178 ++++++++++---- lib/screens/telemetry_screen.dart | 168 +++++++------ lib/widgets/path_management_dialog.dart | 15 +- lib/widgets/repeater_login_dialog.dart | 14 +- lib/widgets/room_login_dialog.dart | 15 +- 47 files changed, 2512 insertions(+), 229 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index ef2f9b7..87a6755 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -329,6 +329,11 @@ class MeshCoreConnector extends ChangeNotifier { bool? get autoAddRoomServers => _autoAddRoomServers; bool? get autoAddSensors => _autoAddSensors; bool? get autoAddOverwriteOldest => _overwriteOldest; + int get telemetryModeBase => _telemetryModeBase; + int get telemetryModeLoc => _telemetryModeLoc; + int get telemetryModeEnv => _telemetryModeEnv; + int get advertLocationPolicy => _advertLocPolicy; + int get multiAcks => _multiAcks; bool? get clientRepeat => _clientRepeat; int? get firmwareVerCode => _firmwareVerCode; Map? get currentCustomVars => _currentCustomVars; @@ -1922,13 +1927,36 @@ class MeshCoreConnector extends ChangeNotifier { } } - Future setContactFavorite(Contact contact, bool isFavorite) async { + Future setContactFlags( + Contact contact, { + bool? isFavorite, + bool? teleBase, + bool? teleLoc, + bool? teleEnv, + }) async { if (!isConnected) return; final latestContact = await _fetchContactSnapshotFromDevice(contact.publicKey) ?? contact; - final updatedFlags = isFavorite - ? (latestContact.flags | contactFlagFavorite) - : (latestContact.flags & ~contactFlagFavorite); + int updatedFlags = isFavorite != null + ? (isFavorite + ? (latestContact.flags | contactFlagFavorite) + : (latestContact.flags & ~contactFlagFavorite)) + : latestContact.flags; + updatedFlags = teleBase != null + ? (teleBase + ? (updatedFlags | contactFlagTeleBase) + : (updatedFlags & ~contactFlagTeleBase)) + : updatedFlags; + updatedFlags = teleLoc != null + ? (teleLoc + ? (updatedFlags | contactFlagTeleLoc) + : (updatedFlags & ~contactFlagTeleLoc)) + : updatedFlags; + updatedFlags = teleEnv != null + ? (teleEnv + ? (updatedFlags | contactFlagTeleEnv) + : (updatedFlags & ~contactFlagTeleEnv)) + : updatedFlags; await sendFrame( buildUpdateContactPathFrame( @@ -2468,6 +2496,31 @@ class MeshCoreConnector extends ChangeNotifier { await sendCliCommand('set privacy ${enabled ? 'on' : 'off'}'); } + Future setTelemetryModeBase( + int base, + int location, + int env, + int advert, + int multiAcks, + ) async { + if (!isConnected) return; + _telemetryModeBase = base.clamp(teleModeDeny, teleModeAllowAll).toInt(); + _telemetryModeLoc = location.clamp(teleModeDeny, teleModeAllowAll).toInt(); + _telemetryModeEnv = env.clamp(teleModeDeny, teleModeAllowAll).toInt(); + _advertLocPolicy = advert.clamp(0, 1).toInt(); + _multiAcks = multiAcks.clamp(0, 2).toInt(); + await sendFrame( + buildSetOtherParamsFrame( + (_telemetryModeEnv << 4) | + (_telemetryModeLoc << 2) | + _telemetryModeBase, + _advertLocPolicy, + _multiAcks, + ), + ); + notifyListeners(); + } + Future getChannels({int? maxChannels, bool force = false}) async { if (!isConnected) return; if (_isSyncingChannels) { @@ -5124,6 +5177,25 @@ class MeshCoreConnector extends ChangeNotifier { unawaited(_persistDiscoveredContacts()); notifyListeners(); } + + void clearMessagesForContact(Contact contact) { + final contactKeyHex = contact.publicKeyHex; + final messages = _conversations[contactKeyHex]; + if (messages == null) return; + messages.clear(); + unawaited(_messageStore.saveMessages(contactKeyHex, messages)); + markContactRead(contactKeyHex); + notifyListeners(); + } + + void clearMessagesForChannel(int channelIndex) { + final messages = _channelMessages[channelIndex]; + if (messages == null) return; + messages.clear(); + unawaited(_channelMessageStore.saveChannelMessages(channelIndex, messages)); + markChannelRead(channelIndex); + notifyListeners(); + } } const int _phRouteMask = 0x03; diff --git a/lib/connector/meshcore_protocol.dart b/lib/connector/meshcore_protocol.dart index 1a0ada1..01b41d4 100644 --- a/lib/connector/meshcore_protocol.dart +++ b/lib/connector/meshcore_protocol.dart @@ -210,7 +210,7 @@ const int cmdSetChannel = 32; const int cmdSendTracePath = 36; const int cmdSetOtherParams = 38; const int cmdSendAnonReq = 57; -const int cmdGetTelemetryReq = 39; +const int cmdSendTelemetryReq = 39; const int cmdGetCustomVar = 40; const int cmdSetCustomVar = 41; const int cmdSendBinaryReq = 50; @@ -272,6 +272,10 @@ const int advTypeRepeater = 2; const int advTypeRoom = 3; const int advTypeSensor = 4; +const int teleModeDeny = 0; +const int teleModeAllowFlags = 1; // use contact.flags +const int teleModeAllowAll = 2; + // Payload Types const int payloadTypeREQ = 0x00; // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob) @@ -352,6 +356,9 @@ const int contactPubKeyOffset = 1; const int contactTypeOffset = 33; const int contactFlagsOffset = 34; const int contactFlagFavorite = 0x01; +const int contactFlagTeleBase = 0x02; // 'base' permission includes battery +const int contactFlagTeleLoc = 0x04; +const int contactFlagTeleEnv = 0x08; //access environment sensors const int contactPathLenOffset = 35; const int contactPathOffset = 36; const int contactNameOffset = 100; @@ -937,3 +944,18 @@ Uint8List buildSetAutoAddConfigFrame({ writer.writeByte(flags); return writer.toBytes(); } + +//Build CMD_SEND_TELEMETRY_REQ +// Format: [cmd][reserved x3][pub_key? x32] +Uint8List buildSendTelemetryReq(Uint8List? pubKey) { + final writer = BufferWriter(); + writer.writeByte(cmdSendTelemetryReq); + + if (pubKey != null && pubKey.length == pubKeySize) { + writer.writeBytes(Uint8List(3)); // reserved bytes + writer.writeBytes(pubKey); + } else { + writer.writeBytes(Uint8List(4)); // reserved bytes + } + return writer.toBytes(); +} diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index b8ea08f..13788b8 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -1892,7 +1892,7 @@ "map_setAsMyLocation": "Задайте като моя местоположение", "@path_routeWeight": { "placeholders": { - "weight": { + "value": { "type": "String" }, "max": { @@ -1900,6 +1900,35 @@ } } }, + "settings_denyAll": "Откажи всичко", + "settings_allowAll": "Позволи всичко", + "settings_allowByContact": "Позволи по флагове за контакт", + "settings_privacy": "Настройки на поверителността", + "settings_privacySettingsDescription": "Изберете каква информация устройството ви споделя с другите.", + "settings_privacySubtitle": "Контролирайте каква информация се споделя.", + "settings_telemetryBaseMode": "Базов режим на телеметрия", + "settings_telemetryLocationMode": "Режим на местоположение на телеметрията", + "settings_advertLocation": "Място на обявата", + "settings_advertLocationSubtitle": "Включи местоположение в обявата", + "contact_info": "Контактна информация", + "settings_telemetryEnvironmentMode": "Режим на средата на телеметрията", + "contact_telemetry": "Телеметрия", + "contact_lastSeen": "Последно видян", + "contact_clearChat": "Изчисти чата", + "contact_teleBase": "Базата данни за телеметрия", + "contact_settings": "Настройки за контакти", + "contact_teleBaseSubtitle": "Позволи споделяне на ниво на батерията и основна телеметрия", + "contact_teleEnv": "Среда на телеметрия", + "contact_teleLocSubtitle": "Позволи споделяне на данни за местоположение", + "contact_teleLoc": "Местоположение на телеметрията", + "contact_teleEnvSubtitle": "Позволи споделяне на данни от средносферните датчици", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_initialRouteWeight": "Първоначална тежест на маршрута", "appSettings_maxRouteWeight": "Максимално допустимо тегло на маршрута", "appSettings_initialRouteWeightSubtitle": "Начално тегло за новооткрити маршрути", @@ -1910,5 +1939,7 @@ "appSettings_routeWeightFailureDecrementSubtitle": "Тегло, което е било премахнато от пътя след неуспешен опит за доставка.", "appSettings_maxMessageRetries": "Максимален брой опити за изпращане на съобщение", "appSettings_maxMessageRetriesSubtitle": "Брой опити за повторно изпращане, преди съобщението да бъде маркирано като неуспешно.", - "path_routeWeight": "{weight}/{max}" -} + "path_routeWeight": "{weight}/{max}", + "settings_multiAck": "Мулти-потвърди: {value}", + "settings_telemetryModeUpdated": "Режим на телеметрията е обновен" +} \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 681cff6..6745054 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1928,6 +1928,35 @@ } } }, + "settings_allowByContact": "Zulassen durch Kontaktflaggen", + "settings_privacy": "Datenschutzeinstellungen", + "settings_allowAll": "Alles zulassen", + "settings_privacySettingsDescription": "Wählen Sie die Informationen, die Ihr Gerät mit anderen teilt.", + "settings_denyAll": "Alle ablehnen", + "settings_privacySubtitle": "Steuern Sie die Informationen, die freigegeben werden.", + "settings_telemetryLocationMode": "Telemetrie-Ortsmodus", + "settings_telemetryEnvironmentMode": "Telemetrie-Umgebungsmodus", + "settings_advertLocation": "Anzeigenort", + "settings_advertLocationSubtitle": "Ort in der Anzeige einbeziehen", + "settings_telemetryBaseMode": "Telemetrie-Basismodus", + "contact_teleBase": "Telemetriebasis", + "contact_teleBaseSubtitle": "Erlauben des Freigebens des Batteriestands und der grundlegenden Telemetrie", + "contact_teleLoc": "Telemetrieort", + "contact_teleLocSubtitle": "Teilen von Standortdaten zulassen", + "contact_info": "Kontaktinformationen", + "contact_settings": "Kontakteinstellungen", + "contact_telemetry": "Telemetrie", + "contact_teleEnv": "Telemetrieumgebung", + "contact_lastSeen": "Zuletzt gesehen", + "contact_clearChat": "Chat löschen", + "contact_teleEnvSubtitle": "Teilen von Umgebungsensordaten zulassen", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_initialRouteWeightSubtitle": "Ausgangsgewicht für neu entdeckte Pfade", "appSettings_maxRouteWeightSubtitle": "Maximales Gewicht, das ein Weg durch erfolgreiche Lieferungen erreichen kann.", "appSettings_maxRouteWeight": "Maximale Gesamtstreckenlänge", @@ -1938,5 +1967,7 @@ "appSettings_routeWeightFailureDecrementSubtitle": "Gewicht, das nach einem fehlgeschlagenen Versand von einem Weg entfernt wurde", "appSettings_maxMessageRetries": "Maximale Anzahl an Wiederholungsversuchen", "appSettings_maxMessageRetriesSubtitle": "Anzahl der Versuche, eine Nachricht erneut zu senden, bevor sie als fehlgeschlagen markiert wird.", - "path_routeWeight": "{weight}/{max}" -} + "path_routeWeight": "{weight}/{max}", + "settings_telemetryModeUpdated": "Telemetriemodus aktualisiert", + "settings_multiAck": "Mehrfach-Bestätigungen: {value}" +} \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3942afb..2b263c6 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -166,6 +166,26 @@ "settings_privacyModeToggle": "Toggle privacy mode to hide your name and location in advertisements.", "settings_privacyModeEnabled": "Privacy mode enabled", "settings_privacyModeDisabled": "Privacy mode disabled", + "settings_privacy": "Privacy Settings", + "settings_privacySubtitle": "Control what information is shared.", + "settings_privacySettingsDescription": "Choose what information your device shares with others.", + "settings_denyAll": "Deny all", + "settings_allowByContact": "Allow by contact flags", + "settings_allowAll": "Allow all", + "settings_telemetryBaseMode": "Telemetry Base Mode", + "settings_telemetryLocationMode": "Telemetry Location Mode", + "settings_telemetryEnvironmentMode": "Telemetry Environment Mode", + "settings_advertLocation": "Advert Location", + "settings_advertLocationSubtitle": "Include location in advert.", + "settings_multiAck": "Multi-ACKs: {value}", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, + "settings_telemetryModeUpdated": "Telemetry mode updated", "settings_actions": "Actions", "settings_sendAdvertisement": "Send Advertisement", "settings_sendAdvertisementSubtitle": "Broadcast presence now", @@ -472,6 +492,17 @@ } } }, + "contact_info": "Contact Info", + "contact_settings": "Contact Settings", + "contact_telemetry": "Telemetry", + "contact_lastSeen": "Last seen", + "contact_clearChat": "Clear Chat", + "contact_teleBase": "Telemetry Base", + "contact_teleBaseSubtitle": "Allow sharing battery level and basic telemetry", + "contact_teleLoc": "Telemetry Location", + "contact_teleLocSubtitle": "Allow sharing location data", + "contact_teleEnv": "Telemetry Environment", + "contact_teleEnvSubtitle": "Allow sharing environment sensor data", "channels_title": "Channels", "channels_noChannelsConfigured": "No channels configured", "channels_addPublicChannel": "Add Public Channel", @@ -1945,4 +1976,4 @@ "discoveredContacts_deleteContact": "Delete Discovered Contact", "discoveredContacts_deleteContactAll": "Delete All Discovered Contacts", "discoveredContacts_deleteContactAllContent": "Are you sure you want to delete all discovered contacts?" -} +} \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 4a83680..4a49e1b 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1928,6 +1928,35 @@ } } }, + "settings_privacySubtitle": "Controlar qué información se comparte.", + "settings_allowByContact": "Permitir por banderas de contacto", + "settings_denyAll": "Denegar todo", + "settings_telemetryBaseMode": "Modo base de telemetría", + "settings_telemetryEnvironmentMode": "Modo de entorno de telemetría", + "settings_advertLocationSubtitle": "Incluir ubicación en anuncio", + "contact_info": "Información de contacto", + "settings_privacySettingsDescription": "Elige qué información comparte tu dispositivo con otros.", + "settings_allowAll": "Permitir todo", + "settings_privacy": "Configuración de privacidad", + "contact_settings": "Configuración de contacto", + "settings_telemetryLocationMode": "Modo de ubicación de telemetría", + "contact_teleBase": "Base de Telemetría", + "contact_teleLoc": "Ubicación de telemetría", + "settings_advertLocation": "Ubicación de anuncio", + "contact_teleLocSubtitle": "Permitir el intercambio de datos de ubicación", + "contact_clearChat": "Borrar chat", + "contact_telemetry": "Telemetría", + "contact_lastSeen": "Visto por última vez", + "contact_teleBaseSubtitle": "Permitir el intercambio de nivel de batería y telemetría básica", + "contact_teleEnv": "Entorno de Telemetría", + "contact_teleEnvSubtitle": "Permitir el intercambio de datos de sensores de entorno", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_initialRouteWeight": "Peso inicial de la ruta", "appSettings_maxRouteWeight": "Peso máximo permitido para la ruta", "appSettings_initialRouteWeightSubtitle": "Peso inicial para rutas recién descubiertas", @@ -1938,5 +1967,7 @@ "appSettings_routeWeightFailureDecrementSubtitle": "Peso retirado de un camino después de un intento de entrega fallido.", "appSettings_maxMessageRetries": "Número máximo de reintentos de envío de mensajes", "appSettings_maxMessageRetriesSubtitle": "Número de intentos de reintento antes de marcar un mensaje como fallido.", - "path_routeWeight": "{weight}/{max}" -} + "path_routeWeight": "{weight}/{max}", + "settings_telemetryModeUpdated": "Modo de telemetría actualizado", + "settings_multiAck": "Multi-ACKs: {value}" +} \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 1d684bb..e98e317 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1900,6 +1900,35 @@ } } }, + "settings_privacy": "Paramètres de confidentialité", + "settings_privacySubtitle": "Contrôlez les informations partagées", + "settings_telemetryLocationMode": "Mode d'emplacement de télémétrie", + "settings_telemetryEnvironmentMode": "Mode d'environnement de télémétrie", + "settings_advertLocation": "Emplacement de l'annonce", + "settings_advertLocationSubtitle": "Inclure l'emplacement dans l'annonce", + "settings_denyAll": "Refuser tout", + "settings_allowByContact": "Autoriser par drapeaux de contact", + "settings_privacySettingsDescription": "Choisissez les informations que votre appareil partage avec les autres.", + "settings_allowAll": "Autoriser tout", + "contact_info": "Informations de contact", + "settings_telemetryBaseMode": "Mode de base Télémétrie", + "contact_teleBase": "Base de télémétrie", + "contact_teleLoc": "Emplacement de télémétrie", + "contact_teleLocSubtitle": "Autoriser le partage des données de localisation", + "contact_teleEnv": "Environnement Télémétrie", + "contact_teleEnvSubtitle": "Autoriser le partage des données des capteurs d'environnement", + "contact_telemetry": "Télémétrie", + "contact_settings": "Paramètres de contact", + "contact_lastSeen": "Dernière fois vu", + "contact_clearChat": "Effacer la conversation", + "contact_teleBaseSubtitle": "Autoriser le partage du niveau de batterie et de la télémétrie de base", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_maxRouteWeightSubtitle": "Poids maximal qu'un itinéraire peut accumuler grâce à des livraisons réussies.", "appSettings_initialRouteWeight": "Poids initial de l'itinéraire", "appSettings_maxRouteWeight": "Poids maximal autorisé pour le trajet", @@ -1910,5 +1939,7 @@ "appSettings_routeWeightFailureDecrementSubtitle": "Poids retiré d'un itinéraire après une tentative de livraison infructueuse.", "appSettings_maxMessageRetries": "Nombre maximal de tentatives de récupération de messages", "appSettings_maxMessageRetriesSubtitle": "Nombre de tentatives de relance avant de marquer un message comme ayant échoué.", - "path_routeWeight": "{weight}/{max}" -} + "path_routeWeight": "{weight}/{max}", + "settings_multiAck": "Multi-ACKs : {value}", + "settings_telemetryModeUpdated": "Le mode télémétrie a été mis à jour" +} \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 55a1054..f11cde5 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -1900,6 +1900,35 @@ } } }, + "settings_privacySettingsDescription": "Scegli le informazioni che il tuo dispositivo condivide con gli altri.", + "settings_allowByContact": "Consenti in base ai flag di contatto", + "settings_telemetryLocationMode": "Modalità di posizionamento telemetrico", + "settings_telemetryEnvironmentMode": "Modalità di ambiente di telemetria", + "settings_advertLocation": "Posizione dell'annuncio", + "settings_advertLocationSubtitle": "Includi la posizione nell'annuncio", + "settings_privacy": "Impostazioni sulla privacy", + "settings_denyAll": "Negare tutto", + "settings_privacySubtitle": "Controlla le informazioni che vengono condivise.", + "settings_allowAll": "Consenti tutto", + "contact_info": "Informazioni di Contatto", + "settings_telemetryBaseMode": "Modalità di base di telemetria", + "contact_teleBase": "Base di telemetria", + "contact_teleLoc": "Posizione telemetria", + "contact_teleLocSubtitle": "Consenti la condivisione dei dati di posizione", + "contact_clearChat": "Cancella chat", + "contact_telemetry": "Telemetria", + "contact_settings": "Impostazioni di contatto", + "contact_lastSeen": "Ultimo accesso", + "contact_teleBaseSubtitle": "Consenti la condivisione del livello della batteria e della telemetria di base", + "contact_teleEnvSubtitle": "Consenti la condivisione dei dati del sensore ambientale", + "contact_teleEnv": "Ambiente di telemetria", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_initialRouteWeight": "Peso iniziale del percorso", "appSettings_initialRouteWeightSubtitle": "Peso di partenza per nuovi percorsi", "appSettings_maxRouteWeightSubtitle": "Il peso massimo che un percorso può accumulare grazie a consegne di successo.", @@ -1910,5 +1939,7 @@ "appSettings_routeWeightFailureDecrementSubtitle": "Peso rimosso da un percorso dopo un tentativo di consegna fallito.", "appSettings_maxMessageRetries": "Numero massimo di tentativi di invio del messaggio", "appSettings_maxMessageRetriesSubtitle": "Numero di tentativi di riprova prima di considerare un messaggio come fallito.", - "path_routeWeight": "{weight}/{max}" -} + "path_routeWeight": "{weight}/{max}", + "settings_telemetryModeUpdated": "Modalità telemetria aggiornata", + "settings_multiAck": "Multi-ACKs: {value}" +} \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 84b5432..4bb6936 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -826,6 +826,84 @@ abstract class AppLocalizations { /// **'Privacy mode disabled'** String get settings_privacyModeDisabled; + /// No description provided for @settings_privacy. + /// + /// In en, this message translates to: + /// **'Privacy Settings'** + String get settings_privacy; + + /// No description provided for @settings_privacySubtitle. + /// + /// In en, this message translates to: + /// **'Control what information is shared.'** + String get settings_privacySubtitle; + + /// No description provided for @settings_privacySettingsDescription. + /// + /// In en, this message translates to: + /// **'Choose what information your device shares with others.'** + String get settings_privacySettingsDescription; + + /// No description provided for @settings_denyAll. + /// + /// In en, this message translates to: + /// **'Deny all'** + String get settings_denyAll; + + /// No description provided for @settings_allowByContact. + /// + /// In en, this message translates to: + /// **'Allow by contact flags'** + String get settings_allowByContact; + + /// No description provided for @settings_allowAll. + /// + /// In en, this message translates to: + /// **'Allow all'** + String get settings_allowAll; + + /// No description provided for @settings_telemetryBaseMode. + /// + /// In en, this message translates to: + /// **'Telemetry Base Mode'** + String get settings_telemetryBaseMode; + + /// No description provided for @settings_telemetryLocationMode. + /// + /// In en, this message translates to: + /// **'Telemetry Location Mode'** + String get settings_telemetryLocationMode; + + /// No description provided for @settings_telemetryEnvironmentMode. + /// + /// In en, this message translates to: + /// **'Telemetry Environment Mode'** + String get settings_telemetryEnvironmentMode; + + /// No description provided for @settings_advertLocation. + /// + /// In en, this message translates to: + /// **'Advert Location'** + String get settings_advertLocation; + + /// No description provided for @settings_advertLocationSubtitle. + /// + /// In en, this message translates to: + /// **'Include location in advert.'** + String get settings_advertLocationSubtitle; + + /// No description provided for @settings_multiAck. + /// + /// In en, this message translates to: + /// **'Multi-ACKs: {value}'** + String settings_multiAck(String value); + + /// No description provided for @settings_telemetryModeUpdated. + /// + /// In en, this message translates to: + /// **'Telemetry mode updated'** + String get settings_telemetryModeUpdated; + /// No description provided for @settings_actions. /// /// In en, this message translates to: @@ -1846,6 +1924,72 @@ abstract class AppLocalizations { /// **'~ {days} days'** String contacts_lastSeenDaysAgo(int days); + /// No description provided for @contact_info. + /// + /// In en, this message translates to: + /// **'Contact Info'** + String get contact_info; + + /// No description provided for @contact_settings. + /// + /// In en, this message translates to: + /// **'Contact Settings'** + String get contact_settings; + + /// No description provided for @contact_telemetry. + /// + /// In en, this message translates to: + /// **'Telemetry'** + String get contact_telemetry; + + /// No description provided for @contact_lastSeen. + /// + /// In en, this message translates to: + /// **'Last seen'** + String get contact_lastSeen; + + /// No description provided for @contact_clearChat. + /// + /// In en, this message translates to: + /// **'Clear Chat'** + String get contact_clearChat; + + /// No description provided for @contact_teleBase. + /// + /// In en, this message translates to: + /// **'Telemetry Base'** + String get contact_teleBase; + + /// No description provided for @contact_teleBaseSubtitle. + /// + /// In en, this message translates to: + /// **'Allow sharing battery level and basic telemetry'** + String get contact_teleBaseSubtitle; + + /// No description provided for @contact_teleLoc. + /// + /// In en, this message translates to: + /// **'Telemetry Location'** + String get contact_teleLoc; + + /// No description provided for @contact_teleLocSubtitle. + /// + /// In en, this message translates to: + /// **'Allow sharing location data'** + String get contact_teleLocSubtitle; + + /// No description provided for @contact_teleEnv. + /// + /// In en, this message translates to: + /// **'Telemetry Environment'** + String get contact_teleEnv; + + /// No description provided for @contact_teleEnvSubtitle. + /// + /// In en, this message translates to: + /// **'Allow sharing environment sensor data'** + String get contact_teleEnvSubtitle; + /// No description provided for @channels_title. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 2821617..d6537f9 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -398,6 +398,52 @@ class AppLocalizationsBg extends AppLocalizations { String get settings_privacyModeDisabled => 'Режим на поверителност е деактивиран'; + @override + String get settings_privacy => 'Настройки на поверителността'; + + @override + String get settings_privacySubtitle => + 'Контролирайте каква информация се споделя.'; + + @override + String get settings_privacySettingsDescription => + 'Изберете каква информация устройството ви споделя с другите.'; + + @override + String get settings_denyAll => 'Откажи всичко'; + + @override + String get settings_allowByContact => 'Позволи по флагове за контакт'; + + @override + String get settings_allowAll => 'Позволи всичко'; + + @override + String get settings_telemetryBaseMode => 'Базов режим на телеметрия'; + + @override + String get settings_telemetryLocationMode => + 'Режим на местоположение на телеметрията'; + + @override + String get settings_telemetryEnvironmentMode => + 'Режим на средата на телеметрията'; + + @override + String get settings_advertLocation => 'Място на обявата'; + + @override + String get settings_advertLocationSubtitle => + 'Включи местоположение в обявата'; + + @override + String settings_multiAck(String value) { + return 'Мулти-потвърди: $value'; + } + + @override + String get settings_telemetryModeUpdated => 'Режим на телеметрията е обновен'; + @override String get settings_actions => 'Действия'; @@ -989,6 +1035,42 @@ class AppLocalizationsBg extends AppLocalizations { return 'Последно видян $days дни преди.'; } + @override + String get contact_info => 'Контактна информация'; + + @override + String get contact_settings => 'Настройки за контакти'; + + @override + String get contact_telemetry => 'Телеметрия'; + + @override + String get contact_lastSeen => 'Последно видян'; + + @override + String get contact_clearChat => 'Изчисти чата'; + + @override + String get contact_teleBase => 'Базата данни за телеметрия'; + + @override + String get contact_teleBaseSubtitle => + 'Позволи споделяне на ниво на батерията и основна телеметрия'; + + @override + String get contact_teleLoc => 'Местоположение на телеметрията'; + + @override + String get contact_teleLocSubtitle => + 'Позволи споделяне на данни за местоположение'; + + @override + String get contact_teleEnv => 'Среда на телеметрия'; + + @override + String get contact_teleEnvSubtitle => + 'Позволи споделяне на данни от средносферните датчици'; + @override String get channels_title => 'Канали'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 337915e..87fab6f 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -398,6 +398,50 @@ class AppLocalizationsDe extends AppLocalizations { @override String get settings_privacyModeDisabled => 'Datenschutzmodus deaktiviert'; + @override + String get settings_privacy => 'Datenschutzeinstellungen'; + + @override + String get settings_privacySubtitle => + 'Steuern Sie die Informationen, die freigegeben werden.'; + + @override + String get settings_privacySettingsDescription => + 'Wählen Sie die Informationen, die Ihr Gerät mit anderen teilt.'; + + @override + String get settings_denyAll => 'Alle ablehnen'; + + @override + String get settings_allowByContact => 'Zulassen durch Kontaktflaggen'; + + @override + String get settings_allowAll => 'Alles zulassen'; + + @override + String get settings_telemetryBaseMode => 'Telemetrie-Basismodus'; + + @override + String get settings_telemetryLocationMode => 'Telemetrie-Ortsmodus'; + + @override + String get settings_telemetryEnvironmentMode => 'Telemetrie-Umgebungsmodus'; + + @override + String get settings_advertLocation => 'Anzeigenort'; + + @override + String get settings_advertLocationSubtitle => + 'Ort in der Anzeige einbeziehen'; + + @override + String settings_multiAck(String value) { + return 'Mehrfach-Bestätigungen: $value'; + } + + @override + String get settings_telemetryModeUpdated => 'Telemetriemodus aktualisiert'; + @override String get settings_actions => 'Aktionen'; @@ -987,6 +1031,41 @@ class AppLocalizationsDe extends AppLocalizations { return '~ $days Tage'; } + @override + String get contact_info => 'Kontaktinformationen'; + + @override + String get contact_settings => 'Kontakteinstellungen'; + + @override + String get contact_telemetry => 'Telemetrie'; + + @override + String get contact_lastSeen => 'Zuletzt gesehen'; + + @override + String get contact_clearChat => 'Chat löschen'; + + @override + String get contact_teleBase => 'Telemetriebasis'; + + @override + String get contact_teleBaseSubtitle => + 'Erlauben des Freigebens des Batteriestands und der grundlegenden Telemetrie'; + + @override + String get contact_teleLoc => 'Telemetrieort'; + + @override + String get contact_teleLocSubtitle => 'Teilen von Standortdaten zulassen'; + + @override + String get contact_teleEnv => 'Telemetrieumgebung'; + + @override + String get contact_teleEnvSubtitle => + 'Teilen von Umgebungsensordaten zulassen'; + @override String get channels_title => 'Kanäle'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 1e4e5b0..1e0196b 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -392,6 +392,48 @@ class AppLocalizationsEn extends AppLocalizations { @override String get settings_privacyModeDisabled => 'Privacy mode disabled'; + @override + String get settings_privacy => 'Privacy Settings'; + + @override + String get settings_privacySubtitle => 'Control what information is shared.'; + + @override + String get settings_privacySettingsDescription => + 'Choose what information your device shares with others.'; + + @override + String get settings_denyAll => 'Deny all'; + + @override + String get settings_allowByContact => 'Allow by contact flags'; + + @override + String get settings_allowAll => 'Allow all'; + + @override + String get settings_telemetryBaseMode => 'Telemetry Base Mode'; + + @override + String get settings_telemetryLocationMode => 'Telemetry Location Mode'; + + @override + String get settings_telemetryEnvironmentMode => 'Telemetry Environment Mode'; + + @override + String get settings_advertLocation => 'Advert Location'; + + @override + String get settings_advertLocationSubtitle => 'Include location in advert.'; + + @override + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } + + @override + String get settings_telemetryModeUpdated => 'Telemetry mode updated'; + @override String get settings_actions => 'Actions'; @@ -972,6 +1014,40 @@ class AppLocalizationsEn extends AppLocalizations { return '~ $days days'; } + @override + String get contact_info => 'Contact Info'; + + @override + String get contact_settings => 'Contact Settings'; + + @override + String get contact_telemetry => 'Telemetry'; + + @override + String get contact_lastSeen => 'Last seen'; + + @override + String get contact_clearChat => 'Clear Chat'; + + @override + String get contact_teleBase => 'Telemetry Base'; + + @override + String get contact_teleBaseSubtitle => + 'Allow sharing battery level and basic telemetry'; + + @override + String get contact_teleLoc => 'Telemetry Location'; + + @override + String get contact_teleLocSubtitle => 'Allow sharing location data'; + + @override + String get contact_teleEnv => 'Telemetry Environment'; + + @override + String get contact_teleEnvSubtitle => 'Allow sharing environment sensor data'; + @override String get channels_title => 'Channels'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 657d556..dff2e5e 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -396,6 +396,51 @@ class AppLocalizationsEs extends AppLocalizations { @override String get settings_privacyModeDisabled => 'Modo de privacidad desactivado'; + @override + String get settings_privacy => 'Configuración de privacidad'; + + @override + String get settings_privacySubtitle => + 'Controlar qué información se comparte.'; + + @override + String get settings_privacySettingsDescription => + 'Elige qué información comparte tu dispositivo con otros.'; + + @override + String get settings_denyAll => 'Denegar todo'; + + @override + String get settings_allowByContact => 'Permitir por banderas de contacto'; + + @override + String get settings_allowAll => 'Permitir todo'; + + @override + String get settings_telemetryBaseMode => 'Modo base de telemetría'; + + @override + String get settings_telemetryLocationMode => + 'Modo de ubicación de telemetría'; + + @override + String get settings_telemetryEnvironmentMode => + 'Modo de entorno de telemetría'; + + @override + String get settings_advertLocation => 'Ubicación de anuncio'; + + @override + String get settings_advertLocationSubtitle => 'Incluir ubicación en anuncio'; + + @override + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } + + @override + String get settings_telemetryModeUpdated => 'Modo de telemetría actualizado'; + @override String get settings_actions => 'Acciones'; @@ -987,6 +1032,42 @@ class AppLocalizationsEs extends AppLocalizations { return '~ $days días'; } + @override + String get contact_info => 'Información de contacto'; + + @override + String get contact_settings => 'Configuración de contacto'; + + @override + String get contact_telemetry => 'Telemetría'; + + @override + String get contact_lastSeen => 'Visto por última vez'; + + @override + String get contact_clearChat => 'Borrar chat'; + + @override + String get contact_teleBase => 'Base de Telemetría'; + + @override + String get contact_teleBaseSubtitle => + 'Permitir el intercambio de nivel de batería y telemetría básica'; + + @override + String get contact_teleLoc => 'Ubicación de telemetría'; + + @override + String get contact_teleLocSubtitle => + 'Permitir el intercambio de datos de ubicación'; + + @override + String get contact_teleEnv => 'Entorno de Telemetría'; + + @override + String get contact_teleEnvSubtitle => + 'Permitir el intercambio de datos de sensores de entorno'; + @override String get channels_title => 'Canales'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 7aa7ebe..91bf4f4 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -400,6 +400,52 @@ class AppLocalizationsFr extends AppLocalizations { String get settings_privacyModeDisabled => 'Mode de confidentialité désactivé'; + @override + String get settings_privacy => 'Paramètres de confidentialité'; + + @override + String get settings_privacySubtitle => 'Contrôlez les informations partagées'; + + @override + String get settings_privacySettingsDescription => + 'Choisissez les informations que votre appareil partage avec les autres.'; + + @override + String get settings_denyAll => 'Refuser tout'; + + @override + String get settings_allowByContact => 'Autoriser par drapeaux de contact'; + + @override + String get settings_allowAll => 'Autoriser tout'; + + @override + String get settings_telemetryBaseMode => 'Mode de base Télémétrie'; + + @override + String get settings_telemetryLocationMode => + 'Mode d\'emplacement de télémétrie'; + + @override + String get settings_telemetryEnvironmentMode => + 'Mode d\'environnement de télémétrie'; + + @override + String get settings_advertLocation => 'Emplacement de l\'annonce'; + + @override + String get settings_advertLocationSubtitle => + 'Inclure l\'emplacement dans l\'annonce'; + + @override + String settings_multiAck(String value) { + return 'Multi-ACKs : $value'; + } + + @override + String get settings_telemetryModeUpdated => + 'Le mode télémétrie a été mis à jour'; + @override String get settings_actions => 'Actions'; @@ -991,6 +1037,42 @@ class AppLocalizationsFr extends AppLocalizations { return '~ $days jours'; } + @override + String get contact_info => 'Informations de contact'; + + @override + String get contact_settings => 'Paramètres de contact'; + + @override + String get contact_telemetry => 'Télémétrie'; + + @override + String get contact_lastSeen => 'Dernière fois vu'; + + @override + String get contact_clearChat => 'Effacer la conversation'; + + @override + String get contact_teleBase => 'Base de télémétrie'; + + @override + String get contact_teleBaseSubtitle => + 'Autoriser le partage du niveau de batterie et de la télémétrie de base'; + + @override + String get contact_teleLoc => 'Emplacement de télémétrie'; + + @override + String get contact_teleLocSubtitle => + 'Autoriser le partage des données de localisation'; + + @override + String get contact_teleEnv => 'Environnement Télémétrie'; + + @override + String get contact_teleEnvSubtitle => + 'Autoriser le partage des données des capteurs d\'environnement'; + @override String get channels_title => 'Canaux'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 02c5937..b688c06 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -398,6 +398,52 @@ class AppLocalizationsIt extends AppLocalizations { @override String get settings_privacyModeDisabled => 'Modalità privacy disabilitata'; + @override + String get settings_privacy => 'Impostazioni sulla privacy'; + + @override + String get settings_privacySubtitle => + 'Controlla le informazioni che vengono condivise.'; + + @override + String get settings_privacySettingsDescription => + 'Scegli le informazioni che il tuo dispositivo condivide con gli altri.'; + + @override + String get settings_denyAll => 'Negare tutto'; + + @override + String get settings_allowByContact => 'Consenti in base ai flag di contatto'; + + @override + String get settings_allowAll => 'Consenti tutto'; + + @override + String get settings_telemetryBaseMode => 'Modalità di base di telemetria'; + + @override + String get settings_telemetryLocationMode => + 'Modalità di posizionamento telemetrico'; + + @override + String get settings_telemetryEnvironmentMode => + 'Modalità di ambiente di telemetria'; + + @override + String get settings_advertLocation => 'Posizione dell\'annuncio'; + + @override + String get settings_advertLocationSubtitle => + 'Includi la posizione nell\'annuncio'; + + @override + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } + + @override + String get settings_telemetryModeUpdated => 'Modalità telemetria aggiornata'; + @override String get settings_actions => 'Azioni'; @@ -987,6 +1033,42 @@ class AppLocalizationsIt extends AppLocalizations { return 'Ultimo visto $days giorni fa'; } + @override + String get contact_info => 'Informazioni di Contatto'; + + @override + String get contact_settings => 'Impostazioni di contatto'; + + @override + String get contact_telemetry => 'Telemetria'; + + @override + String get contact_lastSeen => 'Ultimo accesso'; + + @override + String get contact_clearChat => 'Cancella chat'; + + @override + String get contact_teleBase => 'Base di telemetria'; + + @override + String get contact_teleBaseSubtitle => + 'Consenti la condivisione del livello della batteria e della telemetria di base'; + + @override + String get contact_teleLoc => 'Posizione telemetria'; + + @override + String get contact_teleLocSubtitle => + 'Consenti la condivisione dei dati di posizione'; + + @override + String get contact_teleEnv => 'Ambiente di telemetria'; + + @override + String get contact_teleEnvSubtitle => + 'Consenti la condivisione dei dati del sensore ambientale'; + @override String get channels_title => 'Canali'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 9e51164..1530886 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -395,6 +395,50 @@ class AppLocalizationsNl extends AppLocalizations { @override String get settings_privacyModeDisabled => 'Privacy modus is uitgeschakeld'; + @override + String get settings_privacy => 'Privacyinstellingen'; + + @override + String get settings_privacySubtitle => + 'Beheer welke informatie wordt gedeeld'; + + @override + String get settings_privacySettingsDescription => + 'Kies welke informatie uw apparaat deelt met anderen'; + + @override + String get settings_denyAll => 'Weiger alles'; + + @override + String get settings_allowByContact => 'Toestaan op basis van contactvlaggen'; + + @override + String get settings_allowAll => 'Alles toestaan'; + + @override + String get settings_telemetryBaseMode => 'Telemetrie-basismodus'; + + @override + String get settings_telemetryLocationMode => 'Telemetrie-locatiemodus'; + + @override + String get settings_telemetryEnvironmentMode => 'Telemetrie-omgevingsmodus'; + + @override + String get settings_advertLocation => 'Advertentielocatie'; + + @override + String get settings_advertLocationSubtitle => + 'Locatie opnemen in advertentie'; + + @override + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } + + @override + String get settings_telemetryModeUpdated => 'Telemetrie-modus bijgewerkt'; + @override String get settings_actions => 'Acties'; @@ -980,6 +1024,40 @@ class AppLocalizationsNl extends AppLocalizations { return 'Laast gezien $days dagen geleden'; } + @override + String get contact_info => 'Contactinformatie'; + + @override + String get contact_settings => 'Contactinstellingen'; + + @override + String get contact_telemetry => 'Telemetrie'; + + @override + String get contact_lastSeen => 'Laatst gezien'; + + @override + String get contact_clearChat => 'Chat leegmaken'; + + @override + String get contact_teleBase => 'Telemetrie_basis'; + + @override + String get contact_teleBaseSubtitle => + 'Sta delen van batterij niveau en basis telemetrie toe'; + + @override + String get contact_teleLoc => 'Telemetrielocatie'; + + @override + String get contact_teleLocSubtitle => 'Locatiegegevens delen toestaan'; + + @override + String get contact_teleEnv => 'Telemetrieomgeving'; + + @override + String get contact_teleEnvSubtitle => 'Delen van omgevingsensordata toestaan'; + @override String get channels_title => 'Kanaal'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 176c17e..16b6512 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -401,6 +401,52 @@ class AppLocalizationsPl extends AppLocalizations { @override String get settings_privacyModeDisabled => 'Tryb prywatności wyłączony'; + @override + String get settings_privacy => 'Ustawienia prywatności'; + + @override + String get settings_privacySubtitle => + 'Kontroluj jakie informacje są udostępniane.'; + + @override + String get settings_privacySettingsDescription => + 'Wybierz jakie informacje urządzenie udostępni innym.'; + + @override + String get settings_denyAll => 'Odmów wszystkim'; + + @override + String get settings_allowByContact => 'Zezwalaj według flag kontaktowych'; + + @override + String get settings_allowAll => 'Zezwalaj na wszystko'; + + @override + String get settings_telemetryBaseMode => 'Tryb podstawowy telemetrii'; + + @override + String get settings_telemetryLocationMode => 'Tryb położenia telemetrycznego'; + + @override + String get settings_telemetryEnvironmentMode => + 'Tryb środowiska telemetrycznego'; + + @override + String get settings_advertLocation => 'Lokalizacja reklamowa'; + + @override + String get settings_advertLocationSubtitle => + 'Uwzględnij lokalizację w ogłoszeniu'; + + @override + String settings_multiAck(String value) { + return 'Wiele potwierdzeń: $value'; + } + + @override + String get settings_telemetryModeUpdated => + 'Tryb telemetryczny zaktualizowany'; + @override String get settings_actions => 'Działania'; @@ -989,6 +1035,42 @@ class AppLocalizationsPl extends AppLocalizations { return 'Ostatnie połączenie $days dni temu'; } + @override + String get contact_info => 'Informacje kontaktowe'; + + @override + String get contact_settings => 'Ustawienia kontaktowe'; + + @override + String get contact_telemetry => 'Telemetryka'; + + @override + String get contact_lastSeen => 'Ostatnio widziany'; + + @override + String get contact_clearChat => 'Wyczyść czat'; + + @override + String get contact_teleBase => 'Baza telemetryczna'; + + @override + String get contact_teleBaseSubtitle => + 'Pozwól na udostępnianie poziomu naładowania baterii i podstawowych danych telemetrycznych'; + + @override + String get contact_teleLoc => 'Lokalizacja telemetryczna'; + + @override + String get contact_teleLocSubtitle => + 'Zezwalaj na udostępnianie danych lokalizacji'; + + @override + String get contact_teleEnv => 'Środowisko telemetryczne'; + + @override + String get contact_teleEnvSubtitle => + 'Zezwalaj na udostępnianie danych czujników środowiskowych'; + @override String get channels_title => 'Kanały'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index a51e1b0..87b44ca 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -398,6 +398,51 @@ class AppLocalizationsPt extends AppLocalizations { @override String get settings_privacyModeDisabled => 'Modo de privacidade desativado'; + @override + String get settings_privacy => 'Configurações de Privacidade'; + + @override + String get settings_privacySubtitle => 'Controle o que é compartilhado.'; + + @override + String get settings_privacySettingsDescription => + 'Escolha quais informações o seu dispositivo compartilha com os outros.'; + + @override + String get settings_denyAll => 'Negar todos'; + + @override + String get settings_allowByContact => 'Permitir por bandeiras de contato'; + + @override + String get settings_allowAll => 'Permitir todos'; + + @override + String get settings_telemetryBaseMode => 'Modo Base de Telemetria'; + + @override + String get settings_telemetryLocationMode => + 'Modo de Localização de Telemetria'; + + @override + String get settings_telemetryEnvironmentMode => + 'Modo de Ambiente de Telemetria'; + + @override + String get settings_advertLocation => 'Localização do Anúncio'; + + @override + String get settings_advertLocationSubtitle => + 'Incluir localização no anúncio'; + + @override + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } + + @override + String get settings_telemetryModeUpdated => 'Modo de telemetria atualizado'; + @override String get settings_actions => 'Ações'; @@ -988,6 +1033,42 @@ class AppLocalizationsPt extends AppLocalizations { return 'Última vez visto $days dias atrás'; } + @override + String get contact_info => 'Informações de Contato'; + + @override + String get contact_settings => 'Configurações de Contato'; + + @override + String get contact_telemetry => 'Telemetria'; + + @override + String get contact_lastSeen => 'Visto pela última vez'; + + @override + String get contact_clearChat => 'Limpar Chat'; + + @override + String get contact_teleBase => 'Base de Telemetria'; + + @override + String get contact_teleBaseSubtitle => + 'Permitir compartilhamento do nível da bateria e telemetria básica'; + + @override + String get contact_teleLoc => 'Localização de Telemetria'; + + @override + String get contact_teleLocSubtitle => + 'Permitir compartilhamento de dados de localização'; + + @override + String get contact_teleEnv => 'Ambiente de Telemetria'; + + @override + String get contact_teleEnvSubtitle => + 'Permitir compartilhamento de dados do sensor de ambiente'; + @override String get channels_title => 'Canais'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 7a6998f..72d2e1c 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -398,6 +398,51 @@ class AppLocalizationsRu extends AppLocalizations { String get settings_privacyModeDisabled => 'Режим конфиденциальности выключен'; + @override + String get settings_privacy => 'Настройки конфиденциальности'; + + @override + String get settings_privacySubtitle => + 'Контролируйте, какую информацию делиться.'; + + @override + String get settings_privacySettingsDescription => + 'Выберите, какую информацию ваше устройство будет делиться с другими.'; + + @override + String get settings_denyAll => 'Отклонить все'; + + @override + String get settings_allowByContact => 'Разрешить по флагам контактов'; + + @override + String get settings_allowAll => 'Разрешить все'; + + @override + String get settings_telemetryBaseMode => 'Базовый режим телеметрии'; + + @override + String get settings_telemetryLocationMode => + 'Режим местоположения телеметрии'; + + @override + String get settings_telemetryEnvironmentMode => 'Режим среды телеметрии'; + + @override + String get settings_advertLocation => 'Местоположение рекламы'; + + @override + String get settings_advertLocationSubtitle => + 'Включить местоположение в объявление'; + + @override + String settings_multiAck(String value) { + return 'Мульти-ACK: $value'; + } + + @override + String get settings_telemetryModeUpdated => 'Режим телеметрии обновлен'; + @override String get settings_actions => 'Действия'; @@ -988,6 +1033,42 @@ class AppLocalizationsRu extends AppLocalizations { return 'Видели $days дн. назад'; } + @override + String get contact_info => 'Контактная информация'; + + @override + String get contact_settings => 'Настройки контактов'; + + @override + String get contact_telemetry => 'Телеметрия'; + + @override + String get contact_lastSeen => 'Последний раз видели'; + + @override + String get contact_clearChat => 'Очистить чат'; + + @override + String get contact_teleBase => 'База телеметрии'; + + @override + String get contact_teleBaseSubtitle => + 'Разрешить обмен уровнем заряда батареи и базовой телеметрией'; + + @override + String get contact_teleLoc => 'Местоположение телеметрии'; + + @override + String get contact_teleLocSubtitle => + 'Разрешить обмен данными о местоположении'; + + @override + String get contact_teleEnv => 'Среда телеметрии'; + + @override + String get contact_teleEnvSubtitle => + 'Разрешить обмен данными датчиков окружающей среды'; + @override String get channels_title => 'Каналы'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index ae6c956..7817af6 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -395,6 +395,49 @@ class AppLocalizationsSk extends AppLocalizations { @override String get settings_privacyModeDisabled => 'Ochranný režim je vypnutý'; + @override + String get settings_privacy => 'Nastavenia súkromia'; + + @override + String get settings_privacySubtitle => 'Ovládni, aké informácie sa zdieľajú.'; + + @override + String get settings_privacySettingsDescription => + 'Vyberte, ktoré informácie váš zariadenie zdieľa s ostatnými.'; + + @override + String get settings_denyAll => 'Zamietnuť všetko'; + + @override + String get settings_allowByContact => 'Povoliť podľa kontaktových vlajok'; + + @override + String get settings_allowAll => 'Povoliť všetko'; + + @override + String get settings_telemetryBaseMode => 'Základný režim telemetrie'; + + @override + String get settings_telemetryLocationMode => 'Režim umiestnenia telemetrie'; + + @override + String get settings_telemetryEnvironmentMode => 'Režim prostredia telemetrie'; + + @override + String get settings_advertLocation => 'Umiestnenie inzerátu'; + + @override + String get settings_advertLocationSubtitle => 'Zahrnúť polohu do inzerátu'; + + @override + String settings_multiAck(String value) { + return 'Viaceré ACK: $value'; + } + + @override + String get settings_telemetryModeUpdated => + 'Režim telemetrie bol aktualizovaný'; + @override String get settings_actions => 'Možné akcie'; @@ -980,6 +1023,41 @@ class AppLocalizationsSk extends AppLocalizations { return 'Posledné zobrazenie $days dní dozadu'; } + @override + String get contact_info => 'Kontaktné informácie'; + + @override + String get contact_settings => 'Nastavenia kontaktov'; + + @override + String get contact_telemetry => 'Telemetria'; + + @override + String get contact_lastSeen => 'Naposledy videný'; + + @override + String get contact_clearChat => 'Vymazať chat'; + + @override + String get contact_teleBase => 'Báza telemetrie'; + + @override + String get contact_teleBaseSubtitle => + 'Povoliť zdieľanie úrovne batérie a základnej telemetrie'; + + @override + String get contact_teleLoc => 'Lokácia telemetrie'; + + @override + String get contact_teleLocSubtitle => 'Povoliť zdieľanie údajov o lokalite'; + + @override + String get contact_teleEnv => 'Prostredie telemetrie'; + + @override + String get contact_teleEnvSubtitle => + 'Povoliť zdieľanie údajov senzorov prostredia'; + @override String get channels_title => 'Kanály'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 96501cd..6032ee0 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -393,6 +393,50 @@ class AppLocalizationsSl extends AppLocalizations { @override String get settings_privacyModeDisabled => 'Privatni način je onemogočen.'; + @override + String get settings_privacy => 'Nastavitve zasebnosti'; + + @override + String get settings_privacySubtitle => + 'Kontrolirajte, katere informacije so deljene.'; + + @override + String get settings_privacySettingsDescription => + 'Izberite, katere informacije vaš naprava deli z drugimi.'; + + @override + String get settings_denyAll => 'Zavrniti vse'; + + @override + String get settings_allowByContact => 'Dovoli po kontaktnih zastavah'; + + @override + String get settings_allowAll => 'Dovoli vse'; + + @override + String get settings_telemetryBaseMode => 'Osnovni način telemetrije'; + + @override + String get settings_telemetryLocationMode => 'Način delovanja telemetrije'; + + @override + String get settings_telemetryEnvironmentMode => + 'Način delovanja okolja telemetrije'; + + @override + String get settings_advertLocation => 'Lokacija oglasa'; + + @override + String get settings_advertLocationSubtitle => 'Vključi lokacijo v oglas.'; + + @override + String settings_multiAck(String value) { + return 'Večkratni potrditvi: $value'; + } + + @override + String get settings_telemetryModeUpdated => 'Način telemetrije posodobljen'; + @override String get settings_actions => 'Akcije'; @@ -977,6 +1021,41 @@ class AppLocalizationsSl extends AppLocalizations { return 'Zadnjič viden pred $days dnem'; } + @override + String get contact_info => 'Kontaktni podatki'; + + @override + String get contact_settings => 'Nastavitve stika'; + + @override + String get contact_telemetry => 'Telemetrija'; + + @override + String get contact_lastSeen => 'Zadnjič videno'; + + @override + String get contact_clearChat => 'Počisti klepet'; + + @override + String get contact_teleBase => 'Baza telemetrije'; + + @override + String get contact_teleBaseSubtitle => + 'Dovoli deljenje stanja baterije in osnovne telemetrije'; + + @override + String get contact_teleLoc => 'Lokacija telemetrije'; + + @override + String get contact_teleLocSubtitle => 'Dovoli deljenje podatkov o lokaciji'; + + @override + String get contact_teleEnv => 'Okolje telemetrije'; + + @override + String get contact_teleEnvSubtitle => + 'Dovoli deljenje podatkov okoljskih senzorjev'; + @override String get channels_title => 'Kanali'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index a834230..5b19be3 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -392,6 +392,49 @@ class AppLocalizationsSv extends AppLocalizations { @override String get settings_privacyModeDisabled => 'Privatläge är avstängt'; + @override + String get settings_privacy => 'Inställningar för sekretess'; + + @override + String get settings_privacySubtitle => + 'Kontrollera vilken information som delas.'; + + @override + String get settings_privacySettingsDescription => + 'Välj vilken information din enhet delar med andra.'; + + @override + String get settings_denyAll => 'Neka alla'; + + @override + String get settings_allowByContact => 'Tillåt via kontaktflaggor'; + + @override + String get settings_allowAll => 'Tillåt alla'; + + @override + String get settings_telemetryBaseMode => 'Telemetribasläge'; + + @override + String get settings_telemetryLocationMode => 'Telemetritillstånd för plats'; + + @override + String get settings_telemetryEnvironmentMode => 'Telemetri miljöläge'; + + @override + String get settings_advertLocation => 'Annonsplacering'; + + @override + String get settings_advertLocationSubtitle => 'Inkludera plats i annonsen'; + + @override + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } + + @override + String get settings_telemetryModeUpdated => 'Telemetri-läge uppdaterat'; + @override String get settings_actions => 'Åtgärder'; @@ -972,6 +1015,40 @@ class AppLocalizationsSv extends AppLocalizations { return 'Senast synlig $days dagar sedan'; } + @override + String get contact_info => 'Kontaktinformation'; + + @override + String get contact_settings => 'Kontaktinställningar'; + + @override + String get contact_telemetry => 'Telemetri'; + + @override + String get contact_lastSeen => 'Senast sedd'; + + @override + String get contact_clearChat => 'Rensa Chatt'; + + @override + String get contact_teleBase => 'Telemetribas'; + + @override + String get contact_teleBaseSubtitle => + 'Tillåt delning av batterinivå och grundläggande telemetri'; + + @override + String get contact_teleLoc => 'Telemetridata plats'; + + @override + String get contact_teleLocSubtitle => 'Tillåt delning av platsdata'; + + @override + String get contact_teleEnv => 'Telemetri Miljö'; + + @override + String get contact_teleEnvSubtitle => 'Tillåt delning av miljösensordata'; + @override String get channels_title => 'Kanaler'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 7db1cc7..096e470 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -395,6 +395,50 @@ class AppLocalizationsUk extends AppLocalizations { @override String get settings_privacyModeDisabled => 'Режим приватності вимкнено'; + @override + String get settings_privacy => 'Налаштування приватності'; + + @override + String get settings_privacySubtitle => + 'Керуйте інформацією, яку буде спільно використовуватися'; + + @override + String get settings_privacySettingsDescription => + 'Виберіть, яку інформацію ваш пристрій буде передавати іншим.'; + + @override + String get settings_denyAll => 'Відхилити все'; + + @override + String get settings_allowByContact => 'Дозволити за контактними прапорцями'; + + @override + String get settings_allowAll => 'Дозволити все'; + + @override + String get settings_telemetryBaseMode => 'Режим базової телеметрії'; + + @override + String get settings_telemetryLocationMode => 'Режим місця телеметрії'; + + @override + String get settings_telemetryEnvironmentMode => 'Режим середовища телеметрії'; + + @override + String get settings_advertLocation => 'Розміщення реклами'; + + @override + String get settings_advertLocationSubtitle => + 'Включити місце розташування в оголошення'; + + @override + String settings_multiAck(String value) { + return 'Багатократне підтвердження: $value'; + } + + @override + String get settings_telemetryModeUpdated => 'Режим телеметрії оновлено'; + @override String get settings_actions => 'Дії'; @@ -983,6 +1027,42 @@ class AppLocalizationsUk extends AppLocalizations { return 'В мережі $days дн. тому'; } + @override + String get contact_info => 'Контактна інформація'; + + @override + String get contact_settings => 'Налаштування контактів'; + + @override + String get contact_telemetry => 'Телеметрія'; + + @override + String get contact_lastSeen => 'Останній раз бачили'; + + @override + String get contact_clearChat => 'Очистити чат'; + + @override + String get contact_teleBase => 'Базовий телебачення'; + + @override + String get contact_teleBaseSubtitle => + 'Дозволити спільний доступ до рівня заряду батареї та базової телеметрії'; + + @override + String get contact_teleLoc => 'Розташування телеметрії'; + + @override + String get contact_teleLocSubtitle => + 'Дозволити спільне використання даних про місцеположення'; + + @override + String get contact_teleEnv => 'Середовище телеметрії'; + + @override + String get contact_teleEnvSubtitle => + 'Дозволити спільний доступ до даних датчиків середовища'; + @override String get channels_title => 'Канали'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index dc1a17e..f142763 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -374,6 +374,47 @@ class AppLocalizationsZh extends AppLocalizations { @override String get settings_privacyModeDisabled => '隐私模式已关闭'; + @override + String get settings_privacy => '隐私设置'; + + @override + String get settings_privacySubtitle => '控制要共享的信息。'; + + @override + String get settings_privacySettingsDescription => '选择您的设备与他人共享的信息。'; + + @override + String get settings_denyAll => '拒绝所有'; + + @override + String get settings_allowByContact => '按联系人标志允许'; + + @override + String get settings_allowAll => '允许全部'; + + @override + String get settings_telemetryBaseMode => '遥测基础模式'; + + @override + String get settings_telemetryLocationMode => '遥测位置模式'; + + @override + String get settings_telemetryEnvironmentMode => '遥测环境模式'; + + @override + String get settings_advertLocation => '广告位置'; + + @override + String get settings_advertLocationSubtitle => '在广告中包含位置'; + + @override + String settings_multiAck(String value) { + return '多重ACK:$value'; + } + + @override + String get settings_telemetryModeUpdated => '遥测模式已更新'; + @override String get settings_actions => '操作'; @@ -923,6 +964,39 @@ class AppLocalizationsZh extends AppLocalizations { return '最后在线 $days 天前'; } + @override + String get contact_info => '联系信息'; + + @override + String get contact_settings => '联系人设置'; + + @override + String get contact_telemetry => '遥测数据'; + + @override + String get contact_lastSeen => '最近出现'; + + @override + String get contact_clearChat => '清除聊天记录'; + + @override + String get contact_teleBase => '遥测基站'; + + @override + String get contact_teleBaseSubtitle => '允许共享电池电量和基本遥测数据'; + + @override + String get contact_teleLoc => '遥测位置'; + + @override + String get contact_teleLocSubtitle => '允许共享位置数据'; + + @override + String get contact_teleEnv => '遥测环境'; + + @override + String get contact_teleEnvSubtitle => '允许共享环境传感器数据'; + @override String get channels_title => '频道'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 3caea31..1b5e78c 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -1900,6 +1900,35 @@ } } }, + "settings_privacy": "Privacyinstellingen", + "settings_privacySubtitle": "Beheer welke informatie wordt gedeeld", + "settings_telemetryLocationMode": "Telemetrie-locatiemodus", + "settings_telemetryEnvironmentMode": "Telemetrie-omgevingsmodus", + "settings_advertLocation": "Advertentielocatie", + "settings_advertLocationSubtitle": "Locatie opnemen in advertentie", + "settings_privacySettingsDescription": "Kies welke informatie uw apparaat deelt met anderen", + "settings_allowByContact": "Toestaan op basis van contactvlaggen", + "settings_allowAll": "Alles toestaan", + "settings_denyAll": "Weiger alles", + "contact_info": "Contactinformatie", + "settings_telemetryBaseMode": "Telemetrie-basismodus", + "contact_teleBase": "Telemetrie_basis", + "contact_teleLoc": "Telemetrielocatie", + "contact_teleLocSubtitle": "Locatiegegevens delen toestaan", + "contact_teleEnv": "Telemetrieomgeving", + "contact_teleEnvSubtitle": "Delen van omgevingsensordata toestaan", + "contact_settings": "Contactinstellingen", + "contact_telemetry": "Telemetrie", + "contact_lastSeen": "Laatst gezien", + "contact_clearChat": "Chat leegmaken", + "contact_teleBaseSubtitle": "Sta delen van batterij niveau en basis telemetrie toe", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_maxRouteWeightSubtitle": "Het maximale gewicht dat een route kan bereiken door succesvolle leveringen.", "appSettings_initialRouteWeight": "เริ่มต้น gewicht van de route", "appSettings_maxRouteWeight": "Maximale gewicht voor de route", @@ -1910,5 +1939,7 @@ "appSettings_routeWeightFailureDecrementSubtitle": "Gewicht verwijderd van een pad na een mislukte levering", "appSettings_maxMessageRetries": "Aantal pogingen om berichten te versturen", "appSettings_maxMessageRetriesSubtitle": "Aantal pogingen om een bericht opnieuw te versturen voordat het als mislukt wordt gemarkeerd", - "path_routeWeight": "{weight}/{max}" -} + "path_routeWeight": "{weight}/{max}", + "settings_telemetryModeUpdated": "Telemetrie-modus bijgewerkt", + "settings_multiAck": "Multi-ACKs: {value}" +} \ No newline at end of file diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index c6e3fc4..ac95748 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1900,6 +1900,35 @@ } } }, + "settings_allowByContact": "Zezwalaj według flag kontaktowych", + "settings_allowAll": "Zezwalaj na wszystko", + "settings_telemetryLocationMode": "Tryb położenia telemetrycznego", + "settings_telemetryEnvironmentMode": "Tryb środowiska telemetrycznego", + "settings_advertLocation": "Lokalizacja reklamowa", + "settings_advertLocationSubtitle": "Uwzględnij lokalizację w ogłoszeniu", + "settings_denyAll": "Odmów wszystkim", + "settings_privacySubtitle": "Kontroluj jakie informacje są udostępniane.", + "settings_privacy": "Ustawienia prywatności", + "settings_privacySettingsDescription": "Wybierz jakie informacje urządzenie udostępni innym.", + "contact_info": "Informacje kontaktowe", + "settings_telemetryBaseMode": "Tryb podstawowy telemetrii", + "contact_teleBase": "Baza telemetryczna", + "contact_teleLoc": "Lokalizacja telemetryczna", + "contact_teleLocSubtitle": "Zezwalaj na udostępnianie danych lokalizacji", + "contact_teleEnv": "Środowisko telemetryczne", + "contact_teleEnvSubtitle": "Zezwalaj na udostępnianie danych czujników środowiskowych", + "contact_telemetry": "Telemetryka", + "contact_clearChat": "Wyczyść czat", + "contact_settings": "Ustawienia kontaktowe", + "contact_lastSeen": "Ostatnio widziany", + "contact_teleBaseSubtitle": "Pozwól na udostępnianie poziomu naładowania baterii i podstawowych danych telemetrycznych", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_initialRouteWeight": "Początkowa waga trasy", "appSettings_maxRouteWeight": "Maksymalny dopuszczalny ciężar pojazdu", "appSettings_initialRouteWeightSubtitle": "Początkowa waga dla nowych, odkrytych ścieżek", @@ -1910,5 +1939,7 @@ "appSettings_routeWeightFailureDecrementSubtitle": "Waga usunięta z trasy po nieudanej dostawie", "appSettings_maxMessageRetries": "Maksymalna liczba prób wysłania wiadomości", "appSettings_maxMessageRetriesSubtitle": "Liczba prób ponownego wysłania wiadomości przed oznaczaniem jej jako nieudanej", - "path_routeWeight": "{weight}/{max}" -} + "path_routeWeight": "{weight}/{max}", + "settings_telemetryModeUpdated": "Tryb telemetryczny zaktualizowany", + "settings_multiAck": "Wiele potwierdzeń: {value}" +} \ No newline at end of file diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index e7e2ec6..adddd13 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -1900,6 +1900,35 @@ } } }, + "settings_privacySettingsDescription": "Escolha quais informações o seu dispositivo compartilha com os outros.", + "settings_allowByContact": "Permitir por bandeiras de contato", + "settings_telemetryLocationMode": "Modo de Localização de Telemetria", + "settings_telemetryEnvironmentMode": "Modo de Ambiente de Telemetria", + "settings_advertLocation": "Localização do Anúncio", + "settings_advertLocationSubtitle": "Incluir localização no anúncio", + "settings_privacySubtitle": "Controle o que é compartilhado.", + "settings_denyAll": "Negar todos", + "settings_allowAll": "Permitir todos", + "settings_privacy": "Configurações de Privacidade", + "contact_info": "Informações de Contato", + "settings_telemetryBaseMode": "Modo Base de Telemetria", + "contact_teleBase": "Base de Telemetria", + "contact_teleLoc": "Localização de Telemetria", + "contact_teleLocSubtitle": "Permitir compartilhamento de dados de localização", + "contact_teleEnv": "Ambiente de Telemetria", + "contact_teleEnvSubtitle": "Permitir compartilhamento de dados do sensor de ambiente", + "contact_lastSeen": "Visto pela última vez", + "contact_clearChat": "Limpar Chat", + "contact_telemetry": "Telemetria", + "contact_settings": "Configurações de Contato", + "contact_teleBaseSubtitle": "Permitir compartilhamento do nível da bateria e telemetria básica", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_initialRouteWeight": "Peso Inicial da Rota", "appSettings_maxRouteWeight": "Peso Máximo da Rota", "appSettings_maxRouteWeightSubtitle": "Peso máximo que um determinado percurso pode acumular com entregas bem-sucedidas.", @@ -1910,5 +1939,7 @@ "appSettings_routeWeightFailureDecrementSubtitle": "Peso removido de um caminho após uma tentativa de entrega malsucedida.", "appSettings_maxMessageRetries": "Número máximo de tentativas de envio de mensagens", "appSettings_maxMessageRetriesSubtitle": "Número de tentativas de reenvio antes de classificar uma mensagem como falha.", - "path_routeWeight": "{weight}/{max}" -} + "path_routeWeight": "{weight}/{max}", + "settings_telemetryModeUpdated": "Modo de telemetria atualizado", + "settings_multiAck": "Multi-ACKs: {value}" +} \ No newline at end of file diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 92a3800..2d3df51 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -1140,6 +1140,35 @@ } } }, + "settings_privacy": "Настройки конфиденциальности", + "settings_privacySubtitle": "Контролируйте, какую информацию делиться.", + "settings_telemetryLocationMode": "Режим местоположения телеметрии", + "settings_telemetryEnvironmentMode": "Режим среды телеметрии", + "settings_advertLocation": "Местоположение рекламы", + "settings_advertLocationSubtitle": "Включить местоположение в объявление", + "settings_allowAll": "Разрешить все", + "settings_privacySettingsDescription": "Выберите, какую информацию ваше устройство будет делиться с другими.", + "settings_denyAll": "Отклонить все", + "settings_allowByContact": "Разрешить по флагам контактов", + "contact_info": "Контактная информация", + "settings_telemetryBaseMode": "Базовый режим телеметрии", + "contact_teleBase": "База телеметрии", + "contact_teleLoc": "Местоположение телеметрии", + "contact_teleLocSubtitle": "Разрешить обмен данными о местоположении", + "contact_teleEnv": "Среда телеметрии", + "contact_teleEnvSubtitle": "Разрешить обмен данными датчиков окружающей среды", + "contact_settings": "Настройки контактов", + "contact_telemetry": "Телеметрия", + "contact_clearChat": "Очистить чат", + "contact_lastSeen": "Последний раз видели", + "contact_teleBaseSubtitle": "Разрешить обмен уровнем заряда батареи и базовой телеметрией", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_maxRouteWeight": "Максимальный допустимый вес маршрута", "appSettings_maxRouteWeightSubtitle": "Максимальный вес, который может быть перевезён по определённому маршруту при успешных доставках.", "appSettings_initialRouteWeightSubtitle": "Начальный вес для новых, только что открытых маршрутов", @@ -1150,5 +1179,7 @@ "appSettings_routeWeightFailureDecrementSubtitle": "Вес, который был удален с пути после неудачной доставки.", "appSettings_maxMessageRetries": "Максимальное количество повторных попыток отправки сообщения", "appSettings_maxMessageRetriesSubtitle": "Количество попыток повторной отправки сообщения перед тем, как пометить его как неудачное.", - "path_routeWeight": "{weight}/{max}" -} + "path_routeWeight": "{weight}/{max}", + "settings_telemetryModeUpdated": "Режим телеметрии обновлен", + "settings_multiAck": "Мульти-ACK: {value}" +} \ No newline at end of file diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 75a7c7d..57eb285 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -1900,6 +1900,35 @@ } } }, + "settings_privacy": "Nastavenia súkromia", + "settings_privacySubtitle": "Ovládni, aké informácie sa zdieľajú.", + "settings_telemetryLocationMode": "Režim umiestnenia telemetrie", + "settings_telemetryBaseMode": "Základný režim telemetrie", + "settings_advertLocation": "Umiestnenie inzerátu", + "settings_telemetryEnvironmentMode": "Režim prostredia telemetrie", + "settings_advertLocationSubtitle": "Zahrnúť polohu do inzerátu", + "settings_allowAll": "Povoliť všetko", + "settings_privacySettingsDescription": "Vyberte, ktoré informácie váš zariadenie zdieľa s ostatnými.", + "settings_denyAll": "Zamietnuť všetko", + "settings_allowByContact": "Povoliť podľa kontaktových vlajok", + "contact_info": "Kontaktné informácie", + "contact_settings": "Nastavenia kontaktov", + "contact_teleBaseSubtitle": "Povoliť zdieľanie úrovne batérie a základnej telemetrie", + "contact_teleLoc": "Lokácia telemetrie", + "contact_teleLocSubtitle": "Povoliť zdieľanie údajov o lokalite", + "contact_teleEnv": "Prostredie telemetrie", + "contact_telemetry": "Telemetria", + "contact_clearChat": "Vymazať chat", + "contact_lastSeen": "Naposledy videný", + "contact_teleBase": "Báza telemetrie", + "contact_teleEnvSubtitle": "Povoliť zdieľanie údajov senzorov prostredia", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_maxRouteWeightSubtitle": "Maximálna hmotnosť, ktorú môže trás prenášať vďaka úspešným zásielkam.", "appSettings_initialRouteWeightSubtitle": "Počiatočná váha pre nové, objavené cesty", "appSettings_initialRouteWeight": "Počiatočná váha trasy", @@ -1910,5 +1939,7 @@ "appSettings_routeWeightFailureDecrementSubtitle": "Hmotnosť odstránená z cesty po neúspešnej doručenie", "appSettings_maxMessageRetries": "Maximalný počet pokusov o doručenie správ", "appSettings_maxMessageRetriesSubtitle": "Počet pokusov o odošleť pred označením správy ako neúspešnej", - "path_routeWeight": "{weight}/{max}" -} + "path_routeWeight": "{weight}/{max}", + "settings_telemetryModeUpdated": "Režim telemetrie bol aktualizovaný", + "settings_multiAck": "Viaceré ACK: {value}" +} \ No newline at end of file diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index 5ab4736..355f8d8 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -1900,6 +1900,35 @@ } } }, + "settings_privacy": "Nastavitve zasebnosti", + "settings_privacySettingsDescription": "Izberite, katere informacije vaš naprava deli z drugimi.", + "settings_telemetryBaseMode": "Osnovni način telemetrije", + "settings_telemetryLocationMode": "Način delovanja telemetrije", + "settings_telemetryEnvironmentMode": "Način delovanja okolja telemetrije", + "settings_advertLocation": "Lokacija oglasa", + "settings_allowByContact": "Dovoli po kontaktnih zastavah", + "settings_denyAll": "Zavrniti vse", + "settings_allowAll": "Dovoli vse", + "settings_privacySubtitle": "Kontrolirajte, katere informacije so deljene.", + "contact_info": "Kontaktni podatki", + "contact_teleBase": "Baza telemetrije", + "contact_teleBaseSubtitle": "Dovoli deljenje stanja baterije in osnovne telemetrije", + "contact_teleLoc": "Lokacija telemetrije", + "contact_lastSeen": "Zadnjič videno", + "contact_settings": "Nastavitve stika", + "settings_advertLocationSubtitle": "Vključi lokacijo v oglas.", + "contact_telemetry": "Telemetrija", + "contact_clearChat": "Počisti klepet", + "contact_teleEnv": "Okolje telemetrije", + "contact_teleEnvSubtitle": "Dovoli deljenje podatkov okoljskih senzorjev", + "contact_teleLocSubtitle": "Dovoli deljenje podatkov o lokaciji", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_maxRouteWeightSubtitle": "Največja teža, ki jo lahko pot doseže s uspešnimi dostavnami.", "appSettings_initialRouteWeight": "Izvirna teža poti", "appSettings_initialRouteWeightSubtitle": "Izguba teže za nove, odkriti poti", @@ -1910,5 +1939,7 @@ "appSettings_routeWeightFailureDecrementSubtitle": "Težo, ki ni bila uspešno dostavljena, odstranili s poti.", "appSettings_maxMessageRetries": "Najve število poskusov pošiljanja sporočil", "appSettings_maxMessageRetriesSubtitle": "Število poskusov ponovnega poslanja, preden se sporočilo označuje kot neuspešno", - "path_routeWeight": "{weight}/{max}" -} + "path_routeWeight": "{weight}/{max}", + "settings_multiAck": "Večkratni potrditvi: {value}", + "settings_telemetryModeUpdated": "Način telemetrije posodobljen" +} \ No newline at end of file diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index 644b43b..84f4e5e 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -1900,6 +1900,35 @@ } } }, + "settings_privacy": "Inställningar för sekretess", + "settings_allowAll": "Tillåt alla", + "settings_privacySubtitle": "Kontrollera vilken information som delas.", + "settings_telemetryEnvironmentMode": "Telemetri miljöläge", + "settings_telemetryBaseMode": "Telemetribasläge", + "settings_telemetryLocationMode": "Telemetritillstånd för plats", + "settings_advertLocation": "Annonsplacering", + "contact_info": "Kontaktinformation", + "contact_settings": "Kontaktinställningar", + "contact_telemetry": "Telemetri", + "settings_denyAll": "Neka alla", + "settings_allowByContact": "Tillåt via kontaktflaggor", + "settings_privacySettingsDescription": "Välj vilken information din enhet delar med andra.", + "contact_lastSeen": "Senast sedd", + "contact_clearChat": "Rensa Chatt", + "contact_teleEnv": "Telemetri Miljö", + "settings_advertLocationSubtitle": "Inkludera plats i annonsen", + "contact_teleEnvSubtitle": "Tillåt delning av miljösensordata", + "contact_teleBase": "Telemetribas", + "contact_teleBaseSubtitle": "Tillåt delning av batterinivå och grundläggande telemetri", + "contact_teleLoc": "Telemetridata plats", + "contact_teleLocSubtitle": "Tillåt delning av platsdata", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_initialRouteWeightSubtitle": "Initial vikt för nyligen upptäckta vägar", "appSettings_maxRouteWeight": "Maximalt tillåtet vikt för rutten", "appSettings_maxRouteWeightSubtitle": "Maximal vikt som en leveransväg kan ackumulera från framgångsrika leveranser.", @@ -1910,5 +1939,7 @@ "appSettings_routeWeightFailureDecrementSubtitle": "Vikt som tagits bort från en väg efter ett misslyckat leveransförsök", "appSettings_maxMessageRetries": "Maximalt antal försök", "appSettings_maxMessageRetriesSubtitle": "Antal försök att skicka om ett meddelande innan det markeras som misslyckat.", - "path_routeWeight": "{weight}/{max}" -} + "path_routeWeight": "{weight}/{max}", + "settings_telemetryModeUpdated": "Telemetri-läge uppdaterat", + "settings_multiAck": "Multi-ACKs: {value}" +} \ No newline at end of file diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index 249fd3b..be1eaa8 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1900,6 +1900,35 @@ } } }, + "settings_privacySubtitle": "Керуйте інформацією, яку буде спільно використовуватися", + "settings_privacy": "Налаштування приватності", + "settings_telemetryBaseMode": "Режим базової телеметрії", + "settings_telemetryLocationMode": "Режим місця телеметрії", + "settings_advertLocation": "Розміщення реклами", + "settings_advertLocationSubtitle": "Включити місце розташування в оголошення", + "settings_privacySettingsDescription": "Виберіть, яку інформацію ваш пристрій буде передавати іншим.", + "settings_allowAll": "Дозволити все", + "settings_denyAll": "Відхилити все", + "settings_allowByContact": "Дозволити за контактними прапорцями", + "settings_telemetryEnvironmentMode": "Режим середовища телеметрії", + "contact_info": "Контактна інформація", + "contact_teleBaseSubtitle": "Дозволити спільний доступ до рівня заряду батареї та базової телеметрії", + "contact_teleLoc": "Розташування телеметрії", + "contact_teleBase": "Базовий телебачення", + "contact_teleLocSubtitle": "Дозволити спільне використання даних про місцеположення", + "contact_settings": "Налаштування контактів", + "contact_telemetry": "Телеметрія", + "contact_clearChat": "Очистити чат", + "contact_lastSeen": "Останній раз бачили", + "contact_teleEnv": "Середовище телеметрії", + "contact_teleEnvSubtitle": "Дозволити спільний доступ до даних датчиків середовища", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_initialRouteWeight": "Початкова вартість маршруту", "appSettings_initialRouteWeightSubtitle": "Початкова вага для нових відкритих шляхів", "appSettings_maxRouteWeight": "Максимальна вага маршруту", @@ -1910,5 +1939,7 @@ "appSettings_routeWeightFailureDecrementSubtitle": "Вага, яка була знята з маршруту після невдалої доставки", "appSettings_maxMessageRetries": "Максимальна кількість повторних спроб надсилання повідомлення", "appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале", - "path_routeWeight": "{weight}/{max}" -} + "path_routeWeight": "{weight}/{max}", + "settings_telemetryModeUpdated": "Режим телеметрії оновлено", + "settings_multiAck": "Багатократне підтвердження: {value}" +} \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 1d4ed30..9493b27 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1905,6 +1905,35 @@ } } }, + "settings_privacySubtitle": "控制要共享的信息。", + "settings_privacySettingsDescription": "选择您的设备与他人共享的信息。", + "settings_telemetryBaseMode": "遥测基础模式", + "settings_telemetryLocationMode": "遥测位置模式", + "settings_advertLocation": "广告位置", + "settings_advertLocationSubtitle": "在广告中包含位置", + "settings_allowByContact": "按联系人标志允许", + "settings_denyAll": "拒绝所有", + "settings_privacy": "隐私设置", + "settings_allowAll": "允许全部", + "contact_info": "联系信息", + "contact_teleBase": "遥测基站", + "contact_teleBaseSubtitle": "允许共享电池电量和基本遥测数据", + "settings_telemetryEnvironmentMode": "遥测环境模式", + "contact_teleLoc": "遥测位置", + "contact_teleEnv": "遥测环境", + "contact_teleEnvSubtitle": "允许共享环境传感器数据", + "contact_clearChat": "清除聊天记录", + "contact_lastSeen": "最近出现", + "contact_settings": "联系人设置", + "contact_teleLocSubtitle": "允许共享位置数据", + "contact_telemetry": "遥测数据", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_maxRouteWeight": "最大路径重量", "appSettings_initialRouteWeightSubtitle": "新发现路径的初始重量", "appSettings_initialRouteWeight": "初始路线权重", @@ -1915,5 +1944,7 @@ "appSettings_routeWeightFailureDecrementSubtitle": "从一条路径上移除的货物,由于无法成功交付而移除。", "appSettings_maxMessageRetries": "最大消息重试次数", "appSettings_maxMessageRetriesSubtitle": "在将消息标记为失败之前,允许尝试的次数", - "path_routeWeight": "{weight}/{max}" -} + "path_routeWeight": "{weight}/{max}", + "settings_multiAck": "多重ACK:{value}", + "settings_telemetryModeUpdated": "遥测模式已更新" +} \ No newline at end of file diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 71467b1..acd1da9 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -214,4 +214,7 @@ class Contact { @override int get hashCode => publicKeyHex.hashCode; + bool get teleBaseEnabled => (flags & contactFlagTeleBase) != 0; + bool get teleLocEnabled => (flags & contactFlagTeleLoc) != 0; + bool get teleEnvEnabled => (flags & contactFlagTeleEnv) != 0; } diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index 20110e1..913b288 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -166,6 +166,33 @@ class _ChannelChatScreenState extends State { ], ), centerTitle: false, + actions: [ + PopupMenuButton( + icon: const Icon(Icons.more_vert), + onSelected: (value) { + if (value == 'clearChat') { + context.read().clearMessagesForChannel( + widget.channel.index, + ); + } + }, + itemBuilder: (context) => [ + PopupMenuItem( + value: 'clearChat', + child: Row( + children: [ + const Icon(Icons.delete, size: 20, color: Colors.red), + const SizedBox(width: 12), + Text( + context.l10n.contact_clearChat, + style: const TextStyle(color: Colors.red), + ), + ], + ), + ), + ], + ), + ], ), body: SafeArea( top: false, diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index ace82b5..574ffbe 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -38,6 +38,7 @@ import '../widgets/gif_picker.dart'; import '../widgets/path_selection_dialog.dart'; import '../utils/app_logger.dart'; import '../l10n/l10n.dart'; +import 'telemetry_screen.dart'; class ChatScreen extends StatefulWidget { final Contact contact; @@ -246,9 +247,77 @@ class _ChatScreenState extends State { tooltip: context.l10n.chat_pathManagement, onPressed: () => _showPathHistory(context), ), - IconButton( - icon: const Icon(Icons.info_outline), - onPressed: () => _showContactInfo(context), + Consumer( + builder: (context, connector, _) { + return PopupMenuButton( + icon: const Icon(Icons.more_vert), + onSelected: (value) { + if (value == 'info') { + _showContactInfo(context); + } + if (value == 'settings') { + _showContactSettings(context); + } + if (value == 'telemetry') { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + TelemetryScreen(contact: widget.contact), + ), + ); + } + if (value == 'clearChat') { + connector.clearMessagesForContact(widget.contact); + } + }, + itemBuilder: (context) => [ + PopupMenuItem( + value: 'info', + child: Row( + children: [ + const Icon(Icons.info_outline, size: 20), + const SizedBox(width: 12), + Text(context.l10n.contact_info), + ], + ), + ), + PopupMenuItem( + value: 'telemetry', + child: Row( + children: [ + const Icon(Icons.bar_chart, size: 20), + const SizedBox(width: 12), + Text(context.l10n.contact_telemetry), + ], + ), + ), + PopupMenuItem( + value: 'settings', + child: Row( + children: [ + const Icon(Icons.settings, size: 20), + const SizedBox(width: 12), + Text(context.l10n.contact_settings), + ], + ), + ), + PopupMenuItem( + value: 'clearChat', + child: Row( + children: [ + const Icon(Icons.delete, size: 20, color: Colors.red), + const SizedBox(width: 12), + Text( + context.l10n.contact_clearChat, + style: const TextStyle(color: Colors.red), + ), + ], + ), + ), + ], + ); + }, ), ], ), @@ -895,11 +964,22 @@ class _ChatScreenState extends State { ); } + int _resolveContactIndex = -1; + Contact _resolveContact(MeshCoreConnector connector) { - return connector.contacts.firstWhere( + if (_resolveContactIndex >= 0 && + _resolveContactIndex < connector.contacts.length && + connector.contacts[_resolveContactIndex].publicKeyHex == + widget.contact.publicKeyHex) { + return connector.contacts[_resolveContactIndex]; + } + _resolveContactIndex = connector.contacts.indexWhere( (c) => c.publicKeyHex == widget.contact.publicKeyHex, - orElse: () => widget.contact, ); + if (_resolveContactIndex == -1) { + return widget.contact; + } + return connector.contacts[_resolveContactIndex]; } Contact _resolveContactFrom4Bytes( @@ -952,59 +1032,127 @@ class _ChatScreenState extends State { void _showContactInfo(BuildContext context) { final connector = Provider.of(context, listen: false); - connector.ensureContactSmazSettingLoaded(widget.contact.publicKeyHex); - + final contact = _resolveContact(connector); showDialog( context: context, - builder: (context) => Consumer( - builder: (context, connector, _) { - final contact = _resolveContact(connector); - final smazEnabled = connector.isContactSmazEnabled( - contact.publicKeyHex, - ); - - return AlertDialog( - title: Text(contact.name), - content: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildInfoRow(context.l10n.chat_type, contact.typeLabel), - _buildInfoRow(context.l10n.chat_path, contact.pathLabel), - if (contact.hasLocation) - _buildInfoRow( - context.l10n.chat_location, - '${contact.latitude?.toStringAsFixed(4)}, ${contact.longitude?.toStringAsFixed(4)}', - ), - _buildInfoRow( - context.l10n.chat_publicKey, - '${contact.publicKeyHex.substring(0, 16)}...', - ), - const Divider(), - SwitchListTile( - contentPadding: EdgeInsets.zero, - title: Text(context.l10n.channels_smazCompression), - subtitle: Text(context.l10n.chat_compressOutgoingMessages), - value: smazEnabled, - onChanged: (value) { - connector.setContactSmazEnabled( - contact.publicKeyHex, - value, - ); - }, - ), - ], - ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(context.l10n.common_close), + builder: (context) => AlertDialog( + title: SelectableText(contact.name), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInfoRow(context.l10n.chat_type, contact.typeLabel), + _buildInfoRow(context.l10n.chat_path, contact.pathLabel), + _buildInfoRow( + context.l10n.contact_lastSeen, + _formatContactLastMessage(contact.lastMessageAt), ), + if (contact.hasLocation) + _buildInfoRow( + context.l10n.chat_location, + '${contact.latitude?.toStringAsFixed(4)}, ${contact.longitude?.toStringAsFixed(4)}', + ), + _buildInfoRow(context.l10n.chat_publicKey, contact.publicKeyHex), ], - ); - }, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(context.l10n.common_close), + ), + ], + ), + ); + } + + void _showContactSettings(BuildContext context) { + final connector = Provider.of(context, listen: false); + connector.ensureContactSmazSettingLoaded(widget.contact.publicKeyHex); + final contact = widget.contact; + bool smazEnabled = connector.isContactSmazEnabled(contact.publicKeyHex); + bool teleBaseEnabled = contact.teleBaseEnabled; + bool teleLocEnabled = contact.teleLocEnabled; + bool teleEnvEnabled = contact.teleEnvEnabled; + showDialog( + context: context, + builder: (context) => StatefulBuilder( + builder: (context, setDialogState) => AlertDialog( + title: Text(context.l10n.contact_settings), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (contact.hasLocation) ...[ + _buildInfoRow( + context.l10n.chat_location, + '${contact.latitude?.toStringAsFixed(4)}, ${contact.longitude?.toStringAsFixed(4)}', + ), + const Divider(height: 8), + ], + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: Text(context.l10n.channels_smazCompression), + subtitle: Text(context.l10n.chat_compressOutgoingMessages), + value: smazEnabled, + onChanged: (value) { + connector.setContactSmazEnabled( + contact.publicKeyHex, + value, + ); + setDialogState(() => smazEnabled = value); + }, + ), + const Divider(height: 8), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: Text(context.l10n.contact_teleBase), + subtitle: Text(context.l10n.contact_teleBaseSubtitle), + value: teleBaseEnabled, + onChanged: (value) { + setDialogState(() => teleBaseEnabled = value); + }, + ), + const Divider(height: 8), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: Text(context.l10n.contact_teleLoc), + subtitle: Text(context.l10n.contact_teleLocSubtitle), + value: teleLocEnabled, + onChanged: (value) { + setDialogState(() => teleLocEnabled = value); + }, + ), + const Divider(height: 8), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: Text(context.l10n.contact_teleEnv), + subtitle: Text(context.l10n.contact_teleEnvSubtitle), + value: teleEnvEnabled, + onChanged: (value) { + setDialogState(() => teleEnvEnabled = value); + }, + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + connector.setContactFlags( + contact, + teleBase: teleBaseEnabled, + teleLoc: teleLocEnabled, + teleEnv: teleEnvEnabled, + ); + Navigator.pop(context); + }, + child: Text(context.l10n.common_close), + ), + ], + ), ), ); } @@ -1019,12 +1167,32 @@ class _ChatScreenState extends State { width: 80, child: Text(label, style: TextStyle(color: Colors.grey[600])), ), - Expanded(child: Text(value)), + Expanded(child: SelectableText(value)), ], ), ); } + String _formatContactLastMessage(DateTime timestamp) { + final diff = DateTime.now().difference(timestamp); + 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); + } + final days = diff.inDays; + return days == 1 + ? context.l10n.contacts_lastSeenDayAgo + : context.l10n.contacts_lastSeenDaysAgo(days); + } + void _openChat(BuildContext context, Contact contact) { // Check if this is a repeater context.read().markContactRead(contact.publicKeyHex); diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index 011e6d0..17eaa24 100644 --- a/lib/screens/contacts_screen.dart +++ b/lib/screens/contacts_screen.dart @@ -1354,7 +1354,10 @@ class _ContactsScreenState extends State ), onTap: () async { Navigator.pop(sheetContext); - await connector.setContactFavorite(contact, !isFavorite); + await connector.setContactFlags( + contact, + isFavorite: !isFavorite, + ); }, ), ListTile( diff --git a/lib/screens/neighbors_screen.dart b/lib/screens/neighbors_screen.dart index 5afeda4..f4c1673 100644 --- a/lib/screens/neighbors_screen.dart +++ b/lib/screens/neighbors_screen.dart @@ -44,6 +44,24 @@ class _NeighborsScreenState extends State { PathSelection? _pendingStatusSelection; List>? _parsedNeighbors; + int _resolveRepeaterIndex = -1; + + Contact _resolveRepeater(MeshCoreConnector connector) { + if (_resolveRepeaterIndex >= 0 && + _resolveRepeaterIndex < connector.contacts.length && + connector.contacts[_resolveRepeaterIndex].publicKeyHex == + widget.repeater.publicKeyHex) { + return connector.contacts[_resolveRepeaterIndex]; + } + _resolveRepeaterIndex = connector.contacts.indexWhere( + (c) => c.publicKeyHex == widget.repeater.publicKeyHex, + ); + if (_resolveRepeaterIndex == -1) { + return widget.repeater; + } + return connector.contacts[_resolveRepeaterIndex]; + } + @override void initState() { super.initState(); @@ -163,13 +181,6 @@ class _NeighborsScreenState extends State { } } - Contact _resolveRepeater(MeshCoreConnector connector) { - return connector.contacts.firstWhere( - (c) => c.publicKeyHex == widget.repeater.publicKeyHex, - orElse: () => widget.repeater, - ); - } - Future _loadNeighbors() async { if (_commandService == null) return; diff --git a/lib/screens/repeater_cli_screen.dart b/lib/screens/repeater_cli_screen.dart index 1c7ff43..52d92aa 100644 --- a/lib/screens/repeater_cli_screen.dart +++ b/lib/screens/repeater_cli_screen.dart @@ -77,11 +77,22 @@ class _RepeaterCliScreenState extends State { }); } + int _resolveRepeaterIndex = -1; + Contact _resolveRepeater(MeshCoreConnector connector) { - return connector.contacts.firstWhere( + if (_resolveRepeaterIndex >= 0 && + _resolveRepeaterIndex < connector.contacts.length && + connector.contacts[_resolveRepeaterIndex].publicKeyHex == + widget.repeater.publicKeyHex) { + return connector.contacts[_resolveRepeaterIndex]; + } + _resolveRepeaterIndex = connector.contacts.indexWhere( (c) => c.publicKeyHex == widget.repeater.publicKeyHex, - orElse: () => widget.repeater, ); + if (_resolveRepeaterIndex == -1) { + return widget.repeater; + } + return connector.contacts[_resolveRepeaterIndex]; } void _handleTextMessageResponse(Uint8List frame) { diff --git a/lib/screens/repeater_hub_screen.dart b/lib/screens/repeater_hub_screen.dart index fd2da8e..8a14253 100644 --- a/lib/screens/repeater_hub_screen.dart +++ b/lib/screens/repeater_hub_screen.dart @@ -205,8 +205,7 @@ class RepeaterHubScreen extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => - TelemetryScreen(repeater: repeater, password: password), + builder: (context) => TelemetryScreen(contact: repeater), ), ); }, diff --git a/lib/screens/repeater_settings_screen.dart b/lib/screens/repeater_settings_screen.dart index bae0f50..6375e0b 100644 --- a/lib/screens/repeater_settings_screen.dart +++ b/lib/screens/repeater_settings_screen.dart @@ -129,11 +129,22 @@ class _RepeaterSettingsScreenState extends State { _commandService?.handleResponse(widget.repeater, parsed.text); } + int _resolveRepeaterIndex = -1; + Contact _resolveRepeater(MeshCoreConnector connector) { - return connector.contacts.firstWhere( + if (_resolveRepeaterIndex >= 0 && + _resolveRepeaterIndex < connector.contacts.length && + connector.contacts[_resolveRepeaterIndex].publicKeyHex == + widget.repeater.publicKeyHex) { + return connector.contacts[_resolveRepeaterIndex]; + } + _resolveRepeaterIndex = connector.contacts.indexWhere( (c) => c.publicKeyHex == widget.repeater.publicKeyHex, - orElse: () => widget.repeater, ); + if (_resolveRepeaterIndex == -1) { + return widget.repeater; + } + return connector.contacts[_resolveRepeaterIndex]; } bool _matchesRepeaterPrefix(Uint8List prefix) { diff --git a/lib/screens/repeater_status_screen.dart b/lib/screens/repeater_status_screen.dart index 95254f4..f938419 100644 --- a/lib/screens/repeater_status_screen.dart +++ b/lib/screens/repeater_status_screen.dart @@ -91,11 +91,22 @@ class _RepeaterStatusScreenState extends State { }); } + int _resolveRepeaterIndex = -1; + Contact _resolveRepeater(MeshCoreConnector connector) { - return connector.contacts.firstWhere( + if (_resolveRepeaterIndex >= 0 && + _resolveRepeaterIndex < connector.contacts.length && + connector.contacts[_resolveRepeaterIndex].publicKeyHex == + widget.repeater.publicKeyHex) { + return connector.contacts[_resolveRepeaterIndex]; + } + _resolveRepeaterIndex = connector.contacts.indexWhere( (c) => c.publicKeyHex == widget.repeater.publicKeyHex, - orElse: () => widget.repeater, ); + if (_resolveRepeaterIndex == -1) { + return widget.repeater; + } + return connector.contacts[_resolveRepeaterIndex]; } void _handleTextMessageResponse(Uint8List frame) { diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index d6118f5..cc61143 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -287,10 +287,10 @@ class _SettingsScreenState extends State { const Divider(height: 1), ListTile( leading: const Icon(Icons.visibility_off_outlined), - title: Text(l10n.settings_privacyMode), - subtitle: Text(l10n.settings_privacyModeSubtitle), + title: Text(l10n.settings_privacy), + subtitle: Text(l10n.settings_privacySubtitle), trailing: const Icon(Icons.chevron_right), - onTap: () => _togglePrivacy(context, connector), + onTap: () => _privacySettings(context, connector), ), ], ), @@ -657,47 +657,6 @@ class _SettingsScreenState extends State { ); } - void _togglePrivacy(BuildContext context, MeshCoreConnector connector) { - final l10n = context.l10n; - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text(l10n.settings_privacyMode), - content: Text(l10n.settings_privacyModeToggle), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(l10n.common_cancel), - ), - TextButton( - onPressed: () async { - Navigator.pop(context); - await connector.setPrivacyMode(true); - await connector.refreshDeviceInfo(); - if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.settings_privacyModeEnabled)), - ); - }, - child: Text(l10n.common_enable), - ), - TextButton( - onPressed: () async { - Navigator.pop(context); - await connector.setPrivacyMode(false); - await connector.refreshDeviceInfo(); - if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.settings_privacyModeDisabled)), - ); - }, - child: Text(l10n.common_disable), - ), - ], - ), - ); - } - void _sendAdvert(BuildContext context, MeshCoreConnector connector) { final l10n = context.l10n; connector.sendSelfAdvert(flood: true); @@ -977,6 +936,137 @@ class _SettingsScreenState extends State { } } +void _privacySettings(BuildContext context, MeshCoreConnector connector) { + final l10n = context.l10n; + + int telemetryMode = connector.telemetryModeBase; + int telemetryLocMode = connector.telemetryModeLoc; + int telemetryEnvMode = connector.telemetryModeEnv; + bool advertLocPolicy = connector.advertLocationPolicy == 0 ? false : true; + int multiAcks = connector.multiAcks; + + final telemModeBase = [ + DropdownMenuItem(value: teleModeDeny, child: Text(l10n.settings_denyAll)), + DropdownMenuItem( + value: teleModeAllowFlags, + child: Text(l10n.settings_allowByContact), + ), + DropdownMenuItem( + value: teleModeAllowAll, + child: Text(l10n.settings_allowAll), + ), + ]; + + showDialog( + context: context, + builder: (dialogContext) => StatefulBuilder( + builder: (context, setDialogState) => AlertDialog( + title: Text(l10n.settings_privacy), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(l10n.settings_privacySettingsDescription), + const SizedBox(height: 16), + FeatureToggleRow( + title: l10n.settings_advertLocation, + subtitle: l10n.settings_advertLocationSubtitle, + value: advertLocPolicy, + onChanged: (value) { + setDialogState(() => advertLocPolicy = value); + advertLocPolicy = value; + }, + ), + const SizedBox(height: 8), + DropdownButtonFormField( + initialValue: telemetryMode, + decoration: InputDecoration( + labelText: l10n.settings_telemetryBaseMode, + border: const OutlineInputBorder(), + ), + items: telemModeBase, + onChanged: (value) { + if (value != null) { + setDialogState(() => telemetryMode = value); + } + }, + ), + const SizedBox(height: 16), + DropdownButtonFormField( + initialValue: telemetryLocMode, + decoration: InputDecoration( + labelText: l10n.settings_telemetryLocationMode, + border: const OutlineInputBorder(), + ), + items: telemModeBase, + onChanged: (value) { + if (value != null) { + setDialogState(() => telemetryLocMode = value); + } + }, + ), + const SizedBox(height: 16), + DropdownButtonFormField( + initialValue: telemetryEnvMode, + decoration: InputDecoration( + labelText: l10n.settings_telemetryEnvironmentMode, + border: const OutlineInputBorder(), + ), + items: telemModeBase, + onChanged: (value) { + if (value != null) { + setDialogState(() => telemetryEnvMode = value); + } + }, + ), + const SizedBox(height: 16), + Text( + l10n.settings_multiAck(multiAcks.toString()), + style: Theme.of(context).textTheme.bodyMedium, + ), + Slider( + value: multiAcks.toDouble(), + min: 0, + max: 2, + divisions: 2, + label: multiAcks.toString(), + onChanged: (value) { + setDialogState(() => multiAcks = value.round()); + }, + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(l10n.common_cancel), + ), + TextButton( + onPressed: () async { + Navigator.pop(context); + await connector.setTelemetryModeBase( + telemetryMode, + telemetryLocMode, + telemetryEnvMode, + advertLocPolicy ? 1 : 0, + multiAcks, + ); + await connector.refreshDeviceInfo(); + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(l10n.settings_telemetryModeUpdated)), + ); + }, + child: Text(l10n.common_save), + ), + ], + ), + ), + ); +} + class _RadioSettingsDialog extends StatefulWidget { final MeshCoreConnector connector; diff --git a/lib/screens/telemetry_screen.dart b/lib/screens/telemetry_screen.dart index 3f95ccd..66911dc 100644 --- a/lib/screens/telemetry_screen.dart +++ b/lib/screens/telemetry_screen.dart @@ -10,30 +10,22 @@ import '../connector/meshcore_connector.dart'; import '../connector/meshcore_protocol.dart'; import '../services/app_settings_service.dart'; import '../services/repeater_command_service.dart'; +import '../utils/app_logger.dart'; import '../widgets/path_management_dialog.dart'; import '../helpers/cayenne_lpp.dart'; import '../utils/battery_utils.dart'; class TelemetryScreen extends StatefulWidget { - final Contact repeater; - final String password; + final Contact contact; - const TelemetryScreen({ - super.key, - required this.repeater, - required this.password, - }); + const TelemetryScreen({super.key, required this.contact}); @override State createState() => _TelemetryScreenState(); } class _TelemetryScreenState extends State { - static const int _statusPayloadOffset = 8; - static const int _statusStatsSize = 52; - static const int _statusResponseBytes = - _statusPayloadOffset + _statusStatsSize; - Uint8List _tagData = Uint8List(4); + int _tagData = 0; bool _isLoading = false; bool _isLoaded = false; @@ -44,6 +36,26 @@ class _TelemetryScreenState extends State { PathSelection? _pendingStatusSelection; List>? _parsedTelemetry; + int _tripTime = 0; + + int _resolveContactIndex = -1; + + Contact _resolveContact(MeshCoreConnector connector) { + if (_resolveContactIndex >= 0 && + _resolveContactIndex < connector.contacts.length && + connector.contacts[_resolveContactIndex].publicKeyHex == + widget.contact.publicKeyHex) { + return connector.contacts[_resolveContactIndex]; + } + _resolveContactIndex = connector.contacts.indexWhere( + (c) => c.publicKeyHex == widget.contact.publicKeyHex, + ); + if (_resolveContactIndex == -1) { + return widget.contact; + } + return connector.contacts[_resolveContactIndex]; + } + @override void initState() { super.initState(); @@ -60,27 +72,62 @@ class _TelemetryScreenState extends State { // Listen for incoming text messages from the repeater _frameSubscription = connector.receivedFrames.listen((frame) { if (frame.isEmpty) return; + final reader = BufferReader(frame); + try { + final cmd = reader.readByte(); + if (cmd == respCodeSent) { + reader.skipBytes(1); // Skip the reserved byte + _tagData = reader.readUInt32LE(); + _tripTime = reader.readUInt32LE(); + _statusTimeout?.cancel(); + _statusTimeout = Timer(Duration(milliseconds: _tripTime), () { + if (!mounted) return; + setState(() { + _isLoading = false; + _isLoaded = false; + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(context.l10n.telemetry_requestTimeout), + backgroundColor: Colors.red, + ), + ); + _recordTelemetryResult(false); + }); + } - if (frame[0] == respCodeSent) { - _tagData = frame.sublist(2, 6); - } + // Check if it's a binary response + if (cmd == pushCodeBinaryResponse) { + if (!mounted) return; + reader.skipBytes(1); // Skip the reserved byte + if (reader.readUInt32LE() != _tagData) return; + _handleTelemetryResponse(reader.readRemainingBytes()); + } - // Check if it's a binary response - if (frame[0] == pushCodeBinaryResponse && - listEquals(frame.sublist(2, 6), _tagData)) { - if (!mounted) return; - _handleStatusResponse(frame.sublist(6)); + // Check if it's a telemetry response (for chat contacts) + if (cmd == pushCodeTelemetryResponse) { + reader.skipBytes(1); // Skip the reserved byte + final pubkey = reader.readBytes(6); + if (!mounted) return; + if (!listEquals(widget.contact.publicKey.sublist(0, 6), pubkey)) { + return; + } + _handleTelemetryResponse(reader.readRemainingBytes()); + } + } catch (e) { + appLogger.error('Error parsing incoming frame: $e'); + // If parsing fails, ignore the frame } }); } - void _handleStatusResponse(Uint8List frame) { + void _handleTelemetryResponse(Uint8List frame) { final parsedTelemetry = CayenneLpp.parseByChannel(frame); final batteryMv = _extractTelemetryBatteryMillivolts(parsedTelemetry); if (batteryMv != null) { final connector = Provider.of(context, listen: false); connector.updateRepeaterBatterySnapshot( - widget.repeater.publicKeyHex, + widget.contact.publicKeyHex, batteryMv, source: 'telemetry', ); @@ -105,13 +152,6 @@ class _TelemetryScreenState extends State { }); } - Contact _resolveRepeater(MeshCoreConnector connector) { - return connector.contacts.firstWhere( - (c) => c.publicKeyHex == widget.repeater.publicKeyHex, - orElse: () => widget.repeater, - ); - } - Future _loadTelemetry() async { if (_commandService == null) return; @@ -121,41 +161,20 @@ class _TelemetryScreenState extends State { }); try { final connector = Provider.of(context, listen: false); - final repeater = _resolveRepeater(connector); - final selection = await connector.preparePathForContactSend(repeater); + final selection = await connector.preparePathForContactSend( + _resolveContact(connector), + ); _pendingStatusSelection = selection; - final frame = buildSendBinaryReq( - repeater.publicKey, - payload: Uint8List.fromList([reqTypeGetTelemetry]), - ); - await connector.sendFrame(frame); - - final pathLengthValue = selection.useFlood ? -1 : selection.hopCount; - var messageBytes = frame.length >= _statusResponseBytes - ? frame.length - : _statusResponseBytes; - if (messageBytes < maxFrameSize) { - messageBytes = maxFrameSize; - } - 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.telemetry_requestTimeout), - backgroundColor: Colors.red, - ), + Uint8List frame; + if (widget.contact.type != advTypeChat) { + frame = buildSendBinaryReq( + widget.contact.publicKey, + payload: Uint8List.fromList([reqTypeGetTelemetry]), ); - _recordStatusResult(false); - }); + } else { + frame = buildSendTelemetryReq(widget.contact.publicKey); + } + await connector.sendFrame(frame); } catch (e) { if (mounted) { setState(() { @@ -173,12 +192,16 @@ class _TelemetryScreenState extends State { } } - void _recordStatusResult(bool success) { + void _recordTelemetryResult(bool success) { final selection = _pendingStatusSelection; if (selection == null) return; final connector = Provider.of(context, listen: false); - final repeater = _resolveRepeater(connector); - connector.recordRepeaterPathResult(repeater, selection, success, null); + connector.recordRepeaterPathResult( + widget.contact, + selection, + success, + null, + ); _pendingStatusSelection = null; } @@ -196,8 +219,7 @@ class _TelemetryScreenState extends State { final connector = context.watch(); final settings = context.watch().settings; final isImperialUnits = settings.unitSystem == UnitSystem.imperial; - final repeater = _resolveRepeater(connector); - final isFloodMode = repeater.pathOverride == -1; + final isFloodMode = widget.contact.pathOverride == -1; return Scaffold( appBar: AppBar( @@ -210,7 +232,7 @@ class _TelemetryScreenState extends State { style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), Text( - repeater.name, + widget.contact.name, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, @@ -225,9 +247,9 @@ class _TelemetryScreenState extends State { tooltip: l10n.repeater_routingMode, onSelected: (mode) async { if (mode == 'flood') { - await connector.setPathOverride(repeater, pathLen: -1); + await connector.setPathOverride(widget.contact, pathLen: -1); } else { - await connector.setPathOverride(repeater, pathLen: null); + await connector.setPathOverride(widget.contact, pathLen: null); } }, itemBuilder: (context) => [ @@ -283,7 +305,7 @@ class _TelemetryScreenState extends State { icon: const Icon(Icons.timeline), tooltip: l10n.repeater_pathManagement, onPressed: () => - PathManagementDialog.show(context, contact: repeater), + PathManagementDialog.show(context, contact: widget.contact), ), IconButton( icon: _isLoading @@ -437,7 +459,7 @@ class _TelemetryScreenState extends State { final l10n = context.l10n; final connector = context.watch(); final batteryMv = - connector.getRepeaterBatteryMillivolts(widget.repeater.publicKeyHex) ?? + connector.getRepeaterBatteryMillivolts(widget.contact.publicKeyHex) ?? (telemetryVolts == null ? null : (telemetryVolts * 1000).round()); if (batteryMv == null) return l10n.common_notAvailable; final chemistry = _batteryChemistry(); @@ -449,7 +471,7 @@ class _TelemetryScreenState extends State { String _batteryChemistry() { final settingsService = context.read(); return settingsService.batteryChemistryForRepeater( - widget.repeater.publicKeyHex, + widget.contact.publicKeyHex, ); } diff --git a/lib/widgets/path_management_dialog.dart b/lib/widgets/path_management_dialog.dart index f667256..e92f301 100644 --- a/lib/widgets/path_management_dialog.dart +++ b/lib/widgets/path_management_dialog.dart @@ -34,11 +34,22 @@ class _PathManagementDialog extends StatefulWidget { class _PathManagementDialogState extends State<_PathManagementDialog> { bool _showAllPaths = false; + int _resolveContactIndex = -1; + Contact _resolveContact(MeshCoreConnector connector) { - return connector.contacts.firstWhere( + if (_resolveContactIndex >= 0 && + _resolveContactIndex < connector.contacts.length && + connector.contacts[_resolveContactIndex].publicKeyHex == + widget.contact.publicKeyHex) { + return connector.contacts[_resolveContactIndex]; + } + _resolveContactIndex = connector.contacts.indexWhere( (c) => c.publicKeyHex == widget.contact.publicKeyHex, - orElse: () => widget.contact, ); + if (_resolveContactIndex == -1) { + return widget.contact; + } + return connector.contacts[_resolveContactIndex]; } String _formatRelativeTime(BuildContext context, DateTime? time) { diff --git a/lib/widgets/repeater_login_dialog.dart b/lib/widgets/repeater_login_dialog.dart index ec0af66..ce6c2b7 100644 --- a/lib/widgets/repeater_login_dialog.dart +++ b/lib/widgets/repeater_login_dialog.dart @@ -69,11 +69,21 @@ class _RepeaterLoginDialogState extends State { bool _isLoggingIn = false; + int _resolveRepeaterIndex = -1; Contact _resolveRepeater(MeshCoreConnector connector) { - return connector.contacts.firstWhere( + if (_resolveRepeaterIndex >= 0 && + _resolveRepeaterIndex < connector.contacts.length && + connector.contacts[_resolveRepeaterIndex].publicKeyHex == + widget.repeater.publicKeyHex) { + return connector.contacts[_resolveRepeaterIndex]; + } + _resolveRepeaterIndex = connector.contacts.indexWhere( (c) => c.publicKeyHex == widget.repeater.publicKeyHex, - orElse: () => widget.repeater, ); + if (_resolveRepeaterIndex == -1) { + return widget.repeater; + } + return connector.contacts[_resolveRepeaterIndex]; } Future _handleLogin() async { diff --git a/lib/widgets/room_login_dialog.dart b/lib/widgets/room_login_dialog.dart index 7324f44..91d2c8c 100644 --- a/lib/widgets/room_login_dialog.dart +++ b/lib/widgets/room_login_dialog.dart @@ -64,11 +64,22 @@ class _RoomLoginDialogState extends State { bool _isLoggingIn = false; + int _resolveRepeaterIndex = -1; + Contact _resolveRepeater(MeshCoreConnector connector) { - return connector.contacts.firstWhere( + if (_resolveRepeaterIndex >= 0 && + _resolveRepeaterIndex < connector.contacts.length && + connector.contacts[_resolveRepeaterIndex].publicKeyHex == + widget.room.publicKeyHex) { + return connector.contacts[_resolveRepeaterIndex]; + } + _resolveRepeaterIndex = connector.contacts.indexWhere( (c) => c.publicKeyHex == widget.room.publicKeyHex, - orElse: () => widget.room, ); + if (_resolveRepeaterIndex == -1) { + return widget.room; + } + return connector.contacts[_resolveRepeaterIndex]; } Future _handleLogin() async { From cac0cc15eb9975b92a353364824c7c5bcef990d8 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Thu, 19 Mar 2026 22:49:16 -0700 Subject: [PATCH 08/36] feat: Enhance privacy settings and telemetry (#308) * feat: Enhance privacy settings and telemetry - Implemented telemetry options for contacts, allowing users to enable or disable telemetry data sharing. - Introduced a clear chat option in the chat interface for better message management. - Updated the telemetry screen to handle telemetry data for contacts, including battery level. - Refactored contact settings to include telemetry options and improved UI for better user experience. * feat: Refactor repeater resolution logic across multiple screens --- lib/l10n/app_es.arb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 4a49e1b..1a3475a 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1918,6 +1918,7 @@ "tcpConnectionFailed": "Error en la conexión TCP: {error}", "map_showDiscoveryContacts": "Mostrar Contactos de Descubrimiento", "map_setAsMyLocation": "Establecer mi ubicación", +<<<<<<< HEAD "@path_routeWeight": { "placeholders": { "weight": { @@ -1928,6 +1929,8 @@ } } }, +======= +>>>>>>> c9e3ceb (feat: Enhance privacy settings and telemetry (#308)) "settings_privacySubtitle": "Controlar qué información se comparte.", "settings_allowByContact": "Permitir por banderas de contacto", "settings_denyAll": "Denegar todo", @@ -1957,6 +1960,7 @@ } } }, +<<<<<<< HEAD "appSettings_initialRouteWeight": "Peso inicial de la ruta", "appSettings_maxRouteWeight": "Peso máximo permitido para la ruta", "appSettings_initialRouteWeightSubtitle": "Peso inicial para rutas recién descubiertas", @@ -1970,4 +1974,9 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modo de telemetría actualizado", "settings_multiAck": "Multi-ACKs: {value}" -} \ No newline at end of file +} +======= + "settings_telemetryModeUpdated": "Modo de telemetría actualizado", + "settings_multiAck": "Multi-ACKs: {value}" +} +>>>>>>> c9e3ceb (feat: Enhance privacy settings and telemetry (#308)) From c81c3efe7c2c807a885bf9711a6765da898c05e5 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Sun, 22 Feb 2026 16:00:51 -0800 Subject: [PATCH 09/36] Add show overlaps in public keys of repeaters functionality and localization support --- lib/connector/meshcore_connector.dart | 4 + lib/l10n/app_bg.arb | 3 +- lib/l10n/app_de.arb | 3 +- lib/l10n/app_en.arb | 1 + lib/l10n/app_es.arb | 11 +- lib/l10n/app_fr.arb | 3 +- lib/l10n/app_it.arb | 3 +- lib/l10n/app_localizations.dart | 6 + lib/l10n/app_localizations_bg.dart | 3 + lib/l10n/app_localizations_de.dart | 3 + lib/l10n/app_localizations_en.dart | 3 + lib/l10n/app_localizations_es.dart | 3 + lib/l10n/app_localizations_fr.dart | 3 + lib/l10n/app_localizations_it.dart | 3 + lib/l10n/app_localizations_nl.dart | 3 + lib/l10n/app_localizations_pl.dart | 3 + lib/l10n/app_localizations_pt.dart | 3 + lib/l10n/app_localizations_ru.dart | 3 + lib/l10n/app_localizations_sk.dart | 3 + lib/l10n/app_localizations_sl.dart | 3 + lib/l10n/app_localizations_sv.dart | 3 + lib/l10n/app_localizations_uk.dart | 3 + lib/l10n/app_localizations_zh.dart | 3 + lib/l10n/app_nl.arb | 3 +- lib/l10n/app_pl.arb | 3 +- lib/l10n/app_pt.arb | 3 +- lib/l10n/app_ru.arb | 3 +- lib/l10n/app_sk.arb | 3 +- lib/l10n/app_sl.arb | 3 +- lib/l10n/app_sv.arb | 3 +- lib/l10n/app_uk.arb | 3 +- lib/l10n/app_zh.arb | 3 +- lib/models/app_settings.dart | 6 + lib/screens/map_screen.dart | 182 ++++++++++++++++++------- lib/screens/settings_screen.dart | 11 +- lib/services/app_settings_service.dart | 4 + lib/services/path_history_service.dart | 10 ++ 37 files changed, 242 insertions(+), 77 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 87a6755..7b974f3 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -5196,6 +5196,10 @@ class MeshCoreConnector extends ChangeNotifier { markChannelRead(channelIndex); notifyListeners(); } + + void deleteAllPaths() { + _pathHistoryService?.clearAllHistories(); + } } const int _phRouteMask = 0x03; diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 13788b8..d2c5f8b 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Брой опити за повторно изпращане, преди съобщението да бъде маркирано като неуспешно.", "path_routeWeight": "{weight}/{max}", "settings_multiAck": "Мулти-потвърди: {value}", - "settings_telemetryModeUpdated": "Режим на телеметрията е обновен" + "settings_telemetryModeUpdated": "Режим на телеметрията е обновен", + "map_showOverlaps": "Покриване на ключа на повтаряча" } \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 6745054..0b3af08 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1969,5 +1969,6 @@ "appSettings_maxMessageRetriesSubtitle": "Anzahl der Versuche, eine Nachricht erneut zu senden, bevor sie als fehlgeschlagen markiert wird.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetriemodus aktualisiert", - "settings_multiAck": "Mehrfach-Bestätigungen: {value}" + "settings_multiAck": "Mehrfach-Bestätigungen: {value}", + "map_showOverlaps": "Überlappungen der Repeater-Taste" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2b263c6..18d8c2f 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -878,6 +878,7 @@ "map_chatNodes": "Chat Nodes", "map_repeaters": "Repeaters", "map_otherNodes": "Other Nodes", + "map_showOverlaps": "Repeater Key Overlaps", "map_keyPrefix": "Key Prefix", "map_filterByKeyPrefix": "Filter by key prefix", "map_publicKeyPrefix": "Public key prefix", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 1a3475a..4a49e1b 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1918,7 +1918,6 @@ "tcpConnectionFailed": "Error en la conexión TCP: {error}", "map_showDiscoveryContacts": "Mostrar Contactos de Descubrimiento", "map_setAsMyLocation": "Establecer mi ubicación", -<<<<<<< HEAD "@path_routeWeight": { "placeholders": { "weight": { @@ -1929,8 +1928,6 @@ } } }, -======= ->>>>>>> c9e3ceb (feat: Enhance privacy settings and telemetry (#308)) "settings_privacySubtitle": "Controlar qué información se comparte.", "settings_allowByContact": "Permitir por banderas de contacto", "settings_denyAll": "Denegar todo", @@ -1960,7 +1957,6 @@ } } }, -<<<<<<< HEAD "appSettings_initialRouteWeight": "Peso inicial de la ruta", "appSettings_maxRouteWeight": "Peso máximo permitido para la ruta", "appSettings_initialRouteWeightSubtitle": "Peso inicial para rutas recién descubiertas", @@ -1974,9 +1970,4 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modo de telemetría actualizado", "settings_multiAck": "Multi-ACKs: {value}" -} -======= - "settings_telemetryModeUpdated": "Modo de telemetría actualizado", - "settings_multiAck": "Multi-ACKs: {value}" -} ->>>>>>> c9e3ceb (feat: Enhance privacy settings and telemetry (#308)) +} \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index e98e317..ba929ae 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Nombre de tentatives de relance avant de marquer un message comme ayant échoué.", "path_routeWeight": "{weight}/{max}", "settings_multiAck": "Multi-ACKs : {value}", - "settings_telemetryModeUpdated": "Le mode télémétrie a été mis à jour" + "settings_telemetryModeUpdated": "Le mode télémétrie a été mis à jour", + "map_showOverlaps": "Chevauchement de la touche répétitive" } \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index f11cde5..b4414d6 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Numero di tentativi di riprova prima di considerare un messaggio come fallito.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modalità telemetria aggiornata", - "settings_multiAck": "Multi-ACKs: {value}" + "settings_multiAck": "Multi-ACKs: {value}", + "map_showOverlaps": "Sovrapposizioni della chiave ripetitore" } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 4bb6936..7c2488e 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3052,6 +3052,12 @@ abstract class AppLocalizations { /// **'Other Nodes'** String get map_otherNodes; + /// No description provided for @map_showOverlaps. + /// + /// In en, this message translates to: + /// **'Repeater Key Overlaps'** + String get map_showOverlaps; + /// No description provided for @map_keyPrefix. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index d6537f9..9915d06 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -1689,6 +1689,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get map_otherNodes => 'Други възли'; + @override + String get map_showOverlaps => 'Покриване на ключа на повтаряча'; + @override String get map_keyPrefix => 'Префикс на ключа'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 87fab6f..721730b 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1686,6 +1686,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get map_otherNodes => 'Andere Knoten'; + @override + String get map_showOverlaps => 'Überlappungen der Repeater-Taste'; + @override String get map_keyPrefix => 'Schlüsselpräfix'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 1e0196b..d8b2abe 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1656,6 +1656,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get map_otherNodes => 'Other Nodes'; + @override + String get map_showOverlaps => 'Repeater Key Overlaps'; + @override String get map_keyPrefix => 'Key Prefix'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index dff2e5e..e5c2817 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1685,6 +1685,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get map_otherNodes => 'Otros Nodos'; + @override + String get map_showOverlaps => 'Superposiciones de tecla repetidora'; + @override String get map_keyPrefix => 'Prefijo de clave'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 91bf4f4..3570527 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1695,6 +1695,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get map_otherNodes => 'Autres nœuds'; + @override + String get map_showOverlaps => 'Chevauchement de la touche répétitive'; + @override String get map_keyPrefix => 'Préfixe clé'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index b688c06..1bf328d 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1687,6 +1687,9 @@ class AppLocalizationsIt extends AppLocalizations { @override String get map_otherNodes => 'Altri Nodi'; + @override + String get map_showOverlaps => 'Sovrapposizioni della chiave ripetitore'; + @override String get map_keyPrefix => 'Prefisso Chiave'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 1530886..947fd27 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1674,6 +1674,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get map_otherNodes => 'Andere Nodes'; + @override + String get map_showOverlaps => 'Herhalingssleutel overlapt'; + @override String get map_keyPrefix => 'Prefix sleutel'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 16b6512..aa408c1 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1688,6 +1688,9 @@ class AppLocalizationsPl extends AppLocalizations { @override String get map_otherNodes => 'Inne węzły'; + @override + String get map_showOverlaps => 'Nakładające się klucze powtarzalne'; + @override String get map_keyPrefix => 'Prefiks klucza'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 87b44ca..42e91f9 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1686,6 +1686,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get map_otherNodes => 'Outros Nós'; + @override + String get map_showOverlaps => 'Sobreposições da Chave Repeater'; + @override String get map_keyPrefix => 'Prefixo Chave'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 72d2e1c..4cd5e43 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1689,6 +1689,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get map_otherNodes => 'Другие ноды'; + @override + String get map_showOverlaps => 'Перекрытия ключа повтора'; + @override String get map_keyPrefix => 'Префикс ключа'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 7817af6..b0c0750 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -1675,6 +1675,9 @@ class AppLocalizationsSk extends AppLocalizations { @override String get map_otherNodes => 'Ostatné uzly'; + @override + String get map_showOverlaps => 'Prekrývanie opakovača kľúča'; + @override String get map_keyPrefix => 'Päťciferné predpona'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 6032ee0..3651f3c 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -1671,6 +1671,9 @@ class AppLocalizationsSl extends AppLocalizations { @override String get map_otherNodes => 'Druge vozlišča'; + @override + String get map_showOverlaps => 'Prekrivanje ključa ponovnega predvajanja'; + @override String get map_keyPrefix => 'Predpona ključa'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 5b19be3..e3c7c7d 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -1664,6 +1664,9 @@ class AppLocalizationsSv extends AppLocalizations { @override String get map_otherNodes => 'Andra noder'; + @override + String get map_showOverlaps => 'Repeater-nyckelöverlappningar'; + @override String get map_keyPrefix => 'Nyckelprefix'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 096e470..f56455a 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -1684,6 +1684,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get map_otherNodes => 'Інші вузли'; + @override + String get map_showOverlaps => 'Перекриття ключа повторювача'; + @override String get map_keyPrefix => 'Префікс ключа'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index f142763..54b86a3 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1582,6 +1582,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get map_otherNodes => '其他节点'; + @override + String get map_showOverlaps => '重复键重叠'; + @override String get map_keyPrefix => '关键字前缀'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 1b5e78c..9bf6283 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Aantal pogingen om een bericht opnieuw te versturen voordat het als mislukt wordt gemarkeerd", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetrie-modus bijgewerkt", - "settings_multiAck": "Multi-ACKs: {value}" + "settings_multiAck": "Multi-ACKs: {value}", + "map_showOverlaps": "Herhalingssleutel overlapt" } \ No newline at end of file diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index ac95748..9f7e7fd 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Liczba prób ponownego wysłania wiadomości przed oznaczaniem jej jako nieudanej", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Tryb telemetryczny zaktualizowany", - "settings_multiAck": "Wiele potwierdzeń: {value}" + "settings_multiAck": "Wiele potwierdzeń: {value}", + "map_showOverlaps": "Nakładające się klucze powtarzalne" } \ No newline at end of file diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index adddd13..2ac9b9d 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Número de tentativas de reenvio antes de classificar uma mensagem como falha.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modo de telemetria atualizado", - "settings_multiAck": "Multi-ACKs: {value}" + "settings_multiAck": "Multi-ACKs: {value}", + "map_showOverlaps": "Sobreposições da Chave Repeater" } \ No newline at end of file diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 2d3df51..9d45622 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -1181,5 +1181,6 @@ "appSettings_maxMessageRetriesSubtitle": "Количество попыток повторной отправки сообщения перед тем, как пометить его как неудачное.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрии обновлен", - "settings_multiAck": "Мульти-ACK: {value}" + "settings_multiAck": "Мульти-ACK: {value}", + "map_showOverlaps": "Перекрытия ключа повтора" } \ No newline at end of file diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 57eb285..1a79f85 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Počet pokusov o odošleť pred označením správy ako neúspešnej", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Režim telemetrie bol aktualizovaný", - "settings_multiAck": "Viaceré ACK: {value}" + "settings_multiAck": "Viaceré ACK: {value}", + "map_showOverlaps": "Prekrývanie opakovača kľúča" } \ No newline at end of file diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index 355f8d8..c09e2ec 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Število poskusov ponovnega poslanja, preden se sporočilo označuje kot neuspešno", "path_routeWeight": "{weight}/{max}", "settings_multiAck": "Večkratni potrditvi: {value}", - "settings_telemetryModeUpdated": "Način telemetrije posodobljen" + "settings_telemetryModeUpdated": "Način telemetrije posodobljen", + "map_showOverlaps": "Prekrivanje ključa ponovnega predvajanja" } \ No newline at end of file diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index 84f4e5e..11a8631 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Antal försök att skicka om ett meddelande innan det markeras som misslyckat.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetri-läge uppdaterat", - "settings_multiAck": "Multi-ACKs: {value}" + "settings_multiAck": "Multi-ACKs: {value}", + "map_showOverlaps": "Repeater-nyckelöverlappningar" } \ No newline at end of file diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index be1eaa8..c381c8c 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1941,5 +1941,6 @@ "appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрії оновлено", - "settings_multiAck": "Багатократне підтвердження: {value}" + "settings_multiAck": "Багатократне підтвердження: {value}", + "map_showOverlaps": "Перекриття ключа повторювача" } \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9493b27..b0e7c61 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1946,5 +1946,6 @@ "appSettings_maxMessageRetriesSubtitle": "在将消息标记为失败之前,允许尝试的次数", "path_routeWeight": "{weight}/{max}", "settings_multiAck": "多重ACK:{value}", - "settings_telemetryModeUpdated": "遥测模式已更新" + "settings_telemetryModeUpdated": "遥测模式已更新", + "map_showOverlaps": "重复键重叠" } \ No newline at end of file diff --git a/lib/models/app_settings.dart b/lib/models/app_settings.dart index 8ee904d..31c1741 100644 --- a/lib/models/app_settings.dart +++ b/lib/models/app_settings.dart @@ -18,6 +18,7 @@ class AppSettings { final bool mapShowRepeaters; final bool mapShowChatNodes; final bool mapShowOtherNodes; + final bool mapShowOverlaps; final double mapTimeFilterHours; // 0 = all time final bool mapKeyPrefixEnabled; final String mapKeyPrefix; @@ -53,6 +54,7 @@ class AppSettings { this.mapShowRepeaters = true, this.mapShowChatNodes = true, this.mapShowOtherNodes = true, + this.mapShowOverlaps = false, this.mapTimeFilterHours = 0, // Default to all time this.mapKeyPrefixEnabled = false, this.mapKeyPrefix = '', @@ -92,6 +94,7 @@ class AppSettings { 'map_show_repeaters': mapShowRepeaters, 'map_show_chat_nodes': mapShowChatNodes, 'map_show_other_nodes': mapShowOtherNodes, + 'map_show_overlaps': mapShowOverlaps, 'map_time_filter_hours': mapTimeFilterHours, 'map_key_prefix_enabled': mapKeyPrefixEnabled, 'map_key_prefix': mapKeyPrefix, @@ -137,6 +140,7 @@ class AppSettings { mapShowRepeaters: json['map_show_repeaters'] as bool? ?? true, mapShowChatNodes: json['map_show_chat_nodes'] as bool? ?? true, mapShowOtherNodes: json['map_show_other_nodes'] as bool? ?? true, + mapShowOverlaps: json['map_show_overlaps'] as bool? ?? false, mapTimeFilterHours: (json['map_time_filter_hours'] as num?)?.toDouble() ?? 0, mapKeyPrefixEnabled: json['map_key_prefix_enabled'] as bool? ?? false, @@ -196,6 +200,7 @@ class AppSettings { bool? mapShowRepeaters, bool? mapShowChatNodes, bool? mapShowOtherNodes, + bool? mapShowOverlaps, double? mapTimeFilterHours, bool? mapKeyPrefixEnabled, String? mapKeyPrefix, @@ -231,6 +236,7 @@ class AppSettings { mapShowRepeaters: mapShowRepeaters ?? this.mapShowRepeaters, mapShowChatNodes: mapShowChatNodes ?? this.mapShowChatNodes, mapShowOtherNodes: mapShowOtherNodes ?? this.mapShowOtherNodes, + mapShowOverlaps: mapShowOverlaps ?? this.mapShowOverlaps, mapTimeFilterHours: mapTimeFilterHours ?? this.mapTimeFilterHours, mapKeyPrefixEnabled: mapKeyPrefixEnabled ?? this.mapKeyPrefixEnabled, mapKeyPrefix: mapKeyPrefix ?? this.mapKeyPrefix, diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 6aaebf0..e7558c5 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -1,3 +1,4 @@ +import 'dart:collection'; import 'dart:math'; import 'dart:typed_data'; @@ -52,7 +53,7 @@ class MapScreen extends StatefulWidget { class _MapScreenState extends State { // Zoom level at which node labels start to appear - static const double _labelZoomThreshold = 12.0; + static const double _labelZoomThreshold = 14.0; final MapController _mapController = MapController(); final MapMarkerService _markerService = MapMarkerService(); @@ -329,7 +330,9 @@ class _MapScreenState extends State { if (!_isBuildingPathTrace) IconButton( icon: const Icon(Icons.radar), - onPressed: () => _startPath(), + onPressed: () => _startPath( + LatLng(connector.selfLatitude!, connector.selfLongitude!), + ), tooltip: context.l10n.contacts_pathTrace, ), if (!_isBuildingPathTrace) @@ -580,6 +583,7 @@ class _MapScreenState extends State { // Index known-location repeaters by their 1-byte hash. // null value = two repeaters share the same hash byte (ambiguous collision). final repeaterByHash = {}; + for (final c in withLocation) { if (c.type == advTypeRepeater) { if (repeaterByHash.containsKey(c.publicKey[0])) { @@ -595,6 +599,11 @@ class _MapScreenState extends State { for (final contact in allContacts) { if (contact.hasLocation) continue; + if (contact.lastSeen.isBefore( + DateTime.now().subtract(const Duration(hours: 24)), + )) { + continue; // skip stale contacts + } final anchorSet = {}; @@ -641,10 +650,19 @@ class _MapScreenState extends State { continue; // discard implausible guesses near (0, 0) } } else { - double lat = 0, lon = 0; + double lat = 0, lon = 0, weight = 1.0; + int counted = 0; for (final a in anchors) { - lat += a.latitude; - lon += a.longitude; + if (counted == 0) { + lat = a.latitude; + lon = a.longitude; + } else { + lat += a.latitude * weight; + lon += a.longitude * weight; + } + // weight subsequent anchors less to create a bias towards the first (if more than 2) + weight = weight / 2; + counted++; } position = _offsetGuessedPosition( LatLng(lat / anchors.length, lon / anchors.length), @@ -812,31 +830,67 @@ class _MapScreenState extends State { return markers; } + List _filterContactsBySettings( + List contacts, + dynamic settings, + ) { + List filtered = []; + bool addContact = false; + for (final contact in contacts) { + addContact = false; + if (!contact.hasLocation) continue; + + // Apply node type filters + if (contact.type == advTypeRepeater && + (settings.mapShowRepeaters || + _isBuildingPathTrace || + settings.mapShowOverlaps)) { + addContact = true; + } + if (contact.type == advTypeChat && + (settings.mapShowChatNodes || _isBuildingPathTrace)) { + addContact = true; + } + if (contact.type != advTypeChat && + contact.type != advTypeRepeater && + (settings.mapShowOtherNodes || + _isBuildingPathTrace || + settings.mapShowOverlaps)) { + addContact = true; + } + + final hasOverlap = contacts + .where( + (c) => + c.publicKeyHex != contact.publicKeyHex && + c.publicKey.first == contact.publicKey.first && + (c.type == advTypeRepeater || c.type == advTypeRoom) && + (contact.type == advTypeRepeater || + contact.type == advTypeRoom), + ) + .firstOrNull; + + if (hasOverlap == null && + settings.mapShowOverlaps && + !_isBuildingPathTrace) { + addContact = false; + } + + if (addContact) { + filtered.add(contact); + } + } + return filtered; + } + List _buildMarkers( List contacts, settings, { required bool showLabels, }) { final markers = []; - - for (final contact in contacts) { - if (!contact.hasLocation) continue; - - // Apply node type filters - if (contact.type == advTypeRepeater && - (!settings.mapShowRepeaters && !_isBuildingPathTrace)) { - continue; - } - if (contact.type == advTypeChat && - !(settings.mapShowChatNodes && !_isBuildingPathTrace)) { - continue; - } - if (contact.type != advTypeChat && - contact.type != advTypeRepeater && - (!settings.mapShowOtherNodes && !_isBuildingPathTrace)) { - continue; - } - + final filteredContacts = _filterContactsBySettings(contacts, settings); + for (final contact in filteredContacts) { final marker = Marker( point: LatLng(contact.latitude!, contact.longitude!), width: 35, @@ -852,7 +906,9 @@ class _MapScreenState extends State { Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( - color: _getNodeColor(contact.type), + color: settings.mapShowOverlaps && !_isBuildingPathTrace + ? Colors.red + : _getNodeColor(contact.type), shape: BoxShape.circle, border: Border.all(color: Colors.white, width: 2), boxShadow: [ @@ -879,7 +935,9 @@ class _MapScreenState extends State { markers.add( _buildNodeLabelMarker( point: LatLng(contact.latitude!, contact.longitude!), - label: contact.name, + label: settings.mapShowOverlaps && !_isBuildingPathTrace + ? "${contact.publicKeyHex.substring(0, 2)}:${contact.name}" + : contact.name, ), ); } @@ -959,20 +1017,12 @@ class _MapScreenState extends State { int markerCount, int guessedCount, ) { - int nodeCount = 0; - for (final contact in contactsWithLocation) { - // Apply node type filters - if (contact.type == advTypeRepeater && !settings.mapShowRepeaters) { - continue; - } - if (contact.type == advTypeChat && !settings.mapShowChatNodes) continue; - if (contact.type != advTypeChat && - contact.type != advTypeRepeater && - !settings.mapShowOtherNodes) { - continue; - } - nodeCount++; - } + final filteredContacts = _filterContactsBySettings( + contactsWithLocation, + settings, + ); + + final nodeCount = filteredContacts.length; return Positioned( top: 16, @@ -1846,6 +1896,15 @@ class _MapScreenState extends State { }, contentPadding: EdgeInsets.zero, ), + CheckboxListTile( + title: Text(context.l10n.map_showOverlaps), + value: settings.mapShowOverlaps, + onChanged: (value) { + service.setMapShowOverlaps(value ?? true); + }, + contentPadding: EdgeInsets.zero, + ), + const SizedBox(height: 16), Text( context.l10n.map_keyPrefix, @@ -2004,12 +2063,13 @@ class _MapScreenState extends State { }); } - void _startPath() { + void _startPath(LatLng position) { setState(() { _isBuildingPathTrace = true; _pathTrace.clear(); _points.clear(); _polylines.clear(); + _points.add(position); }); } @@ -2055,14 +2115,14 @@ class _MapScreenState extends State { .join(','), style: TextStyle(fontSize: 18), ), - const SizedBox(height: 6), + // const SizedBox(height: 6), Wrap( alignment: WrapAlignment.center, - spacing: 8, - runSpacing: 8, + spacing: 1, + runSpacing: 1, children: [ if (_pathTrace.isNotEmpty) - ElevatedButton( + IconButton( onPressed: () { Navigator.push( context, @@ -2077,15 +2137,37 @@ class _MapScreenState extends State { _isBuildingPathTrace = false; }); }, - child: Text(l10n.map_runTrace), + tooltip: "Path Trace", + icon: const Icon(Icons.arrow_forward_outlined), ), if (_pathTrace.isNotEmpty) - ElevatedButton( + IconButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PathTraceMapScreen( + title: l10n.contacts_pathTrace, + path: Uint8List.fromList(_pathTrace), + flipPathAround: true, + ), + ), + ); + setState(() { + _isBuildingPathTrace = false; + }); + }, + tooltip: "Build Return Path", + icon: const Icon(Icons.replay), + ), + if (_pathTrace.isNotEmpty) + IconButton( onPressed: _removePath, - child: Text(l10n.map_removeLast), + tooltip: "Remove Last Point", + icon: const Icon(Icons.delete), ), if (_pathTrace.isEmpty) - ElevatedButton( + IconButton( onPressed: () { setState(() { _isBuildingPathTrace = false; @@ -2097,7 +2179,7 @@ class _MapScreenState extends State { SnackBar(content: Text(l10n.map_pathTraceCancelled)), ); }, - child: Text(l10n.common_cancel), + icon: const Icon(Icons.close), ), ], ), diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index cc61143..c42d4e3 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -311,10 +311,13 @@ class _SettingsScreenState extends State { ), ), ListTile( - leading: const Icon(Icons.cell_tower), - title: Text(l10n.settings_sendAdvertisement), - subtitle: Text(l10n.settings_sendAdvertisementSubtitle), - onTap: () => _sendAdvert(context, connector), + leading: const Icon(Icons.delete_outline, color: Colors.red), + title: Text("Delete All Paths"), + subtitle: Text( + "Clear all path data from contacts.", + style: TextStyle(color: Colors.red[700]), + ), + onTap: () => connector.deleteAllPaths(), ), const Divider(height: 1), ListTile( diff --git a/lib/services/app_settings_service.dart b/lib/services/app_settings_service.dart index e6697f4..6414617 100644 --- a/lib/services/app_settings_service.dart +++ b/lib/services/app_settings_service.dart @@ -64,6 +64,10 @@ class AppSettingsService extends ChangeNotifier { await updateSettings(_settings.copyWith(mapShowOtherNodes: value)); } + Future setMapShowOverlaps(bool value) async { + await updateSettings(_settings.copyWith(mapShowOverlaps: value)); + } + Future setMapTimeFilterHours(double value) async { await updateSettings(_settings.copyWith(mapTimeFilterHours: value)); } diff --git a/lib/services/path_history_service.dart b/lib/services/path_history_service.dart index 68a9245..fc81c56 100644 --- a/lib/services/path_history_service.dart +++ b/lib/services/path_history_service.dart @@ -565,6 +565,16 @@ class PathHistoryService extends ChangeNotifier { _floodStats.remove(oldest); } } + + void clearAllHistories() { + _cache.clear(); + _cacheAccessOrder.clear(); + _autoRotationIndex.clear(); + _floodStats.clear(); + _storage.clearAllPathHistories(); + _version = 0; + notifyListeners(); + } } class _DeferredPathRecord { From 77be2b8e6fc47cd326425ab7abcb060564014d8f Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Tue, 24 Feb 2026 19:01:22 -0800 Subject: [PATCH 10/36] Refactor code structure for improved readability and maintainability --- lib/l10n/app_bg.arb | 3 +- lib/l10n/app_de.arb | 3 +- lib/l10n/app_en.arb | 3 +- lib/l10n/app_es.arb | 4 +- lib/l10n/app_fr.arb | 3 +- lib/l10n/app_it.arb | 3 +- lib/l10n/app_localizations.dart | 8 +- lib/l10n/app_localizations_bg.dart | 3 + lib/l10n/app_localizations_de.dart | 4 + lib/l10n/app_localizations_en.dart | 5 +- lib/l10n/app_localizations_es.dart | 3 + lib/l10n/app_localizations_fr.dart | 3 + lib/l10n/app_localizations_it.dart | 4 + lib/l10n/app_localizations_nl.dart | 3 + lib/l10n/app_localizations_pl.dart | 3 + lib/l10n/app_localizations_pt.dart | 3 + lib/l10n/app_localizations_ru.dart | 3 + lib/l10n/app_localizations_sk.dart | 3 + lib/l10n/app_localizations_sl.dart | 3 + lib/l10n/app_localizations_sv.dart | 3 + lib/l10n/app_localizations_uk.dart | 3 + lib/l10n/app_localizations_zh.dart | 3 + lib/l10n/app_nl.arb | 3 +- lib/l10n/app_pl.arb | 3 +- lib/l10n/app_pt.arb | 3 +- lib/l10n/app_ru.arb | 3 +- lib/l10n/app_sk.arb | 3 +- lib/l10n/app_sl.arb | 3 +- lib/l10n/app_sv.arb | 3 +- lib/l10n/app_uk.arb | 3 +- lib/l10n/app_zh.arb | 3 +- lib/screens/map_screen.dart | 134 ++++++++++++++++++++++------- 32 files changed, 187 insertions(+), 50 deletions(-) diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index d2c5f8b..c0479d1 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_multiAck": "Мулти-потвърди: {value}", "settings_telemetryModeUpdated": "Режим на телеметрията е обновен", - "map_showOverlaps": "Покриване на ключа на повтаряча" + "map_showOverlaps": "Покриване на ключа на повтаряча", + "map_runTraceWithReturnPath": "Върни се по същия път." } \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 0b3af08..98ddb93 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1970,5 +1970,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetriemodus aktualisiert", "settings_multiAck": "Mehrfach-Bestätigungen: {value}", - "map_showOverlaps": "Überlappungen der Repeater-Taste" + "map_showOverlaps": "Überlappungen der Repeater-Taste", + "map_runTraceWithReturnPath": "Auf dem gleichen Pfad zurückkehren." } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 18d8c2f..d8623d3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -892,7 +892,8 @@ "map_joinRoom": "Join Room", "map_manageRepeater": "Manage Repeater", "map_tapToAdd": "Tap on nodes to add them to the path.", - "map_runTrace": "Run Path Trace", + "map_runTrace": "Run path trace", + "map_runTraceWithReturnPath": "Return back on the same path.", "map_removeLast": "Remove Last", "map_pathTraceCancelled": "Path trace cancelled.", "mapCache_title": "Offline Map Cache", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 4a49e1b..a65d80f 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1969,5 +1969,7 @@ "appSettings_maxMessageRetriesSubtitle": "Número de intentos de reintento antes de marcar un mensaje como fallido.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modo de telemetría actualizado", - "settings_multiAck": "Multi-ACKs: {value}" + "settings_multiAck": "Multi-ACKs: {value}", + "map_showOverlaps": "Superposiciones de tecla repetidora", + "map_runTraceWithReturnPath": "Volver atrás por el mismo camino." } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index ba929ae..8bb0a46 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_multiAck": "Multi-ACKs : {value}", "settings_telemetryModeUpdated": "Le mode télémétrie a été mis à jour", - "map_showOverlaps": "Chevauchement de la touche répétitive" + "map_showOverlaps": "Chevauchement de la touche répétitive", + "map_runTraceWithReturnPath": "Revenir sur le même chemin." } \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index b4414d6..b168e75 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modalità telemetria aggiornata", "settings_multiAck": "Multi-ACKs: {value}", - "map_showOverlaps": "Sovrapposizioni della chiave ripetitore" + "map_showOverlaps": "Sovrapposizioni della chiave ripetitore", + "map_runTraceWithReturnPath": "Tornare indietro sullo stesso percorso" } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 7c2488e..ce5833a 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3139,9 +3139,15 @@ abstract class AppLocalizations { /// No description provided for @map_runTrace. /// /// In en, this message translates to: - /// **'Run Path Trace'** + /// **'Run path trace'** String get map_runTrace; + /// No description provided for @map_runTraceWithReturnPath. + /// + /// In en, this message translates to: + /// **'Return back on the same path.'** + String get map_runTraceWithReturnPath; + /// No description provided for @map_removeLast. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 9915d06..53d8ef2 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -1736,6 +1736,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get map_runTrace => 'Изпълни Път на Следване'; + @override + String get map_runTraceWithReturnPath => 'Върни се по същия път.'; + @override String get map_removeLast => 'Премахни Последно'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 721730b..535eb45 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1733,6 +1733,10 @@ class AppLocalizationsDe extends AppLocalizations { @override String get map_runTrace => 'Pfadverlauf ausführen'; + @override + String get map_runTraceWithReturnPath => + 'Auf dem gleichen Pfad zurückkehren.'; + @override String get map_removeLast => 'Letztes Entfernen'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index d8b2abe..2fe75ec 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1699,7 +1699,10 @@ class AppLocalizationsEn extends AppLocalizations { String get map_tapToAdd => 'Tap on nodes to add them to the path.'; @override - String get map_runTrace => 'Run Path Trace'; + String get map_runTrace => 'Run path trace'; + + @override + String get map_runTraceWithReturnPath => 'Return back on the same path.'; @override String get map_removeLast => 'Remove Last'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index e5c2817..70e0a79 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1731,6 +1731,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get map_runTrace => 'Ejecutar Rastreo de Ruta'; + @override + String get map_runTraceWithReturnPath => 'Volver atrás por el mismo camino.'; + @override String get map_removeLast => 'Eliminar último'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 3570527..5be46c8 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1742,6 +1742,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get map_runTrace => 'Exécuter la traçage de chemin'; + @override + String get map_runTraceWithReturnPath => 'Revenir sur le même chemin.'; + @override String get map_removeLast => 'Supprimer le dernier'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 1bf328d..63b501f 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1732,6 +1732,10 @@ class AppLocalizationsIt extends AppLocalizations { @override String get map_runTrace => 'Esegui Path Trace'; + @override + String get map_runTraceWithReturnPath => + 'Tornare indietro sullo stesso percorso'; + @override String get map_removeLast => 'Rimuovi ultimo'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 947fd27..adf2392 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1721,6 +1721,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get map_runTrace => 'Padeshulp traceren'; + @override + String get map_runTraceWithReturnPath => 'Terugkeren op hetzelfde pad.'; + @override String get map_removeLast => 'Verwijder Laatste'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index aa408c1..b2e5e7e 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1734,6 +1734,9 @@ class AppLocalizationsPl extends AppLocalizations { @override String get map_runTrace => 'Uruchom ślad ścieżki'; + @override + String get map_runTraceWithReturnPath => 'Wróć z powrotem tą samą ścieżką'; + @override String get map_removeLast => 'Usuń ostatni'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 42e91f9..1bc971a 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1732,6 +1732,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get map_runTrace => 'Executar Traçado de Caminho'; + @override + String get map_runTraceWithReturnPath => 'Retornar ao mesmo caminho.'; + @override String get map_removeLast => 'Remover Último'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 4cd5e43..f71bff0 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1735,6 +1735,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get map_runTrace => 'Запустить трассировку пути'; + @override + String get map_runTraceWithReturnPath => 'Вернуться обратно по тому же пути'; + @override String get map_removeLast => 'Удалить последний'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index b0c0750..d5d00a0 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -1721,6 +1721,9 @@ class AppLocalizationsSk extends AppLocalizations { @override String get map_runTrace => 'Spustiť trasovaním cesty'; + @override + String get map_runTraceWithReturnPath => 'Vráťte sa späť po tej istej ceste.'; + @override String get map_removeLast => 'Odstrániť posledný'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 3651f3c..47066c9 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -1716,6 +1716,9 @@ class AppLocalizationsSl extends AppLocalizations { @override String get map_runTrace => 'Zaženi sledenje poti'; + @override + String get map_runTraceWithReturnPath => 'Vrni se nazaj po isti poti.'; + @override String get map_removeLast => 'Odstrani Zadnji'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index e3c7c7d..6e5fd20 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -1710,6 +1710,9 @@ class AppLocalizationsSv extends AppLocalizations { @override String get map_runTrace => 'Kör spårsökning'; + @override + String get map_runTraceWithReturnPath => 'Gå tillbaka på samma väg'; + @override String get map_removeLast => 'Ta bort sista'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index f56455a..c068ed3 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -1730,6 +1730,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get map_runTrace => 'Виконати трасування шляху'; + @override + String get map_runTraceWithReturnPath => 'Повернутися назад тим же шляхом'; + @override String get map_removeLast => 'Видалити останній'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 54b86a3..78e9c94 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1627,6 +1627,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get map_runTrace => '运行路径追踪'; + @override + String get map_runTraceWithReturnPath => '沿着相同的路径返回'; + @override String get map_removeLast => '移除最后一个'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 9bf6283..43c08b1 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetrie-modus bijgewerkt", "settings_multiAck": "Multi-ACKs: {value}", - "map_showOverlaps": "Herhalingssleutel overlapt" + "map_showOverlaps": "Herhalingssleutel overlapt", + "map_runTraceWithReturnPath": "Terugkeren op hetzelfde pad." } \ No newline at end of file diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 9f7e7fd..1c08c8a 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Tryb telemetryczny zaktualizowany", "settings_multiAck": "Wiele potwierdzeń: {value}", - "map_showOverlaps": "Nakładające się klucze powtarzalne" + "map_showOverlaps": "Nakładające się klucze powtarzalne", + "map_runTraceWithReturnPath": "Wróć z powrotem tą samą ścieżką" } \ No newline at end of file diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 2ac9b9d..1ee4130 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modo de telemetria atualizado", "settings_multiAck": "Multi-ACKs: {value}", - "map_showOverlaps": "Sobreposições da Chave Repeater" + "map_showOverlaps": "Sobreposições da Chave Repeater", + "map_runTraceWithReturnPath": "Retornar ao mesmo caminho." } \ No newline at end of file diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 9d45622..ab32362 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -1182,5 +1182,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрии обновлен", "settings_multiAck": "Мульти-ACK: {value}", - "map_showOverlaps": "Перекрытия ключа повтора" + "map_showOverlaps": "Перекрытия ключа повтора", + "map_runTraceWithReturnPath": "Вернуться обратно по тому же пути" } \ No newline at end of file diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 1a79f85..12c2f9a 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Režim telemetrie bol aktualizovaný", "settings_multiAck": "Viaceré ACK: {value}", - "map_showOverlaps": "Prekrývanie opakovača kľúča" + "map_showOverlaps": "Prekrývanie opakovača kľúča", + "map_runTraceWithReturnPath": "Vráťte sa späť po tej istej ceste." } \ No newline at end of file diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index c09e2ec..54ea1f5 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_multiAck": "Večkratni potrditvi: {value}", "settings_telemetryModeUpdated": "Način telemetrije posodobljen", - "map_showOverlaps": "Prekrivanje ključa ponovnega predvajanja" + "map_showOverlaps": "Prekrivanje ključa ponovnega predvajanja", + "map_runTraceWithReturnPath": "Vrni se nazaj po isti poti." } \ No newline at end of file diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index 11a8631..1bb0c8a 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetri-läge uppdaterat", "settings_multiAck": "Multi-ACKs: {value}", - "map_showOverlaps": "Repeater-nyckelöverlappningar" + "map_showOverlaps": "Repeater-nyckelöverlappningar", + "map_runTraceWithReturnPath": "Gå tillbaka på samma väg" } \ No newline at end of file diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index c381c8c..e55a582 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1942,5 +1942,6 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрії оновлено", "settings_multiAck": "Багатократне підтвердження: {value}", - "map_showOverlaps": "Перекриття ключа повторювача" + "map_showOverlaps": "Перекриття ключа повторювача", + "map_runTraceWithReturnPath": "Повернутися назад тим же шляхом" } \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index b0e7c61..b415904 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1947,5 +1947,6 @@ "path_routeWeight": "{weight}/{max}", "settings_multiAck": "多重ACK:{value}", "settings_telemetryModeUpdated": "遥测模式已更新", - "map_showOverlaps": "重复键重叠" + "map_showOverlaps": "重复键重叠", + "map_runTraceWithReturnPath": "沿着相同的路径返回" } \ No newline at end of file diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index e7558c5..f5efd3b 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -480,10 +480,12 @@ class _MapScreenState extends State { point: highlightPosition, width: 40, height: 40, - child: Icon( - Icons.location_on_outlined, - color: Colors.red[600], - size: 34, + child: IgnorePointer( + child: Icon( + Icons.location_on_outlined, + color: Colors.red[600], + size: 34, + ), ), ), if (!_isBuildingPathTrace) @@ -506,28 +508,33 @@ class _MapScreenState extends State { ), width: 40, height: 40, - child: Container( - padding: const EdgeInsets.all(4), - decoration: BoxDecoration( - color: Colors.teal, - shape: BoxShape.circle, - border: Border.all( - color: Colors.white, - width: 2, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.3), - blurRadius: 4, - offset: const Offset(0, 2), + child: IgnorePointer( + ignoring: true, + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Colors.teal, + shape: BoxShape.circle, + border: Border.all( + color: Colors.white, + width: 2, ), - ], - ), - alignment: Alignment.center, - child: const Icon( - Icons.person_pin_circle, - color: Colors.white, - size: 20, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues( + alpha: 0.3, + ), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + alignment: Alignment.center, + child: const Icon( + Icons.person_pin_circle, + color: Colors.white, + size: 20, + ), ), ), ), @@ -547,6 +554,7 @@ class _MapScreenState extends State { ), if (!_isBuildingPathTrace) _buildLegend( + contacts, contactsWithLocation, settings, sharedMarkers.length, @@ -832,13 +840,16 @@ class _MapScreenState extends State { List _filterContactsBySettings( List contacts, - dynamic settings, - ) { + dynamic settings, { + bool noLocations = false, + }) { List filtered = []; bool addContact = false; for (final contact in contacts) { addContact = false; - if (!contact.hasLocation) continue; + if (!contact.hasLocation && !noLocations) { + continue; + } // Apply node type filters if (contact.type == advTypeRepeater && @@ -1012,17 +1023,25 @@ class _MapScreenState extends State { } Widget _buildLegend( + List contacts, List contactsWithLocation, settings, int markerCount, int guessedCount, ) { final filteredContacts = _filterContactsBySettings( - contactsWithLocation, + contacts, settings, + noLocations: false, + ); + final filteredContactsAll = _filterContactsBySettings( + contacts, + settings, + noLocations: true, ); final nodeCount = filteredContacts.length; + final nodeCountAll = filteredContactsAll.length; return Positioned( top: 16, @@ -1058,6 +1077,54 @@ class _MapScreenState extends State { fontSize: 14, ), ), + Row( + children: [ + Icon( + Icons.location_on, + size: 16, + color: Colors.grey, + ), + Text( + ": $nodeCount", + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ], + ), + Row( + children: [ + const Icon( + Icons.wrong_location, + size: 16, + color: Colors.grey, + ), + Text( + ": ${nodeCountAll - nodeCount}", + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ], + ), + Row( + children: [ + const Icon( + Icons.add_outlined, + size: 16, + color: Colors.grey, + ), + Text( + ": $nodeCountAll", + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ], + ), Text( context.l10n.map_pinsCount(markerCount), style: const TextStyle( @@ -2137,7 +2204,7 @@ class _MapScreenState extends State { _isBuildingPathTrace = false; }); }, - tooltip: "Path Trace", + tooltip: l10n.map_runTrace, icon: const Icon(Icons.arrow_forward_outlined), ), if (_pathTrace.isNotEmpty) @@ -2157,14 +2224,14 @@ class _MapScreenState extends State { _isBuildingPathTrace = false; }); }, - tooltip: "Build Return Path", + tooltip: l10n.map_runTraceWithReturnPath, icon: const Icon(Icons.replay), ), if (_pathTrace.isNotEmpty) IconButton( onPressed: _removePath, - tooltip: "Remove Last Point", - icon: const Icon(Icons.delete), + tooltip: l10n.map_removeLast, + icon: const Icon(Icons.undo), ), if (_pathTrace.isEmpty) IconButton( @@ -2179,6 +2246,7 @@ class _MapScreenState extends State { SnackBar(content: Text(l10n.map_pathTraceCancelled)), ); }, + tooltip: l10n.common_cancel, icon: const Icon(Icons.close), ), ], From e313bea3fc549467b6f668537f58122376a09936 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Sat, 21 Mar 2026 09:38:52 -0700 Subject: [PATCH 11/36] Remove unused _sendAdvert method from SettingsScreen --- lib/screens/settings_screen.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index c42d4e3..310cf49 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -660,14 +660,6 @@ class _SettingsScreenState extends State { ); } - void _sendAdvert(BuildContext context, MeshCoreConnector connector) { - final l10n = context.l10n; - connector.sendSelfAdvert(flood: true); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(l10n.settings_advertisementSent))); - } - void _syncTime(BuildContext context, MeshCoreConnector connector) { final l10n = context.l10n; connector.syncTime(); From 4f609f160ffbd95c5f33e0753f188cef203115ce Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Sat, 21 Mar 2026 09:39:03 -0700 Subject: [PATCH 12/36] feat: Add location validation and improve contact latitude/longitude handling --- lib/connector/meshcore_connector.dart | 30 ++++++++++++++------------- lib/models/contact.dart | 13 ++++++------ 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 7b974f3..d2dedce 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -4913,6 +4913,17 @@ class MeshCoreConnector extends ChangeNotifier { ); } + bool hasValidLocation(double? latitude, double? longitude) { + const double epsilon = 1e-6; + final lat = latitude ?? 0.0; + final lon = longitude ?? 0.0; + return (lat.abs() > epsilon || lon.abs() > epsilon) && + lat >= -90.0 && + lat <= 90.0 && + lon >= -180.0 && + lon <= 180.0; + } + void _handlePayloadAdvertReceived( Uint8List rawPacket, Uint8List payload, @@ -4950,6 +4961,9 @@ class MeshCoreConnector extends ChangeNotifier { latitude = advert.readInt32LE() / 1e6; longitude = advert.readInt32LE() / 1e6; } + // Validate location values if present + hasLocation = hasValidLocation(latitude, longitude); + if (hasName && advert.remaining > 0) { name = advert.readString(); } @@ -5015,20 +5029,8 @@ class MeshCoreConnector extends ChangeNotifier { // CRITICAL: Preserve user's path override when contact is refreshed from device _contacts[existingIndex] = existing.copyWith( - latitude: - hasLocation && - latitude != null && - latitude.abs() <= 90 && - (latitude != 0 || longitude != 0) - ? latitude - : existing.latitude, - longitude: - hasLocation && - longitude != null && - longitude.abs() <= 180 && - (latitude != 0 || longitude != 0) - ? longitude - : existing.longitude, + latitude: hasLocation ? latitude : existing.latitude, + longitude: hasLocation ? longitude : existing.longitude, name: hasName ? name : existing.name, path: Uint8List.fromList(path.reversed.toList()), pathLength: path.length, diff --git a/lib/models/contact.dart b/lib/models/contact.dart index acd1da9..9a5c19c 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -181,12 +181,13 @@ class Contact { final lastMod = reader.readUInt32LE(); double? lat, lon; - final latRaw = reader.readInt32LE(); - final lonRaw = reader.readInt32LE(); - - if (latRaw != 0 || lonRaw != 0) { - lat = latRaw / 1e6; - lon = lonRaw / 1e6; + if (reader.remaining >= 8) { + final latRaw = reader.readInt32LE(); + final lonRaw = reader.readInt32LE(); + if (latRaw != 0 || lonRaw != 0) { + lat = latRaw / 1e6; + lon = lonRaw / 1e6; + } } return Contact( From dbefb0b5f419e90328cca9a775e2136f944b5630 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Sat, 21 Mar 2026 13:01:02 -0700 Subject: [PATCH 13/36] feat: Enhance MeshCoreConnector with storage metrics and improve error handling - Added storageUsedKb and storageTotalKb properties to MeshCoreConnector. - Updated battery and storage frame parsing with improved error handling. - Refactored log RX data handling to use BufferReader for better readability and error management. - Enhanced message parsing in ChannelMessage and Message classes to utilize BufferReader. - Introduced new text type for signed messages in meshcore_protocol.dart. - Updated BLE debug log screen to use BufferReader for payload parsing. - Refactored message retry service to handle ACK hashes as integers instead of Uint8List. - Improved message storage serialization and deserialization to accommodate new expectedAckHash type. - Added wasPulled property to Contact model for better state management. --- lib/connector/meshcore_connector.dart | 191 ++++++++++++++---------- lib/connector/meshcore_protocol.dart | 135 ++++++++--------- lib/models/channel.dart | 21 +-- lib/models/channel_message.dart | 150 +++++++++---------- lib/models/contact.dart | 2 + lib/models/message.dart | 54 +++---- lib/screens/ble_debug_log_screen.dart | 110 +++++++------- lib/services/ble_debug_log_service.dart | 13 ++ lib/services/message_retry_service.dart | 30 ++-- lib/storage/message_store.dart | 8 +- lib/widgets/debug_frame_viewer.dart | 8 + 11 files changed, 382 insertions(+), 340 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 87a6755..e8555ff 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -257,6 +257,9 @@ class MeshCoreConnector extends ChangeNotifier { int? _activeChannelIndex; List _channelOrder = []; + int _storageUsedKb = -1; + int _storageTotalKb = -1; + // Getters MeshCoreConnectionState get state => _state; BluetoothDevice? get device => _device; @@ -338,6 +341,8 @@ class MeshCoreConnector extends ChangeNotifier { int? get firmwareVerCode => _firmwareVerCode; Map? get currentCustomVars => _currentCustomVars; int? get batteryMillivolts => _batteryMillivolts; + int? get storageUsedKb => _storageUsedKb; + int? get storageTotalKb => _storageTotalKb; int get maxContacts => _maxContacts; int get maxChannels => _maxChannels; Set get knownContactKeys => Set.unmodifiable(_knownContactKeys); @@ -3037,14 +3042,23 @@ class MeshCoreConnector extends ChangeNotifier { // [1-2] = battery_mv (uint16 LE) // [3-6] = storage_used_kb (uint32 LE) // [7-10] = storage_total_kb (uint32 LE) - if (frame.length >= 3) { - _batteryMillivolts = readUint16LE(frame, 1); + try { + final reader = BufferReader(frame); + reader.skipBytes(1); + _batteryMillivolts = reader.readInt16LE(); + _storageUsedKb = reader.readUInt32LE(); + _storageTotalKb = reader.readUInt32LE(); final volts = (_batteryMillivolts! / 1000.0).toStringAsFixed(2); _appDebugLogService?.info( 'Pulled battery: $volts V ($_batteryMillivolts mV)', tag: 'Battery', ); notifyListeners(); + } catch (e) { + _appDebugLogService?.error( + 'Error parsing battery and storage frame: $e', + tag: 'Connector', + ); } } @@ -3702,68 +3716,89 @@ class MeshCoreConnector extends ChangeNotifier { void _handleLogRxData(Uint8List frame) { if (frame.length < 4) return; - final raw = Uint8List.fromList(frame.sublist(3)); - final packet = _parseRawPacket(raw); - if (packet == null || packet.payloadType != _payloadTypeGroupText) return; + try { + final reader = BufferReader(frame); + reader.skipBytes(3); // Skip header - final payload = packet.payload; - if (payload.length <= _cipherMacSize) return; - final channelHash = payload[0]; - final encrypted = Uint8List.fromList(payload.sublist(1)); + final raw = reader.readRemainingBytes(); + final packet = _parseRawPacket(raw); + if (packet == null || packet.payloadType != _payloadTypeGroupText) return; - // Use cached channels as fallback if live channels not yet loaded - final channelsToSearch = _channels.isNotEmpty ? _channels : _cachedChannels; - for (final channel in channelsToSearch) { - if (channel.isEmpty) continue; - final hash = _computeChannelHash(channel.psk); - if (hash != channelHash) continue; + final payload = packet.payload; + if (payload.length <= _cipherMacSize) return; + final channelHash = payload[0]; + final encrypted = Uint8List.fromList(payload.sublist(1)); - final decrypted = _decryptPayload(channel.psk, encrypted); - if (decrypted == null || decrypted.length < 6) return; + // Use cached channels as fallback if live channels not yet loaded + final channelsToSearch = _channels.isNotEmpty + ? _channels + : _cachedChannels; + for (final channel in channelsToSearch) { + if (channel.isEmpty) continue; + final hash = _computeChannelHash(channel.psk); + if (hash != channelHash) continue; + try { + final decryptedBytes = _decryptPayload(channel.psk, encrypted); + if (decryptedBytes == null || decryptedBytes.length < 6) return; + final decrypted = BufferReader(decryptedBytes); + // Skip header + SNR + reserved (2) + decrypted.skipBytes(4); + final txtType = decrypted.readByte(); + if ((txtType >> 2) != 0) { + return; + } - final txtType = decrypted[4]; - if ((txtType >> 2) != 0) { - return; + final timestampRaw = decrypted.readUInt32LE(); + final text = decrypted.readString(); + final parsed = _splitSenderText(text); + final decodedText = + Smaz.tryDecodePrefixed(parsed.text) ?? parsed.text; + if (_shouldDropSelfChannelMessage( + parsed.senderName, + packet.pathBytes, + )) { + return; + } + + final pktHash = _computePacketHash( + packet.payloadType, + packet.payload, + ); + + final message = ChannelMessage( + senderKey: null, + senderName: parsed.senderName, + text: decodedText, + timestamp: DateTime.fromMillisecondsSinceEpoch(timestampRaw * 1000), + isOutgoing: false, + status: ChannelMessageStatus.sent, + pathLength: packet.isFlood ? packet.hopCount : 0, + pathBytes: packet.pathBytes, + channelIndex: channel.index, + packetHash: pktHash, + ); + + _updateContactLastMessageAtByName( + parsed.senderName, + message.timestamp, + pathBytes: message.pathBytes, + ); + final isNew = _addChannelMessage(channel.index, message); + _maybeIncrementChannelUnread(message, isNew: isNew); + notifyListeners(); + if (isNew) { + final label = channel.name.isEmpty + ? 'Channel ${channel.index}' + : channel.name; + _maybeNotifyChannelMessage(message, channelName: label); + } + return; + } catch (e) { + appLogger.warn('Decryption failed for channel ${channel.index}: $e'); + } } - - final timestampRaw = readUint32LE(decrypted, 0); - final text = readCString(decrypted, 5, decrypted.length - 5); - final parsed = _splitSenderText(text); - final decodedText = Smaz.tryDecodePrefixed(parsed.text) ?? parsed.text; - if (_shouldDropSelfChannelMessage(parsed.senderName, packet.pathBytes)) { - return; - } - - final pktHash = _computePacketHash(packet.payloadType, packet.payload); - - final message = ChannelMessage( - senderKey: null, - senderName: parsed.senderName, - text: decodedText, - timestamp: DateTime.fromMillisecondsSinceEpoch(timestampRaw * 1000), - isOutgoing: false, - status: ChannelMessageStatus.sent, - pathLength: packet.isFlood ? packet.hopCount : 0, - pathBytes: packet.pathBytes, - channelIndex: channel.index, - packetHash: pktHash, - ); - - _updateContactLastMessageAtByName( - parsed.senderName, - message.timestamp, - pathBytes: message.pathBytes, - ); - final isNew = _addChannelMessage(channel.index, message); - _maybeIncrementChannelUnread(message, isNew: isNew); - notifyListeners(); - if (isNew) { - final label = channel.name.isEmpty - ? 'Channel ${channel.index}' - : channel.name; - _maybeNotifyChannelMessage(message, channelName: label); - } - return; + } catch (e) { + appLogger.warn('Error handling log RX data frame: $e'); } } @@ -3774,15 +3809,15 @@ class MeshCoreConnector extends ChangeNotifier { // [2-5] = expected_ack_hash (uint32) // [6-9] = estimated_timeout_ms (uint32) - if (frame.length >= 10) { - final ackHash = Uint8List.fromList(frame.sublist(2, 6)); - final timeoutMs = readUint32LE(frame, 6); + try { + final reader = BufferReader(frame); + reader.skipBytes(2); // code + is_flood + final ackHash = reader.readUInt32LE(); + final timeoutMs = reader.readUInt32LE(); // Check if this is a CLI command ACK - if so, ignore it if (_lastSentWasCliCommand) { - final ackHashHex = ackHash - .map((b) => b.toRadixString(16).padLeft(2, '0')) - .join(); + final ackHashHex = ackHash.toRadixString(16).padLeft(8, '0'); debugPrint('Ignoring CLI command ACK (sent): $ackHashHex'); _lastSentWasCliCommand = false; return; @@ -3801,7 +3836,8 @@ class MeshCoreConnector extends ChangeNotifier { if (_markNextPendingChannelMessageSent()) { return; } - } else { + } catch (e) { + appLogger.warn('Error handling message sent frame: $e'); // Fallback to old behavior for (var messages in _conversations.values) { for (int i = messages.length - 1; i >= 0; i--) { @@ -3880,9 +3916,11 @@ class MeshCoreConnector extends ChangeNotifier { // [1-4] = ack_hash (uint32) // [5-8] = trip_time_ms (uint32) - if (frame.length >= 9) { - final ackHash = Uint8List.fromList(frame.sublist(1, 5)); - final tripTimeMs = readUint32LE(frame, 5); + try { + final reader = BufferReader(frame); + reader.skipBytes(1); // Skip code + final ackHash = reader.readUInt32LE(); + final tripTimeMs = reader.readUInt32LE(); // CLI command ACKs are already filtered in _handleMessageSent, so this should only see real messages @@ -3894,7 +3932,8 @@ class MeshCoreConnector extends ChangeNotifier { if (_retryService != null) { _retryService!.handleAckReceived(ackHash, tripTimeMs); } - } else { + } catch (e) { + appLogger.warn('Error handling send confirmed frame: $e'); // Fallback to old behavior for (var messages in _conversations.values) { for (int i = messages.length - 1; i >= 0; i--) { @@ -3909,10 +3948,8 @@ class MeshCoreConnector extends ChangeNotifier { } } - bool _handleRepeaterCommandSent(Uint8List ackHash, int timeoutMs) { - final ackHashHex = ackHash - .map((b) => b.toRadixString(16).padLeft(2, '0')) - .join(); + bool _handleRepeaterCommandSent(int ackHash, int timeoutMs) { + final ackHashHex = ackHash.toRadixString(16).padLeft(8, '0'); final entry = _pendingRepeaterAcks[ackHashHex]; if (entry == null) return false; @@ -3930,10 +3967,8 @@ class MeshCoreConnector extends ChangeNotifier { return true; } - bool _handleRepeaterCommandAck(Uint8List ackHash, int tripTimeMs) { - final ackHashHex = ackHash - .map((b) => b.toRadixString(16).padLeft(2, '0')) - .join(); + bool _handleRepeaterCommandAck(int ackHash, int tripTimeMs) { + final ackHashHex = ackHash.toRadixString(16).padLeft(8, '0'); final entry = _pendingRepeaterAcks.remove(ackHashHex); if (entry == null) return false; entry.timeout?.cancel(); diff --git a/lib/connector/meshcore_protocol.dart b/lib/connector/meshcore_protocol.dart index 01b41d4..a2e20cd 100644 --- a/lib/connector/meshcore_protocol.dart +++ b/lib/connector/meshcore_protocol.dart @@ -220,6 +220,7 @@ const int cmdGetAutoAddConfig = 59; // Text message types const int txtTypePlain = 0; const int txtTypeCliData = 1; +const int txtTypeSigned = 2; // Repeater request types (for server requests) const int reqTypeGetStatus = 0x01; @@ -314,6 +315,7 @@ const int autoAddSensorFlag = // Sizes const int pubKeySize = 32; +const int signatureSize = 64; const int maxPathSize = 64; const int pathHashSize = 1; const int maxNameSize = 32; @@ -377,88 +379,79 @@ const int msgTextOffset = 38; class ParsedContactText { final Uint8List senderPrefix; final String text; - const ParsedContactText({required this.senderPrefix, required this.text}); } ParsedContactText? parseContactMessageText(Uint8List frame) { if (frame.isEmpty) return null; - final code = frame[0]; - if (code != respCodeContactMsgRecv && code != respCodeContactMsgRecvV3) { - return null; - } - // Companion radio layout: - // [code][snr?][res?][res?][prefix x6][path_len][txt_type][timestamp x4][extra?][text...] - final isV3 = code == respCodeContactMsgRecvV3; - final prefixOffset = isV3 ? 4 : 1; - const prefixLen = 6; - final txtTypeOffset = prefixOffset + prefixLen + 1; - final timestampOffset = txtTypeOffset + 1; - final baseTextOffset = timestampOffset + 4; - if (frame.length <= baseTextOffset) return null; - - final flags = frame[txtTypeOffset]; - final shiftedType = flags >> 2; - final rawType = flags; - final isPlain = shiftedType == txtTypePlain || rawType == txtTypePlain; - final isCli = shiftedType == txtTypeCliData || rawType == txtTypeCliData; - if (!isPlain && !isCli) { - return null; - } - - var text = readCString( - frame, - baseTextOffset, - frame.length - baseTextOffset, - ).trim(); - if (text.isEmpty && frame.length > baseTextOffset + 4) { - text = readCString( - frame, - baseTextOffset + 4, - frame.length - (baseTextOffset + 4), - ).trim(); - } - if (text.isEmpty) return null; - - final senderPrefix = frame.sublist(prefixOffset, prefixOffset + prefixLen); - return ParsedContactText(senderPrefix: senderPrefix, text: text); -} - -// Helper to read uint32 little-endian -int readUint32LE(Uint8List data, int offset) { - return data[offset] | - (data[offset + 1] << 8) | - (data[offset + 2] << 16) | - (data[offset + 3] << 24); -} - -// Helper to read uint16 little-endian -int readUint16LE(Uint8List data, int offset) { - return data[offset] | (data[offset + 1] << 8); -} - -// Helper to read int32 little-endian -int readInt32LE(Uint8List data, int offset) { - int val = readUint32LE(data, offset); - if (val >= 0x80000000) val -= 0x100000000; - return val; -} - -// Helper to read null-terminated UTF-8 string -String readCString(Uint8List data, int offset, int maxLen) { - int end = offset; - while (end < offset + maxLen && end < data.length && data[end] != 0) { - end++; - } + final message = BufferReader(frame); try { - return utf8.decode(data.sublist(offset, end), allowMalformed: true); + final code = message.readByte(); + if (code != respCodeContactMsgRecv && code != respCodeContactMsgRecvV3) { + return null; + } + + // Companion radio layout: + // [code][snr?][res?][res?][prefix x6][path_len][txt_type][timestamp x4][extra?][text...] + if (code == respCodeContactMsgRecvV3) { + // Skip SNR and reserved bytes in v3 layout + message.skipBytes(3); + } + final senderPrefix = message.readBytes(6); // public key + message.skipBytes(1); // path length + final textType = message.readByte(); + message.skipBytes(4); // timestamp (4 bytes) + + final shiftedType = textType >> 2; + final isSigned = shiftedType == txtTypeSigned || textType == txtTypeSigned; + if (isSigned) { + // Signed messages have a 4-byte signature after the timestamp, before the text + message.skipBytes(4); + } + final text = message.readString(); + if (text.isEmpty) return null; + + return ParsedContactText(senderPrefix: senderPrefix, text: text); } catch (e) { - // Fallback to Latin-1 if UTF-8 decoding fails - return String.fromCharCodes(data.sublist(offset, end)); + return null; } } +// // Helper to read uint32 little-endian +// int readUint32LE(Uint8List data, int offset) { +// return data[offset] | +// (data[offset + 1] << 8) | +// (data[offset + 2] << 16) | +// (data[offset + 3] << 24); +// } + +// // Helper to read uint16 little-endian +// int readUint16LE(Uint8List data, int offset) { +// return data[offset] | (data[offset + 1] << 8); +// } + +// // Helper to read int32 little-endian +// int readInt32LE(Uint8List data, int offset) { +// int val = readUint32LE(data, offset); +// if (val >= 0x80000000) val -= 0x100000000; +// return val; +// } + +// // Helper to read null-terminated UTF-8 string +// String readCString(Uint8List data, int offset, int maxLen) { +// int end = offset; +// while (end < offset + maxLen && end < data.length && data[end] != 0) { +// end++; +// } +// try { +// return utf8.decode(data.sublist(offset, end), allowMalformed: true); +// } catch (e) { +// // Fallback to Latin-1 if UTF-8 decoding fails +// return String.fromCharCodes(data.sublist(offset, end)); +// } +// } + // Helper to convert public key to hex string String pubKeyToHex(Uint8List pubKey) { return pubKey.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); diff --git a/lib/models/channel.dart b/lib/models/channel.dart index 1a2ecdc..4fdd627 100644 --- a/lib/models/channel.dart +++ b/lib/models/channel.dart @@ -24,20 +24,23 @@ class Channel { bool get isPublicChannel => pskHex == publicChannelPsk; - static Channel? fromFrame(Uint8List data) { + static Channel? fromFrame(Uint8List frame) { // CHANNEL_INFO format: // [0] = RESP_CODE_CHANNEL_INFO (18) // [1] = channel_idx // [2-33] = name (32 bytes, null-terminated) // [34-49] = psk (16 bytes) - if (data.length < 50) return null; - if (data[0] != respCodeChannelInfo) return null; - - final index = data[1]; - final name = readCString(data, 2, 32); - final psk = Uint8List.fromList(data.sublist(34, 50)); - - return Channel(index: index, name: name, psk: psk); + if (frame.length < 50) return null; + final reader = BufferReader(frame); + try { + if (reader.readByte() != respCodeChannelInfo) return null; + final index = reader.readByte(); + final name = reader.readCStringGreedy(32); + final psk = reader.readBytes(16); + return Channel(index: index, name: name, psk: psk); + } catch (e) { + return null; + } } static Channel empty(int index) { diff --git a/lib/models/channel_message.dart b/lib/models/channel_message.dart index b0af3eb..ab81630 100644 --- a/lib/models/channel_message.dart +++ b/lib/models/channel_message.dart @@ -109,89 +109,85 @@ class ChannelMessage { ); } - static ChannelMessage? fromFrame(Uint8List data) { + static ChannelMessage? fromFrame(Uint8List frame) { // CHANNEL_MSG_RECV format varies by version: // V3: [0]=code [1]=SNR [2]=rsv1 [3]=rsv2 [4]=channel_idx [5]=path_len [path... optional] [txt_type] [timestamp x4] [text...] // Non-V3: [0]=code [1]=channel_idx [2]=path_len [3]=txt_type [4-7]=timestamp [8+]=text - if (data.length < 8) return null; + if (frame.length < 8) return null; + final reader = BufferReader(frame); + try { + final code = reader.readByte(); + if (code != respCodeChannelMsgRecv && code != respCodeChannelMsgRecvV3) { + return null; + } - final code = data[0]; - if (code != respCodeChannelMsgRecv && code != respCodeChannelMsgRecvV3) { + int pathLen; + int txtType; + Uint8List pathBytes = Uint8List(0); + int channelIdx; + if (code == respCodeChannelMsgRecvV3) { + reader.skipBytes(1); // Skip SNR + final flags = reader.readByte(); + reader.skipBytes(1); // Skip reserved byte + channelIdx = reader.readByte(); + pathLen = reader.readByte(); + txtType = reader.readByte(); + final hasPath = (flags & 0x01) != 0; + if (hasPath) { + reader.rewind(); // Rewind to read path length again for pathBytes + pathBytes = reader.readBytes(pathLen); + // Force text type to plain if path is present + txtType = txtTypePlain; + } else { + pathLen = 0; + } + } else { + channelIdx = reader.readByte(); + pathLen = reader.readByte(); + txtType = reader.readByte(); + } + final timestampRaw = reader.readUInt32LE(); + + if (txtType != txtTypePlain) { + return null; + } + + final text = reader.readString(); + + // Extract sender name and actual message from "name: msg" format + String senderName = 'Unknown'; + String actualText = text; + + final colonIndex = text.indexOf(':'); + if (colonIndex > 0 && colonIndex < text.length - 1 && colonIndex < 50) { + final potentialSender = text.substring(0, colonIndex); + if (!RegExp(r'[:\[\]]').hasMatch(potentialSender)) { + senderName = potentialSender; + final offset = + (colonIndex + 1 < text.length && text[colonIndex + 1] == ' ') + ? colonIndex + 2 + : colonIndex + 1; + actualText = text.substring(offset); + } + } + + final decodedText = Smaz.tryDecodePrefixed(actualText) ?? actualText; + + return ChannelMessage( + senderKey: null, + senderName: senderName, + text: decodedText, + timestamp: DateTime.fromMillisecondsSinceEpoch(timestampRaw * 1000), + isOutgoing: false, + status: ChannelMessageStatus.sent, + pathLength: pathLen, + pathBytes: pathBytes, + channelIndex: channelIdx, + ); + } catch (e) { + // If parsing fails, return null to avoid crashes return null; } - - int timestampOffset, textOffset, pathLenOffset, txtTypeOffset; - Uint8List pathBytes = Uint8List(0); - int channelIdx; - - if (code == respCodeChannelMsgRecvV3) { - channelIdx = data[4]; - pathLenOffset = 5; - final pathLen = data[pathLenOffset].toSigned(8); - var cursor = 6; - final hasPathBytesFlag = (data[2] & 0x01) != 0; - final canFitPath = pathLen > 0 && data.length >= cursor + pathLen + 5; - final hasValidTxtType = - cursor < data.length && - (data[cursor] == txtTypePlain || data[cursor] == txtTypeCliData); - if ((hasPathBytesFlag || (canFitPath && !hasValidTxtType)) && - canFitPath) { - pathBytes = Uint8List.fromList(data.sublist(cursor, cursor + pathLen)); - cursor += pathLen; - } - txtTypeOffset = cursor; - cursor += 1; // txt_type - timestampOffset = cursor; - textOffset = cursor + 4; - } else { - channelIdx = data[1]; - pathLenOffset = 2; - txtTypeOffset = 3; - timestampOffset = 4; - textOffset = 8; - } - - if (data.length < textOffset + 1) return null; - - final txtType = data[txtTypeOffset]; - if (txtType != txtTypePlain) { - return null; - } - - final pathLen = data[pathLenOffset].toSigned(8); - final timestampRaw = readUint32LE(data, timestampOffset); - final text = readCString(data, textOffset, data.length - textOffset); - - // Extract sender name and actual message from "name: msg" format - String senderName = 'Unknown'; - String actualText = text; - - final colonIndex = text.indexOf(':'); - if (colonIndex > 0 && colonIndex < text.length - 1 && colonIndex < 50) { - final potentialSender = text.substring(0, colonIndex); - if (!RegExp(r'[:\[\]]').hasMatch(potentialSender)) { - senderName = potentialSender; - final offset = - (colonIndex + 1 < text.length && text[colonIndex + 1] == ' ') - ? colonIndex + 2 - : colonIndex + 1; - actualText = text.substring(offset); - } - } - - final decodedText = Smaz.tryDecodePrefixed(actualText) ?? actualText; - - return ChannelMessage( - senderKey: null, - senderName: senderName, - text: decodedText, - timestamp: DateTime.fromMillisecondsSinceEpoch(timestampRaw * 1000), - isOutgoing: false, - status: ChannelMessageStatus.sent, - pathLength: pathLen, - pathBytes: pathBytes, - channelIndex: channelIdx, - ); } static ChannelMessage outgoing( diff --git a/lib/models/contact.dart b/lib/models/contact.dart index acd1da9..5c80893 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -18,6 +18,7 @@ class Contact { final DateTime lastSeen; final DateTime lastMessageAt; final bool isActive; + final bool wasPulled; final Uint8List? rawPacket; Contact({ @@ -34,6 +35,7 @@ class Contact { required this.lastSeen, DateTime? lastMessageAt, this.isActive = true, + this.wasPulled = false, this.rawPacket, }) : lastMessageAt = lastMessageAt ?? lastSeen; diff --git a/lib/models/message.dart b/lib/models/message.dart index 6f6ed88..d1660dd 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -16,7 +16,7 @@ class Message { final String? messageId; final int retryCount; final int? estimatedTimeoutMs; - final Uint8List? expectedAckHash; + final int? expectedAckHash; final DateTime? sentAt; final DateTime? deliveredAt; final int? tripTimeMs; @@ -56,7 +56,7 @@ class Message { MessageStatus? status, int? retryCount, int? estimatedTimeoutMs, - Uint8List? expectedAckHash, + int? expectedAckHash, DateTime? sentAt, DateTime? deliveredAt, int? tripTimeMs, @@ -90,33 +90,35 @@ class Message { ); } - static Message? fromFrame(Uint8List data, Uint8List selfPubKey) { - if (data.length < msgTextOffset + 1) return null; + static Message? fromFrame(Uint8List frame, Uint8List selfPubKey) { + if (frame.length < msgTextOffset + 1) return null; + final reader = BufferReader(frame); + try { + final code = reader.readByte(); + if (code != respCodeContactMsgRecv && code != respCodeContactMsgRecvV3) { + return null; + } - final code = data[0]; - if (code != respCodeContactMsgRecv && code != respCodeContactMsgRecvV3) { + final senderKey = reader.readBytes(pubKeySize); + final timestampRaw = reader.readInt32LE(); + final flags = reader.readByte(); + if ((flags >> 2) != txtTypePlain) { + return null; + } + final text = reader.readString(); + + return Message( + senderKey: senderKey, + text: text, + timestamp: DateTime.fromMillisecondsSinceEpoch(timestampRaw * 1000), + isOutgoing: false, + isCli: false, + status: MessageStatus.delivered, + pathBytes: Uint8List(0), + ); + } catch (e) { return null; } - - final senderKey = Uint8List.fromList( - data.sublist(msgPubKeyOffset, msgPubKeyOffset + pubKeySize), - ); - final timestampRaw = readUint32LE(data, msgTimestampOffset); - final flags = data[msgFlagsOffset]; - if ((flags >> 2) != txtTypePlain) { - return null; - } - final text = readCString(data, msgTextOffset, data.length - msgTextOffset); - - return Message( - senderKey: senderKey, - text: text, - timestamp: DateTime.fromMillisecondsSinceEpoch(timestampRaw * 1000), - isOutgoing: false, - isCli: false, - status: MessageStatus.delivered, - pathBytes: Uint8List(0), - ); } static Message outgoing( diff --git a/lib/screens/ble_debug_log_screen.dart b/lib/screens/ble_debug_log_screen.dart index a90f9f0..1009bc4 100644 --- a/lib/screens/ble_debug_log_screen.dart +++ b/lib/screens/ble_debug_log_screen.dart @@ -283,66 +283,66 @@ class _BleDebugLogScreenState extends State { if (payload.length < 101) { return 'ADVERT (short)'; } - var offset = 0; - final pubKey = _bytesToHex( - payload.sublist(offset, offset + 32), - spaced: false, - ); - offset += 32; - final timestamp = readUint32LE(payload, offset); - offset += 4; - offset += 64; // signature - final flags = payload[offset++]; - final role = _deviceRoleLabel(flags & 0x0F); - final hasLocation = (flags & 0x10) != 0; - final hasFeature1 = (flags & 0x20) != 0; - final hasFeature2 = (flags & 0x40) != 0; - final hasName = (flags & 0x80) != 0; - String? name; - double? lat; - double? lon; - if (hasLocation && payload.length >= offset + 8) { - lat = readInt32LE(payload, offset) / 1000000.0; - lon = readInt32LE(payload, offset + 4) / 1000000.0; - offset += 8; + final reader = BufferReader(payload); + try { + final pubKey = _bytesToHex(reader.readBytes(pubKeySize), spaced: false); + + final timestamp = reader.readUInt32LE(); + reader.skipBytes(signatureSize); + final flags = reader.readByte(); + final role = _deviceRoleLabel(flags & 0x0F); + final hasLocation = (flags & 0x10) != 0; + final hasFeature1 = (flags & 0x20) != 0; + final hasFeature2 = (flags & 0x40) != 0; + final hasName = (flags & 0x80) != 0; + String? name; + double? lat; + double? lon; + if (hasLocation) { + lat = reader.readInt32LE() / 1000000.0; + lon = reader.readInt32LE() / 1000000.0; + } + if (hasFeature1) reader.skipBytes(2); + if (hasFeature2) reader.skipBytes(2); + if (hasName) { + name = reader.readCStringGreedy(maxNameSize); + } + final namePart = (name != null && name.isNotEmpty) ? ' name="$name"' : ''; + final locPart = (lat != null && lon != null) + ? ' loc=${lat.toStringAsFixed(6)},${lon.toStringAsFixed(6)}' + : ''; + return 'ADVERT role=$role ts=$timestamp$namePart$locPart key=${pubKey.substring(0, 12)}…'; + } catch (e) { + return 'ADVERT (invalid)'; } - if (hasFeature1) offset += 2; - if (hasFeature2) offset += 2; - if (hasName && payload.length > offset) { - final rawName = String.fromCharCodes(payload.sublist(offset)); - final nul = rawName.indexOf('\u0000'); - name = nul >= 0 ? rawName.substring(0, nul) : rawName; - name = name.trim(); - } - final namePart = (name != null && name.isNotEmpty) ? ' name="$name"' : ''; - final locPart = (lat != null && lon != null) - ? ' loc=${lat.toStringAsFixed(6)},${lon.toStringAsFixed(6)}' - : ''; - return 'ADVERT role=$role ts=$timestamp$namePart$locPart key=${pubKey.substring(0, 12)}…'; } String _decodeControlSummary(Uint8List payload) { - if (payload.isEmpty) return 'CONTROL (empty)'; - final flags = payload[0]; - final subType = flags & 0xF0; - if (subType == 0x80) { - if (payload.length < 6) return 'CONTROL DISCOVER_REQ (short)'; - final typeFilter = payload[1]; - final tag = readUint32LE(payload, 2); - final since = payload.length >= 10 ? readUint32LE(payload, 6) : 0; - return 'CONTROL DISCOVER_REQ filter=0x${typeFilter.toRadixString(16).padLeft(2, '0')} tag=$tag since=$since'; + final reader = BufferReader(payload); + try { + final flags = reader.readByte(); + final subType = flags & 0xF0; + if (subType == 0x80) { + if (payload.length < 6) return 'CONTROL DISCOVER_REQ (short)'; + final typeFilter = reader.readByte(); + final tag = reader.readInt32LE(); + final since = payload.length >= 10 ? reader.readInt32LE() : 0; + return 'CONTROL DISCOVER_REQ filter=0x${typeFilter.toRadixString(16).padLeft(2, '0')} tag=$tag since=$since'; + } + if (subType == 0x90) { + if (payload.length < 14) return 'CONTROL DISCOVER_RESP (short)'; + final nodeType = flags & 0x0F; + final snrRaw = payload[1]; + final snrSigned = snrRaw > 127 ? snrRaw - 256 : snrRaw; + final snr = snrSigned / 4.0; + final tag = reader.readInt32LE(); + final keyLen = payload.length - 6; + return 'CONTROL DISCOVER_RESP node=${_deviceRoleLabel(nodeType)} snr=${snr.toStringAsFixed(2)} tag=$tag key=$keyLen'; + } + return 'CONTROL subtype=0x${subType.toRadixString(16).padLeft(2, '0')}'; + } catch (e) { + return 'CONTROL (invalid)'; } - if (subType == 0x90) { - if (payload.length < 14) return 'CONTROL DISCOVER_RESP (short)'; - final nodeType = flags & 0x0F; - final snrRaw = payload[1]; - final snrSigned = snrRaw > 127 ? snrRaw - 256 : snrRaw; - final snr = snrSigned / 4.0; - final tag = readUint32LE(payload, 2); - final keyLen = payload.length - 6; - return 'CONTROL DISCOVER_RESP node=${_deviceRoleLabel(nodeType)} snr=${snr.toStringAsFixed(2)} tag=$tag key=$keyLen'; - } - return 'CONTROL subtype=0x${subType.toRadixString(16).padLeft(2, '0')}'; } String _payloadTypeLabel(int payloadType) { diff --git a/lib/services/ble_debug_log_service.dart b/lib/services/ble_debug_log_service.dart index df2822b..745b243 100644 --- a/lib/services/ble_debug_log_service.dart +++ b/lib/services/ble_debug_log_service.dart @@ -245,6 +245,19 @@ class BleDebugLogService extends ChangeNotifier { } } + // Helper to read uint32 little-endian + int readUint32LE(Uint8List data, int offset) { + return data[offset] | + (data[offset + 1] << 8) | + (data[offset + 2] << 16) | + (data[offset + 3] << 24); + } + + // // Helper to read uint16 little-endian + int readUint16LE(Uint8List data, int offset) { + return data[offset] | (data[offset + 1] << 8); + } + String _frameDetail(int code, Uint8List frame) { switch (code) { case respCodeSent: diff --git a/lib/services/message_retry_service.dart b/lib/services/message_retry_service.dart index b284425..1920418 100644 --- a/lib/services/message_retry_service.dart +++ b/lib/services/message_retry_service.dart @@ -11,7 +11,7 @@ import 'app_debug_log_service.dart'; class _AckHistoryEntry { final String messageId; - final List ackHashes; + final List ackHashes; final DateTime timestamp; _AckHistoryEntry({ @@ -77,7 +77,7 @@ class MessageRetryService extends ChangeNotifier { final Map _pendingContacts = {}; final Map> _attemptPathHistory = {}; final Map _ackHashToMessageId = {}; - final Map> _expectedAckHashes = {}; + final Map> _expectedAckHashes = {}; final List<_AckHistoryEntry> _ackHistory = []; final Map> _sendQueue = {}; final Set _activeMessages = {}; @@ -341,13 +341,11 @@ class MessageRetryService extends ChangeNotifier { config.sendMessage(contact, message.text, attempt, timestampSeconds); } - bool updateMessageFromSent(Uint8List ackHash, int timeoutMs) { + bool updateMessageFromSent(int ackHash, int timeoutMs) { final config = _config; if (config == null) return false; - final ackHashHex = ackHash - .map((b) => b.toRadixString(16).padLeft(2, '0')) - .join(); + final ackHashHex = ackHash.toRadixString(16).padLeft(8, '0'); // Try hash-based matching (fixes LoRa message drops causing mismatches) String? messageId = _expectedHashToMessageId.remove(ackHashHex); @@ -389,10 +387,8 @@ class MessageRetryService extends ChangeNotifier { // Add this ACK hash to the list of expected ACKs for this message (for history) _expectedAckHashes[messageId] ??= []; - if (!_expectedAckHashes[messageId]!.any( - (hash) => listEquals(hash, ackHash), - )) { - _expectedAckHashes[messageId]!.add(Uint8List.fromList(ackHash)); + if (!_expectedAckHashes[messageId]!.any((hash) => hash == ackHash)) { + _expectedAckHashes[messageId]!.add(ackHash); } // Calculate timeout: prefer ML prediction, then device-provided, then physics fallback @@ -559,10 +555,10 @@ class MessageRetryService extends ChangeNotifier { } } - bool _checkAckHistory(Uint8List ackHash) { + bool _checkAckHistory(int ackHash) { for (final entry in _ackHistory) { for (final expectedHash in entry.ackHashes) { - if (listEquals(expectedHash, ackHash)) { + if (expectedHash == ackHash) { return true; } } @@ -570,13 +566,11 @@ class MessageRetryService extends ChangeNotifier { return false; } - void handleAckReceived(Uint8List ackHash, int tripTimeMs) { + void handleAckReceived(int ackHash, int tripTimeMs) { final config = _config; String? matchedMessageId; int? matchedAttemptIndex; - final ackHashHex = ackHash - .map((b) => b.toRadixString(16).padLeft(2, '0')) - .join(); + final ackHashHex = ackHash.toRadixString(16).padLeft(8, '0'); // Clean up old ACK hash mappings (older than 15 minutes) final cutoffTime = DateTime.now().subtract(const Duration(minutes: 15)); @@ -606,7 +600,7 @@ class MessageRetryService extends ChangeNotifier { final expectedHashes = entry.value; for (final expectedHash in expectedHashes) { - if (listEquals(expectedHash, ackHash)) { + if (expectedHash == ackHash) { matchedMessageId = messageId; matchedAttemptIndex = expectedHashes.indexOf(expectedHash); break; @@ -689,7 +683,7 @@ class MessageRetryService extends ChangeNotifier { for (var entry in _pendingMessages.entries) { final message = entry.value; if (message.expectedAckHash != null && - listEquals(message.expectedAckHash, ackHash)) { + message.expectedAckHash == ackHash) { final contact = _pendingContacts[entry.key]; return contact?.publicKeyHex; } diff --git a/lib/storage/message_store.dart b/lib/storage/message_store.dart index 44d3621..5550911 100644 --- a/lib/storage/message_store.dart +++ b/lib/storage/message_store.dart @@ -85,9 +85,7 @@ class MessageStore { 'messageId': msg.messageId, 'retryCount': msg.retryCount, 'estimatedTimeoutMs': msg.estimatedTimeoutMs, - 'expectedAckHash': msg.expectedAckHash != null - ? base64Encode(msg.expectedAckHash!) - : null, + 'expectedAckHash': msg.expectedAckHash, 'sentAt': msg.sentAt?.millisecondsSinceEpoch, 'deliveredAt': msg.deliveredAt?.millisecondsSinceEpoch, 'tripTimeMs': msg.tripTimeMs, @@ -119,9 +117,7 @@ class MessageStore { messageId: json['messageId'] as String?, retryCount: json['retryCount'] as int? ?? 0, estimatedTimeoutMs: json['estimatedTimeoutMs'] as int?, - expectedAckHash: json['expectedAckHash'] != null - ? Uint8List.fromList(base64Decode(json['expectedAckHash'] as String)) - : null, + expectedAckHash: json['expectedAckHash'] as int? ?? 0, sentAt: json['sentAt'] != null ? DateTime.fromMillisecondsSinceEpoch(json['sentAt'] as int) : null, diff --git a/lib/widgets/debug_frame_viewer.dart b/lib/widgets/debug_frame_viewer.dart index c8dc371..05e312b 100644 --- a/lib/widgets/debug_frame_viewer.dart +++ b/lib/widgets/debug_frame_viewer.dart @@ -10,6 +10,14 @@ class DebugFrameViewer { Uint8List frame, String title, ) { + // Helper to read uint32 little-endian + int readUint32LE(Uint8List data, int offset) { + return data[offset] | + (data[offset + 1] << 8) | + (data[offset + 2] << 16) | + (data[offset + 3] << 24); + } + final hexString = frame .map((b) => b.toRadixString(16).padLeft(2, '0')) .join(' '); From 767dc1164edae9b42be64911375bc3232819199c Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Sun, 22 Mar 2026 10:50:11 -0700 Subject: [PATCH 14/36] refactor: Replace string reading methods with CString equivalents and improve error handling --- lib/connector/meshcore_connector.dart | 92 ++++++++++------------ lib/connector/meshcore_protocol.dart | 71 +++++++---------- lib/models/channel_message.dart | 10 +-- lib/models/message.dart | 2 +- lib/services/ble_debug_log_service.dart | 13 --- lib/services/message_retry_service.dart | 9 +-- lib/widgets/debug_frame_viewer.dart | 8 -- test/services/retry_and_protocol_test.dart | 12 +-- 8 files changed, 82 insertions(+), 135 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index e8555ff..5dc660e 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -2127,9 +2127,7 @@ class MeshCoreConnector extends ChangeNotifier { outboundText, selfKey, ); - final ackHashHex = ackHash - .map((b) => b.toRadixString(16).padLeft(2, '0')) - .join(); + final ackHashHex = ackHashToHex(ackHash); final messageBytes = utf8.encode(outboundText).length; _pendingRepeaterAcks[ackHashHex]?.timeout?.cancel(); _pendingRepeaterAcks[ackHashHex] = _RepeaterAckContext( @@ -2901,7 +2899,7 @@ class MeshCoreConnector extends ChangeNotifier { _currentSf = reader.readByte(); _currentCr = reader.readByte(); - _selfName = reader.readString(); + _selfName = reader.readCString(); } catch (e) { _appDebugLogService?.error( 'Error parsing SELF_INFO frame: $e', @@ -3554,7 +3552,7 @@ class MeshCoreConnector extends ChangeNotifier { reader.skipBytes(4); // Skip extra 4 bytes for signed/plain variants } - final msgText = reader.readString(); + final msgText = reader.readCString(); final flags = txtType; final shiftedType = flags >> 2; @@ -3724,10 +3722,9 @@ class MeshCoreConnector extends ChangeNotifier { final packet = _parseRawPacket(raw); if (packet == null || packet.payloadType != _payloadTypeGroupText) return; - final payload = packet.payload; - if (payload.length <= _cipherMacSize) return; - final channelHash = payload[0]; - final encrypted = Uint8List.fromList(payload.sublist(1)); + final payload = BufferReader(packet.payload); + final channelHash = payload.readByte(); + final encrypted = Uint8List.fromList(payload.readRemainingBytes()); // Use cached channels as fallback if live channels not yet loaded final channelsToSearch = _channels.isNotEmpty @@ -3741,15 +3738,14 @@ class MeshCoreConnector extends ChangeNotifier { final decryptedBytes = _decryptPayload(channel.psk, encrypted); if (decryptedBytes == null || decryptedBytes.length < 6) return; final decrypted = BufferReader(decryptedBytes); - // Skip header + SNR + reserved (2) - decrypted.skipBytes(4); + + final timestampRaw = decrypted.readUInt32LE(); final txtType = decrypted.readByte(); if ((txtType >> 2) != 0) { return; } - final timestampRaw = decrypted.readUInt32LE(); - final text = decrypted.readString(); + final text = decrypted.readCString(); final parsed = _splitSenderText(text); final decodedText = Smaz.tryDecodePrefixed(parsed.text) ?? parsed.text; @@ -3811,13 +3807,13 @@ class MeshCoreConnector extends ChangeNotifier { try { final reader = BufferReader(frame); - reader.skipBytes(2); // code + is_flood + reader.skipBytes(2); //Skip code and is_flood final ackHash = reader.readUInt32LE(); final timeoutMs = reader.readUInt32LE(); // Check if this is a CLI command ACK - if so, ignore it if (_lastSentWasCliCommand) { - final ackHashHex = ackHash.toRadixString(16).padLeft(8, '0'); + final ackHashHex = ackHashToHex(ackHash); debugPrint('Ignoring CLI command ACK (sent): $ackHashHex'); _lastSentWasCliCommand = false; return; @@ -3949,7 +3945,7 @@ class MeshCoreConnector extends ChangeNotifier { } bool _handleRepeaterCommandSent(int ackHash, int timeoutMs) { - final ackHashHex = ackHash.toRadixString(16).padLeft(8, '0'); + final ackHashHex = ackHashToHex(ackHash); final entry = _pendingRepeaterAcks[ackHashHex]; if (entry == null) return false; @@ -3968,7 +3964,7 @@ class MeshCoreConnector extends ChangeNotifier { } bool _handleRepeaterCommandAck(int ackHash, int tripTimeMs) { - final ackHashHex = ackHash.toRadixString(16).padLeft(8, '0'); + final ackHashHex = ackHashToHex(ackHash); final entry = _pendingRepeaterAcks.remove(ackHashHex); if (entry == null) return false; entry.timeout?.cancel(); @@ -4319,36 +4315,34 @@ class MeshCoreConnector extends ChangeNotifier { } _RawPacket? _parseRawPacket(Uint8List raw) { - if (raw.length < 3) return null; - var index = 0; - final header = raw[index++]; - final routeType = header & _phRouteMask; - final hasTransport = - routeType == _routeTransportFlood || routeType == _routeTransportDirect; - if (hasTransport) { - if (raw.length < index + 4) return null; - index += 4; - } - if (raw.length <= index) return null; - final pathLenRaw = raw[index++]; - final pathByteLen = _decodePathByteLen(pathLenRaw); - if (raw.length < index + pathByteLen) return null; - final pathBytes = Uint8List.fromList( - raw.sublist(index, index + pathByteLen), - ); - index += pathByteLen; - if (raw.length <= index) return null; - final payload = Uint8List.fromList(raw.sublist(index)); + try { + final reader = BufferReader(raw); + final header = reader.readByte(); + final routeType = header & _phRouteMask; + final hasTransport = + routeType == _routeTransportFlood || + routeType == _routeTransportDirect; + if (hasTransport) { + reader.skipBytes(2); // Skip reserved bytes in transport header + } + final pathLenRaw = reader.readByte(); + final pathByteLen = _decodePathByteLen(pathLenRaw); + final pathBytes = reader.readBytes(pathByteLen); + final payload = reader.readBytes(reader.remaining); - return _RawPacket( - header: header, - routeType: routeType, - payloadType: (header >> _phTypeShift) & _phTypeMask, - payloadVer: (header >> _phVerShift) & _phVerMask, - pathLenRaw: pathLenRaw, - pathBytes: pathBytes, - payload: payload, - ); + return _RawPacket( + header: header, + routeType: routeType, + payloadType: (header >> _phTypeShift) & _phTypeMask, + payloadVer: (header >> _phVerShift) & _phVerMask, + pathLenRaw: pathLenRaw, + pathBytes: pathBytes, + payload: payload, + ); + } catch (e) { + appLogger.warn('Error parsing raw packet: $e'); + return null; + } } int _computeChannelHash(Uint8List psk) { @@ -4767,7 +4761,7 @@ class MeshCoreConnector extends ChangeNotifier { void _handleCustomVars(Uint8List frame) { final buf = BufferReader(frame.sublist(1)); try { - _currentCustomVars = _parseKeyValueString(buf.readString()); + _currentCustomVars = _parseKeyValueString(buf.readCString()); } catch (e) { appLogger.warn('Malformed custom vars frame: $e', tag: 'Connector'); } @@ -4924,7 +4918,7 @@ class MeshCoreConnector extends ChangeNotifier { longitude = packet.readInt32LE() / 1e6; } if (hasName && packet.remaining > 0) { - name = packet.readString(); + name = packet.readCString(); } } catch (e) { appLogger.warn('Malformed advert frame: $e', tag: 'Connector'); @@ -4986,7 +4980,7 @@ class MeshCoreConnector extends ChangeNotifier { longitude = advert.readInt32LE() / 1e6; } if (hasName && advert.remaining > 0) { - name = advert.readString(); + name = advert.readCString(); } } catch (e) { appLogger.warn('Malformed advert frame: $e', tag: 'Connector'); diff --git a/lib/connector/meshcore_protocol.dart b/lib/connector/meshcore_protocol.dart index a2e20cd..b368756 100644 --- a/lib/connector/meshcore_protocol.dart +++ b/lib/connector/meshcore_protocol.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:flutter/widgets.dart'; + // Buffer Reader - sequential binary data reader with pointer tracking class BufferReader { int _pointer = 0; @@ -37,16 +39,6 @@ class BufferReader { Uint8List readRemainingBytes() => readBytes(remaining); - String readString() { - _lastPointer = _pointer; - final value = readRemainingBytes(); - try { - return utf8.decode(Uint8List.fromList(value), allowMalformed: true); - } catch (e) { - return String.fromCharCodes(value); // Latin-1 fallback - } - } - String readCStringGreedy(int maxLength) { _lastPointer = _pointer; final value = []; @@ -62,11 +54,12 @@ class BufferReader { } } - String readCString(int maxLength) { + String readCString({int maxLength = -1}) { final backupPointer = _pointer; final value = []; int counter = 0; - while (counter < maxLength) { + final maxLen = maxLength >= 0 ? maxLength : remaining; + while (counter < maxLen) { final byte = readByte(); if (byte == 0) break; value.add(byte); @@ -409,48 +402,40 @@ ParsedContactText? parseContactMessageText(Uint8List frame) { // Signed messages have a 4-byte signature after the timestamp, before the text message.skipBytes(4); } - final text = message.readString(); + final text = message.readCString(); if (text.isEmpty) return null; return ParsedContactText(senderPrefix: senderPrefix, text: text); } catch (e) { + debugPrint('Error parsing contact message text: $e'); return null; } } -// // Helper to read uint32 little-endian -// int readUint32LE(Uint8List data, int offset) { -// return data[offset] | -// (data[offset + 1] << 8) | -// (data[offset + 2] << 16) | -// (data[offset + 3] << 24); -// } +// Helper to read uint32 little-endian +int readUint32LE(Uint8List data, int offset) { + return data[offset] | + (data[offset + 1] << 8) | + (data[offset + 2] << 16) | + (data[offset + 3] << 24); +} -// // Helper to read uint16 little-endian -// int readUint16LE(Uint8List data, int offset) { -// return data[offset] | (data[offset + 1] << 8); -// } +// Helper to read uint16 little-endian +int readUint16LE(Uint8List data, int offset) { + return data[offset] | (data[offset + 1] << 8); +} -// // Helper to read int32 little-endian -// int readInt32LE(Uint8List data, int offset) { -// int val = readUint32LE(data, offset); -// if (val >= 0x80000000) val -= 0x100000000; -// return val; -// } +// Helper to read int32 little-endian +int readInt32LE(Uint8List data, int offset) { + int val = readUint32LE(data, offset); + if (val >= 0x80000000) val -= 0x100000000; + return val; +} -// // Helper to read null-terminated UTF-8 string -// String readCString(Uint8List data, int offset, int maxLen) { -// int end = offset; -// while (end < offset + maxLen && end < data.length && data[end] != 0) { -// end++; -// } -// try { -// return utf8.decode(data.sublist(offset, end), allowMalformed: true); -// } catch (e) { -// // Fallback to Latin-1 if UTF-8 decoding fails -// return String.fromCharCodes(data.sublist(offset, end)); -// } -// } +// Helper to convert uint32 to hex string +String ackHashToHex(int ackHash) { + return ackHash.toRadixString(16).padLeft(8, '0'); +} // Helper to convert public key to hex string String pubKeyToHex(Uint8List pubKey) { diff --git a/lib/models/channel_message.dart b/lib/models/channel_message.dart index ab81630..98a8f1d 100644 --- a/lib/models/channel_message.dart +++ b/lib/models/channel_message.dart @@ -2,6 +2,7 @@ import 'dart:typed_data'; import '../connector/meshcore_protocol.dart'; import '../helpers/reaction_helper.dart'; import '../helpers/smaz.dart'; +import '../utils/app_logger.dart'; enum ChannelMessageStatus { pending, sent, failed } @@ -114,8 +115,8 @@ class ChannelMessage { // V3: [0]=code [1]=SNR [2]=rsv1 [3]=rsv2 [4]=channel_idx [5]=path_len [path... optional] [txt_type] [timestamp x4] [text...] // Non-V3: [0]=code [1]=channel_idx [2]=path_len [3]=txt_type [4-7]=timestamp [8+]=text if (frame.length < 8) return null; - final reader = BufferReader(frame); try { + final reader = BufferReader(frame); final code = reader.readByte(); if (code != respCodeChannelMsgRecv && code != respCodeChannelMsgRecvV3) { return null; @@ -133,11 +134,9 @@ class ChannelMessage { pathLen = reader.readByte(); txtType = reader.readByte(); final hasPath = (flags & 0x01) != 0; - if (hasPath) { + if (hasPath && pathLen > 0) { reader.rewind(); // Rewind to read path length again for pathBytes pathBytes = reader.readBytes(pathLen); - // Force text type to plain if path is present - txtType = txtTypePlain; } else { pathLen = 0; } @@ -152,7 +151,7 @@ class ChannelMessage { return null; } - final text = reader.readString(); + final text = reader.readCString(); // Extract sender name and actual message from "name: msg" format String senderName = 'Unknown'; @@ -185,6 +184,7 @@ class ChannelMessage { channelIndex: channelIdx, ); } catch (e) { + appLogger.error('Error parsing channel message frame: $e'); // If parsing fails, return null to avoid crashes return null; } diff --git a/lib/models/message.dart b/lib/models/message.dart index d1660dd..6b930c0 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -105,7 +105,7 @@ class Message { if ((flags >> 2) != txtTypePlain) { return null; } - final text = reader.readString(); + final text = reader.readCString(); return Message( senderKey: senderKey, diff --git a/lib/services/ble_debug_log_service.dart b/lib/services/ble_debug_log_service.dart index 745b243..df2822b 100644 --- a/lib/services/ble_debug_log_service.dart +++ b/lib/services/ble_debug_log_service.dart @@ -245,19 +245,6 @@ class BleDebugLogService extends ChangeNotifier { } } - // Helper to read uint32 little-endian - int readUint32LE(Uint8List data, int offset) { - return data[offset] | - (data[offset + 1] << 8) | - (data[offset + 2] << 16) | - (data[offset + 3] << 24); - } - - // // Helper to read uint16 little-endian - int readUint16LE(Uint8List data, int offset) { - return data[offset] | (data[offset + 1] << 8); - } - String _frameDetail(int code, Uint8List frame) { switch (code) { case respCodeSent: diff --git a/lib/services/message_retry_service.dart b/lib/services/message_retry_service.dart index 1920418..94f3caf 100644 --- a/lib/services/message_retry_service.dart +++ b/lib/services/message_retry_service.dart @@ -98,7 +98,7 @@ class MessageRetryService extends ChangeNotifier { /// Compute expected ACK hash using same algorithm as firmware: /// SHA256([timestamp(4)][attempt(1)][text][sender_pubkey(32)]) -> first 4 bytes - static Uint8List computeExpectedAckHash( + static int computeExpectedAckHash( int timestampSeconds, int attempt, String text, @@ -126,7 +126,8 @@ class MessageRetryService extends ChangeNotifier { // Compute SHA256 and return first 4 bytes final hash = sha256.convert(buffer); - return Uint8List.fromList(hash.bytes.sublist(0, 4)); + final bytes = Uint8List.fromList(hash.bytes.sublist(0, 4)); + return (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0]; } Future sendMessageWithRetry({ @@ -324,9 +325,7 @@ class MessageRetryService extends ChangeNotifier { outboundText, selfPubKey, ); - final expectedHashHex = expectedHash - .map((b) => b.toRadixString(16).padLeft(2, '0')) - .join(); + final expectedHashHex = expectedHash.toRadixString(16).padLeft(8, '0'); _expectedHashToMessageId[expectedHashHex] = messageId; final shortText = message.text.length > 20 diff --git a/lib/widgets/debug_frame_viewer.dart b/lib/widgets/debug_frame_viewer.dart index 05e312b..c8dc371 100644 --- a/lib/widgets/debug_frame_viewer.dart +++ b/lib/widgets/debug_frame_viewer.dart @@ -10,14 +10,6 @@ class DebugFrameViewer { Uint8List frame, String title, ) { - // Helper to read uint32 little-endian - int readUint32LE(Uint8List data, int offset) { - return data[offset] | - (data[offset + 1] << 8) | - (data[offset + 2] << 16) | - (data[offset + 3] << 24); - } - final hexString = frame .map((b) => b.toRadixString(16).padLeft(2, '0')) .join(' '); diff --git a/test/services/retry_and_protocol_test.dart b/test/services/retry_and_protocol_test.dart index b58da45..48d4cfb 100644 --- a/test/services/retry_and_protocol_test.dart +++ b/test/services/retry_and_protocol_test.dart @@ -169,16 +169,6 @@ void main() { expect(first, equals(second)); }); - test('hash is exactly 4 bytes long', () { - final hash = MessageRetryService.computeExpectedAckHash( - fixedTs, - 0, - fixedText, - fixedKey, - ); - expect(hash.length, equals(4)); - }); - test('hash matches manual SHA-256 computation', () { for (int attempt = 0; attempt < 4; attempt++) { final actual = MessageRetryService.computeExpectedAckHash( @@ -509,7 +499,7 @@ void main() { fixedText, fixedKey, ); - final hex = hash.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); + final hex = hash.toRadixString(16).padLeft(8, '0'); expect( hashes.containsKey(hex), isFalse, From 9a75c912af9cad4f85791b8ca115f8ed7598b7e6 Mon Sep 17 00:00:00 2001 From: Sebastian Szymbor Date: Mon, 12 Jan 2026 16:29:23 +0100 Subject: [PATCH 15/36] Update Polish localization strings for clarity --- lib/l10n/app_localizations_pl.dart | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 176c17e..f55c29f 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1622,20 +1622,10 @@ class AppLocalizationsPl extends AppLocalizations { String get map_showSharedMarkers => 'Pokaż współdzielone znaki.'; @override - String get map_showGuessedLocations => - 'Wyświetl lokalizacje zgadanych węzłów'; + String get map_lastSeenTime => 'Ostatni raz widziany'; @override - String get map_showDiscoveryContacts => 'Pokaż kontakty odkrywania'; - - @override - String get map_guessedLocation => 'Wydana lokalizacja'; - - @override - String get map_lastSeenTime => 'Ostatni raz widiany'; - - @override - String get map_sharedPin => 'Podzielony PIN'; + String get map_sharedPin => 'Współdzielony PIN'; @override String get map_joinRoom => 'Dołącz do pokoju'; @@ -1722,7 +1712,7 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get mapCache_downloadTilesButton => 'Pobierz Paski'; + String get mapCache_downloadTilesButton => 'Pobierz kafelki'; @override String get mapCache_clearCacheButton => 'Wyczyść pamięć podręczną'; @@ -2177,7 +2167,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_encryptedAdvertInterval => - 'Zaszyfrowany Interwał Reklamowy'; + 'Interwał Zaszyfrowanego Rozgłoszenia'; @override String get repeater_dangerZone => 'Strefa Zagrożeń'; @@ -2191,7 +2181,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_rebootRepeaterConfirm => - 'Czy na pewno chcesz zrestartować ten repeater?'; + 'Czy na pewno chcesz zrestartować ten powtarzacz?'; @override String get repeater_regenerateIdentityKey => 'Wygeneruj klucz tożsamości'; @@ -2202,7 +2192,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_regenerateIdentityKeyConfirm => - 'To zostanie wygenerowane nowe tożsamość dla powtarzacza. Kontynuować?'; + 'Zostanie wygenerowana nowa tożsamość dla powtarzacza. Kontynuować?'; @override String get repeater_eraseFileSystem => 'Wyczyść System Plików'; @@ -2337,7 +2327,7 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_cliQuickClock => 'Godzina'; @override - String get repeater_cliHelpAdvert => 'Wysyła pakiet reklamowy'; + String get repeater_cliHelpAdvert => 'Wysyła pakiet rozgłoszeniowy'; @override String get repeater_cliHelpReboot => @@ -2392,11 +2382,11 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_cliHelpSetAdvertInterval => - 'Ustawia interwał timera w minutach do wysyłania pakietu reklamy lokalnej (bezpośredniej). Ustaw na 0, aby wyłączyć.'; + 'Ustawia interwał timera w minutach do wysyłania pakietu rozgłoszenia lokalnego (bezpośredniego). Ustaw na 0, aby wyłączyć.'; @override String get repeater_cliHelpSetFloodAdvertInterval => - 'Ustawia interwał timera w godzinach do wysłania pakietu reklamowego typu \"powiew\". Ustaw na 0, aby wyłączyć.'; + 'Ustawia interwał timera w godzinach do wysłania pakietu rozgłoszeniowego typu \"flood\". Ustaw na 0, aby wyłączyć.'; @override String get repeater_cliHelpSetGuestPassword => @@ -2478,7 +2468,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_cliHelpNeighbors => - 'Wyświetla listę innych węzłów powtarzających się, które usłyszano dzięki reklamom zero-hop. Każda linia to: id-prefix-hex:timestamp:snr-times-4'; + 'Wyświetla listę innych węzłów powtarzających się, które usłyszano dzięki rozgłszeniom zero-hop. Każda linia to: id-prefix-hex:timestamp:snr-times-4'; @override String get repeater_cliHelpNeighborRemove => From 7c16dde989230442f4ec0aa62de871fdfa296497 Mon Sep 17 00:00:00 2001 From: thesebas Date: Wed, 18 Feb 2026 19:03:17 +0100 Subject: [PATCH 16/36] Update Polish localization strings --- lib/l10n/app_localizations_pl.dart | 2 +- lib/l10n/app_pl.arb | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index f55c29f..d838732 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -2468,7 +2468,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_cliHelpNeighbors => - 'Wyświetla listę innych węzłów powtarzających się, które usłyszano dzięki rozgłszeniom zero-hop. Każda linia to: id-prefix-hex:timestamp:snr-times-4'; + 'Wyświetla listę innych węzłów powtarzających się, które usłyszano dzięki rozgłoszeniom zero-hop. Każda linia to: id-prefix-hex:timestamp:snr-times-4'; @override String get repeater_cliHelpNeighborRemove => diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index c6e3fc4..ea8fbc0 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -680,8 +680,8 @@ "map_publicKeyPrefix": "Przewód klucza publicznego", "map_markers": "Oznaczarki", "map_showSharedMarkers": "Pokaż współdzielone znaki.", - "map_lastSeenTime": "Ostatni raz widiany", - "map_sharedPin": "Podzielony PIN", + "map_lastSeenTime": "Ostatni raz widziany", + "map_sharedPin": "Współdzielony PIN", "map_joinRoom": "Dołącz do pokoju", "map_manageRepeater": "Zarządzaj Powtórzami", "mapCache_title": "Bufor Map Offline", @@ -742,7 +742,7 @@ } } }, - "mapCache_downloadTilesButton": "Pobierz Paski", + "mapCache_downloadTilesButton": "Pobierz kafelki", "mapCache_clearCacheButton": "Wyczyść pamięć podręczną", "mapCache_failedDownloads": "Nieudane pobrania: {count}", "@mapCache_failedDownloads": { @@ -1029,14 +1029,14 @@ } } }, - "repeater_encryptedAdvertInterval": "Zaszyfrowany Interwał Reklamowy", + "repeater_encryptedAdvertInterval": "Interwał Zaszyfrowanego Rozgłoszenia", "repeater_dangerZone": "Strefa Zagrożeń", "repeater_rebootRepeater": "Zrestartuj Powtarzacz", "repeater_rebootRepeaterSubtitle": "Zrestartuj urządzenie powtarzające.", - "repeater_rebootRepeaterConfirm": "Czy na pewno chcesz zrestartować ten repeater?", + "repeater_rebootRepeaterConfirm": "Czy na pewno chcesz zrestartować ten powtarzacz?", "repeater_regenerateIdentityKey": "Wygeneruj klucz tożsamości", "repeater_regenerateIdentityKeySubtitle": "Wygeneruj nową parę kluczy publicznych/prywatnych", - "repeater_regenerateIdentityKeyConfirm": "To zostanie wygenerowane nowe tożsamość dla powtarzacza. Kontynuować?", + "repeater_regenerateIdentityKeyConfirm": "Zostanie wygenerowana nowa tożsamość dla powtarzacza. Kontynuować?", "repeater_eraseFileSystem": "Wyczyść System Plików", "repeater_eraseFileSystemSubtitle": "Sformatuj system plików powielacza", "repeater_eraseFileSystemConfirm": "OSTRZEŻENIE: To spowoduje usunięcie wszystkich danych z powtarzacza. Nie da się tego cofnąć!", @@ -1117,7 +1117,7 @@ "repeater_cliQuickVersion": "Wersja", "repeater_cliQuickAdvertise": "Reklama", "repeater_cliQuickClock": "Godzina", - "repeater_cliHelpAdvert": "Wysyła pakiet reklamowy", + "repeater_cliHelpAdvert": "Wysyła pakiet rozgłoszeniowy", "repeater_cliHelpReboot": "Zresetuj urządzenie. (Uwaga, może pojawić się 'Timeout', co jest normalne)", "repeater_cliHelpClock": "Wyświetla aktualny czas zgodnie z zegarem urządzenia.", "repeater_cliHelpPassword": "Ustawia nowe hasło administratora dla urządzenia.", @@ -1131,8 +1131,8 @@ "repeater_cliHelpSetIntThresh": "Ustawia Próg Interferencji (w dB). Domyślnie wynosi 14. Ustaw na 0, aby wyłączyć wykrywanie zakłóceń kanału.", "repeater_cliHelpSetAgcResetInterval": "Ustawia interwał do zresetowania Automatycznego Sterownika Głośności. Ustaw na 0, aby wyłączyć.", "repeater_cliHelpSetMultiAcks": "Włącza lub wyłącza funkcję 'podwójnych potwierdzeń'.", - "repeater_cliHelpSetAdvertInterval": "Ustawia interwał timera w minutach do wysyłania pakietu reklamy lokalnej (bezpośredniej). Ustaw na 0, aby wyłączyć.", - "repeater_cliHelpSetFloodAdvertInterval": "Ustawia interwał timera w godzinach do wysłania pakietu reklamowego typu \"powiew\". Ustaw na 0, aby wyłączyć.", + "repeater_cliHelpSetAdvertInterval": "Ustawia interwał timera w minutach do wysyłania pakietu rozgłoszenia lokalnego (bezpośredniego). Ustaw na 0, aby wyłączyć.", + "repeater_cliHelpSetFloodAdvertInterval": "Ustawia interwał timera w godzinach do wysłania pakietu rozgłoszeniowego typu \"flood\". Ustaw na 0, aby wyłączyć.", "repeater_cliHelpSetGuestPassword": "Ustawia/aktualizuje hasło gościa. (dla repeaterów, loginy gości mogą wysyłać żądanie \"Get Stats\")", "repeater_cliHelpSetName": "Ustawia nazwę reklamy.", "repeater_cliHelpSetLat": "Ustawia współrzędną geograficzne (w stopniach dziesiętnych) mapy reklam.", @@ -1153,7 +1153,7 @@ "repeater_cliHelpLogStart": "Rozpoczyna się logowanie pakietów do systemu plików.", "repeater_cliHelpLogStop": "Zatrzymuje logowanie pakietów do systemu plików.", "repeater_cliHelpLogErase": "Usuwa logi pakietów z systemu plików.", - "repeater_cliHelpNeighbors": "Wyświetla listę innych węzłów powtarzających się, które usłyszano dzięki reklamom zero-hop. Każda linia to: id-prefix-hex:timestamp:snr-times-4", + "repeater_cliHelpNeighbors": "Wyświetla listę innych węzłów powtarzających się, które usłyszano dzięki rozgłoszeniom zero-hop. Każda linia to: id-prefix-hex:timestamp:snr-times-4", "repeater_cliHelpNeighborRemove": "Usuwa pierwszy pasujący wpis (z prefiksem pubkey (hex)) z listy sąsiadów.", "repeater_cliHelpRegion": "(tylko seria) Wyświetla wszystkie zdefiniowane regiony i aktualne uprawnienia do powodzi.", "repeater_cliHelpRegionLoad": "ZAPOMNIJ: to jest specjalne wywołanie wielokomendowe. Każda następna komenda jest nazwą regionu (wcięta spacjami, aby wskazywać hierarchię nadrzędną, z minimum jedną spacją). Zakończona wysłaniem pustej linii/komendy.", From 5321974cbbf427e535cbe871b9cd7cc3ad4a5071 Mon Sep 17 00:00:00 2001 From: thesebas Date: Wed, 18 Feb 2026 19:10:07 +0100 Subject: [PATCH 17/36] Update Polish localization strings for consistency and clarity --- lib/l10n/app_localizations_pl.dart | 54 +++++++++++++++--------------- lib/l10n/app_pl.arb | 54 +++++++++++++++--------------- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index d838732..9aa74b8 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -389,11 +389,11 @@ class AppLocalizationsPl extends AppLocalizations { @override String get settings_privacyModeSubtitle => - 'Ukryj imię/lokalizację w reklamach'; + 'Ukryj imię/lokalizację w rozgłoszeniach'; @override String get settings_privacyModeToggle => - 'Włącz tryb prywatności, aby ukryć swoje imię i lokalizację w reklamach.'; + 'Włącz tryb prywatności, aby ukryć swoje imię i lokalizację w rozgłoszeniach.'; @override String get settings_privacyModeEnabled => 'Tryb prywatności włączony'; @@ -405,14 +405,14 @@ class AppLocalizationsPl extends AppLocalizations { String get settings_actions => 'Działania'; @override - String get settings_sendAdvertisement => 'Wyślij Reklamę'; + String get settings_sendAdvertisement => 'Wyślij rozgłoszenie'; @override String get settings_sendAdvertisementSubtitle => 'Obecność transmisji jest teraz'; @override - String get settings_advertisementSent => 'Reklama wysłana'; + String get settings_advertisementSent => 'Rozgłoszenie wysłane'; @override String get settings_syncTime => 'Czas synchronizacji'; @@ -628,7 +628,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get appSettings_enableNotificationsSubtitle => - 'Otrzymuj powiadomienia o wiadomościach i reklamach.'; + 'Otrzymuj powiadomienia o wiadomościach i rozgłoszeniach.'; @override String get appSettings_notificationPermissionDenied => @@ -658,11 +658,11 @@ class AppLocalizationsPl extends AppLocalizations { @override String get appSettings_advertisementNotifications => - 'Powiadomienia Reklamowe'; + 'Powiadomienia o rozgłoszeniach'; @override String get appSettings_advertisementNotificationsSubtitle => - 'Wyświetl powiadomienie, gdy zostaną odkryte nowe węzły.'; + 'Wyświetl powiadomienie, gdy zostaną wykryte nowe węzły.'; @override String get appSettings_messaging => 'Wiadomości'; @@ -867,7 +867,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get contacts_contactsWillAppear => - 'Kontakty będą wyświetlane, gdy urządzenia reklamują się.'; + 'Kontakty będą wyświetlane, gdy urządzenia nadają rozgłoszenia.'; @override String get contacts_unread => 'Nieprzeczytane'; @@ -1354,10 +1354,10 @@ class AppLocalizationsPl extends AppLocalizations { 'Historia ścieżek jest pełna. Usuń wpisy, aby dodać nowe.'; @override - String get chat_hopSingular => 'Skacz'; + String get chat_hopSingular => 'skok'; @override - String get chat_hopPlural => 'skoczkowie'; + String get chat_hopPlural => 'skoki'; @override String chat_hopsCount(int count) { @@ -1450,7 +1450,7 @@ class AppLocalizationsPl extends AppLocalizations { String get chat_compressOutgoingMessages => 'Kompresuj wychodzące wiadomości'; @override - String get chat_floodForced => 'Powodowana Powódź'; + String get chat_floodForced => 'Zalew (wymuszony)'; @override String get chat_directForced => 'Bezpośrednio (wymuszono)'; @@ -1461,7 +1461,7 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get chat_floodAuto => 'Powodzie (automatyczne)'; + String get chat_floodAuto => 'Zalew (automatyczny)'; @override String get chat_direct => 'Bezpośrednio'; @@ -2012,10 +2012,10 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_noiseFloor => 'Poziom Szumów'; @override - String get repeater_txAirtime => 'TX Airtime'; + String get repeater_txAirtime => 'Czas nadawania TX'; @override - String get repeater_rxAirtime => 'RX Airtime'; + String get repeater_rxAirtime => 'Czas odbioru RX'; @override String get repeater_packetStatistics => 'Statystyki pakietów'; @@ -2093,7 +2093,7 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_frequencyHelper => '300-2500 MHz'; @override - String get repeater_txPower => 'TX Power'; + String get repeater_txPower => 'Moc TX'; @override String get repeater_txPowerHelper => '1-30 dBm'; @@ -2102,7 +2102,7 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_bandwidth => 'Przepustowość'; @override - String get repeater_spreadingFactor => 'Rozkład Czynnika'; + String get repeater_spreadingFactor => 'Współczynnik rozpraszania'; @override String get repeater_codingRate => 'Stawka kodowania'; @@ -2144,13 +2144,13 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_privacyModeSubtitle => - 'Ukryj imię/lokalizację w reklamach'; + 'Ukryj imię/lokalizację w rozgłoszeniach'; @override - String get repeater_advertisementSettings => 'Ustawienia Reklam'; + String get repeater_advertisementSettings => 'Ustawienia rozgłoszeń'; @override - String get repeater_localAdvertInterval => 'Interwał Reklamy Lokalnej'; + String get repeater_localAdvertInterval => 'Interwał rozgłoszenia lokalnego'; @override String repeater_localAdvertIntervalMinutes(int minutes) { @@ -2158,7 +2158,7 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get repeater_floodAdvertInterval => 'Interwał Reklamy Powodziowej'; + String get repeater_floodAdvertInterval => 'Interwał rozgłoszenia zalewowego'; @override String repeater_floodAdvertIntervalHours(int hours) { @@ -2237,7 +2237,7 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_refreshRadioSettings => 'Odśwież Ustawienia Radio'; @override - String get repeater_refreshTxPower => 'Odśwież TX power'; + String get repeater_refreshTxPower => 'Odśwież moc TX'; @override String get repeater_refreshLocationSettings => @@ -2393,15 +2393,15 @@ class AppLocalizationsPl extends AppLocalizations { 'Ustawia/aktualizuje hasło gościa. (dla repeaterów, loginy gości mogą wysyłać żądanie \"Get Stats\")'; @override - String get repeater_cliHelpSetName => 'Ustawia nazwę reklamy.'; + String get repeater_cliHelpSetName => 'Ustawia nazwę rozgłoszenia.'; @override String get repeater_cliHelpSetLat => - 'Ustawia współrzędną geograficzne (w stopniach dziesiętnych) mapy reklam.'; + 'Ustawia współrzędną geograficzną (w stopniach dziesiętnych) mapy rozgłoszeń.'; @override String get repeater_cliHelpSetLon => - 'Ustawia współrzędną długościową mapy reklamy. (stopnie dziesiętne)'; + 'Ustawia współrzędną długościową mapy rozgłoszeń. (stopnie dziesiętne)'; @override String get repeater_cliHelpSetRadio => @@ -2530,11 +2530,11 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_cliHelpGpsAdvert => - 'Udostępnia konfigurację reklamy lokalizacji węzła:\n- brak: nie uwzględniaj lokalizacji w reklamach\n- udostępnia: udostępnia lokalizację GPS (z SensorManager)\n- ustawienia: reklamuj lokalizację przechowywaną w ustawieniach'; + 'Udostępnia konfigurację rozgłoszeń lokalizacji węzła:\n- brak: nie uwzględniaj lokalizacji w rozgłoszeniach\n- udostępnia: udostępnia lokalizację GPS (z SensorManager)\n- ustawienia: rozgłaszaj lokalizację przechowywaną w ustawieniach'; @override String get repeater_cliHelpGpsAdvertSet => - 'Ustawia konfigurację reklamy w lokalizacji.'; + 'Ustawia konfigurację rozgłoszeń lokalizacji.'; @override String get repeater_commandsListTitle => 'Lista poleceń'; @@ -2714,7 +2714,7 @@ class AppLocalizationsPl extends AppLocalizations { String get channelPath_unknownPath => 'Nieznane'; @override - String get channelPath_floodPath => 'Powodzenie'; + String get channelPath_floodPath => 'Zalew'; @override String get channelPath_directPath => 'Bezpośrednio'; diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index ea8fbc0..8837732 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -99,14 +99,14 @@ "settings_latitude": "Szerokość", "settings_longitude": "Długość", "settings_privacyMode": "Tryb Prywatny", - "settings_privacyModeSubtitle": "Ukryj imię/lokalizację w reklamach", - "settings_privacyModeToggle": "Włącz tryb prywatności, aby ukryć swoje imię i lokalizację w reklamach.", + "settings_privacyModeSubtitle": "Ukryj imię/lokalizację w rozgłoszeniach", + "settings_privacyModeToggle": "Włącz tryb prywatności, aby ukryć swoje imię i lokalizację w rozgłoszeniach.", "settings_privacyModeEnabled": "Tryb prywatności włączony", "settings_privacyModeDisabled": "Tryb prywatności wyłączony", "settings_actions": "Działania", - "settings_sendAdvertisement": "Wyślij Reklamę", + "settings_sendAdvertisement": "Wyślij rozgłoszenie", "settings_sendAdvertisementSubtitle": "Obecność transmisji jest teraz", - "settings_advertisementSent": "Reklama wysłana", + "settings_advertisementSent": "Rozgłoszenie wysłane", "settings_syncTime": "Czas synchronizacji", "settings_syncTimeSubtitle": "Ustaw zegar urządzenia na czas telefonu.", "settings_timeSynchronized": "Synchronizacja czasu", @@ -179,7 +179,7 @@ "appSettings_languageBg": "Български", "appSettings_notifications": "Powiadomienia", "appSettings_enableNotifications": "Włącz Powiadomienia", - "appSettings_enableNotificationsSubtitle": "Otrzymuj powiadomienia o wiadomościach i reklamach.", + "appSettings_enableNotificationsSubtitle": "Otrzymuj powiadomienia o wiadomościach i rozgłoszeniach.", "appSettings_notificationPermissionDenied": "Odmowa zezwolenia na powiadomienia", "appSettings_notificationsEnabled": "Powiadomienia włączone", "appSettings_notificationsDisabled": "Powiadomienia wyłączone", @@ -187,8 +187,8 @@ "appSettings_messageNotificationsSubtitle": "Pokaż powiadomienie przy otrzymywaniu nowych wiadomości", "appSettings_channelMessageNotifications": "Powiadomienia o Wiadomościach na Kanałach", "appSettings_channelMessageNotificationsSubtitle": "Pokaż powiadomienie przy odbieraniu wiadomości z kanału", - "appSettings_advertisementNotifications": "Powiadomienia Reklamowe", - "appSettings_advertisementNotificationsSubtitle": "Wyświetl powiadomienie, gdy zostaną odkryte nowe węzły.", + "appSettings_advertisementNotifications": "Powiadomienia o rozgłoszeniach", + "appSettings_advertisementNotificationsSubtitle": "Wyświetl powiadomienie, gdy zostaną wykryte nowe węzły.", "appSettings_messaging": "Wiadomości", "appSettings_clearPathOnMaxRetry": "Wyczyść Ścieżkę na Maksymalnej Próbie", "appSettings_clearPathOnMaxRetrySubtitle": "Resetuj ścieżkę kontaktu po 5 nieudanych próbach wysłania", @@ -256,7 +256,7 @@ "appSettings_appDebugLoggingDisabled": "Zasubskrybowane logi debugowania aplikacji wyłączone.", "contacts_title": "Kontakty", "contacts_noContacts": "Brak jeszcze kontaktów.", - "contacts_contactsWillAppear": "Kontakty będą wyświetlane, gdy urządzenia reklamują się.", + "contacts_contactsWillAppear": "Kontakty będą wyświetlane, gdy urządzenia nadają rozgłoszenia.", "contacts_searchContacts": "Wyszukaj kontakty...", "contacts_noUnreadContacts": "Brak nieprzeczytanych kontaktów", "contacts_noContactsFound": "Brak znalezionych kontaktów ani grup.", @@ -548,8 +548,8 @@ "chat_forceFloodMode": "Wymusz Tryb Powodowany", "chat_recentAckPaths": "Ostatnie ścieżki ACK (naciśnij, aby użyć):", "chat_pathHistoryFull": "Historia ścieżek jest pełna. Usuń wpisy, aby dodać nowe.", - "chat_hopSingular": "Skacz", - "chat_hopPlural": "skoczkowie", + "chat_hopSingular": "skok", + "chat_hopPlural": "skoki", "chat_hopsCount": "{count} {count, plural, =1{hop} other{hops}}", "@chat_hopsCount": { "placeholders": { @@ -589,7 +589,7 @@ "chat_path": "Ścieżka", "chat_publicKey": "Klucz Publiczny", "chat_compressOutgoingMessages": "Kompresuj wychodzące wiadomości", - "chat_floodForced": "Powodowana Powódź", + "chat_floodForced": "Zalew (wymuszony)", "chat_directForced": "Bezpośrednio (wymuszono)", "chat_hopsForced": "{count} skoków (wymuszonych)", "@chat_hopsForced": { @@ -599,7 +599,7 @@ } } }, - "chat_floodAuto": "Powodzie (automatyczne)", + "chat_floodAuto": "Zalew (automatyczny)", "chat_direct": "Bezpośrednio", "chat_poiShared": "Wspólny POI", "chat_unread": "Niezgłoszone: {count}", @@ -912,8 +912,8 @@ "repeater_lastRssi": "Ostatni RSSI", "repeater_lastSnr": "Ostatnie SNR", "repeater_noiseFloor": "Poziom Szumów", - "repeater_txAirtime": "TX Airtime", - "repeater_rxAirtime": "RX Airtime", + "repeater_txAirtime": "Czas nadawania TX", + "repeater_rxAirtime": "Czas odbioru RX", "repeater_packetStatistics": "Statystyki pakietów", "repeater_sent": "Wysłane", "repeater_received": "Otrzymano", @@ -993,10 +993,10 @@ "repeater_radioSettings": "Ustawienia radia", "repeater_frequencyMhz": "Częstotliwość (MHz)", "repeater_frequencyHelper": "300-2500 MHz", - "repeater_txPower": "TX Power", + "repeater_txPower": "Moc TX", "repeater_txPowerHelper": "1-30 dBm", "repeater_bandwidth": "Przepustowość", - "repeater_spreadingFactor": "Rozkład Czynnika", + "repeater_spreadingFactor": "Współczynnik rozpraszania", "repeater_codingRate": "Stawka kodowania", "repeater_locationSettings": "Ustawienia Lokalizacji", "repeater_latitude": "Szerokość", @@ -1009,9 +1009,9 @@ "repeater_guestAccess": "Dostęp dla gości", "repeater_guestAccessSubtitle": "Umożliw dostęp tylko do odczytu dla gości.", "repeater_privacyMode": "Tryb Prywatności", - "repeater_privacyModeSubtitle": "Ukryj imię/lokalizację w reklamach", - "repeater_advertisementSettings": "Ustawienia Reklam", - "repeater_localAdvertInterval": "Interwał Reklamy Lokalnej", + "repeater_privacyModeSubtitle": "Ukryj imię/lokalizację w rozgłoszeniach", + "repeater_advertisementSettings": "Ustawienia rozgłoszeń", + "repeater_localAdvertInterval": "Interwał rozgłoszenia lokalnego", "repeater_localAdvertIntervalMinutes": "{minutes} minut", "@repeater_localAdvertIntervalMinutes": { "placeholders": { @@ -1020,7 +1020,7 @@ } } }, - "repeater_floodAdvertInterval": "Interwał Reklamy Powodziowej", + "repeater_floodAdvertInterval": "Interwał rozgłoszenia zalewowego", "repeater_floodAdvertIntervalHours": "{hours} godzin", "@repeater_floodAdvertIntervalHours": { "placeholders": { @@ -1069,7 +1069,7 @@ }, "repeater_refreshBasicSettings": "Odśwież Podstawowe Ustawienia", "repeater_refreshRadioSettings": "Odśwież Ustawienia Radio", - "repeater_refreshTxPower": "Odśwież TX power", + "repeater_refreshTxPower": "Odśwież moc TX", "repeater_refreshLocationSettings": "Odśwież Ustawienia Lokalizacji", "repeater_refreshPacketForwarding": "Odśwież trasowanie pakietów", "repeater_refreshGuestAccess": "Odśwież dostęp gościa", @@ -1134,9 +1134,9 @@ "repeater_cliHelpSetAdvertInterval": "Ustawia interwał timera w minutach do wysyłania pakietu rozgłoszenia lokalnego (bezpośredniego). Ustaw na 0, aby wyłączyć.", "repeater_cliHelpSetFloodAdvertInterval": "Ustawia interwał timera w godzinach do wysłania pakietu rozgłoszeniowego typu \"flood\". Ustaw na 0, aby wyłączyć.", "repeater_cliHelpSetGuestPassword": "Ustawia/aktualizuje hasło gościa. (dla repeaterów, loginy gości mogą wysyłać żądanie \"Get Stats\")", - "repeater_cliHelpSetName": "Ustawia nazwę reklamy.", - "repeater_cliHelpSetLat": "Ustawia współrzędną geograficzne (w stopniach dziesiętnych) mapy reklam.", - "repeater_cliHelpSetLon": "Ustawia współrzędną długościową mapy reklamy. (stopnie dziesiętne)", + "repeater_cliHelpSetName": "Ustawia nazwę rozgłoszenia.", + "repeater_cliHelpSetLat": "Ustawia współrzędną geograficzną (w stopniach dziesiętnych) mapy rozgłoszeń.", + "repeater_cliHelpSetLon": "Ustawia współrzędną długościową mapy rozgłoszeń. (stopnie dziesiętne)", "repeater_cliHelpSetRadio": "Ustawia nowe parametry radia i zapisuje je w preferencjach. Wymaga polecenia \"reboot\" do zastosowania.", "repeater_cliHelpSetRxDelay": "Ustawienia (eksperymentalne) bazowe (muszą być > 1, aby działać) do stosowania lekkiego opóźnienia dla odebranych pakietów, w oparciu o siłę sygnału/wynik. Ustaw na 0, aby wyłączyć.", "repeater_cliHelpSetTxDelay": "Ustawia czynnik mnożony przez czas utrzymania w trybie zalewowym dla pakietu oraz z wykorzystaniem losowego systemu slotów, aby opóźnić jego przesyłanie (zmniejszając prawdopodobieństwo kolizji).", @@ -1169,8 +1169,8 @@ "repeater_cliHelpGpsOnOff": "Włącza/wyłącza nawigację GPS.", "repeater_cliHelpGpsSync": "Synchronizuje czas węzła z zegarem GPS.", "repeater_cliHelpGpsSetLoc": "Ustawia pozycję węzła na współrzędne GPS i zapisuje preferencje.", - "repeater_cliHelpGpsAdvert": "Udostępnia konfigurację reklamy lokalizacji węzła:\n- brak: nie uwzględniaj lokalizacji w reklamach\n- udostępnia: udostępnia lokalizację GPS (z SensorManager)\n- ustawienia: reklamuj lokalizację przechowywaną w ustawieniach", - "repeater_cliHelpGpsAdvertSet": "Ustawia konfigurację reklamy w lokalizacji.", + "repeater_cliHelpGpsAdvert": "Udostępnia konfigurację rozgłoszeń lokalizacji węzła:\n- brak: nie uwzględniaj lokalizacji w rozgłoszeniach\n- udostępnia: udostępnia lokalizację GPS (z SensorManager)\n- ustawienia: rozgłaszaj lokalizację przechowywaną w ustawieniach", + "repeater_cliHelpGpsAdvertSet": "Ustawia konfigurację rozgłoszeń lokalizacji.", "repeater_commandsListTitle": "Lista poleceń", "repeater_commandsListNote": "ZAPAMIĘTAJ: dla różnych poleceń \"set ...\" istnieje również polecenie \"get ...\".", "repeater_general": "Ogólne", @@ -1290,7 +1290,7 @@ } }, "channelPath_unknownPath": "Nieznane", - "channelPath_floodPath": "Powodzenie", + "channelPath_floodPath": "Zalew", "channelPath_directPath": "Bezpośrednio", "channelPath_observedZeroOf": "0 z {total} skoków", "@channelPath_observedZeroOf": { From 532401cc94379c301d82dce634ce585dd54bb7e6 Mon Sep 17 00:00:00 2001 From: thesebas Date: Wed, 18 Feb 2026 19:47:35 +0100 Subject: [PATCH 18/36] Refactor code structure for improved readability and maintainability --- lib/l10n/app_localizations_pl.dart | 256 +++++++++++++++-------------- lib/l10n/app_pl.arb | 245 +++++++++++++-------------- 2 files changed, 254 insertions(+), 247 deletions(-) diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 9aa74b8..f2dcda2 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -42,7 +42,7 @@ class AppLocalizationsPl extends AppLocalizations { String get common_deleteAll => 'Usuń wszystko'; @override - String get common_close => 'Zamknąć'; + String get common_close => 'Zamknij'; @override String get common_edit => 'Edytuj'; @@ -75,7 +75,7 @@ class AppLocalizationsPl extends AppLocalizations { String get common_copy => 'Kopiuj'; @override - String get common_retry => 'Spróbować'; + String get common_retry => 'Ponów'; @override String get common_hide => 'Ukryj'; @@ -87,10 +87,10 @@ class AppLocalizationsPl extends AppLocalizations { String get common_enable => 'Włącz'; @override - String get common_disable => 'Wyłączyć'; + String get common_disable => 'Wyłącz'; @override - String get common_reboot => 'Zrestartować'; + String get common_reboot => 'Uruchom ponownie'; @override String get common_loading => 'Ładowanie...'; @@ -253,7 +253,7 @@ class AppLocalizationsPl extends AppLocalizations { String get scanner_disconnecting => 'Odłączanie...'; @override - String get scanner_notConnected => 'Niepołączony'; + String get scanner_notConnected => 'Nie połączono'; @override String scanner_connectedTo(String deviceName) { @@ -327,7 +327,7 @@ class AppLocalizationsPl extends AppLocalizations { String get settings_nodeNameHint => 'Wprowadź nazwę węzła'; @override - String get settings_nodeNameUpdated => 'Imię zaktualizowane'; + String get settings_nodeNameUpdated => 'Nazwa zaktualizowana'; @override String get settings_radioSettings => 'Ustawienia radia'; @@ -378,14 +378,14 @@ class AppLocalizationsPl extends AppLocalizations { String get settings_longitude => 'Długość'; @override - String get settings_contactSettings => 'Ustawienia kontaktowe'; + String get settings_contactSettings => 'Ustawienia kontaktów'; @override String get settings_contactSettingsSubtitle => 'Ustawienia dotyczące sposobu dodawania kontaktów'; @override - String get settings_privacyMode => 'Tryb Prywatny'; + String get settings_privacyMode => 'Tryb prywatności'; @override String get settings_privacyModeSubtitle => @@ -408,21 +408,20 @@ class AppLocalizationsPl extends AppLocalizations { String get settings_sendAdvertisement => 'Wyślij rozgłoszenie'; @override - String get settings_sendAdvertisementSubtitle => - 'Obecność transmisji jest teraz'; + String get settings_sendAdvertisementSubtitle => 'Nadaj obecność teraz'; @override String get settings_advertisementSent => 'Rozgłoszenie wysłane'; @override - String get settings_syncTime => 'Czas synchronizacji'; + String get settings_syncTime => 'Synchronizacja czasu'; @override String get settings_syncTimeSubtitle => 'Ustaw zegar urządzenia na czas telefonu.'; @override - String get settings_timeSynchronized => 'Synchronizacja czasu'; + String get settings_timeSynchronized => 'Czas zsynchronizowany'; @override String get settings_refreshContacts => 'Odśwież Kontakty'; @@ -445,20 +444,20 @@ class AppLocalizationsPl extends AppLocalizations { String get settings_debug => 'Debug'; @override - String get settings_bleDebugLog => 'Log błędów BLE'; + String get settings_bleDebugLog => 'Dziennik debugowania BLE'; @override String get settings_bleDebugLogSubtitle => 'Polecenia BLE, odpowiedzi i surowe dane'; @override - String get settings_appDebugLog => 'Log Wykonywania Aplikacji'; + String get settings_appDebugLog => 'Dziennik debugowania aplikacji'; @override String get settings_appDebugLogSubtitle => 'Komunikaty debugowania aplikacji'; @override - String get settings_about => 'O mnie'; + String get settings_about => 'O aplikacji'; @override String settings_aboutVersion(String version) { @@ -477,7 +476,7 @@ class AppLocalizationsPl extends AppLocalizations { 'Dane wysokościowe LOS: Open-Meteo (CC BY 4.0)'; @override - String get settings_infoName => 'Imię'; + String get settings_infoName => 'Nazwa'; @override String get settings_infoId => 'ID'; @@ -498,7 +497,7 @@ class AppLocalizationsPl extends AppLocalizations { String get settings_infoChannelCount => 'Liczba kanałów'; @override - String get settings_presets => 'Preset'; + String get settings_presets => 'Presety'; @override String get settings_frequency => 'Częstotliwość (MHz)'; @@ -514,13 +513,13 @@ class AppLocalizationsPl extends AppLocalizations { String get settings_bandwidth => 'Przepustowość'; @override - String get settings_spreadingFactor => 'Rozkład Czynnika'; + String get settings_spreadingFactor => 'Współczynnik rozpraszania'; @override - String get settings_codingRate => 'Stawka Kodowania'; + String get settings_codingRate => 'Współczynnik kodowania'; @override - String get settings_txPower => 'TX Moc (dBm)'; + String get settings_txPower => 'Moc TX (dBm)'; @override String get settings_txPowerHelper => '0 - 22'; @@ -566,7 +565,7 @@ class AppLocalizationsPl extends AppLocalizations { String get appSettings_language => 'Język'; @override - String get appSettings_languageSystem => 'Domyślny systemowy'; + String get appSettings_languageSystem => 'Domyślny systemu'; @override String get appSettings_languageEn => 'English'; @@ -669,7 +668,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get appSettings_clearPathOnMaxRetry => - 'Wyczyść Ścieżkę na Maksymalnej Próbie'; + 'Wyczyść ścieżkę po maks. liczbie prób'; @override String get appSettings_clearPathOnMaxRetrySubtitle => @@ -677,14 +676,14 @@ class AppLocalizationsPl extends AppLocalizations { @override String get appSettings_pathsWillBeCleared => - 'Droga będzie wyczyszczona po 5 nieudanych próbach.'; + 'Ścieżka zostanie wyczyszczona po 5 nieudanych próbach.'; @override String get appSettings_pathsWillNotBeCleared => - 'Droga nie zostanie automatycznie wyczyszczona.'; + 'Ścieżka nie zostanie automatycznie wyczyszczona.'; @override - String get appSettings_autoRouteRotation => 'Automatyczne Rotowanie Trasy'; + String get appSettings_autoRouteRotation => 'Automatyczna rotacja trasy'; @override String get appSettings_autoRouteRotationSubtitle => @@ -749,7 +748,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String appSettings_batteryChemistryPerDevice(String deviceName) { - return 'Ustawione na urządzenie ($deviceName)'; + return 'Ustaw dla urządzenia ($deviceName)'; } @override @@ -769,11 +768,11 @@ class AppLocalizationsPl extends AppLocalizations { String get appSettings_mapDisplay => 'Wyświetlanie mapy'; @override - String get appSettings_showRepeaters => 'Pokaż Powtórniki'; + String get appSettings_showRepeaters => 'Pokaż powtarzacze'; @override String get appSettings_showRepeatersSubtitle => - 'Wyświetl węzły powtarzające się na mapie'; + 'Wyświetl węzły powtarzaczy na mapie'; @override String get appSettings_showChatNodes => 'Pokaż Węzły Rozmowy'; @@ -807,7 +806,7 @@ class AppLocalizationsPl extends AppLocalizations { String get appSettings_showNodesDiscoveredWithin => 'Pokaż węzły odkryte w:'; @override - String get appSettings_allTime => 'Wszystko czasowo'; + String get appSettings_allTime => 'Cały czas'; @override String get appSettings_lastHour => 'Ostatnia godzina'; @@ -819,10 +818,10 @@ class AppLocalizationsPl extends AppLocalizations { String get appSettings_last24Hours => 'Ostatnie 24 godziny'; @override - String get appSettings_lastWeek => 'Tydzień temu'; + String get appSettings_lastWeek => 'Ostatni tydzień'; @override - String get appSettings_offlineMapCache => 'Bufor Map Offline'; + String get appSettings_offlineMapCache => 'Pamięć podręczna map offline'; @override String get appSettings_unitsTitle => 'Jednostki'; @@ -853,11 +852,11 @@ class AppLocalizationsPl extends AppLocalizations { @override String get appSettings_appDebugLoggingEnabled => - 'Zdebugowanie aplikacji włączone'; + 'Logowanie debugowania aplikacji włączone'; @override String get appSettings_appDebugLoggingDisabled => - 'Zasubskrybowane logi debugowania aplikacji wyłączone.'; + 'Logowanie debugowania aplikacji wyłączone.'; @override String get contacts_title => 'Kontakty'; @@ -916,7 +915,7 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get contacts_manageRepeater => 'Zarządzaj Powtórzami'; + String get contacts_manageRepeater => 'Zarządzaj powtarzaczem'; @override String get contacts_manageRoom => 'Zarządzaj Serwerem Pokoju'; @@ -966,11 +965,11 @@ class AppLocalizationsPl extends AppLocalizations { String get contacts_noMembers => 'Brak członków'; @override - String get contacts_lastSeenNow => 'Ostatnie połączenie'; + String get contacts_lastSeenNow => 'Widziano przed chwilą'; @override String contacts_lastSeenMinsAgo(int minutes) { - return 'Ostatnie połączenie $minutes min temu'; + return 'Widziano $minutes min temu'; } @override @@ -978,7 +977,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String contacts_lastSeenHoursAgo(int hours) { - return 'Ostatnie połączenie $hours godzin temu'; + return 'Widziano $hours godz. temu'; } @override @@ -986,7 +985,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String contacts_lastSeenDaysAgo(int days) { - return 'Ostatnie połączenie $days dni temu'; + return 'Widziano $days dni temu'; } @override @@ -1016,7 +1015,7 @@ class AppLocalizationsPl extends AppLocalizations { String get channels_public => 'Publiczny'; @override - String get channels_private => 'Prywatne'; + String get channels_private => 'Prywatny'; @override String get channels_publicChannel => 'Kanał publiczny'; @@ -1064,7 +1063,7 @@ class AppLocalizationsPl extends AppLocalizations { String get channels_usePublicChannel => 'Użyj kanału publicznego'; @override - String get channels_standardPublicPsk => 'Standard public PSK'; + String get channels_standardPublicPsk => 'Standardowy publiczny PSK'; @override String get channels_pskHex => 'PSK (Hex)'; @@ -1112,7 +1111,7 @@ class AppLocalizationsPl extends AppLocalizations { String get channels_sortLatestMessages => 'Najnowsze wiadomości'; @override - String get channels_sortUnread => 'Niezgłoszone'; + String get channels_sortUnread => 'Nieprzeczytane'; @override String get channels_createPrivateChannel => 'Utwórz Prywatny Kanał'; @@ -1171,7 +1170,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String chat_replyTo(String name) { - return 'Odpowiedz $name'; + return 'Odpowiedz do $name'; } @override @@ -1197,11 +1196,11 @@ class AppLocalizationsPl extends AppLocalizations { String get chat_messageDeleted => 'Wiadomość usunięta'; @override - String get chat_retryingMessage => 'Próba ponowienia'; + String get chat_retryingMessage => 'Ponawianie wiadomości'; @override String chat_retryCount(int current, int max) { - return 'Spróbuj $current/$max'; + return 'Próba $current/$max'; } @override @@ -1220,10 +1219,10 @@ class AppLocalizationsPl extends AppLocalizations { String get emojiCategorySmileys => 'Emoji'; @override - String get emojiCategoryGestures => 'Gestikulacje'; + String get emojiCategoryGestures => 'Gesty'; @override - String get emojiCategoryHearts => 'Serce'; + String get emojiCategoryHearts => 'Serca'; @override String get emojiCategoryObjects => 'Obiekty'; @@ -1275,10 +1274,10 @@ class AppLocalizationsPl extends AppLocalizations { 'Włącz logowanie debugowania aplikacji w ustawieniach'; @override - String get debugLog_frames => 'Ramy'; + String get debugLog_frames => 'Ramki'; @override - String get debugLog_rawLogRx => 'Surowe Log-RX'; + String get debugLog_rawLogRx => 'Surowy log RX'; @override String get debugLog_noBleActivity => 'Brak aktywności BLE jeszcze.'; @@ -1298,7 +1297,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String debugFrame_destinationPubKey(String pubKey) { - return '- Oznaczenie PubKey: $pubKey'; + return '- Docelowy klucz publiczny: $pubKey'; } @override @@ -1320,7 +1319,7 @@ class AppLocalizationsPl extends AppLocalizations { String get debugFrame_textTypeCli => 'CLI'; @override - String get debugFrame_textTypePlain => 'Proste'; + String get debugFrame_textTypePlain => 'Zwykły'; @override String debugFrame_text(String text) { @@ -1328,7 +1327,7 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get debugFrame_hexDump => 'Wyjście SzESZCZNULNE:'; + String get debugFrame_hexDump => 'Zrzut hex:'; @override String get chat_pathManagement => 'Zarządzanie ścieżkami'; @@ -1343,7 +1342,7 @@ class AppLocalizationsPl extends AppLocalizations { String get chat_autoUseSavedPath => 'Automatyczne (użyj zapisanej ścieżki)'; @override - String get chat_forceFloodMode => 'Wymusz Tryb Powodowany'; + String get chat_forceFloodMode => 'Wymuś tryb zalewowy'; @override String get chat_recentAckPaths => @@ -1364,8 +1363,10 @@ class AppLocalizationsPl extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'hops', - one: 'hop', + other: 'skoków', + many: 'skoków', + few: 'skoki', + one: 'skok', ); return '$count $_temp0'; } @@ -1384,7 +1385,7 @@ class AppLocalizationsPl extends AppLocalizations { String get chat_pathActions => 'Działania ścieżki:'; @override - String get chat_setCustomPath => 'Ustaw Ścieżkę Dostosowaną'; + String get chat_setCustomPath => 'Ustaw ścieżkę niestandardową'; @override String get chat_setCustomPathSubtitle => 'Ręcznie określ trasę.'; @@ -1394,11 +1395,11 @@ class AppLocalizationsPl extends AppLocalizations { @override String get chat_clearPathSubtitle => - 'Zmusz do ponownej identyfikacji przy następnym wysłaniu'; + 'Wymuś ponowne wyznaczenie trasy przy następnym wysłaniu'; @override String get chat_pathCleared => - 'Ścieżka oczyszczona. Kolejne powiadomienie odnajdzie trasę.'; + 'Ścieżka wyczyszczona. Następna wiadomość odnajdzie trasę.'; @override String get chat_floodModeSubtitle => @@ -1438,7 +1439,7 @@ class AppLocalizationsPl extends AppLocalizations { 'Urządzenie nie zostało jeszcze potwierdzone.'; @override - String get chat_type => 'Wprowadź'; + String get chat_type => 'Typ'; @override String get chat_path => 'Ścieżka'; @@ -1471,7 +1472,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String chat_unread(int count) { - return 'Niezgłoszone: $count'; + return 'Nieprzeczytane: $count'; } @override @@ -1515,7 +1516,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String map_pinsCount(int count) { - return 'Pinki: $count'; + return 'Pinezki: $count'; } @override @@ -1531,13 +1532,13 @@ class AppLocalizationsPl extends AppLocalizations { String get map_sensor => 'Czujnik'; @override - String get map_pinDm => 'Zablokuj (DM)'; + String get map_pinDm => 'Pinezka (DM)'; @override - String get map_pinPrivate => 'Zablokuj (Prywatnie)'; + String get map_pinPrivate => 'Pinezka (prywatna)'; @override - String get map_pinPublic => 'Oznacz jako publiczne'; + String get map_pinPublic => 'Pinezka (publiczna)'; @override String get map_lastSeen => 'Ostatni raz widziany'; @@ -1559,10 +1560,10 @@ class AppLocalizationsPl extends AppLocalizations { String get map_shareMarkerHere => 'Udostępnij znacznik tutaj'; @override - String get map_setAsMyLocation => 'Ustaw jako moje lokalizację'; + String get map_setAsMyLocation => 'Ustaw jako moją lokalizację'; @override - String get map_pinLabel => 'Oznacz etykietę'; + String get map_pinLabel => 'Etykieta pinezki'; @override String get map_label => 'Etykieta'; @@ -1584,12 +1585,12 @@ class AppLocalizationsPl extends AppLocalizations { @override String map_publicLocationShareConfirm(String channelLabel) { - return 'Wkrótce udostępnisz lokalizację w $channelLabel. Ten kanał jest publiczny i każdy z PSK może go zobaczyć.'; + return 'Zamierzasz udostępnić lokalizację w $channelLabel. Ten kanał jest publiczny i każdy z PSK może go zobaczyć.'; } @override String get map_connectToShareMarkers => - 'Połącz się z urządzeniem, aby udostępniać znacznik.'; + 'Połącz się z urządzeniem, aby udostępniać znaczniki.'; @override String get map_filterNodes => 'Filtruj Węzły'; @@ -1613,25 +1614,25 @@ class AppLocalizationsPl extends AppLocalizations { String get map_filterByKeyPrefix => 'Filtruj po prefiksie klucza'; @override - String get map_publicKeyPrefix => 'Przewód klucza publicznego'; + String get map_publicKeyPrefix => 'Prefiks klucza publicznego'; @override - String get map_markers => 'Oznaczarki'; + String get map_markers => 'Znaczniki'; @override - String get map_showSharedMarkers => 'Pokaż współdzielone znaki.'; + String get map_showSharedMarkers => 'Pokaż udostępnione znaczniki.'; @override String get map_lastSeenTime => 'Ostatni raz widziany'; @override - String get map_sharedPin => 'Współdzielony PIN'; + String get map_sharedPin => 'Udostępniona pinezka'; @override String get map_joinRoom => 'Dołącz do pokoju'; @override - String get map_manageRepeater => 'Zarządzaj Powtórzami'; + String get map_manageRepeater => 'Zarządzaj powtarzaczem'; @override String get map_tapToAdd => 'Kliknij na węzły, aby dodać je do ścieżki.'; @@ -1669,12 +1670,12 @@ class AppLocalizationsPl extends AppLocalizations { @override String mapCache_cachedTiles(int count) { - return 'Pamiętanych $count płytek'; + return 'Zapisano $count płytek w pamięci podręcznej'; } @override String mapCache_cachedTilesWithFailed(int downloaded, int failed) { - return 'Pamiętane $downloaded płytki ($failed nieudane)'; + return 'Zapisano $downloaded płytek ($failed nieudane)'; } @override @@ -1772,13 +1773,13 @@ class AppLocalizationsPl extends AppLocalizations { String get time_month => 'miesiąc'; @override - String get time_months => 'miesiace'; + String get time_months => 'miesiące'; @override String get time_minutes => 'minuty'; @override - String get time_allTime => 'Wszystko czasowo'; + String get time_allTime => 'Cały czas'; @override String get dialog_disconnect => 'Odłącz'; @@ -1788,7 +1789,7 @@ class AppLocalizationsPl extends AppLocalizations { 'Czy na pewno chcesz się odłączyć od tego urządzenia?'; @override - String get login_repeaterLogin => 'Powtórz Logowanie'; + String get login_repeaterLogin => 'Logowanie do powtarzacza'; @override String get login_roomLogin => 'Logowanie do pokoju'; @@ -1815,7 +1816,7 @@ class AppLocalizationsPl extends AppLocalizations { 'Wprowadź hasło do pokoju, aby uzyskać dostęp do ustawień i statusu.'; @override - String get login_routing => 'Przekierowanie'; + String get login_routing => 'Trasowanie'; @override String get login_routingMode => 'Tryb routingu'; @@ -1824,7 +1825,7 @@ class AppLocalizationsPl extends AppLocalizations { String get login_autoUseSavedPath => 'Automatycznie (użyj zapisanej ścieżki)'; @override - String get login_forceFloodMode => 'Wymusz Tryb Powodowany'; + String get login_forceFloodMode => 'Wymuś tryb zalewowy'; @override String get login_managePaths => 'Zarządzaj Ścieżkami'; @@ -1847,14 +1848,14 @@ class AppLocalizationsPl extends AppLocalizations { 'Logowanie nie powiodło się. Hasło jest nieprawidłowe albo repeater jest nieosiągalny.'; @override - String get common_reload => 'Ponownie załadować'; + String get common_reload => 'Odśwież'; @override String get common_clear => 'Wyczyść'; @override String path_currentPath(String path) { - return 'Aktualny ścieżka: $path'; + return 'Aktualna ścieżka: $path'; } @override @@ -1862,8 +1863,10 @@ class AppLocalizationsPl extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'hops', - one: 'hop', + other: 'skoków', + many: 'skoków', + few: 'skoki', + one: 'skok', ); return 'Użyj ścieżki $count $_temp0.'; } @@ -1872,7 +1875,7 @@ class AppLocalizationsPl extends AppLocalizations { String get path_enterCustomPath => 'Wprowadź własną ścieżkę'; @override - String get path_currentPathLabel => 'Aktualny ścieżka'; + String get path_currentPathLabel => 'Aktualna ścieżka'; @override String get path_hexPrefixInstructions => @@ -1880,14 +1883,14 @@ class AppLocalizationsPl extends AppLocalizations { @override String get path_hexPrefixExample => - 'A1,F2,3C (każedy węzeł używa pierwszego bajtu swojego klucza publicznego)'; + 'A1,F2,3C (każdy węzeł używa pierwszego bajtu swojego klucza publicznego)'; @override - String get path_labelHexPrefixes => 'Ścieżka (przesunięcia bitowe)'; + String get path_labelHexPrefixes => 'Ścieżka (prefiksy hex)'; @override String get path_helperMaxHops => - 'Maksymalnie 64 skoki. Każda prefiks ma 2 znaki szesnastkowe (1 bajt).'; + 'Maksymalnie 64 skoki. Każdy prefiks ma 2 znaki szesnastkowe (1 bajt).'; @override String get path_selectFromContacts => 'Albo wybierz z kontaktów:'; @@ -1913,7 +1916,7 @@ class AppLocalizationsPl extends AppLocalizations { String get path_setPath => 'Ustaw Ścieżkę'; @override - String get repeater_management => 'Zarządzanie Powtórzami'; + String get repeater_management => 'Zarządzanie powtarzaczami'; @override String get room_management => 'Zarządzanie Serwerem Pokoju'; @@ -1929,7 +1932,7 @@ class AppLocalizationsPl extends AppLocalizations { 'Wyświetl status powtarzacza, statystyki i sąsiadów.'; @override - String get repeater_telemetry => 'Telemetry'; + String get repeater_telemetry => 'Telemetria'; @override String get repeater_telemetrySubtitle => @@ -1939,7 +1942,7 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_cli => 'CLI'; @override - String get repeater_cliSubtitle => 'Wyślij polecenia do powielacza'; + String get repeater_cliSubtitle => 'Wyślij polecenia do powtarzacza'; @override String get repeater_neighbors => 'Sąsiedzi'; @@ -1965,7 +1968,7 @@ class AppLocalizationsPl extends AppLocalizations { 'Automatycznie (użyj zapisanej ścieżki)'; @override - String get repeater_forceFloodMode => 'Wymusz Tryb Powodowany'; + String get repeater_forceFloodMode => 'Wymuś tryb zalewowy'; @override String get repeater_pathManagement => 'Zarządzanie ścieżkami'; @@ -1974,7 +1977,8 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_refresh => 'Odśwież'; @override - String get repeater_statusRequestTimeout => 'Życzenie statusu timed out.'; + String get repeater_statusRequestTimeout => + 'Przekroczono czas oczekiwania na status.'; @override String repeater_errorLoadingStatus(String error) { @@ -1991,7 +1995,7 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_clockAtLogin => 'Godzina (przy logowaniu)'; @override - String get repeater_uptime => 'Dostępność'; + String get repeater_uptime => 'Czas pracy'; @override String get repeater_queueLength => 'Długość kolejki'; @@ -2041,17 +2045,17 @@ class AppLocalizationsPl extends AppLocalizations { @override String repeater_packetTxTotal(int total, String flood, String direct) { - return 'Razem: $total, Powodzenie: $flood, Bezpośrednio: $direct'; + return 'Razem: $total, Zalew: $flood, Bezpośrednio: $direct'; } @override String repeater_packetRxTotal(int total, String flood, String direct) { - return 'Razem: $total, Powodzenie: $flood, Bezpośrednio: $direct'; + return 'Razem: $total, Zalew: $flood, Bezpośrednio: $direct'; } @override String repeater_duplicatesFloodDirect(String flood, String direct) { - return 'Powodzie: $flood, Bezpośrednie: $direct'; + return 'Zalew: $flood, Bezpośrednie: $direct'; } @override @@ -2060,7 +2064,7 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get repeater_settingsTitle => 'Ustawienia Powtórki'; + String get repeater_settingsTitle => 'Ustawienia powtarzacza'; @override String get repeater_basicSettings => 'Podstawowe Ustawienia'; @@ -2075,13 +2079,13 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_adminPassword => 'Hasło Administracyjne'; @override - String get repeater_adminPasswordHelper => 'Pełny dostęp hasło'; + String get repeater_adminPasswordHelper => 'Hasło z pełnym dostępem'; @override String get repeater_guestPassword => 'Hasło gościa'; @override - String get repeater_guestPasswordHelper => 'Dostęp tylko do odczytu hasło'; + String get repeater_guestPasswordHelper => 'Hasło tylko do odczytu'; @override String get repeater_radioSettings => 'Ustawienia radia'; @@ -2105,7 +2109,7 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_spreadingFactor => 'Współczynnik rozpraszania'; @override - String get repeater_codingRate => 'Stawka kodowania'; + String get repeater_codingRate => 'Współczynnik kodowania'; @override String get repeater_locationSettings => 'Ustawienia Lokalizacji'; @@ -2130,7 +2134,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_packetForwardingSubtitle => - 'Włącz repeater, aby przekazywać pakiety.'; + 'Włącz powtarzacz, aby przekazywać pakiety.'; @override String get repeater_guestAccess => 'Dostęp dla gości'; @@ -2140,7 +2144,7 @@ class AppLocalizationsPl extends AppLocalizations { 'Umożliw dostęp tylko do odczytu dla gości.'; @override - String get repeater_privacyMode => 'Tryb Prywatności'; + String get repeater_privacyMode => 'Tryb prywatności'; @override String get repeater_privacyModeSubtitle => @@ -2199,7 +2203,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_eraseFileSystemSubtitle => - 'Sformatuj system plików powielacza'; + 'Sformatuj system plików powtarzacza'; @override String get repeater_eraseFileSystemConfirm => @@ -2234,7 +2238,7 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_refreshBasicSettings => 'Odśwież Podstawowe Ustawienia'; @override - String get repeater_refreshRadioSettings => 'Odśwież Ustawienia Radio'; + String get repeater_refreshRadioSettings => 'Odśwież ustawienia radia'; @override String get repeater_refreshTxPower => 'Odśwież moc TX'; @@ -2254,7 +2258,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_refreshAdvertisementSettings => - 'Odśwież Ustawienia Reklamy'; + 'Odśwież ustawienia rozgłoszeń'; @override String repeater_refreshed(String label) { @@ -2298,7 +2302,7 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_enterCommandFirst => 'Wprowadź najpierw polecenie'; @override - String get repeater_cliCommandFrameTitle => 'Określony Wyraz Polecenia CLI'; + String get repeater_cliCommandFrameTitle => 'Ramka polecenia CLI'; @override String repeater_cliCommandError(String error) { @@ -2306,10 +2310,10 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get repeater_cliQuickGetName => 'Pobierz imię'; + String get repeater_cliQuickGetName => 'Pobierz nazwę'; @override - String get repeater_cliQuickGetRadio => 'Uzyskaj Radio'; + String get repeater_cliQuickGetRadio => 'Pobierz radio'; @override String get repeater_cliQuickGetTx => 'Pobierz TX'; @@ -2321,7 +2325,7 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_cliQuickVersion => 'Wersja'; @override - String get repeater_cliQuickAdvertise => 'Reklama'; + String get repeater_cliQuickAdvertise => 'Rozgłoś'; @override String get repeater_cliQuickClock => 'Godzina'; @@ -2366,7 +2370,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_cliHelpSetFloodMax => - 'Ustawia maksymalną liczbę skoków pakietu powrotnego (jeśli >= max, pakiet nie jest przekierowywany)'; + 'Ustawia maksymalną liczbę skoków pakietu zalewowego (jeśli >= max, pakiet nie jest przekierowywany)'; @override String get repeater_cliHelpSetIntThresh => @@ -2374,7 +2378,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_cliHelpSetAgcResetInterval => - 'Ustawia interwał do zresetowania Automatycznego Sterownika Głośności. Ustaw na 0, aby wyłączyć.'; + 'Ustawia interwał do zresetowania automatycznego wzmocnienia (AGC). Ustaw na 0, aby wyłączyć.'; @override String get repeater_cliHelpSetMultiAcks => @@ -2452,7 +2456,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_cliHelpGetBridgeType => - 'Uzyskano typ mostu: brak, rs232, espnow'; + 'Pobiera typ mostka: brak, rs232, espnow'; @override String get repeater_cliHelpLogStart => @@ -2476,11 +2480,11 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_cliHelpRegion => - '(tylko seria) Wyświetla wszystkie zdefiniowane regiony i aktualne uprawnienia do powodzi.'; + '(tylko port szeregowy) Wyświetla wszystkie zdefiniowane regiony i aktualne uprawnienia do zalewu.'; @override String get repeater_cliHelpRegionLoad => - 'ZAPOMNIJ: to jest specjalne wywołanie wielokomendowe. Każda następna komenda jest nazwą regionu (wcięta spacjami, aby wskazywać hierarchię nadrzędną, z minimum jedną spacją). Zakończona wysłaniem pustej linii/komendy.'; + 'UWAGA: to jest specjalne wywołanie wielokomendowe. Każda następna komenda jest nazwą regionu (wcięta spacjami, aby wskazywać hierarchię nadrzędną, z minimum jedną spacją). Zakończona wysłaniem pustej linii/komendy.'; @override String get repeater_cliHelpRegionGet => @@ -2496,11 +2500,11 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_cliHelpRegionAllowf => - 'Ustawia uprawnienia \'P\'łytkowe dla podanego regionu. (\'\' dla zakresu globalnego/starszego)'; + 'Ustawia uprawnienia \'F\' (zalewowe) dla podanego regionu. (\'\' dla zakresu globalnego/starszego)'; @override String get repeater_cliHelpRegionDenyf => - 'Usuwa uprawnienie \'Pływające\' dla podanej strefy. (ZALECANE: na tym etapie NIE zaleca się używania tego na globalnym/starszym zakresie!!).'; + 'Usuwa uprawnienie \'F\' (zalewowe) dla podanej strefy. (ZALECANE: na tym etapie NIE zaleca się używania tego na globalnym/starszym zakresie!!).'; @override String get repeater_cliHelpRegionHome => @@ -2541,7 +2545,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_commandsListNote => - 'ZAPAMIĘTAJ: dla różnych poleceń \"set ...\" istnieje również polecenie \"get ...\".'; + 'UWAGA: dla różnych poleceń \"set ...\" istnieje również polecenie \"get ...\".'; @override String get repeater_general => 'Ogólne'; @@ -2574,15 +2578,15 @@ class AppLocalizationsPl extends AppLocalizations { 'Polecenie GPS zostało wprowadzone w celu zarządzania tematami związanymi z lokalizacją.'; @override - String get telemetry_receivedData => 'Otrzymano Dane Telemetrii'; + String get telemetry_receivedData => 'Odebrane dane telemetrii'; @override String get telemetry_requestTimeout => - 'Życzenie o danych telemetrycznych nie udało się.'; + 'Przekroczono czas oczekiwania na telemetrię.'; @override String telemetry_errorLoading(String error) { - return 'Błąd podczas ładowania telemetry: $error'; + return 'Błąd podczas ładowania telemetrii: $error'; } @override @@ -2606,7 +2610,7 @@ class AppLocalizationsPl extends AppLocalizations { String get telemetry_temperatureLabel => 'Temperatura'; @override - String get telemetry_currentLabel => 'Obecny'; + String get telemetry_currentLabel => 'Prąd'; @override String telemetry_batteryValue(int percent, String volts) { @@ -2615,7 +2619,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String telemetry_voltageValue(String volts) { - return '${volts}W'; + return '${volts}V'; } @override @@ -2663,10 +2667,10 @@ class AppLocalizationsPl extends AppLocalizations { String get channelPath_viewMap => 'Wyświetl mapę'; @override - String get channelPath_otherObservedPaths => 'Inne Zauważone Ścieżki'; + String get channelPath_otherObservedPaths => 'Inne zaobserwowane ścieżki'; @override - String get channelPath_repeaterHops => 'Skoki Powtórki'; + String get channelPath_repeaterHops => 'Skoki powtarzaczy'; @override String get channelPath_noHopDetails => @@ -2694,7 +2698,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String channelPath_observedPathTitle(int index, String hops) { - return 'Obserwowany ścieżka $index • $hops'; + return 'Obserwowana ścieżka $index • $hops'; } @override @@ -2734,7 +2738,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get channelPath_noRepeaterLocations => - 'Brak dostępnych lokalizacji powtarzaczy dla tego ścieżki.'; + 'Brak dostępnych lokalizacji powtarzaczy dla tej ścieżki.'; @override String channelPath_primaryPath(int index) { diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 8837732..5c66b65 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -17,7 +17,7 @@ "common_unknownDevice": "Nieznane urządzenie", "common_save": "Zapisz", "common_delete": "Usuń", - "common_close": "Zamknąć", + "common_close": "Zamknij", "common_edit": "Edytuj", "common_add": "Dodaj", "common_settings": "Ustawienia", @@ -28,12 +28,12 @@ "common_continue": "Kontynuuj", "common_share": "Udostępnij", "common_copy": "Kopiuj", - "common_retry": "Spróbować", + "common_retry": "Ponów", "common_hide": "Ukryj", "common_remove": "Usuń", "common_enable": "Włącz", - "common_disable": "Wyłączyć", - "common_reboot": "Zrestartować", + "common_disable": "Wyłącz", + "common_reboot": "Uruchom ponownie", "common_loading": "Ładowanie...", "common_notAvailable": "—", "common_voltageValue": "{volts} V", @@ -56,7 +56,7 @@ "scanner_scanning": "Skanowanie urządzeń...", "scanner_connecting": "Łączenie...", "scanner_disconnecting": "Odłączanie...", - "scanner_notConnected": "Niepołączony", + "scanner_notConnected": "Nie połączono", "scanner_connectedTo": "Połączono z {deviceName}", "@scanner_connectedTo": { "placeholders": { @@ -87,7 +87,7 @@ "settings_nodeName": "Nazwa węzła", "settings_nodeNameNotSet": "Nie ustawione", "settings_nodeNameHint": "Wprowadź nazwę węzła", - "settings_nodeNameUpdated": "Imię zaktualizowane", + "settings_nodeNameUpdated": "Nazwa zaktualizowana", "settings_radioSettings": "Ustawienia radia", "settings_radioSettingsSubtitle": "Częstotliwość, moc, współczynnik rozpraszania", "settings_radioSettingsUpdated": "Ustawienia radia zostały zaktualizowane", @@ -98,29 +98,29 @@ "settings_locationInvalid": "Nieprawidłowa szerokość geograficzna lub długość geograficzna.", "settings_latitude": "Szerokość", "settings_longitude": "Długość", - "settings_privacyMode": "Tryb Prywatny", + "settings_privacyMode": "Tryb prywatności", "settings_privacyModeSubtitle": "Ukryj imię/lokalizację w rozgłoszeniach", "settings_privacyModeToggle": "Włącz tryb prywatności, aby ukryć swoje imię i lokalizację w rozgłoszeniach.", "settings_privacyModeEnabled": "Tryb prywatności włączony", "settings_privacyModeDisabled": "Tryb prywatności wyłączony", "settings_actions": "Działania", "settings_sendAdvertisement": "Wyślij rozgłoszenie", - "settings_sendAdvertisementSubtitle": "Obecność transmisji jest teraz", + "settings_sendAdvertisementSubtitle": "Nadaj obecność teraz", "settings_advertisementSent": "Rozgłoszenie wysłane", - "settings_syncTime": "Czas synchronizacji", + "settings_syncTime": "Synchronizacja czasu", "settings_syncTimeSubtitle": "Ustaw zegar urządzenia na czas telefonu.", - "settings_timeSynchronized": "Synchronizacja czasu", + "settings_timeSynchronized": "Czas zsynchronizowany", "settings_refreshContacts": "Odśwież Kontakty", "settings_refreshContactsSubtitle": "Odśwież listę kontaktów z urządzenia", "settings_rebootDevice": "Zrestartuj Urządzenie", "settings_rebootDeviceSubtitle": "Zrestartuj urządzenie MeshCore", "settings_rebootDeviceConfirm": "Czy na pewno chcesz zrestartować urządzenie? Będziesz odłączony.", "settings_debug": "Debug", - "settings_bleDebugLog": "Log błędów BLE", + "settings_bleDebugLog": "Dziennik debugowania BLE", "settings_bleDebugLogSubtitle": "Polecenia BLE, odpowiedzi i surowe dane", - "settings_appDebugLog": "Log Wykonywania Aplikacji", + "settings_appDebugLog": "Dziennik debugowania aplikacji", "settings_appDebugLogSubtitle": "Komunikaty debugowania aplikacji", - "settings_about": "O mnie", + "settings_about": "O aplikacji", "settings_aboutVersion": "MeshCore Open v{version}", "@settings_aboutVersion": { "placeholders": { @@ -131,21 +131,24 @@ }, "settings_aboutLegalese": "Projekt MeshCore Open Source 2026", "settings_aboutDescription": "Otwarty kod źródłowy klient Flutter dla urządzeń do sieci mesh LoRa MeshCore.", - "settings_infoName": "Imię", + "settings_infoName": "Nazwa", "settings_infoId": "ID", "settings_infoStatus": "Status", "settings_infoBattery": "Bateria", "settings_infoPublicKey": "Klucz Publiczny", "settings_infoContactsCount": "Liczba kontaktów", "settings_infoChannelCount": "Liczba kanałów", - "settings_presets": "Preset", + "settings_presets": "Presety", + "settings_preset915Mhz": "915 MHz", + "settings_preset868Mhz": "868 MHz", + "settings_preset433Mhz": "433 MHz", "settings_frequency": "Częstotliwość (MHz)", "settings_frequencyHelper": "300,0 - 2500,0", "settings_frequencyInvalid": "Nieprawidłowa częstotliwość (300-2500 MHz)", "settings_bandwidth": "Przepustowość", - "settings_spreadingFactor": "Rozkład Czynnika", - "settings_codingRate": "Stawka Kodowania", - "settings_txPower": "TX Moc (dBm)", + "settings_spreadingFactor": "Współczynnik rozpraszania", + "settings_codingRate": "Współczynnik kodowania", + "settings_txPower": "Moc TX (dBm)", "settings_txPowerHelper": "0 - 22", "settings_txPowerInvalid": "Nieprawidłowa moc TX (0-22 dBm)", "settings_error": "Błąd: {message}", @@ -163,7 +166,7 @@ "appSettings_themeLight": "Jasne", "appSettings_themeDark": "Ciemny", "appSettings_language": "Język", - "appSettings_languageSystem": "Domyślny systemowy", + "appSettings_languageSystem": "Domyślny systemu", "appSettings_languageEn": "English", "appSettings_languageFr": "Français", "appSettings_languageEs": "Español", @@ -190,17 +193,17 @@ "appSettings_advertisementNotifications": "Powiadomienia o rozgłoszeniach", "appSettings_advertisementNotificationsSubtitle": "Wyświetl powiadomienie, gdy zostaną wykryte nowe węzły.", "appSettings_messaging": "Wiadomości", - "appSettings_clearPathOnMaxRetry": "Wyczyść Ścieżkę na Maksymalnej Próbie", + "appSettings_clearPathOnMaxRetry": "Wyczyść ścieżkę po maks. liczbie prób", "appSettings_clearPathOnMaxRetrySubtitle": "Resetuj ścieżkę kontaktu po 5 nieudanych próbach wysłania", - "appSettings_pathsWillBeCleared": "Droga będzie wyczyszczona po 5 nieudanych próbach.", - "appSettings_pathsWillNotBeCleared": "Droga nie zostanie automatycznie wyczyszczona.", - "appSettings_autoRouteRotation": "Automatyczne Rotowanie Trasy", + "appSettings_pathsWillBeCleared": "Ścieżka zostanie wyczyszczona po 5 nieudanych próbach.", + "appSettings_pathsWillNotBeCleared": "Ścieżka nie zostanie automatycznie wyczyszczona.", + "appSettings_autoRouteRotation": "Automatyczna rotacja trasy", "appSettings_autoRouteRotationSubtitle": "Przełączaj się między najlepszymi ścieżkami a trybem zalewowym.", "appSettings_autoRouteRotationEnabled": "Automatyczne obracanie tras włączone", "appSettings_autoRouteRotationDisabled": "Automatyczne obracanie tras wyłączone", "appSettings_battery": "Bateria", "appSettings_batteryChemistry": "Chemia Baterii", - "appSettings_batteryChemistryPerDevice": "Ustawione na urządzenie ({deviceName})", + "appSettings_batteryChemistryPerDevice": "Ustaw dla urządzenia ({deviceName})", "@appSettings_batteryChemistryPerDevice": { "placeholders": { "deviceName": { @@ -213,8 +216,8 @@ "appSettings_batteryLifepo4": "LiFePO4 (2,6-3,65 V)", "appSettings_batteryLipo": "LiPo (3,0-4,2V)", "appSettings_mapDisplay": "Wyświetlanie mapy", - "appSettings_showRepeaters": "Pokaż Powtórniki", - "appSettings_showRepeatersSubtitle": "Wyświetl węzły powtarzające się na mapie", + "appSettings_showRepeaters": "Pokaż powtarzacze", + "appSettings_showRepeatersSubtitle": "Wyświetl węzły powtarzaczy na mapie", "appSettings_showChatNodes": "Pokaż Węzły Rozmowy", "appSettings_showChatNodesSubtitle": "Wyświetl węzły czatu na mapie", "appSettings_showOtherNodes": "Pokaż inne węzły", @@ -231,12 +234,12 @@ }, "appSettings_mapTimeFilter": "Filtrowanie Czasu Mapy", "appSettings_showNodesDiscoveredWithin": "Pokaż węzły odkryte w:", - "appSettings_allTime": "Wszystko czasowo", + "appSettings_allTime": "Cały czas", "appSettings_lastHour": "Ostatnia godzina", "appSettings_last6Hours": "Ostatnie 6 godzin", "appSettings_last24Hours": "Ostatnie 24 godziny", - "appSettings_lastWeek": "Tydzień temu", - "appSettings_offlineMapCache": "Bufor Map Offline", + "appSettings_lastWeek": "Ostatni tydzień", + "appSettings_offlineMapCache": "Pamięć podręczna map offline", "appSettings_noAreaSelected": "Nie zaznaczono żadnej powierzchni.", "appSettings_areaSelectedZoom": "Wybrany obszar (skala {minZoom}-{maxZoom})", "@appSettings_areaSelectedZoom": { @@ -252,8 +255,8 @@ "appSettings_debugCard": "Debug", "appSettings_appDebugLogging": "Logowanie Debugowania Aplikacji", "appSettings_appDebugLoggingSubtitle": "Loguj wiadomości debugowania aplikacji w celu rozwiązywania problemów.", - "appSettings_appDebugLoggingEnabled": "Zdebugowanie aplikacji włączone", - "appSettings_appDebugLoggingDisabled": "Zasubskrybowane logi debugowania aplikacji wyłączone.", + "appSettings_appDebugLoggingEnabled": "Logowanie debugowania aplikacji włączone", + "appSettings_appDebugLoggingDisabled": "Logowanie debugowania aplikacji wyłączone.", "contacts_title": "Kontakty", "contacts_noContacts": "Brak jeszcze kontaktów.", "contacts_contactsWillAppear": "Kontakty będą wyświetlane, gdy urządzenia nadają rozgłoszenia.", @@ -269,7 +272,7 @@ } } }, - "contacts_manageRepeater": "Zarządzaj Powtórzami", + "contacts_manageRepeater": "Zarządzaj powtarzaczem", "contacts_roomLogin": "Logowanie do pokoju", "contacts_openChat": "Otwórz czat", "contacts_editGroup": "Edytuj Grupę", @@ -297,8 +300,8 @@ "contacts_filterContacts": "Filtruj kontakty...", "contacts_noContactsMatchFilter": "Brak pasujących kontaktów do Twojego filtra", "contacts_noMembers": "Brak członków", - "contacts_lastSeenNow": "Ostatnie połączenie", - "contacts_lastSeenMinsAgo": "Ostatnie połączenie {minutes} min temu", + "contacts_lastSeenNow": "Widziano przed chwilą", + "contacts_lastSeenMinsAgo": "Widziano {minutes} min temu", "@contacts_lastSeenMinsAgo": { "placeholders": { "minutes": { @@ -307,7 +310,7 @@ } }, "contacts_lastSeenHourAgo": "Ostatni raz widziany 1 godzinę temu", - "contacts_lastSeenHoursAgo": "Ostatnie połączenie {hours} godzin temu", + "contacts_lastSeenHoursAgo": "Widziano {hours} godz. temu", "@contacts_lastSeenHoursAgo": { "placeholders": { "hours": { @@ -316,7 +319,7 @@ } }, "contacts_lastSeenDayAgo": "Ostatni raz widziany 1 dzień temu", - "contacts_lastSeenDaysAgo": "Ostatnie połączenie {days} dni temu", + "contacts_lastSeenDaysAgo": "Widziano {days} dni temu", "@contacts_lastSeenDaysAgo": { "placeholders": { "days": { @@ -339,7 +342,7 @@ }, "channels_hashtagChannel": "Kanał z hashtagami", "channels_public": "Publiczny", - "channels_private": "Prywatne", + "channels_private": "Prywatny", "channels_publicChannel": "Kanał publiczny", "channels_privateChannel": "Prywatny kanał", "channels_editChannel": "Edytuj kanał", @@ -366,7 +369,7 @@ "channels_channelIndexLabel": "Indeks kanału", "channels_channelName": "Nazwa kanału", "channels_usePublicChannel": "Użyj kanału publicznego", - "channels_standardPublicPsk": "Standard public PSK", + "channels_standardPublicPsk": "Standardowy publiczny PSK", "channels_pskHex": "PSK (Hex)", "channels_generateRandomPsk": "Wygeneruj losowy klucz PSK", "channels_enterChannelName": "Proszę podać nazwę kanału.", @@ -401,7 +404,7 @@ "channels_sortManual": "Ręczna", "channels_sortAZ": "A-Z", "channels_sortLatestMessages": "Najnowsze wiadomości", - "channels_sortUnread": "Niezgłoszone", + "channels_sortUnread": "Nieprzeczytane", "chat_noMessages": "Brak jeszcze wiadomości", "chat_sendMessageToStart": "Wyślij wiadomość, aby rozpocząć.", "chat_originalMessageNotFound": "Błąd: Nie znaleziono oryginalnego komunikatu", @@ -413,7 +416,7 @@ } } }, - "chat_replyTo": "Odpowiedz {name}", + "chat_replyTo": "Odpowiedz do {name}", "@chat_replyTo": { "placeholders": { "name": { @@ -441,8 +444,8 @@ }, "chat_messageCopied": "Wiadomość skopiowana", "chat_messageDeleted": "Wiadomość usunięta", - "chat_retryingMessage": "Próba ponowienia", - "chat_retryCount": "Spróbuj {current}/{max}", + "chat_retryingMessage": "Ponawianie wiadomości", + "chat_retryCount": "Próba {current}/{max}", "@chat_retryCount": { "placeholders": { "current": { @@ -458,8 +461,8 @@ "chat_addReaction": "Dodaj Reakcję", "chat_me": "Ja", "emojiCategorySmileys": "Emoji", - "emojiCategoryGestures": "Gestikulacje", - "emojiCategoryHearts": "Serce", + "emojiCategoryGestures": "Gesty", + "emojiCategoryHearts": "Serca", "emojiCategoryObjects": "Obiekty", "gifPicker_title": "Wybierz GIF", "gifPicker_searchHint": "Wyszukaj GIF-y...", @@ -476,8 +479,8 @@ "debugLog_bleCopied": "Skopiowany log BLE", "debugLog_noEntries": "Nie ma jeszcze żadnych logów debugowania.", "debugLog_enableInSettings": "Włącz logowanie debugowania aplikacji w ustawieniach", - "debugLog_frames": "Ramy", - "debugLog_rawLogRx": "Surowe Log-RX", + "debugLog_frames": "Ramki", + "debugLog_rawLogRx": "Surowy log RX", "debugLog_noBleActivity": "Brak aktywności BLE jeszcze.", "debugFrame_length": "Długość ramy: {count} bajtów", "@debugFrame_length": { @@ -496,7 +499,7 @@ } }, "debugFrame_textMessageHeader": "Wiadomość tekstowa:", - "debugFrame_destinationPubKey": "- Oznaczenie PubKey: {pubKey}", + "debugFrame_destinationPubKey": "- Docelowy klucz publiczny: {pubKey}", "@debugFrame_destinationPubKey": { "placeholders": { "pubKey": { @@ -532,7 +535,7 @@ } }, "debugFrame_textTypeCli": "CLI", - "debugFrame_textTypePlain": "Proste", + "debugFrame_textTypePlain": "Zwykły", "debugFrame_text": "- Tekst: \"{text}\"", "@debugFrame_text": { "placeholders": { @@ -541,16 +544,16 @@ } } }, - "debugFrame_hexDump": "Wyjście SzESZCZNULNE:", + "debugFrame_hexDump": "Zrzut hex:", "chat_pathManagement": "Zarządzanie ścieżkami", "chat_routingMode": "Tryb routingu", "chat_autoUseSavedPath": "Automatyczne (użyj zapisanej ścieżki)", - "chat_forceFloodMode": "Wymusz Tryb Powodowany", + "chat_forceFloodMode": "Wymuś tryb zalewowy", "chat_recentAckPaths": "Ostatnie ścieżki ACK (naciśnij, aby użyć):", "chat_pathHistoryFull": "Historia ścieżek jest pełna. Usuń wpisy, aby dodać nowe.", "chat_hopSingular": "skok", "chat_hopPlural": "skoki", - "chat_hopsCount": "{count} {count, plural, =1{hop} other{hops}}", + "chat_hopsCount": "{count} {count, plural, one{skok} few{skoki} many{skoków} other{skoków}}", "@chat_hopsCount": { "placeholders": { "count": { @@ -562,11 +565,11 @@ "chat_removePath": "Usuń ścieżkę", "chat_noPathHistoryYet": "Brak jeszcze historii ścieżek.\nWyślij wiadomość, aby odkryć ścieżki.", "chat_pathActions": "Działania ścieżki:", - "chat_setCustomPath": "Ustaw Ścieżkę Dostosowaną", + "chat_setCustomPath": "Ustaw ścieżkę niestandardową", "chat_setCustomPathSubtitle": "Ręcznie określ trasę.", "chat_clearPath": "Wyczyść Ścieżkę", - "chat_clearPathSubtitle": "Zmusz do ponownej identyfikacji przy następnym wysłaniu", - "chat_pathCleared": "Ścieżka oczyszczona. Kolejne powiadomienie odnajdzie trasę.", + "chat_clearPathSubtitle": "Wymuś ponowne wyznaczenie trasy przy następnym wysłaniu", + "chat_pathCleared": "Ścieżka wyczyszczona. Następna wiadomość odnajdzie trasę.", "chat_floodModeSubtitle": "Użyj przełącznika routingu w pasku narzędzi.", "chat_floodModeEnabled": "Tryb powodziowy włączony. Włącz ponownie za pomocą ikony routingu w pasku narzędzi.", "chat_fullPath": "Pełna ścieżka", @@ -585,7 +588,7 @@ "chat_pathSavedLocally": "Zapisano lokalnie. Połącz się, aby zsynchronizować.", "chat_pathDeviceConfirmed": "Urządzenie potwierdzone.", "chat_pathDeviceNotConfirmed": "Urządzenie nie zostało jeszcze potwierdzone.", - "chat_type": "Wprowadź", + "chat_type": "Typ", "chat_path": "Ścieżka", "chat_publicKey": "Klucz Publiczny", "chat_compressOutgoingMessages": "Kompresuj wychodzące wiadomości", @@ -602,7 +605,7 @@ "chat_floodAuto": "Zalew (automatyczny)", "chat_direct": "Bezpośrednio", "chat_poiShared": "Wspólny POI", - "chat_unread": "Niezgłoszone: {count}", + "chat_unread": "Nieprzeczytane: {count}", "@chat_unread": { "placeholders": { "count": { @@ -633,7 +636,7 @@ } } }, - "map_pinsCount": "Pinki: {count}", + "map_pinsCount": "Pinezki: {count}", "@map_pinsCount": { "placeholders": { "count": { @@ -645,23 +648,23 @@ "map_repeater": "Powtórzacz", "map_room": "Pokój", "map_sensor": "Czujnik", - "map_pinDm": "Zablokuj (DM)", - "map_pinPrivate": "Zablokuj (Prywatnie)", - "map_pinPublic": "Oznacz jako publiczne", + "map_pinDm": "Pinezka (DM)", + "map_pinPrivate": "Pinezka (prywatna)", + "map_pinPublic": "Pinezka (publiczna)", "map_lastSeen": "Ostatni raz widziany", "map_disconnectConfirm": "Czy na pewno chcesz się odłączyć od tego urządzenia?", "map_from": "Od", "map_source": "Źródło", "map_flags": "Flagi", "map_shareMarkerHere": "Udostępnij znacznik tutaj", - "map_pinLabel": "Oznacz etykietę", + "map_pinLabel": "Etykieta pinezki", "map_label": "Etykieta", "map_pointOfInterest": "Punkt zainteresowań", "map_sendToContact": "Wyślij do kontaktu", "map_sendToChannel": "Wyślij do kanału", "map_noChannelsAvailable": "Brak dostępnych kanałów", "map_publicLocationShare": "Udostępnij lokalizację publicznie", - "map_publicLocationShareConfirm": "Wkrótce udostępnisz lokalizację w {channelLabel}. Ten kanał jest publiczny i każdy z PSK może go zobaczyć.", + "map_publicLocationShareConfirm": "Zamierzasz udostępnić lokalizację w {channelLabel}. Ten kanał jest publiczny i każdy z PSK może go zobaczyć.", "@map_publicLocationShareConfirm": { "placeholders": { "channelLabel": { @@ -669,7 +672,7 @@ } } }, - "map_connectToShareMarkers": "Połącz się z urządzeniem, aby udostępniać znacznik.", + "map_connectToShareMarkers": "Połącz się z urządzeniem, aby udostępniać znaczniki.", "map_filterNodes": "Filtruj Węzły", "map_nodeTypes": "Typy węzłów", "map_chatNodes": "Węzły czatu", @@ -677,13 +680,13 @@ "map_otherNodes": "Inne węzły", "map_keyPrefix": "Prefiks klucza", "map_filterByKeyPrefix": "Filtruj po prefiksie klucza", - "map_publicKeyPrefix": "Przewód klucza publicznego", - "map_markers": "Oznaczarki", - "map_showSharedMarkers": "Pokaż współdzielone znaki.", + "map_publicKeyPrefix": "Prefiks klucza publicznego", + "map_markers": "Znaczniki", + "map_showSharedMarkers": "Pokaż udostępnione znaczniki.", "map_lastSeenTime": "Ostatni raz widziany", - "map_sharedPin": "Współdzielony PIN", + "map_sharedPin": "Udostępniona pinezka", "map_joinRoom": "Dołącz do pokoju", - "map_manageRepeater": "Zarządzaj Powtórzami", + "map_manageRepeater": "Zarządzaj powtarzaczem", "mapCache_title": "Bufor Map Offline", "mapCache_selectAreaFirst": "Wybierz obszar do wstępnego pobrania.", "mapCache_noTilesToDownload": "Brak dostępnych płytek do pobrania dla tego obszaru.", @@ -697,7 +700,7 @@ } }, "mapCache_downloadAction": "Pobierz", - "mapCache_cachedTiles": "Pamiętanych {count} płytek", + "mapCache_cachedTiles": "Zapisano {count} płytek w pamięci podręcznej", "@mapCache_cachedTiles": { "placeholders": { "count": { @@ -705,7 +708,7 @@ } } }, - "mapCache_cachedTilesWithFailed": "Pamiętane {downloaded} płytki ({failed} nieudane)", + "mapCache_cachedTilesWithFailed": "Zapisano {downloaded} płytek ({failed} nieudane)", "@mapCache_cachedTilesWithFailed": { "placeholders": { "downloaded": { @@ -801,12 +804,12 @@ "time_week": "tydzień", "time_weeks": "tygodnie", "time_month": "miesiąc", - "time_months": "miesiace", + "time_months": "miesiące", "time_minutes": "minuty", - "time_allTime": "Wszystko czasowo", + "time_allTime": "Cały czas", "dialog_disconnect": "Odłącz", "dialog_disconnectConfirm": "Czy na pewno chcesz się odłączyć od tego urządzenia?", - "login_repeaterLogin": "Powtórz Logowanie", + "login_repeaterLogin": "Logowanie do powtarzacza", "login_roomLogin": "Logowanie do pokoju", "login_password": "Hasło", "login_enterPassword": "Wprowadź hasło", @@ -814,10 +817,10 @@ "login_savePasswordSubtitle": "Hasło będzie bezpiecznie przechowywane na tym urządzeniu.", "login_repeaterDescription": "Wprowadź hasło do powtarzacza, aby uzyskać dostęp do ustawień i statusu.", "login_roomDescription": "Wprowadź hasło do pokoju, aby uzyskać dostęp do ustawień i statusu.", - "login_routing": "Przekierowanie", + "login_routing": "Trasowanie", "login_routingMode": "Tryb routingu", "login_autoUseSavedPath": "Automatycznie (użyj zapisanej ścieżki)", - "login_forceFloodMode": "Wymusz Tryb Powodowany", + "login_forceFloodMode": "Wymuś tryb zalewowy", "login_managePaths": "Zarządzaj Ścieżkami", "login_login": "Zaloguj się", "login_attempt": "Próba {current}/{max}", @@ -840,9 +843,9 @@ } }, "login_failedMessage": "Logowanie nie powiodło się. Hasło jest nieprawidłowe albo repeater jest nieosiągalny.", - "common_reload": "Ponownie załadować", + "common_reload": "Odśwież", "common_clear": "Wyczyść", - "path_currentPath": "Aktualny ścieżka: {path}", + "path_currentPath": "Aktualna ścieżka: {path}", "@path_currentPath": { "placeholders": { "path": { @@ -850,7 +853,7 @@ } } }, - "path_usingHopsPath": "Użyj ścieżki {count} {count, plural, =1{hop} other{hops}}.", + "path_usingHopsPath": "Użyj ścieżki {count} {count, plural, one{skok} few{skoki} many{skoków} other{skoków}}.", "@path_usingHopsPath": { "placeholders": { "count": { @@ -859,11 +862,11 @@ } }, "path_enterCustomPath": "Wprowadź własną ścieżkę", - "path_currentPathLabel": "Aktualny ścieżka", + "path_currentPathLabel": "Aktualna ścieżka", "path_hexPrefixInstructions": "Wprowadź 2-znakowe prefiksy szesnastkowe dla każdego skoku, oddzielone przecinkami.", - "path_hexPrefixExample": "A1,F2,3C (każedy węzeł używa pierwszego bajtu swojego klucza publicznego)", - "path_labelHexPrefixes": "Ścieżka (przesunięcia bitowe)", - "path_helperMaxHops": "Maksymalnie 64 skoki. Każda prefiks ma 2 znaki szesnastkowe (1 bajt).", + "path_hexPrefixExample": "A1,F2,3C (każdy węzeł używa pierwszego bajtu swojego klucza publicznego)", + "path_labelHexPrefixes": "Ścieżka (prefiksy hex)", + "path_helperMaxHops": "Maksymalnie 64 skoki. Każdy prefiks ma 2 znaki szesnastkowe (1 bajt).", "path_selectFromContacts": "Albo wybierz z kontaktów:", "path_noRepeatersFound": "Nie znaleziono repeaterów ani serwerów pokoi.", "path_customPathsRequire": "Dostosowane ścieżki wymagają pośrednich skoków, które mogą przekazywać wiadomości.", @@ -877,23 +880,23 @@ }, "path_tooLong": "Ścieżka jest zbyt długa. Dozwolonych skoków wynosi 64.", "path_setPath": "Ustaw Ścieżkę", - "repeater_management": "Zarządzanie Powtórzami", + "repeater_management": "Zarządzanie powtarzaczami", "repeater_managementTools": "Narzędzia Zarządzania", "repeater_status": "Status", "repeater_statusSubtitle": "Wyświetl status powtarzacza, statystyki i sąsiadów.", - "repeater_telemetry": "Telemetry", + "repeater_telemetry": "Telemetria", "repeater_telemetrySubtitle": "Wyświetl dane telemetryczne z czujników i statystyki systemu", "repeater_cli": "CLI", - "repeater_cliSubtitle": "Wyślij polecenia do powielacza", + "repeater_cliSubtitle": "Wyślij polecenia do powtarzacza", "repeater_settings": "Ustawienia", "repeater_settingsSubtitle": "Skonfiguruj parametry powtarzacza", "repeater_statusTitle": "Status powtarzacza", "repeater_routingMode": "Tryb routingu", "repeater_autoUseSavedPath": "Automatycznie (użyj zapisanej ścieżki)", - "repeater_forceFloodMode": "Wymusz Tryb Powodowany", + "repeater_forceFloodMode": "Wymuś tryb zalewowy", "repeater_pathManagement": "Zarządzanie ścieżkami", "repeater_refresh": "Odśwież", - "repeater_statusRequestTimeout": "Życzenie statusu timed out.", + "repeater_statusRequestTimeout": "Przekroczono czas oczekiwania na status.", "repeater_errorLoadingStatus": "Błąd podczas ładowania statusu: {error}", "@repeater_errorLoadingStatus": { "placeholders": { @@ -905,7 +908,7 @@ "repeater_systemInformation": "Informacje o systemie", "repeater_battery": "Bateria", "repeater_clockAtLogin": "Godzina (przy logowaniu)", - "repeater_uptime": "Dostępność", + "repeater_uptime": "Czas pracy", "repeater_queueLength": "Długość kolejki", "repeater_debugFlags": "Opcje debugowania", "repeater_radioStatistics": "Statystyki Radia", @@ -935,7 +938,7 @@ } } }, - "repeater_packetTxTotal": "Razem: {total}, Powodzenie: {flood}, Bezpośrednio: {direct}", + "repeater_packetTxTotal": "Razem: {total}, Zalew: {flood}, Bezpośrednio: {direct}", "@repeater_packetTxTotal": { "placeholders": { "total": { @@ -949,7 +952,7 @@ } } }, - "repeater_packetRxTotal": "Razem: {total}, Powodzenie: {flood}, Bezpośrednio: {direct}", + "repeater_packetRxTotal": "Razem: {total}, Zalew: {flood}, Bezpośrednio: {direct}", "@repeater_packetRxTotal": { "placeholders": { "total": { @@ -963,7 +966,7 @@ } } }, - "repeater_duplicatesFloodDirect": "Powodzie: {flood}, Bezpośrednie: {direct}", + "repeater_duplicatesFloodDirect": "Zalew: {flood}, Bezpośrednie: {direct}", "@repeater_duplicatesFloodDirect": { "placeholders": { "flood": { @@ -982,14 +985,14 @@ } } }, - "repeater_settingsTitle": "Ustawienia Powtórki", + "repeater_settingsTitle": "Ustawienia powtarzacza", "repeater_basicSettings": "Podstawowe Ustawienia", "repeater_repeaterName": "Nazwa Powtórnika", "repeater_repeaterNameHelper": "Wyświetl nazwę tego powtarzacza", "repeater_adminPassword": "Hasło Administracyjne", - "repeater_adminPasswordHelper": "Pełny dostęp hasło", + "repeater_adminPasswordHelper": "Hasło z pełnym dostępem", "repeater_guestPassword": "Hasło gościa", - "repeater_guestPasswordHelper": "Dostęp tylko do odczytu hasło", + "repeater_guestPasswordHelper": "Hasło tylko do odczytu", "repeater_radioSettings": "Ustawienia radia", "repeater_frequencyMhz": "Częstotliwość (MHz)", "repeater_frequencyHelper": "300-2500 MHz", @@ -997,7 +1000,7 @@ "repeater_txPowerHelper": "1-30 dBm", "repeater_bandwidth": "Przepustowość", "repeater_spreadingFactor": "Współczynnik rozpraszania", - "repeater_codingRate": "Stawka kodowania", + "repeater_codingRate": "Współczynnik kodowania", "repeater_locationSettings": "Ustawienia Lokalizacji", "repeater_latitude": "Szerokość", "repeater_latitudeHelper": "Stopnie dziesiętne (np. 37.7749)", @@ -1005,10 +1008,10 @@ "repeater_longitudeHelper": "Stopnie dziesiętne (np. -122,4194)", "repeater_features": "Funkcje", "repeater_packetForwarding": "Przekierowanie pakietów", - "repeater_packetForwardingSubtitle": "Włącz repeater, aby przekazywać pakiety.", + "repeater_packetForwardingSubtitle": "Włącz powtarzacz, aby przekazywać pakiety.", "repeater_guestAccess": "Dostęp dla gości", "repeater_guestAccessSubtitle": "Umożliw dostęp tylko do odczytu dla gości.", - "repeater_privacyMode": "Tryb Prywatności", + "repeater_privacyMode": "Tryb prywatności", "repeater_privacyModeSubtitle": "Ukryj imię/lokalizację w rozgłoszeniach", "repeater_advertisementSettings": "Ustawienia rozgłoszeń", "repeater_localAdvertInterval": "Interwał rozgłoszenia lokalnego", @@ -1038,7 +1041,7 @@ "repeater_regenerateIdentityKeySubtitle": "Wygeneruj nową parę kluczy publicznych/prywatnych", "repeater_regenerateIdentityKeyConfirm": "Zostanie wygenerowana nowa tożsamość dla powtarzacza. Kontynuować?", "repeater_eraseFileSystem": "Wyczyść System Plików", - "repeater_eraseFileSystemSubtitle": "Sformatuj system plików powielacza", + "repeater_eraseFileSystemSubtitle": "Sformatuj system plików powtarzacza", "repeater_eraseFileSystemConfirm": "OSTRZEŻENIE: To spowoduje usunięcie wszystkich danych z powtarzacza. Nie da się tego cofnąć!", "repeater_eraseSerialOnly": "Usunięcie jest dostępne tylko przez konsolę szeregową.", "repeater_commandSent": "Polecenie wysłane: {command}", @@ -1068,13 +1071,13 @@ } }, "repeater_refreshBasicSettings": "Odśwież Podstawowe Ustawienia", - "repeater_refreshRadioSettings": "Odśwież Ustawienia Radio", + "repeater_refreshRadioSettings": "Odśwież ustawienia radia", "repeater_refreshTxPower": "Odśwież moc TX", "repeater_refreshLocationSettings": "Odśwież Ustawienia Lokalizacji", "repeater_refreshPacketForwarding": "Odśwież trasowanie pakietów", "repeater_refreshGuestAccess": "Odśwież dostęp gościa", "repeater_refreshPrivacyMode": "Odśwież Tryb Prywatności", - "repeater_refreshAdvertisementSettings": "Odśwież Ustawienia Reklamy", + "repeater_refreshAdvertisementSettings": "Odśwież ustawienia rozgłoszeń", "repeater_refreshed": "{label} odświeżone", "@repeater_refreshed": { "placeholders": { @@ -1101,7 +1104,7 @@ "repeater_previousCommand": "Poprzednia komenda", "repeater_nextCommand": "Następna komenda", "repeater_enterCommandFirst": "Wprowadź najpierw polecenie", - "repeater_cliCommandFrameTitle": "Określony Wyraz Polecenia CLI", + "repeater_cliCommandFrameTitle": "Ramka polecenia CLI", "repeater_cliCommandError": "Błąd: {error}", "@repeater_cliCommandError": { "placeholders": { @@ -1110,12 +1113,12 @@ } } }, - "repeater_cliQuickGetName": "Pobierz imię", - "repeater_cliQuickGetRadio": "Uzyskaj Radio", + "repeater_cliQuickGetName": "Pobierz nazwę", + "repeater_cliQuickGetRadio": "Pobierz radio", "repeater_cliQuickGetTx": "Pobierz TX", "repeater_cliQuickNeighbors": "Sąsiedzi", "repeater_cliQuickVersion": "Wersja", - "repeater_cliQuickAdvertise": "Reklama", + "repeater_cliQuickAdvertise": "Rozgłoś", "repeater_cliQuickClock": "Godzina", "repeater_cliHelpAdvert": "Wysyła pakiet rozgłoszeniowy", "repeater_cliHelpReboot": "Zresetuj urządzenie. (Uwaga, może pojawić się 'Timeout', co jest normalne)", @@ -1127,9 +1130,9 @@ "repeater_cliHelpSetTx": "Ustawia moc transmisji LoRa w dBm. (zrestartuj, aby zastosować)", "repeater_cliHelpSetRepeat": "Włącza lub wyłącza rolę powtarzacza dla tego węzła.", "repeater_cliHelpSetAllowReadOnly": "(Serwer pokoju) Jeśli 'włączone', to logowanie z pustym hasłem będzie dozwolone, ale nie można publikować w pokoju (tylko czytać).", - "repeater_cliHelpSetFloodMax": "Ustawia maksymalną liczbę skoków pakietu powrotnego (jeśli >= max, pakiet nie jest przekierowywany)", + "repeater_cliHelpSetFloodMax": "Ustawia maksymalną liczbę skoków pakietu zalewowego (jeśli >= max, pakiet nie jest przekierowywany)", "repeater_cliHelpSetIntThresh": "Ustawia Próg Interferencji (w dB). Domyślnie wynosi 14. Ustaw na 0, aby wyłączyć wykrywanie zakłóceń kanału.", - "repeater_cliHelpSetAgcResetInterval": "Ustawia interwał do zresetowania Automatycznego Sterownika Głośności. Ustaw na 0, aby wyłączyć.", + "repeater_cliHelpSetAgcResetInterval": "Ustawia interwał do zresetowania automatycznego wzmocnienia (AGC). Ustaw na 0, aby wyłączyć.", "repeater_cliHelpSetMultiAcks": "Włącza lub wyłącza funkcję 'podwójnych potwierdzeń'.", "repeater_cliHelpSetAdvertInterval": "Ustawia interwał timera w minutach do wysyłania pakietu rozgłoszenia lokalnego (bezpośredniego). Ustaw na 0, aby wyłączyć.", "repeater_cliHelpSetFloodAdvertInterval": "Ustawia interwał timera w godzinach do wysłania pakietu rozgłoszeniowego typu \"flood\". Ustaw na 0, aby wyłączyć.", @@ -1149,19 +1152,19 @@ "repeater_cliHelpSetAdcMultiplier": "Ustawia niestandardowy współczynnik do korekty zgłaszanego napięcia baterii (obsługa tylko na wybranych płytach).", "repeater_cliHelpTempRadio": "Ustawia tymczasowe parametry radia na podany czas trwania w minutach, a następnie powraca do oryginalnych parametrów radia. (nie zapisuje zmian w preferencjach).", "repeater_cliHelpSetPerm": "Modyfikuje ACL. Usuwa dopasowaną wpis (z prefiksem pubkey), jeśli \"permissions\" wynosi zero. Dodaje nowy wpis, jeśli pubkey-hex ma pełną długość i nie znajduje się obecnie w ACL. Aktualizuje wpis, dopasowując prefiks pubkey. Bit uprawnień zależy od roli firmware, ale dolne 2 bity to: 0 (Gość), 1 (tylko odczyt), 2 (odczyt i zapis), 3 (administrator).", - "repeater_cliHelpGetBridgeType": "Uzyskano typ mostu: brak, rs232, espnow", + "repeater_cliHelpGetBridgeType": "Pobiera typ mostka: brak, rs232, espnow", "repeater_cliHelpLogStart": "Rozpoczyna się logowanie pakietów do systemu plików.", "repeater_cliHelpLogStop": "Zatrzymuje logowanie pakietów do systemu plików.", "repeater_cliHelpLogErase": "Usuwa logi pakietów z systemu plików.", "repeater_cliHelpNeighbors": "Wyświetla listę innych węzłów powtarzających się, które usłyszano dzięki rozgłoszeniom zero-hop. Każda linia to: id-prefix-hex:timestamp:snr-times-4", "repeater_cliHelpNeighborRemove": "Usuwa pierwszy pasujący wpis (z prefiksem pubkey (hex)) z listy sąsiadów.", - "repeater_cliHelpRegion": "(tylko seria) Wyświetla wszystkie zdefiniowane regiony i aktualne uprawnienia do powodzi.", - "repeater_cliHelpRegionLoad": "ZAPOMNIJ: to jest specjalne wywołanie wielokomendowe. Każda następna komenda jest nazwą regionu (wcięta spacjami, aby wskazywać hierarchię nadrzędną, z minimum jedną spacją). Zakończona wysłaniem pustej linii/komendy.", + "repeater_cliHelpRegion": "(tylko port szeregowy) Wyświetla wszystkie zdefiniowane regiony i aktualne uprawnienia do zalewu.", + "repeater_cliHelpRegionLoad": "UWAGA: to jest specjalne wywołanie wielokomendowe. Każda następna komenda jest nazwą regionu (wcięta spacjami, aby wskazywać hierarchię nadrzędną, z minimum jedną spacją). Zakończona wysłaniem pustej linii/komendy.", "repeater_cliHelpRegionGet": "Wyszukuje region o podanej nazwie prefiksu (lub \"\" dla zakresu globalnego). Odpowiada \"-> region-name (parent-name) 'F'\"", "repeater_cliHelpRegionPut": "Dodaje lub aktualizuje definicję regionu z podaną nazwą.", "repeater_cliHelpRegionRemove": "Usuwa definicję regionu o podanej nazwie. (musi się dokładnie zgadzać i nie może mieć podregionów).", - "repeater_cliHelpRegionAllowf": "Ustawia uprawnienia 'P'łytkowe dla podanego regionu. ('' dla zakresu globalnego/starszego)", - "repeater_cliHelpRegionDenyf": "Usuwa uprawnienie 'Pływające' dla podanej strefy. (ZALECANE: na tym etapie NIE zaleca się używania tego na globalnym/starszym zakresie!!).", + "repeater_cliHelpRegionAllowf": "Ustawia uprawnienia 'F' (zalewowe) dla podanego regionu. ('' dla zakresu globalnego/starszego)", + "repeater_cliHelpRegionDenyf": "Usuwa uprawnienie 'F' (zalewowe) dla podanej strefy. (ZALECANE: na tym etapie NIE zaleca się używania tego na globalnym/starszym zakresie!!).", "repeater_cliHelpRegionHome": "Odpowiada z aktualnej 'home' region. (Uwaga: nie zostało jeszcze zastosowane, zarezerwowane na przyszłość).", "repeater_cliHelpRegionHomeSet": "Ustawia region 'domowe'.", "repeater_cliHelpRegionSave": "Zapisuje listę/mapę regionów do pamięci.", @@ -1172,7 +1175,7 @@ "repeater_cliHelpGpsAdvert": "Udostępnia konfigurację rozgłoszeń lokalizacji węzła:\n- brak: nie uwzględniaj lokalizacji w rozgłoszeniach\n- udostępnia: udostępnia lokalizację GPS (z SensorManager)\n- ustawienia: rozgłaszaj lokalizację przechowywaną w ustawieniach", "repeater_cliHelpGpsAdvertSet": "Ustawia konfigurację rozgłoszeń lokalizacji.", "repeater_commandsListTitle": "Lista poleceń", - "repeater_commandsListNote": "ZAPAMIĘTAJ: dla różnych poleceń \"set ...\" istnieje również polecenie \"get ...\".", + "repeater_commandsListNote": "UWAGA: dla różnych poleceń \"set ...\" istnieje również polecenie \"get ...\".", "repeater_general": "Ogólne", "repeater_settingsCategory": "Ustawienia", "repeater_bridge": "Most", @@ -1182,9 +1185,9 @@ "repeater_regionNote": "Wprowadzono komendy regionalne w celu zarządzania definicjami i uprawnieniami regionów.", "repeater_gpsManagement": "Zarządzanie GPS", "repeater_gpsNote": "Polecenie GPS zostało wprowadzone w celu zarządzania tematami związanymi z lokalizacją.", - "telemetry_receivedData": "Otrzymano Dane Telemetrii", - "telemetry_requestTimeout": "Życzenie o danych telemetrycznych nie udało się.", - "telemetry_errorLoading": "Błąd podczas ładowania telemetry: {error}", + "telemetry_receivedData": "Odebrane dane telemetrii", + "telemetry_requestTimeout": "Przekroczono czas oczekiwania na telemetrię.", + "telemetry_errorLoading": "Błąd podczas ładowania telemetrii: {error}", "@telemetry_errorLoading": { "placeholders": { "error": { @@ -1205,7 +1208,7 @@ "telemetry_voltageLabel": "Napięcie", "telemetry_mcuTemperatureLabel": "Temperatura MCU", "telemetry_temperatureLabel": "Temperatura", - "telemetry_currentLabel": "Obecny", + "telemetry_currentLabel": "Prąd", "telemetry_batteryValue": "{percent}% / {volts}V", "@telemetry_batteryValue": { "placeholders": { @@ -1217,7 +1220,7 @@ } } }, - "telemetry_voltageValue": "{volts}W", + "telemetry_voltageValue": "{volts}V", "@telemetry_voltageValue": { "placeholders": { "volts": { @@ -1246,8 +1249,8 @@ }, "channelPath_title": "Ścieżka pakietu", "channelPath_viewMap": "Wyświetl mapę", - "channelPath_otherObservedPaths": "Inne Zauważone Ścieżki", - "channelPath_repeaterHops": "Skoki Powtórki", + "channelPath_otherObservedPaths": "Inne zaobserwowane ścieżki", + "channelPath_repeaterHops": "Skoki powtarzaczy", "channelPath_noHopDetails": "Szczegóły dotyczące tego pakietu nie zostały podane.", "channelPath_messageDetails": "Szczegóły wiadomości", "channelPath_senderLabel": "Nadawca", @@ -1255,7 +1258,7 @@ "channelPath_repeatsLabel": "Powtórzenia", "channelPath_pathLabel": "Ścieżka {index}", "channelPath_observedLabel": "Obserwowane", - "channelPath_observedPathTitle": "Obserwowany ścieżka {index} • {hops}", + "channelPath_observedPathTitle": "Obserwowana ścieżka {index} • {hops}", "@channelPath_observedPathTitle": { "placeholders": { "index": { @@ -1312,7 +1315,7 @@ } }, "channelPath_mapTitle": "Mapa ścieżek", - "channelPath_noRepeaterLocations": "Brak dostępnych lokalizacji powtarzaczy dla tego ścieżki.", + "channelPath_noRepeaterLocations": "Brak dostępnych lokalizacji powtarzaczy dla tej ścieżki.", "channelPath_primaryPath": "Ścieżka {index} (Główna)", "@channelPath_primaryPath": { "placeholders": { From 35e296f1cd2396aac97b432ad8cc2a3b71a18d95 Mon Sep 17 00:00:00 2001 From: thesebas Date: Wed, 18 Feb 2026 20:02:20 +0100 Subject: [PATCH 19/36] Fix rebase merge error --- lib/l10n/app_localizations_pl.dart | 10 ++++++++++ lib/l10n/app_pl.arb | 13 +++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index f2dcda2..33737d3 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1622,6 +1622,16 @@ class AppLocalizationsPl extends AppLocalizations { @override String get map_showSharedMarkers => 'Pokaż udostępnione znaczniki.'; + @override + String get map_showGuessedLocations => + 'Pokaż przypuszczalne lokalizacje węzłów'; + + @override + String get map_showDiscoveryContacts => 'Pokaż odkryte kontakty'; + + @override + String get map_guessedLocation => 'Przypuszczalna lokalizacja'; + @override String get map_lastSeenTime => 'Ostatni raz widziany'; diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 5c66b65..9704a30 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -139,9 +139,6 @@ "settings_infoContactsCount": "Liczba kontaktów", "settings_infoChannelCount": "Liczba kanałów", "settings_presets": "Presety", - "settings_preset915Mhz": "915 MHz", - "settings_preset868Mhz": "868 MHz", - "settings_preset433Mhz": "433 MHz", "settings_frequency": "Częstotliwość (MHz)", "settings_frequencyHelper": "300,0 - 2500,0", "settings_frequencyInvalid": "Nieprawidłowa częstotliwość (300-2500 MHz)", @@ -1813,7 +1810,7 @@ "contactsSettings_autoAddRepeatersSubtitle": "Zezwól na automatyczne dodawanie odkrytych repeaterów przez towarzysza.", "contactsSettings_autoAddRoomServersTitle": "Automatycznie dodaj serwery pokojowe", "contactsSettings_autoAddUsersTitle": "Automatycznie dodaj użytkowników", - "settings_contactSettings": "Ustawienia kontaktowe", + "settings_contactSettings": "Ustawienia kontaktów", "contactsSettings_otherTitle": "Inne ustawienia związane z kontaktami", "contactsSettings_autoAddTitle": "Automatyczne odnajdywanie", "contactsSettings_autoAddRoomServersSubtitle": "Zezwól towarzyszowi na automatyczne dodawanie znalezionych serwerów pokojowych.", @@ -1831,8 +1828,8 @@ "common_deleteAll": "Usuń wszystko", "discoveredContacts_deleteContactAllContent": "Czy na pewno chcesz usunąć wszystkie znalezione kontakty?", "discoveredContacts_deleteContactAll": "Usuń wszystkie odkryte kontakty", - "map_guessedLocation": "Wydana lokalizacja", - "map_showGuessedLocations": "Wyświetl lokalizacje zgadanych węzłów", + "map_guessedLocation": "Przypuszczalna lokalizacja", + "map_showGuessedLocations": "Pokaż przypuszczalne lokalizacje węzłów", "usbScreenSubtitle": "Wybierz wykryty urządzenie szeregowe i podłącz je bezpośrednio do swojego węzła MeshCore.", "usbScreenTitle": "Połącz przez USB", "connectionChoiceUsbLabel": "USB", @@ -1891,8 +1888,8 @@ "tcpErrorUnsupported": "Transport protokoł TCP nie jest obsługiwany na tym urządzeniu.", "tcpErrorTimedOut": "Połączenie TCP zakończyło się bez powodzenia.", "tcpConnectionFailed": "Błąd połączenia TCP: {error}", - "map_showDiscoveryContacts": "Pokaż kontakty odkrywania", - "map_setAsMyLocation": "Ustaw jako moje lokalizację", + "map_showDiscoveryContacts": "Pokaż odkryte kontakty", + "map_setAsMyLocation": "Ustaw jako moją lokalizację", "@path_routeWeight": { "placeholders": { "weight": { From 53cd3f4461a179269e043c71df31dd67a6606cb8 Mon Sep 17 00:00:00 2001 From: thesebas Date: Mon, 16 Mar 2026 15:36:57 +0100 Subject: [PATCH 20/36] Some additional label adjustments --- lib/l10n/app_localizations_pl.dart | 69 +++++++++++++++--------------- lib/l10n/app_pl.arb | 68 ++++++++++++++--------------- 2 files changed, 69 insertions(+), 68 deletions(-) diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 33737d3..7b2183a 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -152,7 +152,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get tcpErrorUnsupported => - 'Transport protokoł TCP nie jest obsługiwany na tym urządzeniu.'; + 'Transport TCP nie jest obsługiwany na tej platformie.'; @override String get tcpErrorTimedOut => @@ -168,7 +168,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get usbScreenSubtitle => - 'Wybierz wykryty urządzenie szeregowe i podłącz je bezpośrednio do swojego węzła MeshCore.'; + 'Wybierz wykryte urządzenie szeregowe i połącz się bezpośrednio ze swoim węzłem MeshCore.'; @override String get usbScreenStatus => 'Wybierz urządzenie USB'; @@ -469,7 +469,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get settings_aboutDescription => - 'Otwarty kod źródłowy klient Flutter dla urządzeń do sieci mesh LoRa MeshCore.'; + 'Otwartoźródłowy klient Flutter dla urządzeń MeshCore LoRa do sieci mesh.'; @override String get settings_aboutOpenMeteoAttribution => @@ -800,7 +800,7 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get appSettings_mapTimeFilter => 'Filtrowanie Czasu Mapy'; + String get appSettings_mapTimeFilter => 'Filtr czasu mapy'; @override String get appSettings_showNodesDiscoveredWithin => 'Pokaż węzły odkryte w:'; @@ -833,7 +833,7 @@ class AppLocalizationsPl extends AppLocalizations { String get appSettings_unitsImperial => 'Imperialne (ft / mi)'; @override - String get appSettings_noAreaSelected => 'Nie zaznaczono żadnej powierzchni.'; + String get appSettings_noAreaSelected => 'Nie wybrano żadnego obszaru.'; @override String appSettings_areaSelectedZoom(int minZoom, int maxZoom) { @@ -848,7 +848,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get appSettings_appDebugLoggingSubtitle => - 'Loguj wiadomości debugowania aplikacji w celu rozwiązywania problemów.'; + 'Rejestruj komunikaty debugowania aplikacji w celu diagnozowania problemów.'; @override String get appSettings_appDebugLoggingEnabled => @@ -911,7 +911,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String contacts_removeConfirm(String contactName) { - return 'Usuń $contactName z kontaktów?'; + return 'Usunąć $contactName z kontaktów?'; } @override @@ -1009,7 +1009,7 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get channels_hashtagChannel => 'Kanał z hashtagami'; + String get channels_hashtagChannel => 'Kanał hashtagów'; @override String get channels_public => 'Publiczny'; @@ -1075,7 +1075,8 @@ class AppLocalizationsPl extends AppLocalizations { String get channels_enterChannelName => 'Proszę podać nazwę kanału.'; @override - String get channels_pskMustBe32Hex => 'PSK musi mieć 32 znaki szesnastkowe.'; + String get channels_pskMustBe32Hex => + 'PSK musi składać się z 32 znaków szesnastkowych.'; @override String channels_channelAdded(String name) { @@ -1280,7 +1281,7 @@ class AppLocalizationsPl extends AppLocalizations { String get debugLog_rawLogRx => 'Surowy log RX'; @override - String get debugLog_noBleActivity => 'Brak aktywności BLE jeszcze.'; + String get debugLog_noBleActivity => 'Brak aktywności BLE.'; @override String debugFrame_length(int count) { @@ -1302,7 +1303,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String debugFrame_timestamp(int timestamp) { - return '- Timestamp: $timestamp'; + return '- Znacznik czasu: $timestamp'; } @override @@ -1379,7 +1380,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get chat_noPathHistoryYet => - 'Brak jeszcze historii ścieżek.\nWyślij wiadomość, aby odkryć ścieżki.'; + 'Brak historii ścieżek.\nWyślij wiadomość, aby odkryć ścieżki.'; @override String get chat_pathActions => 'Działania ścieżki:'; @@ -1407,7 +1408,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get chat_floodModeEnabled => - 'Tryb powodziowy włączony. Włącz ponownie za pomocą ikony routingu w pasku narzędzi.'; + 'Tryb zalewowy włączony. Przełącz z powrotem ikoną routingu w pasku aplikacji.'; @override String get chat_fullPath => 'Pełna ścieżka'; @@ -1523,7 +1524,7 @@ class AppLocalizationsPl extends AppLocalizations { String get map_chat => 'Rozmowa'; @override - String get map_repeater => 'Powtórzacz'; + String get map_repeater => 'Powtarzacz'; @override String get map_room => 'Pokój'; @@ -1657,22 +1658,22 @@ class AppLocalizationsPl extends AppLocalizations { String get map_pathTraceCancelled => 'Śledzenie ścieżki anulowano.'; @override - String get mapCache_title => 'Bufor Map Offline'; + String get mapCache_title => 'Pamięć podręczna map offline'; @override String get mapCache_selectAreaFirst => - 'Wybierz obszar do wstępnego pobrania.'; + 'Najpierw wybierz obszar do zapisania w pamięci podręcznej.'; @override String get mapCache_noTilesToDownload => - 'Brak dostępnych płytek do pobrania dla tego obszaru.'; + 'Brak kafelków do pobrania dla tego obszaru.'; @override - String get mapCache_downloadTilesTitle => 'Pobierz płytki'; + String get mapCache_downloadTilesTitle => 'Pobierz kafelki'; @override String mapCache_downloadTilesPrompt(int count) { - return 'Pobierz $count płytek do użytku offline?'; + return 'Pobrać $count kafelków do użytku offline?'; } @override @@ -1680,12 +1681,12 @@ class AppLocalizationsPl extends AppLocalizations { @override String mapCache_cachedTiles(int count) { - return 'Zapisano $count płytek w pamięci podręcznej'; + return 'Zapisano w pamięci podręcznej $count kafelków'; } @override String mapCache_cachedTilesWithFailed(int downloaded, int failed) { - return 'Zapisano $downloaded płytek ($failed nieudane)'; + return 'Zapisano w pamięci podręcznej $downloaded kafelków ($failed nieudanych)'; } @override @@ -1694,14 +1695,14 @@ class AppLocalizationsPl extends AppLocalizations { @override String get mapCache_clearOfflineCachePrompt => - 'Usuń wszystkie tymczasowe kafelki mapy?'; + 'Usunąć wszystkie zapisane kafelki mapy?'; @override String get mapCache_offlineCacheCleared => - 'Pamięć podręczna offline została wyczyszczona'; + 'Wyczyszczono pamięć podręczną offline'; @override - String get mapCache_noAreaSelected => 'Nie zaznaczono żadnej powierzchni.'; + String get mapCache_noAreaSelected => 'Nie wybrano żadnego obszaru.'; @override String get mapCache_cacheArea => 'Obszar pamięci podręcznej'; @@ -1710,11 +1711,11 @@ class AppLocalizationsPl extends AppLocalizations { String get mapCache_useCurrentView => 'Użyj aktualnego widoku'; @override - String get mapCache_zoomRange => 'Zakres powiększenia'; + String get mapCache_zoomRange => 'Zakres przybliżenia'; @override String mapCache_estimatedTiles(int count) { - return 'Szacunkowa liczba płytek: $count'; + return 'Szacowana liczba kafelków: $count'; } @override @@ -1819,7 +1820,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get login_repeaterDescription => - 'Wprowadź hasło do powtarzacza, aby uzyskać dostęp do ustawień i statusu.'; + 'Wprowadź hasło do powtarzacza, aby uzyskać dostęp do ustawień i stanu.'; @override String get login_roomDescription => @@ -1855,7 +1856,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get login_failedMessage => - 'Logowanie nie powiodło się. Hasło jest nieprawidłowe albo repeater jest nieosiągalny.'; + 'Logowanie nie powiodło się. Hasło jest nieprawidłowe albo powtarzacz jest nieosiągalny.'; @override String get common_reload => 'Odśwież'; @@ -1907,7 +1908,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get path_noRepeatersFound => - 'Nie znaleziono repeaterów ani serwerów pokoi.'; + 'Nie znaleziono powtarzaczy ani serwerów pokoi.'; @override String get path_customPathsRequire => @@ -2080,7 +2081,7 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_basicSettings => 'Podstawowe Ustawienia'; @override - String get repeater_repeaterName => 'Nazwa Powtórnika'; + String get repeater_repeaterName => 'Nazwa powtarzacza'; @override String get repeater_repeaterNameHelper => 'Wyświetl nazwę tego powtarzacza'; @@ -2404,7 +2405,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_cliHelpSetGuestPassword => - 'Ustawia/aktualizuje hasło gościa. (dla repeaterów, loginy gości mogą wysyłać żądanie \"Get Stats\")'; + 'Ustawia/aktualizuje hasło gościa. (dla powtarzaczy loginy gości mogą wysyłać żądanie \"Get Stats\")'; @override String get repeater_cliHelpSetName => 'Ustawia nazwę rozgłoszenia.'; @@ -3152,10 +3153,10 @@ class AppLocalizationsPl extends AppLocalizations { String get contacts_ping => 'Pingować'; @override - String get contacts_repeaterPathTrace => 'Śledzenie ścieżki do repeatera'; + String get contacts_repeaterPathTrace => 'Śledzenie ścieżki do powtarzacza'; @override - String get contacts_repeaterPing => 'Repeater pingowy'; + String get contacts_repeaterPing => 'Ping powtarzacza'; @override String get contacts_roomPathTrace => @@ -3353,7 +3354,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get contactsSettings_autoAddRepeatersSubtitle => - 'Zezwól na automatyczne dodawanie odkrytych repeaterów przez towarzysza.'; + 'Zezwól towarzyszowi na automatyczne dodawanie odkrytych powtarzaczy.'; @override String get contactsSettings_autoAddRoomServersTitle => diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 9704a30..3911ace 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -130,7 +130,7 @@ } }, "settings_aboutLegalese": "Projekt MeshCore Open Source 2026", - "settings_aboutDescription": "Otwarty kod źródłowy klient Flutter dla urządzeń do sieci mesh LoRa MeshCore.", + "settings_aboutDescription": "Otwartoźródłowy klient Flutter dla urządzeń MeshCore LoRa do sieci mesh.", "settings_infoName": "Nazwa", "settings_infoId": "ID", "settings_infoStatus": "Status", @@ -229,7 +229,7 @@ } } }, - "appSettings_mapTimeFilter": "Filtrowanie Czasu Mapy", + "appSettings_mapTimeFilter": "Filtr czasu mapy", "appSettings_showNodesDiscoveredWithin": "Pokaż węzły odkryte w:", "appSettings_allTime": "Cały czas", "appSettings_lastHour": "Ostatnia godzina", @@ -237,7 +237,7 @@ "appSettings_last24Hours": "Ostatnie 24 godziny", "appSettings_lastWeek": "Ostatni tydzień", "appSettings_offlineMapCache": "Pamięć podręczna map offline", - "appSettings_noAreaSelected": "Nie zaznaczono żadnej powierzchni.", + "appSettings_noAreaSelected": "Nie wybrano żadnego obszaru.", "appSettings_areaSelectedZoom": "Wybrany obszar (skala {minZoom}-{maxZoom})", "@appSettings_areaSelectedZoom": { "placeholders": { @@ -251,7 +251,7 @@ }, "appSettings_debugCard": "Debug", "appSettings_appDebugLogging": "Logowanie Debugowania Aplikacji", - "appSettings_appDebugLoggingSubtitle": "Loguj wiadomości debugowania aplikacji w celu rozwiązywania problemów.", + "appSettings_appDebugLoggingSubtitle": "Rejestruj komunikaty debugowania aplikacji w celu diagnozowania problemów.", "appSettings_appDebugLoggingEnabled": "Logowanie debugowania aplikacji włączone", "appSettings_appDebugLoggingDisabled": "Logowanie debugowania aplikacji wyłączone.", "contacts_title": "Kontakty", @@ -261,7 +261,7 @@ "contacts_noUnreadContacts": "Brak nieprzeczytanych kontaktów", "contacts_noContactsFound": "Brak znalezionych kontaktów ani grup.", "contacts_deleteContact": "Usuń Kontakt", - "contacts_removeConfirm": "Usuń {contactName} z kontaktów?", + "contacts_removeConfirm": "Usunąć {contactName} z kontaktów?", "@contacts_removeConfirm": { "placeholders": { "contactName": { @@ -337,7 +337,7 @@ } } }, - "channels_hashtagChannel": "Kanał z hashtagami", + "channels_hashtagChannel": "Kanał hashtagów", "channels_public": "Publiczny", "channels_private": "Prywatny", "channels_publicChannel": "Kanał publiczny", @@ -370,7 +370,7 @@ "channels_pskHex": "PSK (Hex)", "channels_generateRandomPsk": "Wygeneruj losowy klucz PSK", "channels_enterChannelName": "Proszę podać nazwę kanału.", - "channels_pskMustBe32Hex": "PSK musi mieć 32 znaki szesnastkowe.", + "channels_pskMustBe32Hex": "PSK musi składać się z 32 znaków szesnastkowych.", "channels_channelAdded": "Kanał \"{name}\" dodany", "@channels_channelAdded": { "placeholders": { @@ -478,7 +478,7 @@ "debugLog_enableInSettings": "Włącz logowanie debugowania aplikacji w ustawieniach", "debugLog_frames": "Ramki", "debugLog_rawLogRx": "Surowy log RX", - "debugLog_noBleActivity": "Brak aktywności BLE jeszcze.", + "debugLog_noBleActivity": "Brak aktywności BLE.", "debugFrame_length": "Długość ramy: {count} bajtów", "@debugFrame_length": { "placeholders": { @@ -504,7 +504,7 @@ } } }, - "debugFrame_timestamp": "- Timestamp: {timestamp}", + "debugFrame_timestamp": "- Znacznik czasu: {timestamp}", "@debugFrame_timestamp": { "placeholders": { "timestamp": { @@ -560,7 +560,7 @@ }, "chat_successes": "Sukcesy", "chat_removePath": "Usuń ścieżkę", - "chat_noPathHistoryYet": "Brak jeszcze historii ścieżek.\nWyślij wiadomość, aby odkryć ścieżki.", + "chat_noPathHistoryYet": "Brak historii ścieżek.\nWyślij wiadomość, aby odkryć ścieżki.", "chat_pathActions": "Działania ścieżki:", "chat_setCustomPath": "Ustaw ścieżkę niestandardową", "chat_setCustomPathSubtitle": "Ręcznie określ trasę.", @@ -568,7 +568,7 @@ "chat_clearPathSubtitle": "Wymuś ponowne wyznaczenie trasy przy następnym wysłaniu", "chat_pathCleared": "Ścieżka wyczyszczona. Następna wiadomość odnajdzie trasę.", "chat_floodModeSubtitle": "Użyj przełącznika routingu w pasku narzędzi.", - "chat_floodModeEnabled": "Tryb powodziowy włączony. Włącz ponownie za pomocą ikony routingu w pasku narzędzi.", + "chat_floodModeEnabled": "Tryb zalewowy włączony. Przełącz z powrotem ikoną routingu w pasku aplikacji.", "chat_fullPath": "Pełna ścieżka", "chat_pathDetailsNotAvailable": "Szczegóły ścieżki jeszcze niedostępne. Spróbuj wysłać wiadomość, aby odświeżyć.", "chat_pathSetHops": "Ścieżka ustawiona: {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}", @@ -642,7 +642,7 @@ } }, "map_chat": "Rozmowa", - "map_repeater": "Powtórzacz", + "map_repeater": "Powtarzacz", "map_room": "Pokój", "map_sensor": "Czujnik", "map_pinDm": "Pinezka (DM)", @@ -684,11 +684,11 @@ "map_sharedPin": "Udostępniona pinezka", "map_joinRoom": "Dołącz do pokoju", "map_manageRepeater": "Zarządzaj powtarzaczem", - "mapCache_title": "Bufor Map Offline", - "mapCache_selectAreaFirst": "Wybierz obszar do wstępnego pobrania.", - "mapCache_noTilesToDownload": "Brak dostępnych płytek do pobrania dla tego obszaru.", - "mapCache_downloadTilesTitle": "Pobierz płytki", - "mapCache_downloadTilesPrompt": "Pobierz {count} płytek do użytku offline?", + "mapCache_title": "Pamięć podręczna map offline", + "mapCache_selectAreaFirst": "Najpierw wybierz obszar do zapisania w pamięci podręcznej.", + "mapCache_noTilesToDownload": "Brak kafelków do pobrania dla tego obszaru.", + "mapCache_downloadTilesTitle": "Pobierz kafelki", + "mapCache_downloadTilesPrompt": "Pobrać {count} kafelków do użytku offline?", "@mapCache_downloadTilesPrompt": { "placeholders": { "count": { @@ -697,7 +697,7 @@ } }, "mapCache_downloadAction": "Pobierz", - "mapCache_cachedTiles": "Zapisano {count} płytek w pamięci podręcznej", + "mapCache_cachedTiles": "Zapisano w pamięci podręcznej {count} kafelków", "@mapCache_cachedTiles": { "placeholders": { "count": { @@ -705,7 +705,7 @@ } } }, - "mapCache_cachedTilesWithFailed": "Zapisano {downloaded} płytek ({failed} nieudane)", + "mapCache_cachedTilesWithFailed": "Zapisano w pamięci podręcznej {downloaded} kafelków ({failed} nieudanych)", "@mapCache_cachedTilesWithFailed": { "placeholders": { "downloaded": { @@ -717,13 +717,13 @@ } }, "mapCache_clearOfflineCacheTitle": "Wyczyść pamięć podręczną offline", - "mapCache_clearOfflineCachePrompt": "Usuń wszystkie tymczasowe kafelki mapy?", - "mapCache_offlineCacheCleared": "Pamięć podręczna offline została wyczyszczona", - "mapCache_noAreaSelected": "Nie zaznaczono żadnej powierzchni.", + "mapCache_clearOfflineCachePrompt": "Usunąć wszystkie zapisane kafelki mapy?", + "mapCache_offlineCacheCleared": "Wyczyszczono pamięć podręczną offline", + "mapCache_noAreaSelected": "Nie wybrano żadnego obszaru.", "mapCache_cacheArea": "Obszar pamięci podręcznej", "mapCache_useCurrentView": "Użyj aktualnego widoku", - "mapCache_zoomRange": "Zakres powiększenia", - "mapCache_estimatedTiles": "Szacunkowa liczba płytek: {count}", + "mapCache_zoomRange": "Zakres przybliżenia", + "mapCache_estimatedTiles": "Szacowana liczba kafelków: {count}", "@mapCache_estimatedTiles": { "placeholders": { "count": { @@ -812,7 +812,7 @@ "login_enterPassword": "Wprowadź hasło", "login_savePassword": "Zapisz hasło", "login_savePasswordSubtitle": "Hasło będzie bezpiecznie przechowywane na tym urządzeniu.", - "login_repeaterDescription": "Wprowadź hasło do powtarzacza, aby uzyskać dostęp do ustawień i statusu.", + "login_repeaterDescription": "Wprowadź hasło do powtarzacza, aby uzyskać dostęp do ustawień i stanu.", "login_roomDescription": "Wprowadź hasło do pokoju, aby uzyskać dostęp do ustawień i statusu.", "login_routing": "Trasowanie", "login_routingMode": "Tryb routingu", @@ -839,7 +839,7 @@ } } }, - "login_failedMessage": "Logowanie nie powiodło się. Hasło jest nieprawidłowe albo repeater jest nieosiągalny.", + "login_failedMessage": "Logowanie nie powiodło się. Hasło jest nieprawidłowe albo powtarzacz jest nieosiągalny.", "common_reload": "Odśwież", "common_clear": "Wyczyść", "path_currentPath": "Aktualna ścieżka: {path}", @@ -865,7 +865,7 @@ "path_labelHexPrefixes": "Ścieżka (prefiksy hex)", "path_helperMaxHops": "Maksymalnie 64 skoki. Każdy prefiks ma 2 znaki szesnastkowe (1 bajt).", "path_selectFromContacts": "Albo wybierz z kontaktów:", - "path_noRepeatersFound": "Nie znaleziono repeaterów ani serwerów pokoi.", + "path_noRepeatersFound": "Nie znaleziono powtarzaczy ani serwerów pokoi.", "path_customPathsRequire": "Dostosowane ścieżki wymagają pośrednich skoków, które mogą przekazywać wiadomości.", "path_invalidHexPrefixes": "Nieprawidłowe prefiksy szesnastkowe: {prefixes}", "@path_invalidHexPrefixes": { @@ -984,7 +984,7 @@ }, "repeater_settingsTitle": "Ustawienia powtarzacza", "repeater_basicSettings": "Podstawowe Ustawienia", - "repeater_repeaterName": "Nazwa Powtórnika", + "repeater_repeaterName": "Nazwa powtarzacza", "repeater_repeaterNameHelper": "Wyświetl nazwę tego powtarzacza", "repeater_adminPassword": "Hasło Administracyjne", "repeater_adminPasswordHelper": "Hasło z pełnym dostępem", @@ -1133,7 +1133,7 @@ "repeater_cliHelpSetMultiAcks": "Włącza lub wyłącza funkcję 'podwójnych potwierdzeń'.", "repeater_cliHelpSetAdvertInterval": "Ustawia interwał timera w minutach do wysyłania pakietu rozgłoszenia lokalnego (bezpośredniego). Ustaw na 0, aby wyłączyć.", "repeater_cliHelpSetFloodAdvertInterval": "Ustawia interwał timera w godzinach do wysłania pakietu rozgłoszeniowego typu \"flood\". Ustaw na 0, aby wyłączyć.", - "repeater_cliHelpSetGuestPassword": "Ustawia/aktualizuje hasło gościa. (dla repeaterów, loginy gości mogą wysyłać żądanie \"Get Stats\")", + "repeater_cliHelpSetGuestPassword": "Ustawia/aktualizuje hasło gościa. (dla powtarzaczy loginy gości mogą wysyłać żądanie \"Get Stats\")", "repeater_cliHelpSetName": "Ustawia nazwę rozgłoszenia.", "repeater_cliHelpSetLat": "Ustawia współrzędną geograficzną (w stopniach dziesiętnych) mapy rozgłoszeń.", "repeater_cliHelpSetLon": "Ustawia współrzędną długościową mapy rozgłoszeń. (stopnie dziesiętne)", @@ -1552,11 +1552,11 @@ "pathTrace_notAvailable": "Ścieżka śledzenia niedostępna.", "contacts_pathTrace": "Śledzenie Ścieżek", "contacts_ping": "Pingować", - "contacts_repeaterPathTrace": "Śledzenie ścieżki do repeatera", + "contacts_repeaterPathTrace": "Śledzenie ścieżki do powtarzacza", "contacts_roomPathTrace": "Śledzenie ścieżki do serwera pokojowego", "contacts_roomPing": "Pinguj serwer pokoju", "pathTrace_refreshTooltip": "Odśwież ścieżkę.", - "contacts_repeaterPing": "Repeater pingowy", + "contacts_repeaterPing": "Ping powtarzacza", "contacts_pathTraceTo": "Śledź trasę do {name}", "contacts_chatTraceRoute": "Śledź trasę promienia", "appSettings_languageRu": "Rosyjski", @@ -1807,7 +1807,7 @@ "settings_contactSettingsSubtitle": "Ustawienia dotyczące sposobu dodawania kontaktów", "contactsSettings_autoAddUsersSubtitle": "Pozwól towarzyszowi automatycznie dodawać znalezione użytkowników.", "contactsSettings_autoAddRepeatersTitle": "Automatyczne dodawanie powtarzalników", - "contactsSettings_autoAddRepeatersSubtitle": "Zezwól na automatyczne dodawanie odkrytych repeaterów przez towarzysza.", + "contactsSettings_autoAddRepeatersSubtitle": "Zezwól towarzyszowi na automatyczne dodawanie odkrytych powtarzaczy.", "contactsSettings_autoAddRoomServersTitle": "Automatycznie dodaj serwery pokojowe", "contactsSettings_autoAddUsersTitle": "Automatycznie dodaj użytkowników", "settings_contactSettings": "Ustawienia kontaktów", @@ -1830,7 +1830,7 @@ "discoveredContacts_deleteContactAll": "Usuń wszystkie odkryte kontakty", "map_guessedLocation": "Przypuszczalna lokalizacja", "map_showGuessedLocations": "Pokaż przypuszczalne lokalizacje węzłów", - "usbScreenSubtitle": "Wybierz wykryty urządzenie szeregowe i podłącz je bezpośrednio do swojego węzła MeshCore.", + "usbScreenSubtitle": "Wybierz wykryte urządzenie szeregowe i połącz się bezpośrednio ze swoim węzłem MeshCore.", "usbScreenTitle": "Połącz przez USB", "connectionChoiceUsbLabel": "USB", "connectionChoiceBluetoothLabel": "Bluetooth", @@ -1885,7 +1885,7 @@ "tcpStatus_connectingTo": "Połączenie z {endpoint}...", "tcpErrorHostRequired": "Wymagana jest adresa IP.", "tcpErrorPortInvalid": "Numer portu musi mieścić się w zakresie od 1 do 65535.", - "tcpErrorUnsupported": "Transport protokoł TCP nie jest obsługiwany na tym urządzeniu.", + "tcpErrorUnsupported": "Transport TCP nie jest obsługiwany na tej platformie.", "tcpErrorTimedOut": "Połączenie TCP zakończyło się bez powodzenia.", "tcpConnectionFailed": "Błąd połączenia TCP: {error}", "map_showDiscoveryContacts": "Pokaż odkryte kontakty", From dc57f9b9c0543d7bc611600e453b3925437b603e Mon Sep 17 00:00:00 2001 From: thesebas Date: Thu, 19 Mar 2026 09:56:00 +0100 Subject: [PATCH 21/36] fix missing labels --- lib/l10n/app_localizations_pl.dart | 2 +- lib/l10n/app_pl.arb | 40 +++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 7b2183a..44c9d1f 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -876,7 +876,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String contacts_searchContacts(int number, String str) { - return 'Wyszukaj kontakty...'; + return 'Wyszukaj $number$str kontakty...'; } @override diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 3911ace..dd96934 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -257,7 +257,17 @@ "contacts_title": "Kontakty", "contacts_noContacts": "Brak jeszcze kontaktów.", "contacts_contactsWillAppear": "Kontakty będą wyświetlane, gdy urządzenia nadają rozgłoszenia.", - "contacts_searchContacts": "Wyszukaj kontakty...", + "contacts_searchContacts": "Wyszukaj {number}{str} kontakty...", + "@contacts_searchContacts": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, "contacts_noUnreadContacts": "Brak nieprzeczytanych kontaktów", "contacts_noContactsFound": "Brak znalezionych kontaktów ani grup.", "contacts_deleteContact": "Usuń Kontakt", @@ -1579,9 +1589,37 @@ "contacts_zeroHopContactAdvertFailed": "Nie udało się wysłać kontaktu.", "notification_activityTitle": "Aktywność MeshCore", "notification_messagesCount": "{count} {count, plural, =1{wiadomość} few{wiadomości} many{wiadomości} other{wiadomości}}", + "@notification_messagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, "notification_channelMessagesCount": "{count} {count, plural, =1{wiadomość kanału} few{wiadomości kanału} many{wiadomości kanału} other{wiadomości kanału}}", + "@notification_channelMessagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, "notification_newNodesCount": "{count} {count, plural, =1{nowy węzeł} few{nowe węzły} many{nowych węzłów} other{nowych węzłów}}", + "@notification_newNodesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, "notification_newTypeDiscovered": "Nowy {contactType} wykryty", + "@notification_newTypeDiscovered": { + "placeholders": { + "contactType": { + "type": "String" + } + } + }, "notification_receivedNewMessage": "Otrzymano nową wiadomość", "settings_gpxExportContacts": "Eksportuj towarzyszy do GPX", "settings_gpxExportRepeaters": "Eksportuj powtórki / serwer pokojowy do GPX", From 5140ff383d6544298f12305ad87f760bdd06687a Mon Sep 17 00:00:00 2001 From: thesebas Date: Thu, 19 Mar 2026 10:03:16 +0100 Subject: [PATCH 22/36] fix plural form of the label --- lib/l10n/app_localizations_pl.dart | 10 +++++++++- lib/l10n/app_pl.arb | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 44c9d1f..0dd44af 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -876,7 +876,15 @@ class AppLocalizationsPl extends AppLocalizations { @override String contacts_searchContacts(int number, String str) { - return 'Wyszukaj $number$str kontakty...'; + String _temp0 = intl.Intl.pluralLogic( + number, + locale: localeName, + other: 'kontaktu', + many: 'kontaktów', + few: 'kontakty', + one: 'kontakt', + ); + return 'Wyszukaj $number$str $_temp0...'; } @override diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index dd96934..fb3b33e 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -257,7 +257,7 @@ "contacts_title": "Kontakty", "contacts_noContacts": "Brak jeszcze kontaktów.", "contacts_contactsWillAppear": "Kontakty będą wyświetlane, gdy urządzenia nadają rozgłoszenia.", - "contacts_searchContacts": "Wyszukaj {number}{str} kontakty...", + "contacts_searchContacts": "Wyszukaj {number}{str} {number, plural, one{kontakt} few{kontakty} many{kontaktów} other{kontaktu}}...", "@contacts_searchContacts": { "placeholders": { "number": { From bd030153c12da511f6f934c073c7f578f6d213ca Mon Sep 17 00:00:00 2001 From: thesebas Date: Sun, 22 Mar 2026 21:58:36 +0100 Subject: [PATCH 23/36] update new labels --- lib/l10n/app_localizations_pl.dart | 22 +++++++++++----------- lib/l10n/app_pl.arb | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 0dd44af..85c1767 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -698,42 +698,42 @@ class AppLocalizationsPl extends AppLocalizations { 'Automatyczne obracanie tras wyłączone'; @override - String get appSettings_maxRouteWeight => - 'Maksymalny dopuszczalny ciężar pojazdu'; + String get appSettings_maxRouteWeight => 'Maksymalna waga ścieżki'; @override String get appSettings_maxRouteWeightSubtitle => - 'Maksymalna waga, jaką ścieżka może zgromadzić dzięki udanym dostawom.'; + 'Maksymalna waga, jaką ścieżka może osiągnąć dzięki udanym dostarczeniom'; @override - String get appSettings_initialRouteWeight => 'Początkowa waga trasy'; + String get appSettings_initialRouteWeight => 'Początkowa waga ścieżki'; @override String get appSettings_initialRouteWeightSubtitle => - 'Początkowa waga dla nowych, odkrytych ścieżek'; + 'Waga początkowa dla nowo odkrytych ścieżek'; @override - String get appSettings_routeWeightSuccessIncrement => 'Wzrost wagi sukcesu'; + String get appSettings_routeWeightSuccessIncrement => + 'Przyrost wagi po sukcesie'; @override String get appSettings_routeWeightSuccessIncrementSubtitle => - 'Waga dodana do ścieżki po pomyślnym dostarczeniu'; + 'Waga dodawana do ścieżki po udanym dostarczeniu'; @override String get appSettings_routeWeightFailureDecrement => - 'Zmniejszenie wagi kary'; + 'Spadek wagi po niepowodzeniu'; @override String get appSettings_routeWeightFailureDecrementSubtitle => - 'Waga usunięta z trasy po nieudanej dostawie'; + 'Waga odejmowana od ścieżki po nieudanym dostarczeniu'; @override String get appSettings_maxMessageRetries => - 'Maksymalna liczba prób wysłania wiadomości'; + 'Maksymalna liczba ponowień wiadomości'; @override String get appSettings_maxMessageRetriesSubtitle => - 'Liczba prób ponownego wysłania wiadomości przed oznaczaniem jej jako nieudanej'; + 'Liczba prób ponowienia przed oznaczeniem wiadomości jako nieudanej'; @override String path_routeWeight(String weight, String max) { diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index fb3b33e..3ce551d 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1938,15 +1938,15 @@ } } }, - "appSettings_initialRouteWeight": "Początkowa waga trasy", - "appSettings_maxRouteWeight": "Maksymalny dopuszczalny ciężar pojazdu", - "appSettings_initialRouteWeightSubtitle": "Początkowa waga dla nowych, odkrytych ścieżek", - "appSettings_maxRouteWeightSubtitle": "Maksymalna waga, jaką ścieżka może zgromadzić dzięki udanym dostawom.", - "appSettings_routeWeightSuccessIncrement": "Wzrost wagi sukcesu", - "appSettings_routeWeightSuccessIncrementSubtitle": "Waga dodana do ścieżki po pomyślnym dostarczeniu", - "appSettings_routeWeightFailureDecrement": "Zmniejszenie wagi kary", - "appSettings_routeWeightFailureDecrementSubtitle": "Waga usunięta z trasy po nieudanej dostawie", - "appSettings_maxMessageRetries": "Maksymalna liczba prób wysłania wiadomości", - "appSettings_maxMessageRetriesSubtitle": "Liczba prób ponownego wysłania wiadomości przed oznaczaniem jej jako nieudanej", + "appSettings_initialRouteWeight": "Początkowa waga ścieżki", + "appSettings_maxRouteWeight": "Maksymalna waga ścieżki", + "appSettings_initialRouteWeightSubtitle": "Waga początkowa dla nowo odkrytych ścieżek", + "appSettings_maxRouteWeightSubtitle": "Maksymalna waga, jaką ścieżka może osiągnąć dzięki udanym dostarczeniom", + "appSettings_routeWeightSuccessIncrement": "Przyrost wagi po sukcesie", + "appSettings_routeWeightSuccessIncrementSubtitle": "Waga dodawana do ścieżki po udanym dostarczeniu", + "appSettings_routeWeightFailureDecrement": "Spadek wagi po niepowodzeniu", + "appSettings_routeWeightFailureDecrementSubtitle": "Waga odejmowana od ścieżki po nieudanym dostarczeniu", + "appSettings_maxMessageRetries": "Maksymalna liczba ponowień wiadomości", + "appSettings_maxMessageRetriesSubtitle": "Liczba prób ponowienia przed oznaczeniem wiadomości jako nieudanej", "path_routeWeight": "{weight}/{max}" } From 630606acdcca1f0a71334590e54541deb6f696d1 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Mon, 23 Mar 2026 08:14:46 -0700 Subject: [PATCH 24/36] Update byte skipping logic and improve clarity in MeshCoreConnector and ChannelMessage --- lib/connector/meshcore_connector.dart | 3 ++- lib/models/channel_message.dart | 2 +- lib/services/message_retry_service.dart | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 5dc660e..04e9583 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -4323,7 +4323,8 @@ class MeshCoreConnector extends ChangeNotifier { routeType == _routeTransportFlood || routeType == _routeTransportDirect; if (hasTransport) { - reader.skipBytes(2); // Skip reserved bytes in transport header + // Skip reserved bytes in transport header made up of two u16 fields + reader.skipBytes(4); } final pathLenRaw = reader.readByte(); final pathByteLen = _decodePathByteLen(pathLenRaw); diff --git a/lib/models/channel_message.dart b/lib/models/channel_message.dart index 98a8f1d..0f0a5b4 100644 --- a/lib/models/channel_message.dart +++ b/lib/models/channel_message.dart @@ -129,11 +129,11 @@ class ChannelMessage { if (code == respCodeChannelMsgRecvV3) { reader.skipBytes(1); // Skip SNR final flags = reader.readByte(); + final hasPath = (flags & 0x01) != 0; reader.skipBytes(1); // Skip reserved byte channelIdx = reader.readByte(); pathLen = reader.readByte(); txtType = reader.readByte(); - final hasPath = (flags & 0x01) != 0; if (hasPath && pathLen > 0) { reader.rewind(); // Rewind to read path length again for pathBytes pathBytes = reader.readBytes(pathLen); diff --git a/lib/services/message_retry_service.dart b/lib/services/message_retry_service.dart index 94f3caf..c8e89aa 100644 --- a/lib/services/message_retry_service.dart +++ b/lib/services/message_retry_service.dart @@ -678,7 +678,7 @@ class MessageRetryService extends ChangeNotifier { } } - String? getContactKeyForAckHash(Uint8List ackHash) { + String? getContactKeyForAckHash(int ackHash) { for (var entry in _pendingMessages.entries) { final message = entry.value; if (message.expectedAckHash != null && From 58252b8a40dcbd1e0377515f3c118309c0f7dcc4 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Mon, 23 Mar 2026 10:14:30 -0700 Subject: [PATCH 25/36] fix: Correct return type of _manualAckHash and improve hash computation --- test/services/retry_and_protocol_test.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/services/retry_and_protocol_test.dart b/test/services/retry_and_protocol_test.dart index 48d4cfb..b6c53b6 100644 --- a/test/services/retry_and_protocol_test.dart +++ b/test/services/retry_and_protocol_test.dart @@ -14,7 +14,7 @@ import 'package:meshcore_open/services/message_retry_service.dart'; /// Replicates the SHA-256 computation from [MessageRetryService.computeExpectedAckHash] /// so tests can cross-check without calling the real implementation twice. -Uint8List _manualAckHash( +int _manualAckHash( int timestampSeconds, int attemptMasked, // already masked to 0x03 String text, @@ -35,7 +35,8 @@ Uint8List _manualAckHash( buffer.setRange(offset, offset + senderPubKey.length, senderPubKey); final hash = sha256.convert(buffer); - return Uint8List.fromList(hash.bytes.sublist(0, 4)); + final bytes = Uint8List.fromList(hash.bytes.sublist(0, 4)); + return (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0]; } Uint8List _makeKey(int seed) { From 7eff1df6e21c8e24c0738c07ccbc8b4c47babc09 Mon Sep 17 00:00:00 2001 From: thesebas Date: Mon, 23 Mar 2026 18:47:18 +0100 Subject: [PATCH 26/36] use correct word for repeater --- lib/l10n/app_localizations_pl.dart | 87 +++++++++++++++--------------- lib/l10n/app_pl.arb | 86 ++++++++++++++--------------- 2 files changed, 86 insertions(+), 87 deletions(-) diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 85c1767..06efab3 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -768,11 +768,11 @@ class AppLocalizationsPl extends AppLocalizations { String get appSettings_mapDisplay => 'Wyświetlanie mapy'; @override - String get appSettings_showRepeaters => 'Pokaż powtarzacze'; + String get appSettings_showRepeaters => 'Pokaż przekaźniki'; @override String get appSettings_showRepeatersSubtitle => - 'Wyświetl węzły powtarzaczy na mapie'; + 'Wyświetl węzły przekaźników na mapie'; @override String get appSettings_showChatNodes => 'Pokaż Węzły Rozmowy'; @@ -899,7 +899,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String contacts_searchRepeaters(int number, String str) { - return 'Wyszukaj $number$str powtórników...'; + return 'Wyszukaj $number$str przekaźników...'; } @override @@ -923,7 +923,7 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get contacts_manageRepeater => 'Zarządzaj powtarzaczem'; + String get contacts_manageRepeater => 'Zarządzaj przekaźnikiem'; @override String get contacts_manageRoom => 'Zarządzaj Serwerem Pokoju'; @@ -1532,7 +1532,7 @@ class AppLocalizationsPl extends AppLocalizations { String get map_chat => 'Rozmowa'; @override - String get map_repeater => 'Powtarzacz'; + String get map_repeater => 'Przekaźnik'; @override String get map_room => 'Pokój'; @@ -1611,7 +1611,7 @@ class AppLocalizationsPl extends AppLocalizations { String get map_chatNodes => 'Węzły czatu'; @override - String get map_repeaters => 'Powtarzacze'; + String get map_repeaters => 'Przekaźniki'; @override String get map_otherNodes => 'Inne węzły'; @@ -1651,7 +1651,7 @@ class AppLocalizationsPl extends AppLocalizations { String get map_joinRoom => 'Dołącz do pokoju'; @override - String get map_manageRepeater => 'Zarządzaj powtarzaczem'; + String get map_manageRepeater => 'Zarządzaj przekaźnikiem'; @override String get map_tapToAdd => 'Kliknij na węzły, aby dodać je do ścieżki.'; @@ -1808,7 +1808,7 @@ class AppLocalizationsPl extends AppLocalizations { 'Czy na pewno chcesz się odłączyć od tego urządzenia?'; @override - String get login_repeaterLogin => 'Logowanie do powtarzacza'; + String get login_repeaterLogin => 'Logowanie do przekaźnika'; @override String get login_roomLogin => 'Logowanie do pokoju'; @@ -1828,7 +1828,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get login_repeaterDescription => - 'Wprowadź hasło do powtarzacza, aby uzyskać dostęp do ustawień i stanu.'; + 'Wprowadź hasło do przekaźnika, aby uzyskać dostęp do ustawień i stanu.'; @override String get login_roomDescription => @@ -1864,7 +1864,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get login_failedMessage => - 'Logowanie nie powiodło się. Hasło jest nieprawidłowe albo powtarzacz jest nieosiągalny.'; + 'Logowanie nie powiodło się. Hasło jest nieprawidłowe albo przekaźnik jest nieosiągalny.'; @override String get common_reload => 'Odśwież'; @@ -1916,7 +1916,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get path_noRepeatersFound => - 'Nie znaleziono powtarzaczy ani serwerów pokoi.'; + 'Nie znaleziono przekaźników ani serwerów pokoi.'; @override String get path_customPathsRequire => @@ -1935,7 +1935,7 @@ class AppLocalizationsPl extends AppLocalizations { String get path_setPath => 'Ustaw Ścieżkę'; @override - String get repeater_management => 'Zarządzanie powtarzaczami'; + String get repeater_management => 'Zarządzanie przekaźnikami'; @override String get room_management => 'Zarządzanie Serwerem Pokoju'; @@ -1948,7 +1948,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_statusSubtitle => - 'Wyświetl status powtarzacza, statystyki i sąsiadów.'; + 'Wyświetl status przekaźnika, statystyki i sąsiadów.'; @override String get repeater_telemetry => 'Telemetria'; @@ -1961,7 +1961,7 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_cli => 'CLI'; @override - String get repeater_cliSubtitle => 'Wyślij polecenia do powtarzacza'; + String get repeater_cliSubtitle => 'Wyślij polecenia do przekaźnika'; @override String get repeater_neighbors => 'Sąsiedzi'; @@ -1974,10 +1974,10 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_settings => 'Ustawienia'; @override - String get repeater_settingsSubtitle => 'Skonfiguruj parametry powtarzacza'; + String get repeater_settingsSubtitle => 'Skonfiguruj parametry przekaźnika'; @override - String get repeater_statusTitle => 'Status powtarzacza'; + String get repeater_statusTitle => 'Status przekaźnika'; @override String get repeater_routingMode => 'Tryb routingu'; @@ -2083,16 +2083,16 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get repeater_settingsTitle => 'Ustawienia powtarzacza'; + String get repeater_settingsTitle => 'Ustawienia przekaźnika'; @override String get repeater_basicSettings => 'Podstawowe Ustawienia'; @override - String get repeater_repeaterName => 'Nazwa powtarzacza'; + String get repeater_repeaterName => 'Nazwa przekaźnika'; @override - String get repeater_repeaterNameHelper => 'Wyświetl nazwę tego powtarzacza'; + String get repeater_repeaterNameHelper => 'Wyświetl nazwę tego przekaźnika'; @override String get repeater_adminPassword => 'Hasło Administracyjne'; @@ -2153,7 +2153,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_packetForwardingSubtitle => - 'Włącz powtarzacz, aby przekazywać pakiety.'; + 'Włącz przekaźnik, aby przekazywać pakiety.'; @override String get repeater_guestAccess => 'Dostęp dla gości'; @@ -2196,15 +2196,14 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_dangerZone => 'Strefa Zagrożeń'; @override - String get repeater_rebootRepeater => 'Zrestartuj Powtarzacz'; + String get repeater_rebootRepeater => 'Zrestartuj Przekaźnik'; @override - String get repeater_rebootRepeaterSubtitle => - 'Zrestartuj urządzenie powtarzające.'; + String get repeater_rebootRepeaterSubtitle => 'Zrestartuj przekaźnik.'; @override String get repeater_rebootRepeaterConfirm => - 'Czy na pewno chcesz zrestartować ten powtarzacz?'; + 'Czy na pewno chcesz zrestartować ten przekaźnik?'; @override String get repeater_regenerateIdentityKey => 'Wygeneruj klucz tożsamości'; @@ -2215,18 +2214,18 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_regenerateIdentityKeyConfirm => - 'Zostanie wygenerowana nowa tożsamość dla powtarzacza. Kontynuować?'; + 'Zostanie wygenerowana nowa tożsamość dla przekaźnika. Kontynuować?'; @override String get repeater_eraseFileSystem => 'Wyczyść System Plików'; @override String get repeater_eraseFileSystemSubtitle => - 'Sformatuj system plików powtarzacza'; + 'Sformatuj system plików przekaźnika'; @override String get repeater_eraseFileSystemConfirm => - 'OSTRZEŻENIE: To spowoduje usunięcie wszystkich danych z powtarzacza. Nie da się tego cofnąć!'; + 'OSTRZEŻENIE: To spowoduje usunięcie wszystkich danych z przekaźnika. Nie da się tego cofnąć!'; @override String get repeater_eraseSerialOnly => @@ -2290,7 +2289,7 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get repeater_cliTitle => 'Powtarzacz CLI'; + String get repeater_cliTitle => 'Przekaźnik CLI'; @override String get repeater_debugNextCommand => 'Debug Następną Komendę'; @@ -2381,7 +2380,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_cliHelpSetRepeat => - 'Włącza lub wyłącza rolę powtarzacza dla tego węzła.'; + 'Włącza lub wyłącza rolę przekaźnika dla tego węzła.'; @override String get repeater_cliHelpSetAllowReadOnly => @@ -2413,7 +2412,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_cliHelpSetGuestPassword => - 'Ustawia/aktualizuje hasło gościa. (dla powtarzaczy loginy gości mogą wysyłać żądanie \"Get Stats\")'; + 'Ustawia/aktualizuje hasło gościa. (dla przekaźników loginy gości mogą wysyłać żądanie \"Get Stats\")'; @override String get repeater_cliHelpSetName => 'Ustawia nazwę rozgłoszenia.'; @@ -2579,11 +2578,11 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_logging => 'Rejestrowanie'; @override - String get repeater_neighborsRepeaterOnly => 'Sąsiedzi (tylko powtarzacz)'; + String get repeater_neighborsRepeaterOnly => 'Sąsiedzi (tylko przekaźnik)'; @override String get repeater_regionManagementRepeaterOnly => - 'Zarządzanie Regionem (tylko Powtarzacz)'; + 'Zarządzanie Regionem (tylko Przekaźnik)'; @override String get repeater_regionNote => @@ -2664,7 +2663,7 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get neighbors_repeatersNeighbors => 'Powtarzacze Sąsiedzi'; + String get neighbors_repeatersNeighbors => 'Przekaźniki Sąsiedzi'; @override String get neighbors_noData => 'Brak danych dotyczących sąsiadów.'; @@ -2689,7 +2688,7 @@ class AppLocalizationsPl extends AppLocalizations { String get channelPath_otherObservedPaths => 'Inne zaobserwowane ścieżki'; @override - String get channelPath_repeaterHops => 'Skoki powtarzaczy'; + String get channelPath_repeaterHops => 'Skoki przekaźników'; @override String get channelPath_noHopDetails => @@ -2757,7 +2756,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get channelPath_noRepeaterLocations => - 'Brak dostępnych lokalizacji powtarzaczy dla tej ścieżki.'; + 'Brak dostępnych lokalizacji przekaźników dla tej ścieżki.'; @override String channelPath_primaryPath(int index) { @@ -2780,7 +2779,7 @@ class AppLocalizationsPl extends AppLocalizations { 'Brak dostępnych szczegółów hopa dla tego pakietu.'; @override - String get channelPath_unknownRepeater => 'Nieznany Powtarzacz'; + String get channelPath_unknownRepeater => 'Nieznany Przekaźnik'; @override String get community_title => 'Społeczność'; @@ -2986,7 +2985,7 @@ class AppLocalizationsPl extends AppLocalizations { String get listFilter_users => 'Użytkownicy'; @override - String get listFilter_repeaters => 'Powtarzacze'; + String get listFilter_repeaters => 'Przekaźniki'; @override String get listFilter_roomServers => 'Serwery pokoju'; @@ -3161,10 +3160,10 @@ class AppLocalizationsPl extends AppLocalizations { String get contacts_ping => 'Pingować'; @override - String get contacts_repeaterPathTrace => 'Śledzenie ścieżki do powtarzacza'; + String get contacts_repeaterPathTrace => 'Śledzenie ścieżki do przekaźnika'; @override - String get contacts_repeaterPing => 'Ping powtarzacza'; + String get contacts_repeaterPing => 'Ping przekaźnika'; @override String get contacts_roomPathTrace => @@ -3280,11 +3279,11 @@ class AppLocalizationsPl extends AppLocalizations { @override String get settings_gpxExportRepeaters => - 'Eksportuj powtórki / serwer pokojowy do GPX'; + 'Eksportuj przekaźniki / serwer pokojowy do GPX'; @override String get settings_gpxExportRepeatersSubtitle => - 'Eksportuje powtarzacze / roomserver z lokalizacją do pliku GPX.'; + 'Eksportuje przekaźniki / roomserver z lokalizacją do pliku GPX.'; @override String get settings_gpxExportContacts => 'Eksportuj towarzyszy do GPX'; @@ -3316,7 +3315,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get settings_gpxExportRepeatersRoom => - 'Lokalizacje serwerów powtarzających i pomieszczeń'; + 'Lokalizacje przekaźników i serwerów pokojowych'; @override String get settings_gpxExportChat => 'Lokalizacje towarzyszy'; @@ -3358,11 +3357,11 @@ class AppLocalizationsPl extends AppLocalizations { @override String get contactsSettings_autoAddRepeatersTitle => - 'Automatyczne dodawanie powtarzalników'; + 'Automatyczne dodawanie przekaźników'; @override String get contactsSettings_autoAddRepeatersSubtitle => - 'Zezwól towarzyszowi na automatyczne dodawanie odkrytych powtarzaczy.'; + 'Zezwól towarzyszowi na automatyczne dodawanie odkrytych przekaźników.'; @override String get contactsSettings_autoAddRoomServersTitle => diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 3ce551d..20a444b 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -213,8 +213,8 @@ "appSettings_batteryLifepo4": "LiFePO4 (2,6-3,65 V)", "appSettings_batteryLipo": "LiPo (3,0-4,2V)", "appSettings_mapDisplay": "Wyświetlanie mapy", - "appSettings_showRepeaters": "Pokaż powtarzacze", - "appSettings_showRepeatersSubtitle": "Wyświetl węzły powtarzaczy na mapie", + "appSettings_showRepeaters": "Pokaż przekaźniki", + "appSettings_showRepeatersSubtitle": "Wyświetl węzły przekaźników na mapie", "appSettings_showChatNodes": "Pokaż Węzły Rozmowy", "appSettings_showChatNodesSubtitle": "Wyświetl węzły czatu na mapie", "appSettings_showOtherNodes": "Pokaż inne węzły", @@ -279,7 +279,7 @@ } } }, - "contacts_manageRepeater": "Zarządzaj powtarzaczem", + "contacts_manageRepeater": "Zarządzaj przekaźnikiem", "contacts_roomLogin": "Logowanie do pokoju", "contacts_openChat": "Otwórz czat", "contacts_editGroup": "Edytuj Grupę", @@ -652,7 +652,7 @@ } }, "map_chat": "Rozmowa", - "map_repeater": "Powtarzacz", + "map_repeater": "Przekaźnik", "map_room": "Pokój", "map_sensor": "Czujnik", "map_pinDm": "Pinezka (DM)", @@ -683,7 +683,7 @@ "map_filterNodes": "Filtruj Węzły", "map_nodeTypes": "Typy węzłów", "map_chatNodes": "Węzły czatu", - "map_repeaters": "Powtarzacze", + "map_repeaters": "Przekaźniki", "map_otherNodes": "Inne węzły", "map_keyPrefix": "Prefiks klucza", "map_filterByKeyPrefix": "Filtruj po prefiksie klucza", @@ -693,7 +693,7 @@ "map_lastSeenTime": "Ostatni raz widziany", "map_sharedPin": "Udostępniona pinezka", "map_joinRoom": "Dołącz do pokoju", - "map_manageRepeater": "Zarządzaj powtarzaczem", + "map_manageRepeater": "Zarządzaj przekaźnikiem", "mapCache_title": "Pamięć podręczna map offline", "mapCache_selectAreaFirst": "Najpierw wybierz obszar do zapisania w pamięci podręcznej.", "mapCache_noTilesToDownload": "Brak kafelków do pobrania dla tego obszaru.", @@ -816,13 +816,13 @@ "time_allTime": "Cały czas", "dialog_disconnect": "Odłącz", "dialog_disconnectConfirm": "Czy na pewno chcesz się odłączyć od tego urządzenia?", - "login_repeaterLogin": "Logowanie do powtarzacza", + "login_repeaterLogin": "Logowanie do przekaźnika", "login_roomLogin": "Logowanie do pokoju", "login_password": "Hasło", "login_enterPassword": "Wprowadź hasło", "login_savePassword": "Zapisz hasło", "login_savePasswordSubtitle": "Hasło będzie bezpiecznie przechowywane na tym urządzeniu.", - "login_repeaterDescription": "Wprowadź hasło do powtarzacza, aby uzyskać dostęp do ustawień i stanu.", + "login_repeaterDescription": "Wprowadź hasło do przekaźnika, aby uzyskać dostęp do ustawień i stanu.", "login_roomDescription": "Wprowadź hasło do pokoju, aby uzyskać dostęp do ustawień i statusu.", "login_routing": "Trasowanie", "login_routingMode": "Tryb routingu", @@ -849,7 +849,7 @@ } } }, - "login_failedMessage": "Logowanie nie powiodło się. Hasło jest nieprawidłowe albo powtarzacz jest nieosiągalny.", + "login_failedMessage": "Logowanie nie powiodło się. Hasło jest nieprawidłowe albo przekaźnik jest nieosiągalny.", "common_reload": "Odśwież", "common_clear": "Wyczyść", "path_currentPath": "Aktualna ścieżka: {path}", @@ -875,7 +875,7 @@ "path_labelHexPrefixes": "Ścieżka (prefiksy hex)", "path_helperMaxHops": "Maksymalnie 64 skoki. Każdy prefiks ma 2 znaki szesnastkowe (1 bajt).", "path_selectFromContacts": "Albo wybierz z kontaktów:", - "path_noRepeatersFound": "Nie znaleziono powtarzaczy ani serwerów pokoi.", + "path_noRepeatersFound": "Nie znaleziono przekaźników ani serwerów pokoi.", "path_customPathsRequire": "Dostosowane ścieżki wymagają pośrednich skoków, które mogą przekazywać wiadomości.", "path_invalidHexPrefixes": "Nieprawidłowe prefiksy szesnastkowe: {prefixes}", "@path_invalidHexPrefixes": { @@ -887,17 +887,17 @@ }, "path_tooLong": "Ścieżka jest zbyt długa. Dozwolonych skoków wynosi 64.", "path_setPath": "Ustaw Ścieżkę", - "repeater_management": "Zarządzanie powtarzaczami", + "repeater_management": "Zarządzanie przekaźnikami", "repeater_managementTools": "Narzędzia Zarządzania", "repeater_status": "Status", - "repeater_statusSubtitle": "Wyświetl status powtarzacza, statystyki i sąsiadów.", + "repeater_statusSubtitle": "Wyświetl status przekaźnika, statystyki i sąsiadów.", "repeater_telemetry": "Telemetria", "repeater_telemetrySubtitle": "Wyświetl dane telemetryczne z czujników i statystyki systemu", "repeater_cli": "CLI", - "repeater_cliSubtitle": "Wyślij polecenia do powtarzacza", + "repeater_cliSubtitle": "Wyślij polecenia do przekaźnika", "repeater_settings": "Ustawienia", - "repeater_settingsSubtitle": "Skonfiguruj parametry powtarzacza", - "repeater_statusTitle": "Status powtarzacza", + "repeater_settingsSubtitle": "Skonfiguruj parametry przekaźnika", + "repeater_statusTitle": "Status przekaźnika", "repeater_routingMode": "Tryb routingu", "repeater_autoUseSavedPath": "Automatycznie (użyj zapisanej ścieżki)", "repeater_forceFloodMode": "Wymuś tryb zalewowy", @@ -992,10 +992,10 @@ } } }, - "repeater_settingsTitle": "Ustawienia powtarzacza", + "repeater_settingsTitle": "Ustawienia przekaźnika", "repeater_basicSettings": "Podstawowe Ustawienia", - "repeater_repeaterName": "Nazwa powtarzacza", - "repeater_repeaterNameHelper": "Wyświetl nazwę tego powtarzacza", + "repeater_repeaterName": "Nazwa przekaźnika", + "repeater_repeaterNameHelper": "Wyświetl nazwę tego przekaźnika", "repeater_adminPassword": "Hasło Administracyjne", "repeater_adminPasswordHelper": "Hasło z pełnym dostępem", "repeater_guestPassword": "Hasło gościa", @@ -1015,7 +1015,7 @@ "repeater_longitudeHelper": "Stopnie dziesiętne (np. -122,4194)", "repeater_features": "Funkcje", "repeater_packetForwarding": "Przekierowanie pakietów", - "repeater_packetForwardingSubtitle": "Włącz powtarzacz, aby przekazywać pakiety.", + "repeater_packetForwardingSubtitle": "Włącz przekaźnik, aby przekazywać pakiety.", "repeater_guestAccess": "Dostęp dla gości", "repeater_guestAccessSubtitle": "Umożliw dostęp tylko do odczytu dla gości.", "repeater_privacyMode": "Tryb prywatności", @@ -1041,15 +1041,15 @@ }, "repeater_encryptedAdvertInterval": "Interwał Zaszyfrowanego Rozgłoszenia", "repeater_dangerZone": "Strefa Zagrożeń", - "repeater_rebootRepeater": "Zrestartuj Powtarzacz", - "repeater_rebootRepeaterSubtitle": "Zrestartuj urządzenie powtarzające.", - "repeater_rebootRepeaterConfirm": "Czy na pewno chcesz zrestartować ten powtarzacz?", + "repeater_rebootRepeater": "Zrestartuj Przekaźnik", + "repeater_rebootRepeaterSubtitle": "Zrestartuj przekaźnik.", + "repeater_rebootRepeaterConfirm": "Czy na pewno chcesz zrestartować ten przekaźnik?", "repeater_regenerateIdentityKey": "Wygeneruj klucz tożsamości", "repeater_regenerateIdentityKeySubtitle": "Wygeneruj nową parę kluczy publicznych/prywatnych", - "repeater_regenerateIdentityKeyConfirm": "Zostanie wygenerowana nowa tożsamość dla powtarzacza. Kontynuować?", + "repeater_regenerateIdentityKeyConfirm": "Zostanie wygenerowana nowa tożsamość dla przekaźnika. Kontynuować?", "repeater_eraseFileSystem": "Wyczyść System Plików", - "repeater_eraseFileSystemSubtitle": "Sformatuj system plików powtarzacza", - "repeater_eraseFileSystemConfirm": "OSTRZEŻENIE: To spowoduje usunięcie wszystkich danych z powtarzacza. Nie da się tego cofnąć!", + "repeater_eraseFileSystemSubtitle": "Sformatuj system plików przekaźnika", + "repeater_eraseFileSystemConfirm": "OSTRZEŻENIE: To spowoduje usunięcie wszystkich danych z przekaźnika. Nie da się tego cofnąć!", "repeater_eraseSerialOnly": "Usunięcie jest dostępne tylko przez konsolę szeregową.", "repeater_commandSent": "Polecenie wysłane: {command}", "@repeater_commandSent": { @@ -1101,7 +1101,7 @@ } } }, - "repeater_cliTitle": "Powtarzacz CLI", + "repeater_cliTitle": "Przekaźnik CLI", "repeater_debugNextCommand": "Debug Następną Komendę", "repeater_commandHelp": "Pomoc", "repeater_clearHistory": "Wyczyść historię", @@ -1135,7 +1135,7 @@ "repeater_cliHelpClearStats": "Resetuje różne wskaźniki statystyk do zera.", "repeater_cliHelpSetAf": "Ustawia czynnik czasu powietrznego.", "repeater_cliHelpSetTx": "Ustawia moc transmisji LoRa w dBm. (zrestartuj, aby zastosować)", - "repeater_cliHelpSetRepeat": "Włącza lub wyłącza rolę powtarzacza dla tego węzła.", + "repeater_cliHelpSetRepeat": "Włącza lub wyłącza rolę przekaźnika dla tego węzła.", "repeater_cliHelpSetAllowReadOnly": "(Serwer pokoju) Jeśli 'włączone', to logowanie z pustym hasłem będzie dozwolone, ale nie można publikować w pokoju (tylko czytać).", "repeater_cliHelpSetFloodMax": "Ustawia maksymalną liczbę skoków pakietu zalewowego (jeśli >= max, pakiet nie jest przekierowywany)", "repeater_cliHelpSetIntThresh": "Ustawia Próg Interferencji (w dB). Domyślnie wynosi 14. Ustaw na 0, aby wyłączyć wykrywanie zakłóceń kanału.", @@ -1143,7 +1143,7 @@ "repeater_cliHelpSetMultiAcks": "Włącza lub wyłącza funkcję 'podwójnych potwierdzeń'.", "repeater_cliHelpSetAdvertInterval": "Ustawia interwał timera w minutach do wysyłania pakietu rozgłoszenia lokalnego (bezpośredniego). Ustaw na 0, aby wyłączyć.", "repeater_cliHelpSetFloodAdvertInterval": "Ustawia interwał timera w godzinach do wysłania pakietu rozgłoszeniowego typu \"flood\". Ustaw na 0, aby wyłączyć.", - "repeater_cliHelpSetGuestPassword": "Ustawia/aktualizuje hasło gościa. (dla powtarzaczy loginy gości mogą wysyłać żądanie \"Get Stats\")", + "repeater_cliHelpSetGuestPassword": "Ustawia/aktualizuje hasło gościa. (dla przekaźników loginy gości mogą wysyłać żądanie \"Get Stats\")", "repeater_cliHelpSetName": "Ustawia nazwę rozgłoszenia.", "repeater_cliHelpSetLat": "Ustawia współrzędną geograficzną (w stopniach dziesiętnych) mapy rozgłoszeń.", "repeater_cliHelpSetLon": "Ustawia współrzędną długościową mapy rozgłoszeń. (stopnie dziesiętne)", @@ -1187,8 +1187,8 @@ "repeater_settingsCategory": "Ustawienia", "repeater_bridge": "Most", "repeater_logging": "Rejestrowanie", - "repeater_neighborsRepeaterOnly": "Sąsiedzi (tylko powtarzacz)", - "repeater_regionManagementRepeaterOnly": "Zarządzanie Regionem (tylko Powtarzacz)", + "repeater_neighborsRepeaterOnly": "Sąsiedzi (tylko przekaźnik)", + "repeater_regionManagementRepeaterOnly": "Zarządzanie Regionem (tylko Przekaźnik)", "repeater_regionNote": "Wprowadzono komendy regionalne w celu zarządzania definicjami i uprawnieniami regionów.", "repeater_gpsManagement": "Zarządzanie GPS", "repeater_gpsNote": "Polecenie GPS zostało wprowadzone w celu zarządzania tematami związanymi z lokalizacją.", @@ -1257,7 +1257,7 @@ "channelPath_title": "Ścieżka pakietu", "channelPath_viewMap": "Wyświetl mapę", "channelPath_otherObservedPaths": "Inne zaobserwowane ścieżki", - "channelPath_repeaterHops": "Skoki powtarzaczy", + "channelPath_repeaterHops": "Skoki przekaźników", "channelPath_noHopDetails": "Szczegóły dotyczące tego pakietu nie zostały podane.", "channelPath_messageDetails": "Szczegóły wiadomości", "channelPath_senderLabel": "Nadawca", @@ -1322,7 +1322,7 @@ } }, "channelPath_mapTitle": "Mapa ścieżek", - "channelPath_noRepeaterLocations": "Brak dostępnych lokalizacji powtarzaczy dla tej ścieżki.", + "channelPath_noRepeaterLocations": "Brak dostępnych lokalizacji przekaźników dla tej ścieżki.", "channelPath_primaryPath": "Ścieżka {index} (Główna)", "@channelPath_primaryPath": { "placeholders": { @@ -1352,7 +1352,7 @@ } }, "channelPath_noHopDetailsAvailable": "Brak dostępnych szczegółów hopa dla tego pakietu.", - "channelPath_unknownRepeater": "Nieznany Powtarzacz", + "channelPath_unknownRepeater": "Nieznany Przekaźnik", "listFilter_tooltip": "Filtruj i sortuj", "listFilter_sortBy": "Sortuj po", "listFilter_latestMessages": "Najnowsze wiadomości", @@ -1361,7 +1361,7 @@ "listFilter_filters": "Filtry", "listFilter_all": "Wszystko", "listFilter_users": "Użytkownicy", - "listFilter_repeaters": "Powtarzacze", + "listFilter_repeaters": "Przekaźniki", "listFilter_roomServers": "Serwery pokoju", "listFilter_unreadOnly": "Tylko nieprzeczytane", "listFilter_newGroup": "Nowa grupa", @@ -1377,7 +1377,7 @@ "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_repeatersNeighbors": "Powtarzacze Sąsiedzi", + "neighbors_repeatersNeighbors": "Przekaźniki Sąsiedzi", "neighbors_noData": "Brak danych dotyczących sąsiadów.", "channels_joinPrivateChannelDesc": "Ręcznie wprowadź klucz tajny.", "channels_createPrivateChannel": "Utwórz Prywatny Kanał", @@ -1562,11 +1562,11 @@ "pathTrace_notAvailable": "Ścieżka śledzenia niedostępna.", "contacts_pathTrace": "Śledzenie Ścieżek", "contacts_ping": "Pingować", - "contacts_repeaterPathTrace": "Śledzenie ścieżki do powtarzacza", + "contacts_repeaterPathTrace": "Śledzenie ścieżki do przekaźnika", "contacts_roomPathTrace": "Śledzenie ścieżki do serwera pokojowego", "contacts_roomPing": "Pinguj serwer pokoju", "pathTrace_refreshTooltip": "Odśwież ścieżkę.", - "contacts_repeaterPing": "Ping powtarzacza", + "contacts_repeaterPing": "Ping przekaźnika", "contacts_pathTraceTo": "Śledź trasę do {name}", "contacts_chatTraceRoute": "Śledź trasę promienia", "appSettings_languageRu": "Rosyjski", @@ -1622,12 +1622,12 @@ }, "notification_receivedNewMessage": "Otrzymano nową wiadomość", "settings_gpxExportContacts": "Eksportuj towarzyszy do GPX", - "settings_gpxExportRepeaters": "Eksportuj powtórki / serwer pokojowy do GPX", - "settings_gpxExportRepeatersSubtitle": "Eksportuje powtarzacze / roomserver z lokalizacją do pliku GPX.", + "settings_gpxExportRepeaters": "Eksportuj przekaźniki / serwer pokojowy do GPX", + "settings_gpxExportRepeatersSubtitle": "Eksportuje przekaźniki / roomserver z lokalizacją do pliku GPX.", "settings_gpxExportSuccess": "Pomyślnie wyeksportowano plik GPX.", "settings_gpxExportNotAvailable": "Nie obsługiwane na Twoim urządzeniu/systemie operacyjnym", "settings_gpxExportError": "Wystąpił błąd podczas eksportowania.", - "settings_gpxExportRepeatersRoom": "Lokalizacje serwerów powtarzających i pomieszczeń", + "settings_gpxExportRepeatersRoom": "Lokalizacje przekaźników i serwerów pokojowych", "settings_gpxExportContactsSubtitle": "Eksportuje towarzyszy z lokalizacją do pliku GPX.", "settings_gpxExportAll": "Eksportuj wszystkie kontakty do GPX", "settings_gpxExportAllSubtitle": "Eksportuje wszystkie kontakty z lokalizacją do pliku GPX.", @@ -1840,12 +1840,12 @@ "contacts_searchFavorites": "Wyszukaj {number}{str} ulubione...", "contacts_searchRoomServers": "Wyszukaj {number}{str} serwerów Room...", "contacts_searchUsers": "Wyszukaj {number}{str} Użytkowników...", - "contacts_searchRepeaters": "Wyszukaj {number}{str} powtórników...", + "contacts_searchRepeaters": "Wyszukaj {number}{str} przekaźników...", "contactsSettings_title": "Ustawienia kontaktów", "settings_contactSettingsSubtitle": "Ustawienia dotyczące sposobu dodawania kontaktów", "contactsSettings_autoAddUsersSubtitle": "Pozwól towarzyszowi automatycznie dodawać znalezione użytkowników.", - "contactsSettings_autoAddRepeatersTitle": "Automatyczne dodawanie powtarzalników", - "contactsSettings_autoAddRepeatersSubtitle": "Zezwól towarzyszowi na automatyczne dodawanie odkrytych powtarzaczy.", + "contactsSettings_autoAddRepeatersTitle": "Automatyczne dodawanie przekaźników", + "contactsSettings_autoAddRepeatersSubtitle": "Zezwól towarzyszowi na automatyczne dodawanie odkrytych przekaźników.", "contactsSettings_autoAddRoomServersTitle": "Automatycznie dodaj serwery pokojowe", "contactsSettings_autoAddUsersTitle": "Automatycznie dodaj użytkowników", "settings_contactSettings": "Ustawienia kontaktów", From fc7283f07663ed8742a8df76c335f0b8a61e3e4d Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Mon, 23 Mar 2026 11:18:37 -0700 Subject: [PATCH 27/36] Update lib/l10n/app_bg.arb Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/l10n/app_bg.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 13788b8..545ec9d 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -1892,7 +1892,7 @@ "map_setAsMyLocation": "Задайте като моя местоположение", "@path_routeWeight": { "placeholders": { - "value": { + "weight": { "type": "String" }, "max": { From 0228c3862165bf91a3243f2b80572445674e91f9 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Mon, 23 Mar 2026 11:24:33 -0700 Subject: [PATCH 28/36] fix: Update battery voltage reading and adjust path length handling in ChannelMessage --- lib/connector/meshcore_connector.dart | 2 +- lib/models/channel_message.dart | 6 ++---- lib/screens/settings_screen.dart | 1 - 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 04e9583..ee9533e 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -3043,7 +3043,7 @@ class MeshCoreConnector extends ChangeNotifier { try { final reader = BufferReader(frame); reader.skipBytes(1); - _batteryMillivolts = reader.readInt16LE(); + _batteryMillivolts = reader.readUInt16LE(); _storageUsedKb = reader.readUInt32LE(); _storageTotalKb = reader.readUInt32LE(); final volts = (_batteryMillivolts! / 1000.0).toStringAsFixed(2); diff --git a/lib/models/channel_message.dart b/lib/models/channel_message.dart index 0f0a5b4..7c09089 100644 --- a/lib/models/channel_message.dart +++ b/lib/models/channel_message.dart @@ -132,17 +132,15 @@ class ChannelMessage { final hasPath = (flags & 0x01) != 0; reader.skipBytes(1); // Skip reserved byte channelIdx = reader.readByte(); - pathLen = reader.readByte(); + pathLen = reader.readInt8(); txtType = reader.readByte(); if (hasPath && pathLen > 0) { reader.rewind(); // Rewind to read path length again for pathBytes pathBytes = reader.readBytes(pathLen); - } else { - pathLen = 0; } } else { channelIdx = reader.readByte(); - pathLen = reader.readByte(); + pathLen = reader.readInt8(); txtType = reader.readByte(); } final timestampRaw = reader.readUInt32LE(); diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index cc61143..46d6352 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -975,7 +975,6 @@ void _privacySettings(BuildContext context, MeshCoreConnector connector) { value: advertLocPolicy, onChanged: (value) { setDialogState(() => advertLocPolicy = value); - advertLocPolicy = value; }, ), const SizedBox(height: 8), From 5f475fce4d6dc8fabe14e52843670b971de750af Mon Sep 17 00:00:00 2001 From: thesebas Date: Mon, 23 Mar 2026 22:53:09 +0100 Subject: [PATCH 29/36] use correct translation for Advert in another few places --- lib/l10n/app_localizations_pl.dart | 15 ++++++++------- lib/l10n/app_pl.arb | 14 +++++++------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 06efab3..37b0107 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -3194,13 +3194,13 @@ class AppLocalizationsPl extends AppLocalizations { 'Kontakt nie został zaimportowany.'; @override - String get contacts_zeroHopAdvert => 'Reklama Zero Hop'; + String get contacts_zeroHopAdvert => 'Rozgłoszenie zero-hop'; @override - String get contacts_floodAdvert => 'Reklama powodziowa'; + String get contacts_floodAdvert => 'Rozgłoszenie zalewowe'; @override - String get contacts_copyAdvertToClipboard => 'Kopiuj ogłoszenie do schowka'; + String get contacts_copyAdvertToClipboard => 'Kopiuj rozgłoszenie do schowka'; @override String get contacts_addContactFromClipboard => 'Dodaj kontakt z schowka'; @@ -3210,22 +3210,23 @@ class AppLocalizationsPl extends AppLocalizations { @override String get contacts_ShareContactZeroHop => - 'Udostępnij kontakt przez ogłoszenie'; + 'Udostępnij kontakt przez rozgłoszenie'; @override String get contacts_zeroHopContactAdvertSent => - 'Wysłano kontakt przez ogłoszenie.'; + 'Wysłano kontakt przez rozgłoszenie.'; @override String get contacts_zeroHopContactAdvertFailed => 'Nie udało się wysłać kontaktu.'; @override - String get contacts_contactAdvertCopied => 'Reklama skopiowana do schowka.'; + String get contacts_contactAdvertCopied => + 'Rozgłoszenie skopiowano do schowka.'; @override String get contacts_contactAdvertCopyFailed => - 'Kopiowanie ogłoszenia do schowka nie powiodło się.'; + 'Kopiowanie rozgłoszenia do schowka nie powiodło się.'; @override String get notification_activityTitle => 'Aktywność MeshCore'; diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 20a444b..8466a8b 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1574,17 +1574,17 @@ "appSettings_enableMessageTracing": "Włącz śledzenie wiadomości", "appSettings_enableMessageTracingSubtitle": "Pokaż szczegółowe metadane trasowania i czasu dla wiadomości", "contacts_contactImportFailed": "Kontakt nie został zaimportowany.", - "contacts_zeroHopAdvert": "Reklama Zero Hop", - "contacts_floodAdvert": "Reklama powodziowa", - "contacts_copyAdvertToClipboard": "Kopiuj ogłoszenie do schowka", + "contacts_zeroHopAdvert": "Rozgłoszenie zero-hop", + "contacts_floodAdvert": "Rozgłoszenie zalewowe", + "contacts_copyAdvertToClipboard": "Kopiuj rozgłoszenie do schowka", "contacts_clipboardEmpty": "Schowek jest pusty.", "contacts_invalidAdvertFormat": "Nieprawidłowe dane kontaktowe", "contacts_addContactFromClipboard": "Dodaj kontakt z schowka", "contacts_contactImported": "Kontakt został zaimportowany.", - "contacts_zeroHopContactAdvertSent": "Wysłano kontakt przez ogłoszenie.", - "contacts_contactAdvertCopied": "Reklama skopiowana do schowka.", - "contacts_contactAdvertCopyFailed": "Kopiowanie ogłoszenia do schowka nie powiodło się.", - "contacts_ShareContactZeroHop": "Udostępnij kontakt przez ogłoszenie", + "contacts_zeroHopContactAdvertSent": "Wysłano kontakt przez rozgłoszenie.", + "contacts_contactAdvertCopied": "Rozgłoszenie skopiowano do schowka.", + "contacts_contactAdvertCopyFailed": "Kopiowanie rozgłoszenia do schowka nie powiodło się.", + "contacts_ShareContactZeroHop": "Udostępnij kontakt przez rozgłoszenie", "contacts_ShareContact": "Kopiuj kontakt do schowka", "contacts_zeroHopContactAdvertFailed": "Nie udało się wysłać kontaktu.", "notification_activityTitle": "Aktywność MeshCore", From 9b1f1e19948fb616b25fbd5e5f717a2b614a4473 Mon Sep 17 00:00:00 2001 From: thesebas Date: Mon, 23 Mar 2026 23:07:00 +0100 Subject: [PATCH 30/36] make the 'lastSeen' labels shorter to not break the contacts list layout --- lib/l10n/app_localizations_pl.dart | 12 ++++++------ lib/l10n/app_pl.arb | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 37b0107..0457b28 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -973,27 +973,27 @@ class AppLocalizationsPl extends AppLocalizations { String get contacts_noMembers => 'Brak członków'; @override - String get contacts_lastSeenNow => 'Widziano przed chwilą'; + String get contacts_lastSeenNow => 'niedawno'; @override String contacts_lastSeenMinsAgo(int minutes) { - return 'Widziano $minutes min temu'; + return '~ $minutes min'; } @override - String get contacts_lastSeenHourAgo => 'Ostatni raz widziany 1 godzinę temu'; + String get contacts_lastSeenHourAgo => '~ 1 godz.'; @override String contacts_lastSeenHoursAgo(int hours) { - return 'Widziano $hours godz. temu'; + return '~ $hours godz.'; } @override - String get contacts_lastSeenDayAgo => 'Ostatni raz widziany 1 dzień temu'; + String get contacts_lastSeenDayAgo => '~ 1 dzień'; @override String contacts_lastSeenDaysAgo(int days) { - return 'Widziano $days dni temu'; + return '~ $days dni'; } @override diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 8466a8b..fe41c20 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -307,8 +307,8 @@ "contacts_filterContacts": "Filtruj kontakty...", "contacts_noContactsMatchFilter": "Brak pasujących kontaktów do Twojego filtra", "contacts_noMembers": "Brak członków", - "contacts_lastSeenNow": "Widziano przed chwilą", - "contacts_lastSeenMinsAgo": "Widziano {minutes} min temu", + "contacts_lastSeenNow": "niedawno", + "contacts_lastSeenMinsAgo": "~ {minutes} min", "@contacts_lastSeenMinsAgo": { "placeholders": { "minutes": { @@ -316,8 +316,8 @@ } } }, - "contacts_lastSeenHourAgo": "Ostatni raz widziany 1 godzinę temu", - "contacts_lastSeenHoursAgo": "Widziano {hours} godz. temu", + "contacts_lastSeenHourAgo": "~ 1 godz.", + "contacts_lastSeenHoursAgo": "~ {hours} godz.", "@contacts_lastSeenHoursAgo": { "placeholders": { "hours": { @@ -325,8 +325,8 @@ } } }, - "contacts_lastSeenDayAgo": "Ostatni raz widziany 1 dzień temu", - "contacts_lastSeenDaysAgo": "Widziano {days} dni temu", + "contacts_lastSeenDayAgo": "~ 1 dzień", + "contacts_lastSeenDaysAgo": "~ {days} dni", "@contacts_lastSeenDaysAgo": { "placeholders": { "days": { From f63bc4b7874c77fa48a12923cd15f3e577267f68 Mon Sep 17 00:00:00 2001 From: thesebas Date: Mon, 23 Mar 2026 23:11:51 +0100 Subject: [PATCH 31/36] some minor adjsts --- lib/l10n/app_localizations_pl.dart | 4 ++-- lib/l10n/app_pl.arb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 0457b28..6fb41dc 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -2490,7 +2490,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get repeater_cliHelpNeighbors => - 'Wyświetla listę innych węzłów powtarzających się, które usłyszano dzięki rozgłoszeniom zero-hop. Każda linia to: id-prefix-hex:timestamp:snr-times-4'; + 'Wyświetla listę innych węzłów przekaźnikowych usłyszanych przez rozgłoszenia zero-hop. Każda linia to: id-prefix-hex:timestamp:snr-times-4'; @override String get repeater_cliHelpNeighborRemove => @@ -2663,7 +2663,7 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get neighbors_repeatersNeighbors => 'Przekaźniki Sąsiedzi'; + String get neighbors_repeatersNeighbors => 'Sąsiedzi przekaźników'; @override String get neighbors_noData => 'Brak danych dotyczących sąsiadów.'; diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index fe41c20..9cf0afc 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1163,7 +1163,7 @@ "repeater_cliHelpLogStart": "Rozpoczyna się logowanie pakietów do systemu plików.", "repeater_cliHelpLogStop": "Zatrzymuje logowanie pakietów do systemu plików.", "repeater_cliHelpLogErase": "Usuwa logi pakietów z systemu plików.", - "repeater_cliHelpNeighbors": "Wyświetla listę innych węzłów powtarzających się, które usłyszano dzięki rozgłoszeniom zero-hop. Każda linia to: id-prefix-hex:timestamp:snr-times-4", + "repeater_cliHelpNeighbors": "Wyświetla listę innych węzłów przekaźnikowych usłyszanych przez rozgłoszenia zero-hop. Każda linia to: id-prefix-hex:timestamp:snr-times-4", "repeater_cliHelpNeighborRemove": "Usuwa pierwszy pasujący wpis (z prefiksem pubkey (hex)) z listy sąsiadów.", "repeater_cliHelpRegion": "(tylko port szeregowy) Wyświetla wszystkie zdefiniowane regiony i aktualne uprawnienia do zalewu.", "repeater_cliHelpRegionLoad": "UWAGA: to jest specjalne wywołanie wielokomendowe. Każda następna komenda jest nazwą regionu (wcięta spacjami, aby wskazywać hierarchię nadrzędną, z minimum jedną spacją). Zakończona wysłaniem pustej linii/komendy.", @@ -1377,7 +1377,7 @@ "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_repeatersNeighbors": "Przekaźniki Sąsiedzi", + "neighbors_repeatersNeighbors": "Sąsiedzi przekaźników", "neighbors_noData": "Brak danych dotyczących sąsiadów.", "channels_joinPrivateChannelDesc": "Ręcznie wprowadź klucz tajny.", "channels_createPrivateChannel": "Utwórz Prywatny Kanał", From e7e2bb91b8f18cf8a20cb2971ed2bc6b07213481 Mon Sep 17 00:00:00 2001 From: zjs81 Date: Mon, 23 Mar 2026 19:24:27 -0700 Subject: [PATCH 32/36] Add radio statistics and localization updates - Implemented radio statistics features in multiple screens including chat, channels, and settings. - Added localization for new strings in Swedish, Ukrainian, and Chinese. - Introduced a setting to jump to the oldest unread message in chat and channels. - Enhanced path management and display for contacts and messages. - Updated app settings to include new boolean for jumping to the oldest unread message. - Improved battery indicator and radio stats display in the app bar. - Removed unused wakelock_plus dependency and updated plugin registrations. --- lib/connector/meshcore_connector.dart | 197 ++++++++++++++++-- lib/connector/meshcore_protocol.dart | 18 ++ lib/helpers/link_handler.dart | 19 +- lib/l10n/app_en.arb | 67 +++++- lib/l10n/app_localizations.dart | 141 +++++++++++++ lib/l10n/app_localizations_bg.dart | 83 ++++++++ lib/l10n/app_localizations_de.dart | 82 ++++++++ lib/l10n/app_localizations_en.dart | 80 +++++++ lib/l10n/app_localizations_es.dart | 82 ++++++++ lib/l10n/app_localizations_fr.dart | 84 ++++++++ lib/l10n/app_localizations_it.dart | 82 ++++++++ lib/l10n/app_localizations_nl.dart | 82 ++++++++ lib/l10n/app_localizations_pl.dart | 82 ++++++++ lib/l10n/app_localizations_pt.dart | 82 ++++++++ lib/l10n/app_localizations_ru.dart | 82 ++++++++ lib/l10n/app_localizations_sk.dart | 80 +++++++ lib/l10n/app_localizations_sl.dart | 82 ++++++++ lib/l10n/app_localizations_sv.dart | 82 ++++++++ lib/l10n/app_localizations_uk.dart | 82 ++++++++ lib/l10n/app_localizations_zh.dart | 76 +++++++ lib/models/app_settings.dart | 6 + lib/models/contact.dart | 14 +- lib/screens/app_settings_screen.dart | 28 +++ lib/screens/channel_chat_screen.dart | 61 +++++- lib/screens/channel_message_path_screen.dart | 2 + lib/screens/channels_screen.dart | 5 +- lib/screens/chat_screen.dart | 75 ++++++- lib/screens/contacts_screen.dart | 9 + lib/screens/device_screen.dart | 67 +++--- lib/screens/map_screen.dart | 3 + lib/screens/path_trace_map.dart | 9 +- lib/screens/settings_screen.dart | 11 + lib/services/app_settings_service.dart | 4 + lib/widgets/app_bar.dart | 16 +- lib/widgets/path_management_dialog.dart | 5 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 - pubspec.yaml | 1 - tools/translate.py | 71 ++++++- 38 files changed, 1955 insertions(+), 99 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 60a825c..ca5dc64 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:math' as math; import 'package:crypto/crypto.dart' as crypto; import 'package:pointycastle/export.dart'; @@ -8,6 +9,7 @@ import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import '../models/channel.dart'; import '../models/channel_message.dart'; +import '../models/companion_radio_stats.dart'; import '../models/contact.dart'; import '../models/message.dart'; import '../models/path_selection.dart'; @@ -143,6 +145,10 @@ class MeshCoreConnector extends ChangeNotifier { Timer? _selfInfoRetryTimer; Timer? _reconnectTimer; Timer? _batteryPollTimer; + Timer? _radioStatsPollTimer; + int _radioStatsPollRefCount = 0; + final ValueNotifier radioStatsNotifier = + ValueNotifier(null); int _reconnectAttempts = 0; bool _notifyListenersDirty = false; static const Duration _notifyListenersDebounce = Duration(milliseconds: 50); @@ -160,6 +166,10 @@ class MeshCoreConnector extends ChangeNotifier { int? _currentCr; bool? _clientRepeat; int? _firmwareVerCode; + int _pathHashByteWidth = 1; + CompanionRadioStats? _latestRadioStats; + Stopwatch? _airtimeBumpStopwatch; + int _prevTotalAirSecs = 0; int? _batteryMillivolts; double? _selfLatitude; double? _selfLongitude; @@ -173,9 +183,13 @@ class MeshCoreConnector extends ChangeNotifier { DateTime _lastRxTime = DateTime.now(); DateTime _lastRadioRxTime = DateTime.fromMillisecondsSinceEpoch(0); DateTime _lastContactMsgRxTime = DateTime.fromMillisecondsSinceEpoch(0); + DateTime _lastChannelMsgRxTime = DateTime.fromMillisecondsSinceEpoch(0); static const int _radioQuietMs = 3000; static const int _radioQuietMaxWaitMs = 3000; - static const int _contactMsgBackoffMs = 5000; + /// When companion radio stats are unavailable, keep the legacy fixed backoff. + static const int _contactMsgBackoffFallbackMs = 5000; + static const int _contactMsgBackoffMinMs = 500; + static const int _contactMsgBackoffMaxMs = 15000; bool _batteryRequested = false; bool _awaitingSelfInfo = false; bool _hasReceivedDeviceInfo = false; @@ -323,6 +337,18 @@ class MeshCoreConnector extends ChangeNotifier { List get directRepeaters => _directRepeaters; int? get currentTxPower => _currentTxPower; int? get maxTxPower => _maxTxPower; + + int get pathHashByteWidth => _pathHashByteWidth; + + CompanionRadioStats? get latestRadioStats => _latestRadioStats; + + bool get supportsCompanionRadioStats => (_firmwareVerCode ?? 0) >= 8; + + bool get radioStatsAirActivityPulse { + final sw = _airtimeBumpStopwatch; + if (sw == null || !sw.isRunning) return false; + return sw.elapsed < const Duration(seconds: 2); + } int? get currentFreqHz => _currentFreqHz; int? get currentBwHz => _currentBwHz; int? get currentSf => _currentSf; @@ -779,15 +805,71 @@ class MeshCoreConnector extends ChangeNotifier { } } - Future _waitForRadioQuiet() async { - // Wait for backoff after receiving a contact message (avoid collision - // with their transmission still propagating through repeaters) - final msSinceContactMsg = DateTime.now() - .difference(_lastContactMsgRxTime) - .inMilliseconds; - if (msSinceContactMsg < _contactMsgBackoffMs) { - final waitMs = _contactMsgBackoffMs - msSinceContactMsg; - debugPrint('Contact message backoff: waiting ${waitMs}ms'); + /// After an incoming DM or channel message, wait before TX so we do not + /// collide with mesh propagation. With companion stats, scale wait by RF + /// conditions (up to [_contactMsgBackoffMaxMs]); otherwise use + /// [_contactMsgBackoffFallbackMs]. + int _contactMessageBackoffTargetMs() { + if (!supportsCompanionRadioStats || _latestRadioStats == null) { + return _contactMsgBackoffFallbackMs; + } + final stats = _latestRadioStats!; + final nf = stats.noiseFloorDbm.toDouble(); + // Quieter (more negative) → lower score; noisier → higher. + const noiseQuietDbm = -118.0; + const noiseNoisyDbm = -88.0; + final noiseT = + ((nf - noiseQuietDbm) / (noiseNoisyDbm - noiseQuietDbm)).clamp(0.0, 1.0); + + final snr = stats.lastSnrDb; + const snrGood = 12.0; + const snrBad = -2.0; + final snrT = + (1.0 - ((snr - snrBad) / (snrGood - snrBad))).clamp(0.0, 1.0); + + final airBusy = _recentAirtimeBusyFraction(); + final severity = + (math.max(noiseT, snrT) * 0.82 + airBusy * 0.18).clamp(0.0, 1.0); + + return (_contactMsgBackoffMinMs + + severity * (_contactMsgBackoffMaxMs - _contactMsgBackoffMinMs)) + .round(); + } + + /// 1.0 shortly after TX/RX airtime counters increase, decaying to 0 over ~8s. + double _recentAirtimeBusyFraction() { + final sw = _airtimeBumpStopwatch; + if (sw == null || !sw.isRunning) return 0; + final ms = sw.elapsedMilliseconds; + const windowMs = 8000; + if (ms >= windowMs) return 0; + return 1.0 - (ms / windowMs); + } + + /// Start of the post-inbound cool-down: the later of BLE message RX time and + /// companion airtime bump ([_airtimeBumpStopwatch], same as the activity dot). + DateTime _postTxBackoffAnchor(DateTime lastInboundRxTime) { + if (!supportsCompanionRadioStats) return lastInboundRxTime; + final sw = _airtimeBumpStopwatch; + if (sw == null || !sw.isRunning) return lastInboundRxTime; + final bumpAt = DateTime.now().subtract(sw.elapsed); + return bumpAt.isAfter(lastInboundRxTime) ? bumpAt : lastInboundRxTime; + } + + Future _waitForRadioQuiet({ + required DateTime lastInboundRxTime, + }) async { + // Wait for backoff after inbound traffic / RF airtime (avoid collision with + // mesh propagation). Elapsed time uses the dot's airtime bump when newer. + final backoffTargetMs = _contactMessageBackoffTargetMs(); + final anchor = _postTxBackoffAnchor(lastInboundRxTime); + final msSinceAnchor = DateTime.now().difference(anchor).inMilliseconds; + if (msSinceAnchor < backoffTargetMs) { + final waitMs = backoffTargetMs - msSinceAnchor; + debugPrint( + 'Post-inbound backoff: waiting ${waitMs}ms ' + '(target=${backoffTargetMs}ms, anchorAge=${msSinceAnchor}ms)', + ); await Future.delayed(Duration(milliseconds: waitMs)); } @@ -821,7 +903,7 @@ class MeshCoreConnector extends ChangeNotifier { ) async { if (!isConnected || text.isEmpty) return; try { - await _waitForRadioQuiet(); + await _waitForRadioQuiet(lastInboundRxTime: _lastContactMsgRxTime); final outboundText = prepareContactOutboundText(contact, text); await sendFrame( buildSendTextMsgFrame( @@ -1097,6 +1179,7 @@ class MeshCoreConnector extends ChangeNotifier { ); await _requestDeviceInfo(); _startBatteryPolling(); + if (_radioStatsPollRefCount > 0) _startRadioStatsPolling(); var gotSelfInfo = await _waitForSelfInfo( timeout: const Duration(seconds: 3), ); @@ -1202,6 +1285,7 @@ class MeshCoreConnector extends ChangeNotifier { _pendingInitialChannelSync = true; await _requestDeviceInfo(); _startBatteryPolling(); + if (_radioStatsPollRefCount > 0) _startRadioStatsPolling(); var gotSelfInfo = await _waitForSelfInfo( timeout: const Duration(seconds: 3), @@ -1489,6 +1573,7 @@ class MeshCoreConnector extends ChangeNotifier { await _requestDeviceInfo(); _startBatteryPolling(); + if (_radioStatsPollRefCount > 0) _startRadioStatsPolling(); final gotSelfInfo = await _waitForSelfInfo( timeout: const Duration(seconds: 3), @@ -1516,6 +1601,7 @@ class MeshCoreConnector extends ChangeNotifier { _pendingInitialContactsSync = false; _bleInitialSyncStarted = false; _pendingDeferredChannelSyncAfterContacts = false; + _pathHashByteWidth = 1; } bool get _shouldAutoReconnect => @@ -1592,6 +1678,7 @@ class MeshCoreConnector extends ChangeNotifier { } _setState(MeshCoreConnectionState.disconnecting); _stopBatteryPolling(); + _stopRadioStatsPolling(); await _usbFrameSubscription?.cancel(); _usbFrameSubscription = null; @@ -1730,6 +1817,49 @@ class MeshCoreConnector extends ChangeNotifier { _batteryPollTimer = null; } + void _startRadioStatsPolling() { + _radioStatsPollTimer?.cancel(); + _radioStatsPollTimer = Timer.periodic(const Duration(seconds: 1), (_) { + if (!isConnected) { + _stopRadioStatsPolling(); + return; + } + unawaited(requestRadioStats()); + }); + } + + void _stopRadioStatsPolling() { + _radioStatsPollTimer?.cancel(); + _radioStatsPollTimer = null; + } + + void acquireRadioStatsPolling() { + _radioStatsPollRefCount++; + if (_radioStatsPollRefCount == 1 && isConnected) { + _startRadioStatsPolling(); + } + } + + void releaseRadioStatsPolling() { + _radioStatsPollRefCount = (_radioStatsPollRefCount - 1).clamp(0, 999); + if (_radioStatsPollRefCount == 0) { + _stopRadioStatsPolling(); + } + } + + Future requestRadioStats() async { + if (!isConnected) return; + if (!supportsCompanionRadioStats) return; + try { + await sendFrame(buildGetStatsFrame(statsTypeRadio)); + } catch (_) {} + } + + Future setPathHashMode(int mode) async { + if (!isConnected) return; + await sendFrame(buildSetPathHashModeFrame(mode.clamp(0, 2))); + } + Future refreshDeviceInfo() async { if (!isConnected) return; if (PlatformInfo.isWeb && @@ -2219,6 +2349,7 @@ class MeshCoreConnector extends ChangeNotifier { // Send the reaction to the device (don't add as a visible message) final reactionQueueId = _nextReactionSendQueueId(); _pendingChannelSentQueue.add(reactionQueueId); + await _waitForRadioQuiet(lastInboundRxTime: _lastChannelMsgRxTime); await sendFrame( buildSendChannelTextMsgFrame(channel.index, text), channelSendQueueId: reactionQueueId, @@ -2243,6 +2374,7 @@ class MeshCoreConnector extends ChangeNotifier { (isChannelSmazEnabled(channel.index) && !isStructuredPayload) ? Smaz.encodeIfSmaller(text) : text; + await _waitForRadioQuiet(lastInboundRxTime: _lastChannelMsgRxTime); await sendFrame( buildSendChannelTextMsgFrame(channel.index, outboundText), channelSendQueueId: message.messageId, @@ -2808,6 +2940,9 @@ class MeshCoreConnector extends ChangeNotifier { case respCodeBattAndStorage: _handleBatteryAndStorage(frame); break; + case respCodeStats: + _handleStatsFrame(frame); + break; case respCodeCustomVars: _handleCustomVars(frame); break; @@ -2880,8 +3015,8 @@ class MeshCoreConnector extends ChangeNotifier { final reader = BufferReader(frame); try { reader.skipBytes(2); - _currentTxPower = reader.readByte(); - _maxTxPower = reader.readByte(); + _currentTxPower = reader.readInt8(); + _maxTxPower = reader.readInt8(); _selfPublicKey = reader.readBytes(pubKeySize); _selfLatitude = reader.readInt32LE() / 1000000.0; _selfLongitude = reader.readInt32LE() / 1000000.0; @@ -2975,6 +3110,13 @@ class MeshCoreConnector extends ChangeNotifier { if (frame.length >= 81) { _clientRepeat = frame[80] != 0; } + // Path hash mode v10+ (byte 81): width = mode + 1 byte(s) per hop + if (frame.length >= 82) { + final mode = (frame[81] & 0xFF).clamp(0, 2); + _pathHashByteWidth = mode + 1; + } else { + _pathHashByteWidth = 1; + } // Firmware reports MAX_CONTACTS / 2 for v3+ device info. final reportedContacts = frame[2]; @@ -3034,6 +3176,19 @@ class MeshCoreConnector extends ChangeNotifier { unawaited(_requestNextQueuedMessage()); } + void _handleStatsFrame(Uint8List frame) { + final stats = CompanionRadioStats.tryParse(frame); + if (stats == null) return; + final total = stats.txAirSecs + stats.rxAirSecs; + if (total > _prevTotalAirSecs) { + (_airtimeBumpStopwatch ??= Stopwatch()).reset(); + _airtimeBumpStopwatch!.start(); + } + _prevTotalAirSecs = total; + _latestRadioStats = stats; + radioStatsNotifier.value = stats; + } + void _handleBatteryAndStorage(Uint8List frame) { // Frame format from C++: // [0] = RESP_CODE_BATT_AND_STORAGE @@ -3402,9 +3557,10 @@ class MeshCoreConnector extends ChangeNotifier { } bool _pathMatchesContact(Uint8List pathBytes, Uint8List publicKey) { - if (pathBytes.isEmpty || publicKey.length < pathHashSize) return false; - for (int i = 0; i + pathHashSize <= pathBytes.length; i += pathHashSize) { - final prefix = pathBytes.sublist(i, i + pathHashSize); + final w = _pathHashByteWidth; + if (pathBytes.isEmpty || publicKey.length < w) return false; + for (int i = 0; i + w <= pathBytes.length; i += w) { + final prefix = pathBytes.sublist(i, i + w); if (_matchesPrefix(publicKey, prefix)) { return true; } @@ -3689,6 +3845,7 @@ class MeshCoreConnector extends ChangeNotifier { if (_shouldDropSelfChannelMessage(parsed.senderName, parsed.pathBytes)) { return; } + _lastChannelMsgRxTime = DateTime.now(); final contentHash = _computeContentHash( parsed.channelIndex!, parsed.timestamp.millisecondsSinceEpoch ~/ 1000, @@ -4680,6 +4837,12 @@ class MeshCoreConnector extends ChangeNotifier { void _handleDisconnection() { _stopBatteryPolling(); + _stopRadioStatsPolling(); + _latestRadioStats = null; + radioStatsNotifier.value = null; + _prevTotalAirSecs = 0; + _airtimeBumpStopwatch?.stop(); + _airtimeBumpStopwatch = null; for (final entry in _pendingRepeaterAcks.values) { entry.timeout?.cancel(); @@ -4818,6 +4981,8 @@ class MeshCoreConnector extends ChangeNotifier { _notifyListenersTimer?.cancel(); _reconnectTimer?.cancel(); _batteryPollTimer?.cancel(); + _radioStatsPollTimer?.cancel(); + radioStatsNotifier.dispose(); _receivedFramesController.close(); _usbManager.dispose(); _tcpConnector.dispose(); diff --git a/lib/connector/meshcore_protocol.dart b/lib/connector/meshcore_protocol.dart index b368756..b42e3e5 100644 --- a/lib/connector/meshcore_protocol.dart +++ b/lib/connector/meshcore_protocol.dart @@ -209,6 +209,8 @@ const int cmdSetCustomVar = 41; const int cmdSendBinaryReq = 50; const int cmdSetAutoAddConfig = 58; const int cmdGetAutoAddConfig = 59; +const int cmdSetPathHashMode = 61; +const int cmdGetStats = 56; // Text message types const int txtTypePlain = 0; @@ -245,6 +247,11 @@ const int respCodeChannelMsgRecvV3 = 17; const int respCodeChannelInfo = 18; const int respCodeCustomVars = 21; const int respCodeAutoAddConfig = 25; +const int respCodeStats = 24; + +const int statsTypeCore = 0; +const int statsTypeRadio = 1; +const int statsTypePackets = 2; // Push codes (async from device) const int pushCodeAdvert = 0x80; @@ -554,6 +561,17 @@ Uint8List buildGetBattAndStorageFrame() { return Uint8List.fromList([cmdGetBattAndStorage]); } +/// Companion radio stats: [56][statsType] where statsType is statsTypeCore/Radio/Packets. +Uint8List buildGetStatsFrame(int statsType) { + return Uint8List.fromList([cmdGetStats, statsType & 0xFF]); +} + +/// Path hash width on air: [61][0][mode], mode 0..2 → (mode+1) bytes per hop hash. +Uint8List buildSetPathHashModeFrame(int mode) { + final m = mode.clamp(0, 2); + return Uint8List.fromList([cmdSetPathHashMode, 0, m]); +} + // Build CMD_SET_DEVICE_TIME frame Uint8List buildSetDeviceTimeFrame(int timestamp) { final writer = BufferWriter(); diff --git a/lib/helpers/link_handler.dart b/lib/helpers/link_handler.dart index 57a5e59..e83897a 100644 --- a/lib/helpers/link_handler.dart +++ b/lib/helpers/link_handler.dart @@ -5,6 +5,17 @@ import '../l10n/l10n.dart'; import '../utils/platform_info.dart'; class LinkHandler { + static TextStyle defaultLinkStyle(BuildContext context, TextStyle base) { + final brightness = Theme.of(context).brightness; + final orange = brightness == Brightness.dark + ? const Color(0xFFFFB74D) + : const Color(0xFFE65100); + return base.copyWith( + color: orange, + decoration: TextDecoration.underline, + ); + } + /// Returns a [SelectableLinkify] on desktop or a [Linkify] on mobile. static Widget buildLinkifyText({ required BuildContext context, @@ -13,13 +24,9 @@ class LinkHandler { TextStyle? linkStyle, }) { final effectiveLinkStyle = - linkStyle ?? - style.copyWith( - color: Colors.green, - decoration: TextDecoration.underline, - ); + linkStyle ?? defaultLinkStyle(context, style); const options = LinkifyOptions(humanize: false, defaultToHttps: false); - const linkifiers = [UrlLinkifier()]; + const linkifiers = [UrlLinkifier(), EmailLinkifier()]; void onOpen(LinkableElement link) => handleLinkTap(context, link.url); if (PlatformInfo.isDesktop) { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d8623d3..113fa2b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1977,5 +1977,68 @@ "discoveredContacts_copyContact": "Copy Contact to clipboard", "discoveredContacts_deleteContact": "Delete Discovered Contact", "discoveredContacts_deleteContactAll": "Delete All Discovered Contacts", - "discoveredContacts_deleteContactAllContent": "Are you sure you want to delete all discovered contacts?" -} \ No newline at end of file + "discoveredContacts_deleteContactAllContent": "Are you sure you want to delete all discovered contacts?", + "chat_sendCooldown": "Please wait a moment before sending again.", + "appSettings_jumpToOldestUnread": "Jump to oldest unread", + "appSettings_jumpToOldestUnreadSubtitle": "When opening a chat with unread messages, scroll to the first unread instead of the latest.", + "appSettings_languageHu": "Hungarian", + "appSettings_languageJa": "Japanese", + "appSettings_languageKo": "Korean", + "radioStats_tooltip": "Radio & mesh stats", + "radioStats_screenTitle": "Radio stats", + "radioStats_notConnected": "Connect to a device to view radio statistics.", + "radioStats_firmwareTooOld": "Radio statistics require companion firmware v8 or newer.", + "radioStats_waiting": "Waiting for data…", + "radioStats_noiseFloor": "Noise floor: {noiseDbm} dBm", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "radioStats_lastRssi": "Last RSSI: {rssiDbm} dBm", + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "radioStats_lastSnr": "Last SNR: {snr} dB", + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "radioStats_txAir": "TX airtime (total): {seconds} s", + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "radioStats_rxAir": "RX airtime (total): {seconds} s", + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "radioStats_chartCaption": "Noise floor (dBm) over recent samples.", + "radioStats_stripNoise": "Noise floor: {noiseDbm} dBm", + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "radioStats_stripWaiting": "Fetching radio stats…", + "radioStats_settingsTile": "Radio stats", + "radioStats_settingsSubtitle": "Noise floor, RSSI, SNR, and airtime" +} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index ce5833a..d2d4040 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -10,7 +10,10 @@ import 'app_localizations_de.dart'; import 'app_localizations_en.dart'; import 'app_localizations_es.dart'; import 'app_localizations_fr.dart'; +import 'app_localizations_hu.dart'; import 'app_localizations_it.dart'; +import 'app_localizations_ja.dart'; +import 'app_localizations_ko.dart'; import 'app_localizations_nl.dart'; import 'app_localizations_pl.dart'; import 'app_localizations_pt.dart'; @@ -112,7 +115,10 @@ abstract class AppLocalizations { Locale('en'), Locale('es'), Locale('fr'), + Locale('hu'), Locale('it'), + Locale('ja'), + Locale('ko'), Locale('nl'), Locale('pl'), Locale('pt'), @@ -6016,6 +6022,132 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Are you sure you want to delete all discovered contacts?'** String get discoveredContacts_deleteContactAllContent; + + /// No description provided for @chat_sendCooldown. + /// + /// In en, this message translates to: + /// **'Please wait a moment before sending again.'** + String get chat_sendCooldown; + + /// No description provided for @appSettings_jumpToOldestUnread. + /// + /// In en, this message translates to: + /// **'Jump to oldest unread'** + String get appSettings_jumpToOldestUnread; + + /// No description provided for @appSettings_jumpToOldestUnreadSubtitle. + /// + /// In en, this message translates to: + /// **'When opening a chat with unread messages, scroll to the first unread instead of the latest.'** + String get appSettings_jumpToOldestUnreadSubtitle; + + /// No description provided for @appSettings_languageHu. + /// + /// In en, this message translates to: + /// **'Hungarian'** + String get appSettings_languageHu; + + /// No description provided for @appSettings_languageJa. + /// + /// In en, this message translates to: + /// **'Japanese'** + String get appSettings_languageJa; + + /// No description provided for @appSettings_languageKo. + /// + /// In en, this message translates to: + /// **'Korean'** + String get appSettings_languageKo; + + /// No description provided for @radioStats_tooltip. + /// + /// In en, this message translates to: + /// **'Radio & mesh stats'** + String get radioStats_tooltip; + + /// No description provided for @radioStats_screenTitle. + /// + /// In en, this message translates to: + /// **'Radio stats'** + String get radioStats_screenTitle; + + /// No description provided for @radioStats_notConnected. + /// + /// In en, this message translates to: + /// **'Connect to a device to view radio statistics.'** + String get radioStats_notConnected; + + /// No description provided for @radioStats_firmwareTooOld. + /// + /// In en, this message translates to: + /// **'Radio statistics require companion firmware v8 or newer.'** + String get radioStats_firmwareTooOld; + + /// No description provided for @radioStats_waiting. + /// + /// In en, this message translates to: + /// **'Waiting for data…'** + String get radioStats_waiting; + + /// No description provided for @radioStats_noiseFloor. + /// + /// In en, this message translates to: + /// **'Noise floor: {noiseDbm} dBm'** + String radioStats_noiseFloor(int noiseDbm); + + /// No description provided for @radioStats_lastRssi. + /// + /// In en, this message translates to: + /// **'Last RSSI: {rssiDbm} dBm'** + String radioStats_lastRssi(int rssiDbm); + + /// No description provided for @radioStats_lastSnr. + /// + /// In en, this message translates to: + /// **'Last SNR: {snr} dB'** + String radioStats_lastSnr(String snr); + + /// No description provided for @radioStats_txAir. + /// + /// In en, this message translates to: + /// **'TX airtime (total): {seconds} s'** + String radioStats_txAir(int seconds); + + /// No description provided for @radioStats_rxAir. + /// + /// In en, this message translates to: + /// **'RX airtime (total): {seconds} s'** + String radioStats_rxAir(int seconds); + + /// No description provided for @radioStats_chartCaption. + /// + /// In en, this message translates to: + /// **'Noise floor (dBm) over recent samples.'** + String get radioStats_chartCaption; + + /// No description provided for @radioStats_stripNoise. + /// + /// In en, this message translates to: + /// **'Noise floor: {noiseDbm} dBm'** + String radioStats_stripNoise(int noiseDbm); + + /// No description provided for @radioStats_stripWaiting. + /// + /// In en, this message translates to: + /// **'Fetching radio stats…'** + String get radioStats_stripWaiting; + + /// No description provided for @radioStats_settingsTile. + /// + /// In en, this message translates to: + /// **'Radio stats'** + String get radioStats_settingsTile; + + /// No description provided for @radioStats_settingsSubtitle. + /// + /// In en, this message translates to: + /// **'Noise floor, RSSI, SNR, and airtime'** + String get radioStats_settingsSubtitle; } class _AppLocalizationsDelegate @@ -6034,7 +6166,10 @@ class _AppLocalizationsDelegate 'en', 'es', 'fr', + 'hu', 'it', + 'ja', + 'ko', 'nl', 'pl', 'pt', @@ -6063,8 +6198,14 @@ AppLocalizations lookupAppLocalizations(Locale locale) { return AppLocalizationsEs(); case 'fr': return AppLocalizationsFr(); + case 'hu': + return AppLocalizationsHu(); case 'it': return AppLocalizationsIt(); + case 'ja': + return AppLocalizationsJa(); + case 'ko': + return AppLocalizationsKo(); case 'nl': return AppLocalizationsNl(); case 'pl': diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 53d8ef2..e010040 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -3484,4 +3484,87 @@ class AppLocalizationsBg extends AppLocalizations { @override String get discoveredContacts_deleteContactAllContent => 'Сигурни ли сте, че искате да изтриете всички открити контакти?'; + + @override + String get chat_sendCooldown => + 'Моля, изчакайте малко, преди да изпратите отново.'; + + @override + String get appSettings_jumpToOldestUnread => + 'Преминете към най-старата непочетена статия'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + 'Когато отворите чат с непрочетени съобщения, плъзнете надолу, за да видите първото непрочетено съобщение, вместо най-новото.'; + + @override + String get appSettings_languageHu => 'Унгарски'; + + @override + String get appSettings_languageJa => 'Японски'; + + @override + String get appSettings_languageKo => 'Корейски'; + + @override + String get radioStats_tooltip => 'Статистика за радио и мрежа'; + + @override + String get radioStats_screenTitle => + 'Статистически данни за радиопредаванията'; + + @override + String get radioStats_notConnected => + 'Свържете се с устройство, за да видите статистически данни за радиопредаване.'; + + @override + String get radioStats_firmwareTooOld => + 'Статистиката на радиостанцията изисква съвместимо софтуерно решение версия 8 или по-нова.'; + + @override + String get radioStats_waiting => 'Изчакване на данни…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'Ниво на шума: $noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return 'Последен RSSI: $rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return 'Последна стойност на SNR: $snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'Време на въздух (общо): $seconds секунди'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'Общо време на използване на RX (в секунди): $seconds с'; + } + + @override + String get radioStats_chartCaption => + 'Ниво на шума (dBm) за последните измервания.'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'Ниво на шума: $noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => 'Извличане на данни за радиото…'; + + @override + String get radioStats_settingsTile => 'Статистически данни за радиостанции'; + + @override + String get radioStats_settingsSubtitle => + 'Ниво на шума, RSSI, SNR и време на пренос'; } diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 535eb45..3967829 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -3494,4 +3494,86 @@ class AppLocalizationsDe extends AppLocalizations { @override String get discoveredContacts_deleteContactAllContent => 'Sind Sie sicher, dass Sie alle gefundenen Kontakte löschen möchten?'; + + @override + String get chat_sendCooldown => + 'Bitte warten Sie einen Moment, bevor Sie erneut senden.'; + + @override + String get appSettings_jumpToOldestUnread => + 'Zum ältesten, nicht gelesenen Eintrag springen'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + 'Wenn Sie ein Chatfenster öffnen, in dem Nachrichten vorhanden sind, die noch nicht gelesen wurden, scrollen Sie zu der ersten unlesenen Nachricht, anstatt zur neuesten.'; + + @override + String get appSettings_languageHu => 'Ungarisch'; + + @override + String get appSettings_languageJa => 'Japanisch'; + + @override + String get appSettings_languageKo => 'Koreanisch'; + + @override + String get radioStats_tooltip => 'Daten zu Radio- und Mesh-Netzwerken'; + + @override + String get radioStats_screenTitle => 'Senderinformationen'; + + @override + String get radioStats_notConnected => + 'Verbinden Sie ein Gerät, um Radiostatisiken anzuzeigen.'; + + @override + String get radioStats_firmwareTooOld => + 'Für die Verwendung der Funkstatistiken ist die Firmware-Version 8 oder höher erforderlich.'; + + @override + String get radioStats_waiting => 'Warte auf Daten…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'Rauschpegel: $noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return 'Letzter RSSI-Wert: $rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return 'Letzter SNR: $snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'Gesamt-TX-Zeit: $seconds s'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'Gesamt-RX-Zeit: $seconds s'; + } + + @override + String get radioStats_chartCaption => + 'Rauschpegel (dBm) basierend auf den letzten Messwerten.'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'Rauschpegel: $noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => 'Abrufen von Radiostatus…'; + + @override + String get radioStats_settingsTile => 'Senderinformationen'; + + @override + String get radioStats_settingsSubtitle => + 'Rauschpegel, RSSI, Signal-Rausch-Verhältnis (SNR) und Nutzzeit'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 2fe75ec..4e90c25 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -3421,4 +3421,84 @@ class AppLocalizationsEn extends AppLocalizations { @override String get discoveredContacts_deleteContactAllContent => 'Are you sure you want to delete all discovered contacts?'; + + @override + String get chat_sendCooldown => 'Please wait a moment before sending again.'; + + @override + String get appSettings_jumpToOldestUnread => 'Jump to oldest unread'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + 'When opening a chat with unread messages, scroll to the first unread instead of the latest.'; + + @override + String get appSettings_languageHu => 'Hungarian'; + + @override + String get appSettings_languageJa => 'Japanese'; + + @override + String get appSettings_languageKo => 'Korean'; + + @override + String get radioStats_tooltip => 'Radio & mesh stats'; + + @override + String get radioStats_screenTitle => 'Radio stats'; + + @override + String get radioStats_notConnected => + 'Connect to a device to view radio statistics.'; + + @override + String get radioStats_firmwareTooOld => + 'Radio statistics require companion firmware v8 or newer.'; + + @override + String get radioStats_waiting => 'Waiting for data…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'Noise floor: $noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return 'Last RSSI: $rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return 'Last SNR: $snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'TX airtime (total): $seconds s'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'RX airtime (total): $seconds s'; + } + + @override + String get radioStats_chartCaption => + 'Noise floor (dBm) over recent samples.'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'Noise floor: $noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => 'Fetching radio stats…'; + + @override + String get radioStats_settingsTile => 'Radio stats'; + + @override + String get radioStats_settingsSubtitle => + 'Noise floor, RSSI, SNR, and airtime'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 70e0a79..af57fc2 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -3487,4 +3487,86 @@ class AppLocalizationsEs extends AppLocalizations { @override String get discoveredContacts_deleteContactAllContent => '¿Está seguro de que desea eliminar todos los contactos descubiertos!'; + + @override + String get chat_sendCooldown => + 'Por favor, espere un momento antes de reenviar.'; + + @override + String get appSettings_jumpToOldestUnread => + 'Ve a el mensaje más antiguo sin leer'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + 'Cuando abras una conversación con mensajes sin leer, desplázate hacia el primer mensaje sin leer en lugar del más reciente.'; + + @override + String get appSettings_languageHu => 'Húngaro'; + + @override + String get appSettings_languageJa => 'Japonés'; + + @override + String get appSettings_languageKo => 'Coreano'; + + @override + String get radioStats_tooltip => 'Estadísticas de radio y malla'; + + @override + String get radioStats_screenTitle => 'Estadísticas de radio'; + + @override + String get radioStats_notConnected => + 'Conéctese a un dispositivo para visualizar estadísticas de radio.'; + + @override + String get radioStats_firmwareTooOld => + 'Las estadísticas de radio requieren un firmware compatible v8 o posterior.'; + + @override + String get radioStats_waiting => 'Esperando datos…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'Nivel de ruido: $noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return 'Último RSSI: $rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return 'Último SNR: $snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'Tiempo de emisión en Texas (total): $seconds s'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'Tiempo de transmisión de RX (total): $seconds s'; + } + + @override + String get radioStats_chartCaption => + 'Nivel de ruido (dBm) en muestras recientes.'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'Nivel de ruido: $noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => 'Obteniendo estadísticas de la radio…'; + + @override + String get radioStats_settingsTile => 'Estadísticas de radio'; + + @override + String get radioStats_settingsSubtitle => + 'Nivel de ruido, RSSI, SNR y tiempo de transmisión'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 5be46c8..22ff6a8 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -3511,4 +3511,88 @@ class AppLocalizationsFr extends AppLocalizations { @override String get discoveredContacts_deleteContactAllContent => 'Êtes-vous sûr de vouloir supprimer tous les contacts découverts ?'; + + @override + String get chat_sendCooldown => + 'Veuillez patienter un instant avant de réessayer.'; + + @override + String get appSettings_jumpToOldestUnread => + 'Accéder au message le plus ancien non lu'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + 'Lorsque vous ouvrez une conversation contenant des messages non lus, faites défiler la page jusqu\'au premier message non lu, plutôt que jusqu\'au dernier.'; + + @override + String get appSettings_languageHu => 'Hongrois'; + + @override + String get appSettings_languageJa => 'Japonais'; + + @override + String get appSettings_languageKo => 'Coréen'; + + @override + String get radioStats_tooltip => + 'Statistiques des radios et des réseaux sans fil'; + + @override + String get radioStats_screenTitle => 'Statistiques de radio'; + + @override + String get radioStats_notConnected => + 'Connectez-vous à un appareil pour visualiser les statistiques de la radio.'; + + @override + String get radioStats_firmwareTooOld => + 'Les statistiques radio nécessitent un firmware compatible v8 ou une version ultérieure.'; + + @override + String get radioStats_waiting => 'En attente des données…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'Niveau de bruit : $noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return 'Dernier RSSI : $rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return 'Dernier SNR : $snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'Temps d\'antenne à la télévision du Texas (total) : $seconds s'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'Temps d\'utilisation de l\'appareil RX (total) : $seconds s'; + } + + @override + String get radioStats_chartCaption => + 'Niveau de bruit (dBm) sur les échantillons récents.'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'Niveau de bruit : $noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => + 'Récupération des statistiques de la radio…'; + + @override + String get radioStats_settingsTile => 'Statistiques de radio'; + + @override + String get radioStats_settingsSubtitle => + 'Niveau de bruit, RSSI, rapport signal/bruit (SNR) et temps d\'antenne'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 63b501f..5e1aa0b 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -3491,4 +3491,86 @@ class AppLocalizationsIt extends AppLocalizations { @override String get discoveredContacts_deleteContactAllContent => 'Sei sicuro di voler eliminare tutti i contatti scoperti?'; + + @override + String get chat_sendCooldown => + 'Si prega di attendere un momento prima di inviare nuovamente.'; + + @override + String get appSettings_jumpToOldestUnread => + 'Vai al messaggio più vecchio non letto'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + 'Quando si apre una chat con messaggi non letti, scorrete verso l\'alto fino al primo messaggio non letto, invece che al più recente.'; + + @override + String get appSettings_languageHu => 'Ungherese'; + + @override + String get appSettings_languageJa => 'Giapponese'; + + @override + String get appSettings_languageKo => 'Coreano'; + + @override + String get radioStats_tooltip => 'Statistiche per radio e reti'; + + @override + String get radioStats_screenTitle => 'Statistiche radio'; + + @override + String get radioStats_notConnected => + 'Connettiti a un dispositivo per visualizzare le statistiche radio.'; + + @override + String get radioStats_firmwareTooOld => + 'Le statistiche radio richiedono il firmware versione 8 o successiva.'; + + @override + String get radioStats_waiting => 'In attesa dei dati…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'Livello di rumore: $noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return 'Ultimo valore RSSI: $rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return 'Ultimo SNR: $snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'Tempo di trasmissione in diretta (totale): $seconds s'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'Tempo di trasmissione RX (totale): $seconds s'; + } + + @override + String get radioStats_chartCaption => + 'Livello di rumore (dBm) misurato su campioni recenti.'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'Livello di rumore: $noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => 'Recupero delle statistiche radio…'; + + @override + String get radioStats_settingsTile => 'Statistiche radio'; + + @override + String get radioStats_settingsSubtitle => + 'Livello di rumore, RSSI, rapporto segnale/rumore (SNR) e tempo di trasmissione'; } diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index adf2392..50774c9 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -3469,4 +3469,86 @@ class AppLocalizationsNl extends AppLocalizations { @override String get discoveredContacts_deleteContactAllContent => 'Weet u zeker dat u alle ontdekte contacten wilt verwijderen?'; + + @override + String get chat_sendCooldown => + 'Gelieve even te wachten voordat u opnieuw verzendt.'; + + @override + String get appSettings_jumpToOldestUnread => + 'Ga naar het oudste ongelezen bericht'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + 'Bij het openen van een chat met ongelezen berichten, scroll dan naar het eerste ongelezen bericht, in plaats van naar het meest recente.'; + + @override + String get appSettings_languageHu => 'Hongaars'; + + @override + String get appSettings_languageJa => 'Japanisch'; + + @override + String get appSettings_languageKo => 'Koreaans'; + + @override + String get radioStats_tooltip => 'Statistieken voor radio en mesh-netwerken'; + + @override + String get radioStats_screenTitle => 'Statistieken over radio'; + + @override + String get radioStats_notConnected => + 'Verbind met een apparaat om radio-statistieken te bekijken.'; + + @override + String get radioStats_firmwareTooOld => + 'Om de statistieken via radio te kunnen gebruiken, is firmware versie 8 of een nieuwere vereist.'; + + @override + String get radioStats_waiting => 'Wacht op gegevens…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'Ruisfrequentie: $noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return 'Laatste RSSI-waarde: $rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return 'Laatste SNR: $snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'TX-tijd (totaal): $seconds s'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'Tijd besteed met RX (totaal): $seconds s'; + } + + @override + String get radioStats_chartCaption => + 'Ruisfrequentie (dBm) over recente metingen.'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'Ruisfrequentie: $noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => 'Radio-statistieken ophalen…'; + + @override + String get radioStats_settingsTile => 'Statistieken over radio'; + + @override + String get radioStats_settingsSubtitle => + 'Ruimtelijke ruis, RSSI, SNR en beschikbare tijd'; } diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index e5d7d36..b1a58c1 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -3503,4 +3503,86 @@ class AppLocalizationsPl extends AppLocalizations { @override String get discoveredContacts_deleteContactAllContent => 'Czy na pewno chcesz usunąć wszystkie znalezione kontakty?'; + + @override + String get chat_sendCooldown => + 'Prosimy o chwilowe oczekiwanie przed ponownym wysłaniem.'; + + @override + String get appSettings_jumpToOldestUnread => + 'Przejdź do najstarszego nieodczytanej wiadomości'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + 'Przy otwieraniu czatu z nieodczytanymi wiadomościami, przewijaj, aby przejść do pierwszej nieodczytanej wiadomości, zamiast do najnowszej.'; + + @override + String get appSettings_languageHu => 'Węgierski'; + + @override + String get appSettings_languageJa => 'Japoński'; + + @override + String get appSettings_languageKo => 'Koreański'; + + @override + String get radioStats_tooltip => 'Statystyki dotyczące radia i siatki'; + + @override + String get radioStats_screenTitle => 'Statystyki radiowe'; + + @override + String get radioStats_notConnected => + 'Połącz się z urządzeniem, aby wyświetlić statystyki radiowe.'; + + @override + String get radioStats_firmwareTooOld => + 'Statystyki radiowe wymagają towarzyszącej oprogramowania w wersji 8 lub nowszej.'; + + @override + String get radioStats_waiting => 'Czekam na dane…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'Poziom szumów: $noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return 'Ostatni poziom RSSI: $rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return 'Ostatni poziom SNR: $snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'Czas emisji w stacji TX (całkowity): $seconds s'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'Czas wykorzystania kanału RX (całkowity): $seconds s'; + } + + @override + String get radioStats_chartCaption => + 'Poziom szumów (dBm) w ostatnich próbkach.'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'Poziom szumów: $noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => 'Pobieranie danych dotyczących radia…'; + + @override + String get radioStats_settingsTile => 'Statystyki radiowe'; + + @override + String get radioStats_settingsSubtitle => + 'Szum tła, RSSI, SNR oraz czas dostępny'; } diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 1bc971a..0d6a099 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -3484,4 +3484,86 @@ class AppLocalizationsPt extends AppLocalizations { @override String get discoveredContacts_deleteContactAllContent => 'Tem certeza de que deseja excluir todos os contatos descobertos?'; + + @override + String get chat_sendCooldown => + 'Por favor, aguarde um momento antes de reenviar.'; + + @override + String get appSettings_jumpToOldestUnread => + 'Vá para a mensagem mais antiga não lida'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + 'Ao abrir uma conversa com mensagens não lidas, role para a primeira mensagem não lida, em vez da mais recente.'; + + @override + String get appSettings_languageHu => 'Húngaro'; + + @override + String get appSettings_languageJa => 'Japonês'; + + @override + String get appSettings_languageKo => 'Coreano'; + + @override + String get radioStats_tooltip => 'Estatísticas de rádio e malha'; + + @override + String get radioStats_screenTitle => 'Estatísticas de rádio'; + + @override + String get radioStats_notConnected => + 'Conecte-se a um dispositivo para visualizar estatísticas de rádio.'; + + @override + String get radioStats_firmwareTooOld => + 'As estatísticas de rádio exigem o firmware v8 ou uma versão mais recente.'; + + @override + String get radioStats_waiting => 'Aguardando dados…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'Nível de ruído: $noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return 'Último RSSI: $rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return 'Último SNR: $snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'Tempo de transmissão da TX (total): $seconds s'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'Tempo de uso do RX (total): $seconds s'; + } + + @override + String get radioStats_chartCaption => + 'Nível de ruído (dBm) em amostras recentes.'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'Nível de ruído: $noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => 'Obtendo estatísticas de rádio…'; + + @override + String get radioStats_settingsTile => 'Estatísticas de rádio'; + + @override + String get radioStats_settingsSubtitle => + 'Nível de ruído, RSSI, SNR e tempo de transmissão'; } diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index f71bff0..0c08b6c 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -3498,4 +3498,86 @@ class AppLocalizationsRu extends AppLocalizations { @override String get discoveredContacts_deleteContactAllContent => 'Вы уверены, что хотите удалить все обнаруженные контакты?'; + + @override + String get chat_sendCooldown => + 'Пожалуйста, подождите немного, прежде чем отправлять сообщение снова.'; + + @override + String get appSettings_jumpToOldestUnread => + 'Перейти к самому старому непрочитанному сообщению'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + 'При открытии чата с непрочитанными сообщениями, прокрутите страницу, чтобы увидеть первое непрочитанное сообщение, а не последнее.'; + + @override + String get appSettings_languageHu => 'Венгерский'; + + @override + String get appSettings_languageJa => 'Японский'; + + @override + String get appSettings_languageKo => 'Корейский'; + + @override + String get radioStats_tooltip => 'Статистика радио и беспроводной сети'; + + @override + String get radioStats_screenTitle => 'Статистика радиовещания'; + + @override + String get radioStats_notConnected => + 'Подключитесь к устройству, чтобы просмотреть статистику радио.'; + + @override + String get radioStats_firmwareTooOld => + 'Для работы радиостатистики требуется установленная версия прошивки v8 или более новая.'; + + @override + String get radioStats_waiting => 'Ожидаем данных…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'Уровень шума: $noiseDbm дБм'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return 'Последнее значение RSSI: $rssiDbm дБм'; + } + + @override + String radioStats_lastSnr(String snr) { + return 'Последнее значение SNR: $snr дБ'; + } + + @override + String radioStats_txAir(int seconds) { + return 'Время эфира на телеканале TX (общее): $seconds секунд'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'Общее время использования RX (в секундах): $seconds с'; + } + + @override + String get radioStats_chartCaption => + 'Уровень шума (дБм) на основе последних измерений.'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'Уровень шума: $noiseDbm дБм'; + } + + @override + String get radioStats_stripWaiting => 'Получение данных о радио…'; + + @override + String get radioStats_settingsTile => 'Статистика радиовещания'; + + @override + String get radioStats_settingsSubtitle => + 'Уровень шума, RSSI, SNR и время передачи'; } diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index d5d00a0..c3884cf 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -3464,4 +3464,84 @@ class AppLocalizationsSk extends AppLocalizations { @override String get discoveredContacts_deleteContactAllContent => 'Ste si istí, že chcete zmazať všetky objavené kontakty?'; + + @override + String get chat_sendCooldown => 'Prosím, počkajte chvíľu, než zašlete znova.'; + + @override + String get appSettings_jumpToOldestUnread => 'Presk oceň'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + 'Pri otvorení chatu s neprečítanými správami, prejdite do prvého neprečítaného, namiesto poslednej.'; + + @override + String get appSettings_languageHu => 'Maďarský'; + + @override + String get appSettings_languageJa => 'Japonský'; + + @override + String get appSettings_languageKo => 'Kórejský'; + + @override + String get radioStats_tooltip => 'Statistiky rádiových a sieťových kanálov'; + + @override + String get radioStats_screenTitle => 'Štatistiky rádiových vysielaní'; + + @override + String get radioStats_notConnected => + 'Pripojte sa k zariadeniu, aby ste mohli sledovať štatistiky rádiového vysielania.'; + + @override + String get radioStats_firmwareTooOld => + 'Statistické údaje z rádia vyžadujú sprievodný softvér verzie v8 alebo novšej.'; + + @override + String get radioStats_waiting => 'Čakám na údaje…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'Úroveň hluku: $noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return 'Posledný údaj RSSI: $rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return 'Posledná hodnota SNR: $snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'Čas vysielania na TX (celkový): $seconds s'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'Čas RX (celkový): $seconds s'; + } + + @override + String get radioStats_chartCaption => + 'Úroveň šumu (dBm) pre posledné vzorky.'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'Úroveň hluku: $noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => 'Získavanie údajov o rádiu…'; + + @override + String get radioStats_settingsTile => 'Štatistiky rádiových vysielaní'; + + @override + String get radioStats_settingsSubtitle => + 'Úroveň hluku, RSSI, SNR a časové rozloženie'; } diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 47066c9..953a89b 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -3467,4 +3467,86 @@ class AppLocalizationsSl extends AppLocalizations { @override String get discoveredContacts_deleteContactAllContent => 'Ste prepričani, da želite izbrisati vse odkrite kontakte?'; + + @override + String get chat_sendCooldown => + 'Prosimo, počakajte trenutek, preden pošljete ponovno.'; + + @override + String get appSettings_jumpToOldestUnread => + 'Pritisnite za najstarejše nepročitano sporočilo'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + 'Ko odpirate klepet z neprebranimi sporočili, se premaknite na prvo neprebrano sporočilo, namesto najnovejšega.'; + + @override + String get appSettings_languageHu => 'Madžarski'; + + @override + String get appSettings_languageJa => 'Japonski'; + + @override + String get appSettings_languageKo => 'Korejski'; + + @override + String get radioStats_tooltip => 'Statistike za radio in mrežo'; + + @override + String get radioStats_screenTitle => 'Radijske statistike'; + + @override + String get radioStats_notConnected => + 'Povežite se z napravo, da si ogledate statistiko o radiju.'; + + @override + String get radioStats_firmwareTooOld => + 'Statistika za radio zahteva združljivo programsko opremo v8 ali kasnejše različice.'; + + @override + String get radioStats_waiting => 'Čakam na podatke…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'Število šuma: $noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return 'Najkasnejše vrednost RSSI: $rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return 'Najkasnejše vrednost SNR: $snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'Čas na TX (skupno): $seconds s'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'Čas, namenjen RX-ju (skupno): $seconds s'; + } + + @override + String get radioStats_chartCaption => + 'Ravnovredna raven šuma (dBm) za nedavne vzorce.'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'Število šuma: $noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => 'Prejemanje statistike o radiju…'; + + @override + String get radioStats_settingsTile => 'Radijske statistike'; + + @override + String get radioStats_settingsSubtitle => + 'Število šumov, RSSI, SNR in čas, ki ga je napolnila oprema'; } diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 6e5fd20..3bf5887 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -3444,4 +3444,86 @@ class AppLocalizationsSv extends AppLocalizations { @override String get discoveredContacts_deleteContactAllContent => 'Är du säker på att du vill ta bort alla upptäckta kontakter?'; + + @override + String get chat_sendCooldown => + 'Vänligen vänta en stund innan du skickar igen.'; + + @override + String get appSettings_jumpToOldestUnread => + 'Gå direkt till det äldsta, obesvarade meddelandet'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + 'När du öppnar en chatt med oinlästa meddelanden, scrolla till det första oinlästa meddelandet istället för det senaste.'; + + @override + String get appSettings_languageHu => 'Ungerskt'; + + @override + String get appSettings_languageJa => 'Japanska'; + + @override + String get appSettings_languageKo => 'Koreanska'; + + @override + String get radioStats_tooltip => 'Radio- och mesh-statistik'; + + @override + String get radioStats_screenTitle => 'Radiostation'; + + @override + String get radioStats_notConnected => + 'Anslut till en enhet för att visa radiostatistik.'; + + @override + String get radioStats_firmwareTooOld => + 'Radio statistik kräver kompatibel firmware version 8 eller senare.'; + + @override + String get radioStats_waiting => 'Väntar på data…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'Bakgrundsnivå: $noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return 'Senaste RSSI-värde: $rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return 'Senaste SNR: $snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'TX-tid (total): $seconds sekunder'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'RX-tid (total): $seconds s'; + } + + @override + String get radioStats_chartCaption => + 'Ljudnivå (dBm) baserat på de senaste mätningarna.'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'Bakgrundsnivå: $noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => 'Hämtar radiostatistik…'; + + @override + String get radioStats_settingsTile => 'Radiostation'; + + @override + String get radioStats_settingsSubtitle => + 'Bakgrundsnivå, RSSI, SNR och tillgänglig tid'; } diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index c068ed3..8b9d505 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -3501,4 +3501,86 @@ class AppLocalizationsUk extends AppLocalizations { @override String get discoveredContacts_deleteContactAllContent => 'Ви впевнені, що хочете видалити всі виявлені контакти?'; + + @override + String get chat_sendCooldown => + 'Будь ласка, зачекайте трохи, перш ніж відправляти знову.'; + + @override + String get appSettings_jumpToOldestUnread => + 'Перейти до найстарішого непрочитаного повідомлення'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + 'При відкритті чату з не прочитаними повідомленнями, прокрутіть до першого не прочитаного повідомлення, а не до останнього.'; + + @override + String get appSettings_languageHu => 'Угорський'; + + @override + String get appSettings_languageJa => 'Японська'; + + @override + String get appSettings_languageKo => 'Кореєська'; + + @override + String get radioStats_tooltip => 'Статистика радіо та мережі'; + + @override + String get radioStats_screenTitle => 'Дані про радіостанції'; + + @override + String get radioStats_notConnected => + 'Підключіться до пристрою, щоб переглядати статистику радіопередач.'; + + @override + String get radioStats_firmwareTooOld => + 'Статистика радіо приймача вимагає супутнього програмного забезпечення версії 8 або новішої.'; + + @override + String get radioStats_waiting => 'Очікую на отримання даних…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'Рівень шуму: $noiseDbm дБм'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return 'Останній показник RSSI: $rssiDbm дБм'; + } + + @override + String radioStats_lastSnr(String snr) { + return 'Останній показник SNR: $snr дБ'; + } + + @override + String radioStats_txAir(int seconds) { + return 'Час трансляції на телеканалі TX (загальний): $seconds секунд'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'Загальний час використання RX: $seconds секунд'; + } + + @override + String get radioStats_chartCaption => + 'Рівень шуму (дБм) на основі останніх вимірювань.'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'Рівень шуму: $noiseDbm дБм'; + } + + @override + String get radioStats_stripWaiting => 'Отримано статистику радіо…'; + + @override + String get radioStats_settingsTile => 'Дані про радіостанції'; + + @override + String get radioStats_settingsSubtitle => + 'Рівень шуму, RSSI, SNR та час, протягом якого пристрій використовує радіоканал.'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 78e9c94..de334f6 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -3222,4 +3222,80 @@ class AppLocalizationsZh extends AppLocalizations { @override String get discoveredContacts_deleteContactAllContent => '您确定要删除所有发现的联系人吗?'; + + @override + String get chat_sendCooldown => '请稍等片刻后再尝试发送。'; + + @override + String get appSettings_jumpToOldestUnread => '跳转到最旧未读的文章'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + '在打开包含未读消息的聊天时,请滚动到第一个未读消息,而不是最新的消息。'; + + @override + String get appSettings_languageHu => '匈牙利'; + + @override + String get appSettings_languageJa => '日语'; + + @override + String get appSettings_languageKo => '韩语'; + + @override + String get radioStats_tooltip => '无线电和网状结构统计数据'; + + @override + String get radioStats_screenTitle => '广播统计数据'; + + @override + String get radioStats_notConnected => '连接到设备以查看收音机统计信息。'; + + @override + String get radioStats_firmwareTooOld => '使用无线电统计功能需要配合使用 v8 或更高版本的固件。'; + + @override + String get radioStats_waiting => '正在等待数据…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return '噪声水平:$noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return '上次 RSSI 值:$rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return '上次 SNR:$snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'TX 频道播出时间(总时长):$seconds 秒'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'RX 使用时长(总时长):$seconds 秒'; + } + + @override + String get radioStats_chartCaption => '近期的噪声水平(dBm)。'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return '噪声水平:$noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => '正在获取收音机数据…'; + + @override + String get radioStats_settingsTile => '广播统计数据'; + + @override + String get radioStats_settingsSubtitle => '噪声水平、RSSI、信噪比和空中时间'; } diff --git a/lib/models/app_settings.dart b/lib/models/app_settings.dart index 31c1741..36d36a6 100644 --- a/lib/models/app_settings.dart +++ b/lib/models/app_settings.dart @@ -48,6 +48,7 @@ class AppSettings { final bool mapShowDiscoveryContacts; final String tcpServerAddress; final int tcpServerPort; + final bool jumpToOldestUnread; AppSettings({ this.clearPathOnMaxRetry = false, @@ -84,6 +85,7 @@ class AppSettings { this.mapShowDiscoveryContacts = true, this.tcpServerAddress = '', this.tcpServerPort = 0, + this.jumpToOldestUnread = false, }) : batteryChemistryByDeviceId = batteryChemistryByDeviceId ?? {}, batteryChemistryByRepeaterId = batteryChemistryByRepeaterId ?? {}, mutedChannels = mutedChannels ?? {}; @@ -124,6 +126,7 @@ class AppSettings { 'map_show_discovery_contacts': mapShowDiscoveryContacts, 'tcp_server_address': tcpServerAddress, 'tcp_server_port': tcpServerPort, + 'jump_to_oldest_unread': jumpToOldestUnread, }; } @@ -192,6 +195,7 @@ class AppSettings { json['map_show_discovery_contacts'] as bool? ?? true, tcpServerAddress: json['tcp_server_address'] as String? ?? '', tcpServerPort: json['tcp_server_port'] as int? ?? 0, + jumpToOldestUnread: json['jump_to_oldest_unread'] as bool? ?? false, ); } @@ -230,6 +234,7 @@ class AppSettings { bool? mapShowDiscoveryContacts, String? tcpServerAddress, int? tcpServerPort, + bool? jumpToOldestUnread, }) { return AppSettings( clearPathOnMaxRetry: clearPathOnMaxRetry ?? this.clearPathOnMaxRetry, @@ -278,6 +283,7 @@ class AppSettings { mapShowDiscoveryContacts ?? this.mapShowDiscoveryContacts, tcpServerAddress: tcpServerAddress ?? this.tcpServerAddress, tcpServerPort: tcpServerPort ?? this.tcpServerPort, + jumpToOldestUnread: jumpToOldestUnread ?? this.jumpToOldestUnread, ); } } diff --git a/lib/models/contact.dart b/lib/models/contact.dart index fe1c915..2699f93 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -119,15 +119,14 @@ class Contact { ); } - String get pathIdList { + /// Formats path bytes into comma-separated hex groups of [hashByteWidth] bytes. + String pathFormattedIdList(int hashByteWidth) { final pathBytes = pathBytesForDisplay; if (pathBytes.isEmpty) return ''; + final w = hashByteWidth.clamp(1, 8); final parts = []; - final groupSize = pathHashSize; - for (int i = 0; i < pathBytes.length; i += groupSize) { - final end = (i + groupSize) <= pathBytes.length - ? (i + groupSize) - : pathBytes.length; + for (int i = 0; i < pathBytes.length; i += w) { + final end = (i + w) <= pathBytes.length ? (i + w) : pathBytes.length; final chunk = pathBytes.sublist(i, end); parts.add( chunk @@ -138,6 +137,9 @@ class Contact { return parts.join(','); } + /// Default grouping uses legacy single-byte hop hash width. + String get pathIdList => pathFormattedIdList(pathHashSize); + String get shortPubKeyHex { return "<${publicKeyHex.substring(0, 8)}...${publicKeyHex.substring(publicKeyHex.length - 8)}>"; } diff --git a/lib/screens/app_settings_screen.dart b/lib/screens/app_settings_screen.dart index 7e0980e..f417715 100644 --- a/lib/screens/app_settings_screen.dart +++ b/lib/screens/app_settings_screen.dart @@ -291,6 +291,16 @@ class AppSettingsScreen extends StatelessWidget { }, ), const Divider(height: 1), + SwitchListTile( + secondary: const Icon(Icons.vertical_align_top), + title: Text(context.l10n.appSettings_jumpToOldestUnread), + subtitle: Text( + context.l10n.appSettings_jumpToOldestUnreadSubtitle, + ), + value: settingsService.settings.jumpToOldestUnread, + onChanged: settingsService.setJumpToOldestUnread, + ), + const Divider(height: 1), SwitchListTile( secondary: const Icon(Icons.alt_route), title: Text(context.l10n.appSettings_autoRouteRotation), @@ -689,6 +699,12 @@ class AppSettingsScreen extends StatelessWidget { return context.l10n.appSettings_languageRu; case 'uk': return context.l10n.appSettings_languageUk; + case 'hu': + return context.l10n.appSettings_languageHu; + case 'ja': + return context.l10n.appSettings_languageJa; + case 'ko': + return context.l10n.appSettings_languageKo; default: return context.l10n.appSettings_languageSystem; } @@ -776,6 +792,18 @@ class AppSettingsScreen extends StatelessWidget { title: Text(context.l10n.appSettings_languageUk), value: 'uk', ), + RadioListTile( + title: Text(context.l10n.appSettings_languageHu), + value: 'hu', + ), + RadioListTile( + title: Text(context.l10n.appSettings_languageJa), + value: 'ja', + ), + RadioListTile( + title: Text(context.l10n.appSettings_languageKo), + value: 'ko', + ), ], ), ), diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index 913b288..7cfba56 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -26,6 +26,7 @@ import '../widgets/gif_message.dart'; import '../widgets/jump_to_bottom_button.dart'; import '../widgets/gif_picker.dart'; import '../widgets/message_status_icon.dart'; +import '../widgets/radio_stats_entry.dart'; import 'channel_message_path_screen.dart'; import 'map_screen.dart'; @@ -47,6 +48,8 @@ class _ChannelChatScreenState extends State { bool _isLoadingOlder = false; MeshCoreConnector? _connector; + DateTime? _lastChannelSendAt; + bool _channelSkipNextBottomSnap = false; @override void initState() { @@ -55,11 +58,45 @@ class _ChannelChatScreenState extends State { _scrollController.onScrollNearTop = _loadOlderMessages; SchedulerBinding.instance.addPostFrameCallback((_) { if (!mounted) return; - _connector = context.read(); - _connector?.setActiveChannel(widget.channel.index); + final connector = context.read(); + final settings = context.read().settings; + final idx = widget.channel.index; + final unread = connector.getUnreadCountForChannelIndex(idx); + ChannelMessage? anchor; + if (settings.jumpToOldestUnread && unread > 0) { + anchor = _findOldestUnreadChannelAnchor( + connector.getChannelMessages(widget.channel), + unread, + ); + } + connector.setActiveChannel(idx); + _connector = connector; + if (anchor != null) { + _channelSkipNextBottomSnap = true; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + _scrollToMessage(anchor!.messageId); + }); + } }); } + ChannelMessage? _findOldestUnreadChannelAnchor( + List messages, + int unreadCount, + ) { + if (unreadCount <= 0 || messages.isEmpty) return null; + var n = 0; + ChannelMessage? oldest; + for (final m in messages.reversed) { + if (m.isOutgoing) continue; + n++; + oldest = m; + if (n >= unreadCount) break; + } + return oldest; + } + void _onTextFieldFocusChange() { if (_textFieldFocusNode.hasFocus && mounted) { _scrollController.handleKeyboardOpen(); @@ -167,6 +204,7 @@ class _ChannelChatScreenState extends State { ), centerTitle: false, actions: [ + const RadioStatsIconButton(), PopupMenuButton( icon: const Icon(Icons.more_vert), onSelected: (value) { @@ -243,6 +281,10 @@ class _ChannelChatScreenState extends State { // Auto-scroll to bottom if user is already at bottom WidgetsBinding.instance.addPostFrameCallback((_) { + if (_channelSkipNextBottomSnap) { + _channelSkipNextBottomSnap = false; + return; + } _scrollController.scrollToBottomIfAtBottom(); }); @@ -468,11 +510,6 @@ class _ChannelChatScreenState extends State { style: TextStyle( fontSize: bodyFontSize * textScale, ), - linkStyle: TextStyle( - fontSize: bodyFontSize * textScale, - color: Colors.green, - decoration: TextDecoration.underline, - ), ), ), if (!enableTracing && isOutgoing) ...[ @@ -1079,6 +1116,16 @@ class _ChannelChatScreenState extends State { final text = _textController.text.trim(); if (text.isEmpty) return; + final now = DateTime.now(); + if (_lastChannelSendAt != null && + now.difference(_lastChannelSendAt!) < const Duration(seconds: 1)) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.chat_sendCooldown)), + ); + return; + } + _lastChannelSendAt = now; + final connector = context.read(); String messageText = text; diff --git a/lib/screens/channel_message_path_screen.dart b/lib/screens/channel_message_path_screen.dart index ea07eae..dd36c21 100644 --- a/lib/screens/channel_message_path_screen.dart +++ b/lib/screens/channel_message_path_screen.dart @@ -64,6 +64,8 @@ class ChannelMessagePathScreen extends StatelessWidget { flipPathAround: true, reversePathAround: !(!channelMessage && !message.isOutgoing), + pathHashByteWidth: + context.read().pathHashByteWidth, ), ), ), diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index 51d2453..3e34568 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -127,7 +127,10 @@ class _ChannelsScreenState extends State canPop: allowBack, child: Scaffold( appBar: AppBar( - title: AppBarTitle(context.l10n.channels_title), + title: AppBarTitle( + context.l10n.channels_title, + indicators: false, + ), centerTitle: true, automaticallyImplyLeading: false, actions: [ diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index 574ffbe..1a5a79b 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -36,6 +36,7 @@ import '../widgets/gif_message.dart'; import '../widgets/jump_to_bottom_button.dart'; import '../widgets/gif_picker.dart'; import '../widgets/path_selection_dialog.dart'; +import '../widgets/radio_stats_entry.dart'; import '../utils/app_logger.dart'; import '../l10n/l10n.dart'; import 'telemetry_screen.dart'; @@ -53,8 +54,11 @@ class _ChatScreenState extends State { final _textController = TextEditingController(); final _scrollController = ChatScrollController(); final _textFieldFocusNode = FocusNode(); + final GlobalKey _unreadScrollKey = GlobalKey(); bool _isLoadingOlder = false; MeshCoreConnector? _connector; + Message? _pendingUnreadScrollTarget; + DateTime? _lastTextSendAt; @override void initState() { @@ -63,11 +67,50 @@ class _ChatScreenState extends State { _scrollController.onScrollNearTop = _loadOlderMessages; SchedulerBinding.instance.addPostFrameCallback((_) { if (!mounted) return; - _connector = context.read(); - _connector?.setActiveContact(widget.contact.publicKeyHex); + final connector = context.read(); + final settings = context.read().settings; + final keyHex = widget.contact.publicKeyHex; + final unread = connector.getUnreadCountForContactKey(keyHex); + Message? anchor; + if (settings.jumpToOldestUnread && unread > 0) { + anchor = _findOldestUnreadAnchor( + connector.getMessages(widget.contact), + unread, + ); + } + connector.setActiveContact(keyHex); + _connector = connector; + if (anchor != null) { + setState(() => _pendingUnreadScrollTarget = anchor); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + final ctx = _unreadScrollKey.currentContext; + if (ctx != null) { + Scrollable.ensureVisible( + ctx, + duration: const Duration(milliseconds: 350), + alignment: 0.15, + ); + } + setState(() => _pendingUnreadScrollTarget = null); + }); + } }); } + Message? _findOldestUnreadAnchor(List messages, int unreadCount) { + if (unreadCount <= 0 || messages.isEmpty) return null; + var n = 0; + Message? oldest; + for (final m in messages.reversed) { + if (m.isOutgoing || m.isCli) continue; + n++; + oldest = m; + if (n >= unreadCount) break; + } + return oldest; + } + void _onTextFieldFocusChange() { if (_textFieldFocusNode.hasFocus && mounted) { _scrollController.handleKeyboardOpen(); @@ -319,6 +362,7 @@ class _ChatScreenState extends State { ); }, ), + const RadioStatsIconButton(), ], ), body: Consumer( @@ -378,6 +422,7 @@ class _ChatScreenState extends State { // Auto-scroll to bottom if user is already at bottom WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; + if (_pendingUnreadScrollTarget != null) return; _scrollController.scrollToBottomIfAtBottom(); }); @@ -424,7 +469,7 @@ class _ChatScreenState extends State { (service) => service.scale, ); final resolvedContact = _resolveContact(connector); - return _MessageBubble( + final bubble = _MessageBubble( message: message, senderName: resolvedContact.type == advTypeRoom ? "${contact.name} [$fourByteHex]" @@ -436,6 +481,10 @@ class _ChatScreenState extends State { onRetryReaction: (msg, emoji) => _sendReaction(msg, contact, emoji), ); + if (identical(message, _pendingUnreadScrollTarget)) { + return KeyedSubtree(key: _unreadScrollKey, child: bubble); + } + return bubble; }, ); }, @@ -561,6 +610,16 @@ class _ChatScreenState extends State { final text = _textController.text.trim(); if (text.isEmpty) return; + final now = DateTime.now(); + if (_lastTextSendAt != null && + now.difference(_lastTextSendAt!) < const Duration(seconds: 1)) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.chat_sendCooldown)), + ); + return; + } + _lastTextSendAt = now; + final maxBytes = maxContactMessageBytes(); if (utf8.encode(text).length > maxBytes) { ScaffoldMessenger.of(context).showSnackBar( @@ -950,6 +1009,7 @@ class _ChatScreenState extends State { path: Uint8List.fromList(pathBytes), flipPathAround: true, targetContact: widget.contact, + pathHashByteWidth: connector.pathHashByteWidth, ), ), ), @@ -1212,7 +1272,9 @@ class _ChatScreenState extends State { connector.getContacts(); } - final pathForInput = currentContact.pathIdList; + final pathForInput = currentContact.pathFormattedIdList( + connector.pathHashByteWidth, + ); final currentPathLabel = _currentPathLabel(currentContact); // Filter out the current contact from available contacts @@ -1607,11 +1669,6 @@ class _MessageBubble extends StatelessWidget { color: textColor, fontSize: bodyFontSize * textScale, ), - linkStyle: TextStyle( - color: Colors.green, - decoration: TextDecoration.underline, - fontSize: bodyFontSize * textScale, - ), ), ), if (!enableTracing && isOutgoing) ...[ diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index 17eaa24..cce6a39 100644 --- a/lib/screens/contacts_screen.dart +++ b/lib/screens/contacts_screen.dart @@ -1244,6 +1244,8 @@ class _ContactsScreenState extends State ? Text(context.l10n.contacts_pathTrace) : Text(context.l10n.contacts_ping), onTap: () { + final hw = + context.read().pathHashByteWidth; Navigator.push( context, MaterialPageRoute( @@ -1254,6 +1256,7 @@ class _ContactsScreenState extends State path: contact.pathBytesForDisplay, flipPathAround: true, targetContact: contact, + pathHashByteWidth: hw, ), ), ); @@ -1274,6 +1277,8 @@ class _ContactsScreenState extends State ? Text(context.l10n.contacts_pathTrace) : Text(context.l10n.contacts_ping), onTap: () { + final hw = + context.read().pathHashByteWidth; Navigator.push( context, MaterialPageRoute( @@ -1284,6 +1289,7 @@ class _ContactsScreenState extends State path: contact.pathBytesForDisplay, flipPathAround: contact.pathBytesForDisplay.isNotEmpty, targetContact: contact, + pathHashByteWidth: hw, ), ), ); @@ -1318,6 +1324,8 @@ class _ContactsScreenState extends State leading: const Icon(Icons.radar, color: Colors.green), title: Text(context.l10n.contacts_chatTraceRoute), onTap: () { + final hw = + context.read().pathHashByteWidth; Navigator.push( context, MaterialPageRoute( @@ -1328,6 +1336,7 @@ class _ContactsScreenState extends State path: contact.pathBytesForDisplay, flipPathAround: true, targetContact: contact, + pathHashByteWidth: hw, ), ), ); diff --git a/lib/screens/device_screen.dart b/lib/screens/device_screen.dart index c5967cf..2343400 100644 --- a/lib/screens/device_screen.dart +++ b/lib/screens/device_screen.dart @@ -7,6 +7,8 @@ import '../utils/dialog_utils.dart'; import '../utils/disconnect_navigation_mixin.dart'; import '../utils/route_transitions.dart'; import '../widgets/quick_switch_bar.dart'; +import '../widgets/battery_indicator_chip.dart'; +import '../widgets/radio_stats_entry.dart'; import 'channels_screen.dart'; import 'contacts_screen.dart'; import 'map_screen.dart'; @@ -40,7 +42,22 @@ class _DeviceScreenState extends State canPop: false, child: Scaffold( appBar: AppBar( - leading: _buildBatteryIndicator(connector, context), + leadingWidth: 128, + leading: Row( + mainAxisSize: MainAxisSize.min, + children: [ + BatteryIndicatorChip( + connector: connector, + showVoltage: _showBatteryVoltage, + onPressed: () { + setState(() { + _showBatteryVoltage = !_showBatteryVoltage; + }); + }, + ), + const RadioStatsIconButton(), + ], + ), titleSpacing: 16, centerTitle: false, title: _buildAppBarTitle(connector, theme), @@ -187,7 +204,15 @@ class _DeviceScreenState extends State ), visualDensity: VisualDensity.compact, ), - _buildBatteryIndicator(connector, context), + BatteryIndicatorChip( + connector: connector, + showVoltage: _showBatteryVoltage, + onPressed: () { + setState(() { + _showBatteryVoltage = !_showBatteryVoltage; + }); + }, + ), ], ), ], @@ -205,44 +230,6 @@ class _DeviceScreenState extends State ); } - Widget _buildBatteryIndicator( - MeshCoreConnector connector, - BuildContext context, - ) { - final theme = Theme.of(context); - final colorScheme = theme.colorScheme; - final percent = connector.batteryPercent; - final millivolts = connector.batteryMillivolts; - final percentLabel = percent != null ? '$percent%' : '--%'; - final voltageLabel = millivolts == null - ? '-- V' - : '${(millivolts / 1000.0).toStringAsFixed(2)} V'; - final displayLabel = _showBatteryVoltage ? voltageLabel : percentLabel; - final icon = _batteryIcon(percent); - - return ActionChip( - avatar: Icon(icon, size: 16, color: colorScheme.onSecondaryContainer), - label: Text(displayLabel), - labelStyle: theme.textTheme.labelMedium?.copyWith( - color: colorScheme.onSecondaryContainer, - fontWeight: FontWeight.w600, - ), - backgroundColor: colorScheme.secondaryContainer, - visualDensity: VisualDensity.compact, - onPressed: () { - setState(() { - _showBatteryVoltage = !_showBatteryVoltage; - }); - }, - ); - } - - IconData _batteryIcon(int? percent) { - if (percent == null) return Icons.battery_unknown; - if (percent <= 15) return Icons.battery_alert; - return Icons.battery_full; - } - void _openQuickDestination(int index, BuildContext context) { if (_quickIndex != index) { setState(() { diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index f5efd3b..f42790c 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -2191,12 +2191,15 @@ class _MapScreenState extends State { if (_pathTrace.isNotEmpty) IconButton( onPressed: () { + final hashW = + context.read().pathHashByteWidth; Navigator.push( context, MaterialPageRoute( builder: (context) => PathTraceMapScreen( title: l10n.contacts_pathTrace, path: Uint8List.fromList(_pathTrace), + pathHashByteWidth: hashW, ), ), ); diff --git a/lib/screens/path_trace_map.dart b/lib/screens/path_trace_map.dart index e64a906..5b02931 100644 --- a/lib/screens/path_trace_map.dart +++ b/lib/screens/path_trace_map.dart @@ -55,6 +55,7 @@ class PathTraceMapScreen extends StatefulWidget { final bool flipPathAround; final bool reversePathAround; final Contact? targetContact; + final int pathHashByteWidth; const PathTraceMapScreen({ super.key, @@ -64,6 +65,7 @@ class PathTraceMapScreen extends StatefulWidget { this.flipPathAround = false, this.reversePathAround = false, this.targetContact, + this.pathHashByteWidth = pathHashSize, }); @override @@ -119,8 +121,13 @@ class _PathTraceMapScreenState extends State { Uint8List traceBytes; if (pathBytes.isEmpty) { + final pk = widget.targetContact?.publicKey; + final n = widget.pathHashByteWidth.clamp(1, pubKeySize); + if (pk != null && pk.length >= n) { + return Uint8List.fromList(pk.sublist(0, n)); + } traceBytes = Uint8List(1); - traceBytes[0] = widget.targetContact?.publicKey[0] ?? 0; + traceBytes[0] = pk?[0] ?? 0; return traceBytes; } diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 5d86b41..a926e2b 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -12,6 +12,7 @@ import '../widgets/app_bar.dart'; import 'app_settings_screen.dart'; import 'app_debug_log_screen.dart'; import 'ble_debug_log_screen.dart'; +import '../widgets/radio_stats_entry.dart'; class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @@ -269,6 +270,16 @@ class _SettingsScreenState extends State { onTap: () => _showRadioSettings(context, connector), ), const Divider(height: 1), + ListTile( + leading: const Icon(Icons.sensors_outlined), + title: Text(l10n.radioStats_settingsTile), + subtitle: Text(l10n.radioStats_settingsSubtitle), + trailing: const Icon(Icons.chevron_right), + enabled: connector.isConnected && + connector.supportsCompanionRadioStats, + onTap: () => pushCompanionRadioStatsScreen(context), + ), + const Divider(height: 1), ListTile( leading: const Icon(Icons.location_on_outlined), title: Text(l10n.settings_location), diff --git a/lib/services/app_settings_service.dart b/lib/services/app_settings_service.dart index 6414617..cb69469 100644 --- a/lib/services/app_settings_service.dart +++ b/lib/services/app_settings_service.dart @@ -218,4 +218,8 @@ class AppSettingsService extends ChangeNotifier { Future setTcpServerPort(int value) async { await updateSettings(_settings.copyWith(tcpServerPort: value)); } + + Future setJumpToOldestUnread(bool value) async { + await updateSettings(_settings.copyWith(jumpToOldestUnread: value)); + } } diff --git a/lib/widgets/app_bar.dart b/lib/widgets/app_bar.dart index b95984b..4a49daf 100644 --- a/lib/widgets/app_bar.dart +++ b/lib/widgets/app_bar.dart @@ -3,6 +3,7 @@ import 'package:meshcore_open/connector/meshcore_connector.dart'; import 'package:meshcore_open/widgets/battery_indicator.dart'; import 'package:provider/provider.dart'; +import 'radio_stats_entry.dart'; import 'snr_indicator.dart'; class AppBarTitle extends StatelessWidget { @@ -10,12 +11,14 @@ class AppBarTitle extends StatelessWidget { final Widget? leading; final Widget? trailing; final bool indicators; + final bool showBatteryIndicator; final bool subtitle; const AppBarTitle( this.title, { this.leading, this.trailing, this.indicators = true, + this.showBatteryIndicator = true, this.subtitle = true, super.key, }); @@ -33,7 +36,8 @@ class AppBarTitle extends StatelessWidget { final compact = availableWidth < 170; final showSubtitle = !compact && connector.isConnected && selfName != null && subtitle; - final showBattery = availableWidth >= 60; + final showBattery = + showBatteryIndicator && availableWidth >= 60; final showSnr = availableWidth >= 110; final showIndicators = (showBattery || showSnr) && indicators; @@ -65,6 +69,16 @@ class AppBarTitle extends StatelessWidget { children: [ if (showBattery) BatteryIndicator(connector: connector), if (showSnr) SNRIndicator(connector: connector), + if (connector.supportsCompanionRadioStats) + ValueListenableBuilder( + valueListenable: connector.radioStatsNotifier, + builder: (context, _, child) => Padding( + padding: const EdgeInsets.only(left: 4), + child: AirActivityDot( + active: connector.radioStatsAirActivityPulse, + ), + ), + ), ], ), trailing ?? const SizedBox.shrink(), diff --git a/lib/widgets/path_management_dialog.dart b/lib/widgets/path_management_dialog.dart index e92f301..4e91a69 100644 --- a/lib/widgets/path_management_dialog.dart +++ b/lib/widgets/path_management_dialog.dart @@ -109,6 +109,7 @@ class _PathManagementDialogState extends State<_PathManagementDialog> { path: Uint8List.fromList(pathBytes), flipPathAround: true, targetContact: widget.contact, + pathHashByteWidth: connector.pathHashByteWidth, ), ), ), @@ -135,7 +136,9 @@ class _PathManagementDialogState extends State<_PathManagementDialog> { connector.getContacts(); } - final pathForInput = currentContact.pathIdList; + final pathForInput = currentContact.pathFormattedIdList( + connector.pathHashByteWidth, + ); final availableContacts = connector.allContacts .where((c) => c.publicKeyHex != currentContact.publicKeyHex) .toList(); diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 4084d9b..2428a77 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -14,7 +14,6 @@ import share_plus 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")) @@ -26,5 +25,4 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 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")) } diff --git a/pubspec.yaml b/pubspec.yaml index 663622b..f347ed3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,7 +55,6 @@ dependencies: cached_network_image: ^3.4.1 flutter_cache_manager: ^3.4.1 flutter_foreground_task: ^9.2.0 - wakelock_plus: ^1.4.0 characters: ^1.4.0 package_info_plus: ^9.0.0 mobile_scanner: ^7.1.4 # QR/barcode scanning diff --git a/tools/translate.py b/tools/translate.py index a51d3d1..905d435 100644 --- a/tools/translate.py +++ b/tools/translate.py @@ -14,6 +14,10 @@ Usage: # Translate all locales (missing strings only): python translate.py --in lib/l10n/app_en.arb --l10n-dir lib/l10n --missing-only + + # New locales copied from app_en.arb still match English → --missing-only skips them. + # Translate every key that still equals the template (e.g. hu, ja, ko): + python translate.py --in lib/l10n/app_en.arb --l10n-dir lib/l10n --copy-of-template --only-locales hu,ja,ko """ import argparse @@ -68,6 +72,7 @@ LOCALE_MAP = { "sk": ("Slovak", "sk"), "sl": ("Slovenian", "sl"), "bg": ("Bulgarian", "bg"), + "hu": ("Hungarian", "hu"), "el": ("Greek", "el"), "he": ("Hebrew", "he"), "th": ("Thai", "th"), @@ -261,6 +266,25 @@ def find_missing_keys(source_data: Dict[str, Any], target_data: Dict[str, Any]) return missing +def find_keys_still_template_copy(source_data: Dict[str, Any], target_data: Dict[str, Any]) -> List[str]: + """Keys whose value is still exactly the same as the template (typical after cp app_en.arb → app_xx.arb).""" + out: List[str] = [] + for key in source_data: + if key == "@@locale" or key.startswith("@"): + continue + src = source_data.get(key) + if not is_translatable_entry(key, src): + continue + if not isinstance(src, str): + continue + tgt = target_data.get(key) + if not isinstance(tgt, str) or tgt.strip() == "": + out.append(key) + elif tgt == src: + out.append(key) + return out + + def get_all_locale_files(l10n_dir: str, template_file: str) -> List[Tuple[str, str]]: """Find all locale .arb files excluding template. Returns [(locale_code, file_path)].""" locales = [] @@ -434,6 +458,15 @@ def main() -> int: ap.add_argument("--to-locale", help="Target locale code (es, fr, de, etc.)") ap.add_argument("--l10n-dir", help="Directory with locale files (translates all locales)") ap.add_argument("--missing-only", action="store_true", help="Only translate missing keys") + ap.add_argument( + "--copy-of-template", + action="store_true", + help="Only translate keys whose target text still equals app_en (use for new locales copied from English)", + ) + ap.add_argument( + "--only-locales", + help="Comma-separated locale codes to process with --l10n-dir (e.g. hu,ja,ko)", + ) ap.add_argument("--model", default="translategemma:latest", help="Ollama model (translategemma:latest or specific versions)") ap.add_argument("--fallback-model", help="Fallback model for failed translations (e.g., translategemma:27b)") ap.add_argument("--host", default="http://localhost:11434", help="Ollama host") @@ -458,6 +491,14 @@ def main() -> int: print("Input JSON must be an object at top-level.", file=sys.stderr) return 2 + if args.missing_only and args.copy_of_template: + print("Use only one of --missing-only or --copy-of-template", file=sys.stderr) + return 2 + + only_locales: Optional[set] = None + if args.only_locales: + only_locales = {x.strip() for x in args.only_locales.split(",") if x.strip()} + # Process all locales if --l10n-dir is provided if args.l10n_dir: locales = get_all_locale_files(args.l10n_dir, args.in_path) @@ -465,6 +506,12 @@ def main() -> int: print(f"No locale files found in {args.l10n_dir}", file=sys.stderr) return 1 + if only_locales is not None: + locales = [(c, p) for c, p in locales if c in only_locales] + missing = only_locales - {c for c, _ in locales} + if missing: + print(f"Warning: no app_*.arb for locale code(s): {', '.join(sorted(missing))}", file=sys.stderr) + print(f"Found {len(locales)} locale file(s) to process") total_translated = 0 @@ -478,7 +525,14 @@ def main() -> int: print(f" [{locale_code}] Failed to read {locale_path}: {e}") continue - if args.missing_only: + missing_keys: Optional[List[str]] + if args.copy_of_template: + missing_keys = find_keys_still_template_copy(source_data, target_data) + if not missing_keys: + print(f" [{locale_code}] No keys still matching template") + continue + print(f" [{locale_code}] {len(missing_keys)} key(s) still same as template") + elif args.missing_only: missing_keys = find_missing_keys(source_data, target_data) if not missing_keys: print(f" [{locale_code}] No missing keys") @@ -509,18 +563,23 @@ def main() -> int: lang_name, lang_code = LOCALE_MAP.get(args.to_locale, (args.to_locale, args.to_locale)) - # Read existing target file if --missing-only + # Read existing target file if --missing-only or --copy-of-template target_data: Dict[str, Any] = {} missing_keys: Optional[List[str]] = None - if args.missing_only and os.path.exists(args.out_path): + if (args.missing_only or args.copy_of_template) and 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 args.copy_of_template: + missing_keys = find_keys_still_template_copy(source_data, target_data) + label = "still matching template" + else: + missing_keys = find_missing_keys(source_data, target_data) + label = "missing" if not missing_keys: - print(f"No missing keys in {args.out_path}") + print(f"No {label} keys in {args.out_path}") return 0 - print(f"Found {len(missing_keys)} missing key(s) to translate") + print(f"Found {len(missing_keys)} {label} key(s) to translate") except Exception as e: print(f"Failed to read target file: {e}", file=sys.stderr) return 2 From 834850fb51d8a758e973314af43aeb1ec9d256d8 Mon Sep 17 00:00:00 2001 From: zjs81 Date: Mon, 23 Mar 2026 19:26:05 -0700 Subject: [PATCH 33/36] Add companion radio stats, adaptive backoff, path hash width, and UI improvements - Companion radio stats: poll and display noise floor, RSSI, SNR, airtime with dedicated ValueNotifier and ref-counted polling - Adaptive RF-aware TX backoff based on radio conditions instead of fixed 5s - Variable-width path hash support (1-3 bytes per hop) - Air activity dot indicator in app bar with tap to open stats screen - Jump to oldest unread setting for chat screens - 1s send cooldown on DM and channel messages - Link style: theme-aware orange, added EmailLinkifier - New languages: Hungarian, Japanese, Korean - Remove dead DeviceScreen and BatteryIndicatorChip - Remove wakelock_plus dependency - TX power fields now read as signed int8 --- lib/connector/meshcore_connector.dart | 19 +- lib/helpers/link_handler.dart | 8 +- lib/l10n/app_bg.arb | 67 +- lib/l10n/app_de.arb | 67 +- lib/l10n/app_es.arb | 67 +- lib/l10n/app_fr.arb | 67 +- lib/l10n/app_hu.arb | 2048 ++++++++++ lib/l10n/app_it.arb | 67 +- lib/l10n/app_ja.arb | 2048 ++++++++++ lib/l10n/app_ko.arb | 2048 ++++++++++ lib/l10n/app_localizations_es.dart | 2 +- lib/l10n/app_localizations_hu.dart | 3589 +++++++++++++++++ lib/l10n/app_localizations_ja.dart | 3404 ++++++++++++++++ lib/l10n/app_localizations_ko.dart | 3403 ++++++++++++++++ lib/l10n/app_localizations_pl.dart | 22 +- lib/l10n/app_localizations_sl.dart | 2 +- lib/l10n/app_localizations_zh.dart | 2 +- lib/l10n/app_nl.arb | 67 +- lib/l10n/app_pl.arb | 65 +- lib/l10n/app_pt.arb | 67 +- lib/l10n/app_ru.arb | 67 +- lib/l10n/app_sk.arb | 67 +- lib/l10n/app_sl.arb | 67 +- lib/l10n/app_sv.arb | 67 +- lib/l10n/app_uk.arb | 67 +- lib/l10n/app_zh.arb | 67 +- lib/models/companion_radio_stats.dart | 48 + lib/screens/app_settings_screen.dart | 4 +- lib/screens/channel_chat_screen.dart | 6 +- lib/screens/channel_message_path_screen.dart | 5 +- lib/screens/channels_screen.dart | 5 +- lib/screens/chat_screen.dart | 6 +- lib/screens/companion_radio_stats_screen.dart | 250 ++ lib/screens/contacts_screen.dart | 15 +- lib/screens/device_screen.dart | 267 -- lib/screens/map_screen.dart | 5 +- lib/screens/settings_screen.dart | 4 +- lib/widgets/app_bar.dart | 15 +- lib/widgets/radio_stats_entry.dart | 147 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 - test/companion_radio_stats_test.dart | 39 + 41 files changed, 17987 insertions(+), 362 deletions(-) create mode 100644 lib/l10n/app_hu.arb create mode 100644 lib/l10n/app_ja.arb create mode 100644 lib/l10n/app_ko.arb create mode 100644 lib/l10n/app_localizations_hu.dart create mode 100644 lib/l10n/app_localizations_ja.dart create mode 100644 lib/l10n/app_localizations_ko.dart create mode 100644 lib/models/companion_radio_stats.dart create mode 100644 lib/screens/companion_radio_stats_screen.dart delete mode 100644 lib/screens/device_screen.dart create mode 100644 lib/widgets/radio_stats_entry.dart create mode 100644 test/companion_radio_stats_test.dart diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index ca5dc64..7208454 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -186,6 +186,7 @@ class MeshCoreConnector extends ChangeNotifier { DateTime _lastChannelMsgRxTime = DateTime.fromMillisecondsSinceEpoch(0); static const int _radioQuietMs = 3000; static const int _radioQuietMaxWaitMs = 3000; + /// When companion radio stats are unavailable, keep the legacy fixed backoff. static const int _contactMsgBackoffFallbackMs = 5000; static const int _contactMsgBackoffMinMs = 500; @@ -349,6 +350,7 @@ class MeshCoreConnector extends ChangeNotifier { if (sw == null || !sw.isRunning) return false; return sw.elapsed < const Duration(seconds: 2); } + int? get currentFreqHz => _currentFreqHz; int? get currentBwHz => _currentBwHz; int? get currentSf => _currentSf; @@ -818,18 +820,19 @@ class MeshCoreConnector extends ChangeNotifier { // Quieter (more negative) → lower score; noisier → higher. const noiseQuietDbm = -118.0; const noiseNoisyDbm = -88.0; - final noiseT = - ((nf - noiseQuietDbm) / (noiseNoisyDbm - noiseQuietDbm)).clamp(0.0, 1.0); + final noiseT = ((nf - noiseQuietDbm) / (noiseNoisyDbm - noiseQuietDbm)) + .clamp(0.0, 1.0); final snr = stats.lastSnrDb; const snrGood = 12.0; const snrBad = -2.0; - final snrT = - (1.0 - ((snr - snrBad) / (snrGood - snrBad))).clamp(0.0, 1.0); + final snrT = (1.0 - ((snr - snrBad) / (snrGood - snrBad))).clamp(0.0, 1.0); final airBusy = _recentAirtimeBusyFraction(); - final severity = - (math.max(noiseT, snrT) * 0.82 + airBusy * 0.18).clamp(0.0, 1.0); + final severity = (math.max(noiseT, snrT) * 0.82 + airBusy * 0.18).clamp( + 0.0, + 1.0, + ); return (_contactMsgBackoffMinMs + severity * (_contactMsgBackoffMaxMs - _contactMsgBackoffMinMs)) @@ -856,9 +859,7 @@ class MeshCoreConnector extends ChangeNotifier { return bumpAt.isAfter(lastInboundRxTime) ? bumpAt : lastInboundRxTime; } - Future _waitForRadioQuiet({ - required DateTime lastInboundRxTime, - }) async { + Future _waitForRadioQuiet({required DateTime lastInboundRxTime}) async { // Wait for backoff after inbound traffic / RF airtime (avoid collision with // mesh propagation). Elapsed time uses the dot's airtime bump when newer. final backoffTargetMs = _contactMessageBackoffTargetMs(); diff --git a/lib/helpers/link_handler.dart b/lib/helpers/link_handler.dart index e83897a..b931ca1 100644 --- a/lib/helpers/link_handler.dart +++ b/lib/helpers/link_handler.dart @@ -10,10 +10,7 @@ class LinkHandler { final orange = brightness == Brightness.dark ? const Color(0xFFFFB74D) : const Color(0xFFE65100); - return base.copyWith( - color: orange, - decoration: TextDecoration.underline, - ); + return base.copyWith(color: orange, decoration: TextDecoration.underline); } /// Returns a [SelectableLinkify] on desktop or a [Linkify] on mobile. @@ -23,8 +20,7 @@ class LinkHandler { required TextStyle style, TextStyle? linkStyle, }) { - final effectiveLinkStyle = - linkStyle ?? defaultLinkStyle(context, style); + final effectiveLinkStyle = linkStyle ?? defaultLinkStyle(context, style); const options = LinkifyOptions(humanize: false, defaultToHttps: false); const linkifiers = [UrlLinkifier(), EmailLinkifier()]; void onOpen(LinkableElement link) => handleLinkTap(context, link.url); diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 7593577..d4ebb4b 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -1943,5 +1943,68 @@ "settings_multiAck": "Мулти-потвърди: {value}", "settings_telemetryModeUpdated": "Режим на телеметрията е обновен", "map_showOverlaps": "Покриване на ключа на повтаряча", - "map_runTraceWithReturnPath": "Върни се по същия път." -} \ No newline at end of file + "map_runTraceWithReturnPath": "Върни се по същия път.", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "chat_sendCooldown": "Моля, изчакайте малко, преди да изпратите отново.", + "appSettings_languageHu": "Унгарски", + "appSettings_jumpToOldestUnread": "Преминете към най-старата непочетена статия", + "appSettings_jumpToOldestUnreadSubtitle": "Когато отворите чат с непрочетени съобщения, плъзнете надолу, за да видите първото непрочетено съобщение, вместо най-новото.", + "appSettings_languageJa": "Японски", + "appSettings_languageKo": "Корейски", + "radioStats_tooltip": "Статистика за радио и мрежа", + "radioStats_screenTitle": "Статистически данни за радиопредаванията", + "radioStats_notConnected": "Свържете се с устройство, за да видите статистически данни за радиопредаване.", + "radioStats_firmwareTooOld": "Статистиката на радиостанцията изисква съвместимо софтуерно решение версия 8 или по-нова.", + "radioStats_waiting": "Изчакване на данни…", + "radioStats_noiseFloor": "Ниво на шума: {noiseDbm} dBm", + "radioStats_lastRssi": "Последен RSSI: {rssiDbm} dBm", + "radioStats_lastSnr": "Последна стойност на SNR: {snr} dB", + "radioStats_txAir": "Време на въздух (общо): {seconds} секунди", + "radioStats_rxAir": "Общо време на използване на RX (в секунди): {seconds} с", + "radioStats_chartCaption": "Ниво на шума (dBm) за последните измервания.", + "radioStats_stripNoise": "Ниво на шума: {noiseDbm} dBm", + "radioStats_stripWaiting": "Извличане на данни за радиото…", + "radioStats_settingsTile": "Статистически данни за радиостанции", + "radioStats_settingsSubtitle": "Ниво на шума, RSSI, SNR и време на пренос" +} diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 98ddb93..c128a3a 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1971,5 +1971,68 @@ "settings_telemetryModeUpdated": "Telemetriemodus aktualisiert", "settings_multiAck": "Mehrfach-Bestätigungen: {value}", "map_showOverlaps": "Überlappungen der Repeater-Taste", - "map_runTraceWithReturnPath": "Auf dem gleichen Pfad zurückkehren." -} \ No newline at end of file + "map_runTraceWithReturnPath": "Auf dem gleichen Pfad zurückkehren.", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "chat_sendCooldown": "Bitte warten Sie einen Moment, bevor Sie erneut senden.", + "appSettings_jumpToOldestUnread": "Zum ältesten, nicht gelesenen Eintrag springen", + "appSettings_languageHu": "Ungarisch", + "appSettings_jumpToOldestUnreadSubtitle": "Wenn Sie ein Chatfenster öffnen, in dem Nachrichten vorhanden sind, die noch nicht gelesen wurden, scrollen Sie zu der ersten unlesenen Nachricht, anstatt zur neuesten.", + "appSettings_languageJa": "Japanisch", + "appSettings_languageKo": "Koreanisch", + "radioStats_tooltip": "Daten zu Radio- und Mesh-Netzwerken", + "radioStats_screenTitle": "Senderinformationen", + "radioStats_notConnected": "Verbinden Sie ein Gerät, um Radiostatisiken anzuzeigen.", + "radioStats_firmwareTooOld": "Für die Verwendung der Funkstatistiken ist die Firmware-Version 8 oder höher erforderlich.", + "radioStats_waiting": "Warte auf Daten…", + "radioStats_noiseFloor": "Rauschpegel: {noiseDbm} dBm", + "radioStats_lastRssi": "Letzter RSSI-Wert: {rssiDbm} dBm", + "radioStats_lastSnr": "Letzter SNR: {snr} dB", + "radioStats_txAir": "Gesamt-TX-Zeit: {seconds} s", + "radioStats_rxAir": "Gesamt-RX-Zeit: {seconds} s", + "radioStats_chartCaption": "Rauschpegel (dBm) basierend auf den letzten Messwerten.", + "radioStats_stripNoise": "Rauschpegel: {noiseDbm} dBm", + "radioStats_stripWaiting": "Abrufen von Radiostatus…", + "radioStats_settingsTile": "Senderinformationen", + "radioStats_settingsSubtitle": "Rauschpegel, RSSI, Signal-Rausch-Verhältnis (SNR) und Nutzzeit" +} diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index a65d80f..154fec6 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1971,5 +1971,68 @@ "settings_telemetryModeUpdated": "Modo de telemetría actualizado", "settings_multiAck": "Multi-ACKs: {value}", "map_showOverlaps": "Superposiciones de tecla repetidora", - "map_runTraceWithReturnPath": "Volver atrás por el mismo camino." -} \ No newline at end of file + "map_runTraceWithReturnPath": "Volver atrás por el mismo camino.", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "appSettings_jumpToOldestUnread": "Salta a los mensajes más antiguos sin leer", + "chat_sendCooldown": "Por favor, espere un momento antes de reenviar.", + "appSettings_languageHu": "Húngaro", + "appSettings_jumpToOldestUnreadSubtitle": "Cuando abras una conversación con mensajes sin leer, desplázate hacia el primer mensaje sin leer en lugar del más reciente.", + "appSettings_languageJa": "Japonés", + "appSettings_languageKo": "Coreano", + "radioStats_tooltip": "Estadísticas de radio y malla", + "radioStats_screenTitle": "Estadísticas de radio", + "radioStats_notConnected": "Conéctese a un dispositivo para visualizar estadísticas de radio.", + "radioStats_firmwareTooOld": "Las estadísticas de radio requieren un firmware compatible v8 o posterior.", + "radioStats_waiting": "Esperando datos…", + "radioStats_noiseFloor": "Nivel de ruido: {noiseDbm} dBm", + "radioStats_lastRssi": "Último RSSI: {rssiDbm} dBm", + "radioStats_lastSnr": "Último SNR: {snr} dB", + "radioStats_txAir": "Tiempo de emisión en Texas (total): {seconds} s", + "radioStats_rxAir": "Tiempo de transmisión de RX (total): {seconds} s", + "radioStats_chartCaption": "Nivel de ruido (dBm) en muestras recientes.", + "radioStats_stripNoise": "Nivel de ruido: {noiseDbm} dBm", + "radioStats_stripWaiting": "Obteniendo estadísticas de la radio…", + "radioStats_settingsTile": "Estadísticas de radio", + "radioStats_settingsSubtitle": "Nivel de ruido, RSSI, SNR y tiempo de transmisión" +} diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 8bb0a46..90948d0 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1943,5 +1943,68 @@ "settings_multiAck": "Multi-ACKs : {value}", "settings_telemetryModeUpdated": "Le mode télémétrie a été mis à jour", "map_showOverlaps": "Chevauchement de la touche répétitive", - "map_runTraceWithReturnPath": "Revenir sur le même chemin." -} \ No newline at end of file + "map_runTraceWithReturnPath": "Revenir sur le même chemin.", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "chat_sendCooldown": "Veuillez patienter un instant avant de réessayer.", + "appSettings_jumpToOldestUnread": "Accéder au message le plus ancien non lu", + "appSettings_languageHu": "Hongrois", + "appSettings_jumpToOldestUnreadSubtitle": "Lorsque vous ouvrez une conversation contenant des messages non lus, faites défiler la page jusqu'au premier message non lu, plutôt que jusqu'au dernier.", + "appSettings_languageJa": "Japonais", + "appSettings_languageKo": "Coréen", + "radioStats_tooltip": "Statistiques des radios et des réseaux sans fil", + "radioStats_screenTitle": "Statistiques de radio", + "radioStats_notConnected": "Connectez-vous à un appareil pour visualiser les statistiques de la radio.", + "radioStats_firmwareTooOld": "Les statistiques radio nécessitent un firmware compatible v8 ou une version ultérieure.", + "radioStats_waiting": "En attente des données…", + "radioStats_noiseFloor": "Niveau de bruit : {noiseDbm} dBm", + "radioStats_lastRssi": "Dernier RSSI : {rssiDbm} dBm", + "radioStats_lastSnr": "Dernier SNR : {snr} dB", + "radioStats_txAir": "Temps d'antenne à la télévision du Texas (total) : {seconds} s", + "radioStats_rxAir": "Temps d'utilisation de l'appareil RX (total) : {seconds} s", + "radioStats_chartCaption": "Niveau de bruit (dBm) sur les échantillons récents.", + "radioStats_stripNoise": "Niveau de bruit : {noiseDbm} dBm", + "radioStats_stripWaiting": "Récupération des statistiques de la radio…", + "radioStats_settingsTile": "Statistiques de radio", + "radioStats_settingsSubtitle": "Niveau de bruit, RSSI, rapport signal/bruit (SNR) et temps d'antenne" +} diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb new file mode 100644 index 0000000..558e1f0 --- /dev/null +++ b/lib/l10n/app_hu.arb @@ -0,0 +1,2048 @@ +{ + "@@locale": "hu", + "appTitle": "MeshCore Open", + "nav_contacts": "Kapcsolatok", + "nav_channels": "Csatornák", + "nav_map": "Térkép", + "common_cancel": "Át kell venni", + "common_ok": "Rendben", + "common_connect": "Kapcsolódj", + "common_unknownDevice": "Tudatlan eszköz", + "common_save": "Mentés", + "common_delete": "Töröl", + "common_deleteAll": "Minden törlés", + "common_close": "Bezárás", + "common_edit": "Szerkesztés", + "common_add": "Hozzáad", + "common_settings": "Beállítások", + "common_disconnect": "Csatlakozást megszakasztani", + "common_connected": "Kapcsolódó", + "common_disconnected": "Elválasztva", + "common_create": "Készítsd", + "common_continue": "Folytatás", + "common_share": "Ossza meg", + "common_copy": "Másolat", + "common_retry": "Újrapróbálja", + "common_hide": "Elrejt", + "common_remove": "Eltávolít", + "common_enable": "Engedélyezés", + "common_disable": "Leteteszt", + "common_reboot": "Újraindítás", + "common_loading": "Betöltés...", + "common_notAvailable": "—", + "common_voltageValue": "{volts} V", + "@common_voltageValue": { + "placeholders": { + "volts": { + "type": "String" + } + } + }, + "common_percentValue": "{percent}%", + "@common_percentValue": { + "placeholders": { + "percent": { + "type": "int" + } + } + }, + "scanner_title": "MeshCore nyitott", + "connectionChoiceUsbLabel": "USB", + "connectionChoiceBluetoothLabel": "Bluetooth", + "connectionChoiceTcpLabel": "TCP", + "tcpScreenTitle": "TCP-n keresztül kapcsolódjon", + "tcpHostLabel": "IP-cím", + "tcpHostHint": "192.168.40.10", + "tcpPortLabel": "Múzeum", + "tcpPortHint": "5000", + "tcpStatus_notConnected": "Adja meg a célpontot, majd kapcsolja össze.", + "tcpStatus_connectingTo": "Kapcsolat a {endpoint}-hez...", + "@tcpStatus_connectingTo": { + "placeholders": { + "endpoint": { + "type": "String" + } + } + }, + "tcpErrorHostRequired": "Az IP-címet meg kell adni.", + "tcpErrorPortInvalid": "Az érték 1 és 65535 között kell lennie.", + "tcpErrorUnsupported": "A TCP-protokoll nem támogatott ez a platformon.", + "tcpErrorTimedOut": "A TCP-kapcsolat időtúllépett.", + "tcpConnectionFailed": "A TCP-kapcsolat sikertelen: {error}", + "@tcpConnectionFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "usbScreenTitle": "USB-en keresztül csatlakoztassuk", + "usbScreenSubtitle": "Válasszon egy azonosított soros eszközt, és közvetlenül csatlakoztassa a MeshCore-hoz.", + "usbScreenStatus": "Válasszon egy USB-es eszközt", + "usbScreenNote": "Az USB-es soros kommunikáció a támogatott Android eszközökön és asztali rendszereken is elérhető.", + "usbScreenEmptyState": "Nincs USB eszköz megtalálva. Csatlakoztasson egyet, majd frissítse a rendszert.", + "usbErrorPermissionDenied": "A USB-es hozzáférés megtagadva.", + "usbErrorDeviceMissing": "Az kiválasztott USB eszköz már nem elérhető.", + "usbErrorInvalidPort": "Válasszon egy érvényes USB-eszközt.", + "usbErrorBusy": "Egy másik USB-csatlakozás kérése már folyamatban van.", + "usbErrorNotConnected": "Nincs csatlakoztatott USB eszköz.", + "usbErrorOpenFailed": "Nem sikerült megnyitni a kiválasztott USB-eszközöt.", + "usbErrorConnectFailed": "Nem sikerült kapcsolatot létesíteni a kiválasztott USB-eszközhöz.", + "usbErrorUnsupported": "Ez a platform nem támogat USB-es soros kommunikációt.", + "usbErrorAlreadyActive": "Az USB-kapcsolat már be van állítva.", + "usbErrorNoDeviceSelected": "Nincs kiválasztva USB eszköz.", + "usbErrorPortClosed": "Az USB-kapcsolat nem aktív.", + "usbErrorConnectTimedOut": "Kapcsolódás sikertelen. Ellenőrizze, hogy a eszköz rendelkezik-e USB-hez tartozó firmware-rel.", + "usbFallbackDeviceName": "Web-szériás eszköz", + "usbStatus_notConnected": "Válasszon egy USB-es eszközt", + "usbStatus_connecting": "USB eszközhez való csatlakozás...", + "usbStatus_searching": "USB eszközök keresése...", + "usbConnectionFailed": "USB-kapcsolat sikertelen: {error}", + "@usbConnectionFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "scanner_scanning": "Készülékek keresése...", + "scanner_connecting": "Kapcsolódás...", + "scanner_disconnecting": "Kapcsolat megszakad...", + "scanner_notConnected": "Nem csatlakozva", + "scanner_connectedTo": "Kapcsolódik a {deviceName}-hez", + "@scanner_connectedTo": { + "placeholders": { + "deviceName": { + "type": "String" + } + } + }, + "scanner_searchingDevices": "MeshCore eszközök keresése...", + "scanner_tapToScan": "A Tap Scan funkció segítségével kereshet MeshCore eszközöket.", + "scanner_connectionFailed": "Kapcsolódás sikertelen: {error}", + "@scanner_connectionFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "scanner_stop": "Megállj", + "scanner_scan": "Szkenálás", + "scanner_bluetoothOff": "A Bluetooth kikapcsolva", + "scanner_bluetoothOffMessage": "Kérjük, kapcsolja be a Bluetooth-ot, hogy eszközök keresése lehessen.", + "scanner_chromeRequired": "Chrome böngésző szükséges", + "scanner_chromeRequiredMessage": "Ez az alkalmazás a Bluetooth funkcióhoz Google Chrome-ot vagy Chromium alapú böngészőt igényel.", + "scanner_enableBluetooth": "Engedje be a Bluetooth funkciót", + "device_quickSwitch": "Gyors váltás", + "device_meshcore": "MeshCore", + "settings_title": "Beállítások", + "settings_deviceInfo": "A készülék információi", + "settings_appSettings": "Alkalmazási beállítások", + "settings_appSettingsSubtitle": "Értesítések, üzenetküldés és térképi beállítások", + "settings_nodeSettings": "Műközép beállítások", + "settings_nodeName": "Vonal neve", + "settings_nodeNameNotSet": "Nem megállapított", + "settings_nodeNameHint": "Adja meg a csomópont nevét", + "settings_nodeNameUpdated": "Neve frissítve", + "settings_radioSettings": "Rádióbeállítások", + "settings_radioSettingsSubtitle": "Frekvencia, teljesítmény, szélesítési tényező", + "settings_radioSettingsUpdated": "A rádió beállítások frissítve", + "settings_location": "Helyszín", + "settings_locationSubtitle": "GPS koordináták", + "settings_locationUpdated": "A helyzet és a GPS beállítások frissítve", + "settings_locationBothRequired": "Kérjük, adja meg a földrajzi szélességet és hosszúságot.", + "settings_locationInvalid": "Érvénytelen szélesszög vagy hosszszög.", + "settings_locationGPSEnable": "GPS engedélyezve", + "settings_locationGPSEnableSubtitle": "Lehetővé teszi, hogy a GPS automatikusan frissítse a helyzetet.", + "settings_locationIntervalSec": "GPS-számolási intervallum (másodpercek)", + "settings_locationIntervalInvalid": "Az intervallum legalább 60 másodpercnek, de legfeljebb 86400 másodpercnak kell lennie.", + "settings_latitude": "Nyugat-–––––––––––––––––––––––––––––––", + "settings_longitude": "hosszúság", + "settings_contactSettings": "Kapcsolat beállítások", + "settings_contactSettingsSubtitle": "Beállítások, amelyek meghatározzák, hogyan lehet új kapcsolatokat hozzáadni.", + "settings_privacyMode": "Adatvédelem mód", + "settings_privacyModeSubtitle": "Elrejtsük a nevét/a helyszínt az űrianyagokban", + "settings_privacyModeToggle": "Engedje be a privát üzemmódot, hogy elrejtse a nevét és a helyét az online hirdetésekben.", + "settings_privacyModeEnabled": "Adatvédelem mód beállítva", + "settings_privacyModeDisabled": "Adatvédelem mód kikapcsolva", + "settings_actions": "Tevékenységek", + "settings_sendAdvertisement": "Hirdetés küldése", + "settings_sendAdvertisementSubtitle": "A nyilvános megjelenés", + "settings_advertisementSent": "Hirdetés elküldve", + "settings_syncTime": "Szinkronizációs idő", + "settings_syncTimeSubtitle": "Állítsa a készülék időzítését a telefon időjére", + "settings_timeSynchronized": "Időben szinkronizált", + "settings_refreshContacts": "Újraindítsd a kapcsolatok listát", + "settings_refreshContactsSubtitle": "Újra töltse a kontaktlista-adatokat a készülékről", + "settings_rebootDevice": "Újraindítás", + "settings_rebootDeviceSubtitle": "Indítsa újra a MeshCore eszközt.", + "settings_rebootDeviceConfirm": "Biztosan szeretné újraindítani a készüléket? Ebben az esetben a kapcsolat megszűnik.", + "settings_debug": "Hibakeresés", + "settings_bleDebugLog": "BLE hibaelhárítási napló", + "settings_bleDebugLogSubtitle": "BLE parancsok, válaszok és alapvető adatok", + "settings_appDebugLog": "App-debug log", + "settings_appDebugLogSubtitle": "Programozási hibajelzések", + "settings_about": "Ról", + "settings_aboutVersion": "MeshCore Open {version} verzió", + "@settings_aboutVersion": { + "placeholders": { + "version": { + "type": "String" + } + } + }, + "settings_aboutLegalese": "2026-os MeshCore nyílt forráskódú projekt", + "settings_aboutDescription": "Egy nyílt forráskódú Flutter kliens a MeshCore LoRa hálózati eszközök számára.", + "settings_aboutOpenMeteoAttribution": "LOS magassági adatok: Open-Meteo (CC BY 4.0)", + "settings_infoName": "Név", + "settings_infoId": "Az azonosító", + "settings_infoStatus": "Állapot", + "settings_infoBattery": "Akku", + "settings_infoPublicKey": "Nyelvkönyv", + "settings_infoContactsCount": "Kapcsolatok száma", + "settings_infoChannelCount": "Csatorna száma", + "settings_presets": "Előre beállított beállítások", + "settings_frequency": "Frekvencia (MHz)", + "settings_frequencyHelper": "300,0 – 2500,0", + "settings_frequencyInvalid": "Érvénytelen frekvencia (300-2500 MHz)", + "settings_bandwidth": "Kapacitás", + "settings_spreadingFactor": "Terjesztési tényező", + "settings_codingRate": "Kódolási sebesség", + "settings_txPower": "TX teljesítmény (dBm)", + "settings_txPowerHelper": "0 – 22", + "settings_txPowerInvalid": "Érvénytelen TX teljesítmény (0-22 dBm)", + "settings_clientRepeat": "Autonóm rendszer újra", + "settings_clientRepeatSubtitle": "Engedje, hogy ez a eszköz mások számára is ismételje a hálózati csomagokat.", + "settings_clientRepeatFreqWarning": "A hálózat nélküli kommunikációhoz 433, 869 vagy 918 MHz frekvenciát igényel.", + "settings_error": "Hiba: {message}", + "@settings_error": { + "placeholders": { + "message": { + "type": "String" + } + } + }, + "appSettings_title": "Alkalmazási beállítások", + "appSettings_appearance": "Megjelenés", + "appSettings_theme": "Téma", + "appSettings_themeSystem": "Alapértékek", + "appSettings_themeLight": "Világítás", + "appSettings_themeDark": "Sötét", + "appSettings_language": "Nyelv", + "appSettings_languageSystem": "Alapértékek", + "appSettings_languageEn": "Angol", + "appSettings_languageFr": "Francia", + "appSettings_languageEs": "Spanyol", + "appSettings_languageDe": "Német", + "appSettings_languagePl": "Lengyel", + "appSettings_languageSl": "szlovén nyelv", + "appSettings_languagePt": "Portugál", + "appSettings_languageIt": "Olasz", + "appSettings_languageZh": "Kínai", + "appSettings_languageSv": "Svéd", + "appSettings_languageNl": "Hollandi", + "appSettings_languageSk": "Szlovén nyelvre fordítás", + "appSettings_languageBg": "Bulgár", + "appSettings_languageRu": "Orosz", + "appSettings_languageUk": "Украинский", + "appSettings_enableMessageTracing": "Engedje meg a üzenetek nyomon követését", + "appSettings_enableMessageTracingSubtitle": "Adja meg a üzenetek részletes útvonal- és időzítési adatokat.", + "appSettings_notifications": "Értesítések", + "appSettings_enableNotifications": "Engedélyezze az értesítéseket", + "appSettings_enableNotificationsSubtitle": "Kapjon értesítéseket üzenetekről és hirdetésekről.", + "appSettings_notificationPermissionDenied": "A értesítési engedély megtagadva", + "appSettings_notificationsEnabled": "A figyelmeztetések engedélyezve", + "appSettings_notificationsDisabled": "A figyelmeztetések kikapcsolva", + "appSettings_messageNotifications": "Üzenet értesítések", + "appSettings_messageNotificationsSubtitle": "A figyelmeztetést megjelenítve, amikor új üzenet érkezik", + "appSettings_channelMessageNotifications": "Csatorna-üzenetek értesítése", + "appSettings_channelMessageNotificationsSubtitle": "A figyelmeztetést megjelenítve, amikor új üzenet érkezik a csatornáról", + "appSettings_advertisementNotifications": "Reklám értesítések", + "appSettings_advertisementNotificationsSubtitle": "A figyelmeztetést megjelenítve, amikor új csomópontok kerülnek felfedezésre.", + "appSettings_messaging": "Üzenetek küldése", + "appSettings_clearPathOnMaxRetry": "Egyértelmű út a Max Retry funkció használatával", + "appSettings_clearPathOnMaxRetrySubtitle": "A kapcsolat visszaállítás 5 sikertelen továbbítás után", + "appSettings_pathsWillBeCleared": "Ha 5-szer sikertelenül próbálunk, a útvonalat automatikusan tisztítjuk.", + "appSettings_pathsWillNotBeCleared": "A utak automatikusan nem tisztítódnak.", + "appSettings_autoRouteRotation": "Autóútok forgása", + "appSettings_autoRouteRotationSubtitle": "Válasszon a legjobb útvonalak között, vagy válassza a vízözön-módot.", + "appSettings_autoRouteRotationEnabled": "Az automatikus útvonalváltás engedélyezve", + "appSettings_autoRouteRotationDisabled": "Az automatikus útvonal-választás funkció kikapcsolva.", + "appSettings_maxRouteWeight": "Maximális útvonal súly", + "appSettings_maxRouteWeightSubtitle": "A lehető legnagyobb súly, amit egy útvonal sikeres szállítmányok során összegyűjthet.", + "appSettings_initialRouteWeight": "A kezdeti útvonal súlya", + "appSettings_initialRouteWeightSubtitle": "Az új, felfedezett útvonalakhoz tartozó kezdeti súly", + "appSettings_routeWeightSuccessIncrement": "Sikerhez vezető növelés", + "appSettings_routeWeightSuccessIncrementSubtitle": "A sikeresen teljesített útvonalhoz hozzáadott súly.", + "appSettings_routeWeightFailureDecrement": "Hibás súly csökkenése", + "appSettings_routeWeightFailureDecrementSubtitle": "A jártatásból eltávolított súly, ami a sikertelen szállítás következménye.", + "appSettings_maxMessageRetries": "Maximális üzenetek újraküldési próbálkozások", + "appSettings_maxMessageRetriesSubtitle": "A próbálkozások száma, mielőtt egy üzenetet hibásnak jelölünk.", + "path_routeWeight": "{weight}/{max}", + "@path_routeWeight": { + "placeholders": { + "weight": { + "type": "String" + }, + "max": { + "type": "String" + } + } + }, + "appSettings_battery": "Akku", + "appSettings_batteryChemistry": "Aakkum töltés kémia", + "appSettings_batteryChemistryPerDevice": "Beállítások {deviceName}-hez", + "@appSettings_batteryChemistryPerDevice": { + "placeholders": { + "deviceName": { + "type": "String" + } + } + }, + "appSettings_batteryChemistryConnectFirst": "Csatlakozzon egy eszközhez, hogy kiválassza", + "appSettings_batteryNmc": "18650 NMC (3,0-4,2 V)", + "appSettings_batteryLifepo4": "LiFePO4 (2,6–3,65 V)", + "appSettings_batteryLipo": "LiPo (3,0-4,2 V)", + "appSettings_mapDisplay": "Térkép megjelenítése", + "appSettings_showRepeaters": "Megismétlés", + "appSettings_showRepeatersSubtitle": "A térképen megjelenítsük a repeater-eket.", + "appSettings_showChatNodes": "Megjeleníts kommunikációs pontokat", + "appSettings_showChatNodesSubtitle": "A chat-szobákat megjelenítsük a térképen", + "appSettings_showOtherNodes": "Mutasson további csomópontokat", + "appSettings_showOtherNodesSubtitle": "Mutassa meg a többi hálózati elemet a térképen", + "appSettings_timeFilter": "Időbeli szűrés", + "appSettings_timeFilterShowAll": "Mutassa meg az összes csomópontot", + "appSettings_timeFilterShowLast": "Mutasson az utolsó {hours} órából származó adatokat.", + "@appSettings_timeFilterShowLast": { + "placeholders": { + "hours": { + "type": "int" + } + } + }, + "appSettings_mapTimeFilter": "Térkép időszűrő", + "appSettings_showNodesDiscoveredWithin": "Megjeleníts olyan węzveket, amelyek a következő területen lettek felfedezve:", + "appSettings_allTime": "Minden időpont", + "appSettings_lastHour": "Az utolsó óra", + "appSettings_last6Hours": "Az utóban 6 óra", + "appSettings_last24Hours": "Az utóbbi 24 óra", + "appSettings_lastWeek": "A múlt héten", + "appSettings_offlineMapCache": "Offline térkép tárolás", + "appSettings_unitsTitle": "Egységek", + "appSettings_unitsMetric": "Méter (m / kilométer)", + "appSettings_unitsImperial": "Királyi (láb / mérföld)", + "appSettings_noAreaSelected": "Nincs kiválasztott terület.", + "appSettings_areaSelectedZoom": "Kiválasztott terület (zoom: {minZoom}-{maxZoom})", + "@appSettings_areaSelectedZoom": { + "placeholders": { + "minZoom": { + "type": "int" + }, + "maxZoom": { + "type": "int" + } + } + }, + "appSettings_debugCard": "Hibakeresés", + "appSettings_appDebugLogging": "App-ban történő hibakereséshez használt naplózás", + "appSettings_appDebugLoggingSubtitle": "Log alkalmazás hibaelhárítási üzenetek", + "appSettings_appDebugLoggingEnabled": "Az alkalmazás hibaelhárítási naplózás engedélyezve", + "appSettings_appDebugLoggingDisabled": "Az alkalmazás hibaelhárítási naplózatának bekapcsolása kiküszöbölve", + "contacts_title": "Kapcsolatok", + "contacts_noContacts": "Jelenleg még nincs kapcsolat.", + "contacts_contactsWillAppear": "A kapcsolatok megjelennek, amikor a eszközök hirdetnek.", + "contacts_unread": "Olvasatlan", + "contacts_searchContactsNoNumber": "Kapcsolatok keresése...", + "contacts_searchContacts": "Keresés {number}-ban {str}…", + "@contacts_searchContacts": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, + "contacts_searchFavorites": "Keresés {number}{str}... Kedvencek", + "@contacts_searchFavorites": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, + "contacts_searchUsers": "Search {number}{str} Users...", + "@contacts_searchUsers": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, + "contacts_searchRepeaters": "Keresés {number}-on, {str} típusú adóállomások között...", + "@contacts_searchRepeaters": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, + "contacts_searchRoomServers": "Keresés {number}-ban {str}...", + "@contacts_searchRoomServers": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, + "contacts_noUnreadContacts": "Nincs olvasatlan üzenetek", + "contacts_noContactsFound": "Nincs megtalálva semmilyen kapcsolat vagy csoport.", + "contacts_deleteContact": "Kapcsolattól töröl", + "contacts_removeConfirm": "Hogy töröljem a {contactName} nevű személyt a kontaktlistából?", + "@contacts_removeConfirm": { + "placeholders": { + "contactName": { + "type": "String" + } + } + }, + "contacts_manageRepeater": "Ellenőriző eszköz kezelése", + "contacts_manageRoom": "A szobai szerver kezelése", + "contacts_roomLogin": "Szoba szerverbe való bejelentkezés", + "contacts_openChat": "Nyitott beszélgetés", + "contacts_editGroup": "Edit csoport", + "contacts_deleteGroup": "Csoport törlése", + "contacts_deleteGroupConfirm": "Hogy töröljem a \"{groupName}\"-t?", + "@contacts_deleteGroupConfirm": { + "placeholders": { + "groupName": { + "type": "String" + } + } + }, + "contacts_newGroup": "Új csoport", + "contacts_groupName": "Csoport neve", + "contacts_groupNameRequired": "A csoportnak meg kell adni a nevét.", + "contacts_groupNameReserved": "Ez a csoportnév foglalt", + "contacts_groupAlreadyExists": "A \"{name}\" nevű csoport már létezik.", + "@contacts_groupAlreadyExists": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "contacts_filterContacts": "Szűrj kontaktokat...", + "contacts_noContactsMatchFilter": "Nincs találat a megadott szűrés alapján.", + "contacts_noMembers": "Nincsenek tagok", + "contacts_lastSeenNow": "utóbbi időben", + "contacts_lastSeenMinsAgo": "~ {minutes} perc", + "@contacts_lastSeenMinsAgo": { + "placeholders": { + "minutes": { + "type": "int" + } + } + }, + "contacts_lastSeenHourAgo": "Kb. 1 óra", + "contacts_lastSeenHoursAgo": "~ {hours} óra", + "@contacts_lastSeenHoursAgo": { + "placeholders": { + "hours": { + "type": "int" + } + } + }, + "contacts_lastSeenDayAgo": "Kb. 1 nap", + "contacts_lastSeenDaysAgo": "~ {days} days", + "@contacts_lastSeenDaysAgo": { + "placeholders": { + "days": { + "type": "int" + } + } + }, + "channels_title": "Csatornák", + "channels_noChannelsConfigured": "Nincs konfigurált csatorna.", + "channels_addPublicChannel": "Hozzon létre nyilvános csatornát", + "channels_searchChannels": "Keresési opciók...", + "channels_noChannelsFound": "Nincs megtalálható csatorna", + "channels_channelIndex": "{index}-os csatorna", + "@channels_channelIndex": { + "placeholders": { + "index": { + "type": "int" + } + } + }, + "channels_hashtagChannel": "Hashtag-ok közössége", + "channels_public": "A nyilvánosság számára", + "channels_private": "Személyes", + "channels_publicChannel": "Össztávos csatorna", + "channels_privateChannel": "Személyes csatorna", + "channels_editChannel": "Csatorna szerkesztése", + "channels_muteChannel": "Csendes csatorna", + "channels_unmuteChannel": "Engedje be a hangot", + "channels_deleteChannel": "Mozdony törlése", + "channels_deleteChannelConfirm": "Törlés {name}? Ez nem visszafordítható.", + "@channels_deleteChannelConfirm": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "channels_channelDeleteFailed": "Nem sikerült törölni a \"{name}\" nevű csatornát.", + "@channels_channelDeleteFailed": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "channels_channelDeleted": "A \"{name}\" nevű csatorna törölve", + "@channels_channelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "channels_addChannel": "Csatorna hozzáadása", + "channels_channelIndexLabel": "Csatorna index", + "channels_channelName": "Csatorna neve", + "channels_usePublicChannel": "Használja a nyilvános csatornát", + "channels_standardPublicPsk": "Általános, állami által finanszírozott PSK", + "channels_pskHex": "PSK (Hexadecimális kód)", + "channels_generateRandomPsk": "Véletlenszerűen generáljon PSK-t", + "channels_enterChannelName": "Kérjük, adja meg egy csatorna nevét", + "channels_pskMustBe32Hex": "A PSK 32-bázisú hexadecimális karakterből áll.", + "channels_channelAdded": "A \"{name}\" csatorna hozzáadva", + "@channels_channelAdded": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "channels_editChannelTitle": "Módosítsd a csatornát {index}", + "@channels_editChannelTitle": { + "placeholders": { + "index": { + "type": "int" + } + } + }, + "channels_smazCompression": "SMAZ kompresszió", + "channels_channelUpdated": "A {name} csatorna frissítve", + "@channels_channelUpdated": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "channels_publicChannelAdded": "A nyilvános csatorna hozzáadva", + "channels_sortBy": "Szűrés", + "channels_sortManual": "Használati útmutató", + "channels_sortAZ": "A-Z", + "channels_sortLatestMessages": "Legfrissebb üzenetek", + "channels_sortUnread": "Olvasatlan", + "channels_createPrivateChannel": "Létrehoz egy privát csatornát", + "channels_createPrivateChannelDesc": "Titkos kulcs segítségével védelem.", + "channels_joinPrivateChannel": "Csatlakozzon egy privát csatornához", + "channels_joinPrivateChannelDesc": "Kézzel adja meg a titkos kulcsot.", + "channels_joinPublicChannel": "Csatlakozzon a nyilvános csatornához", + "channels_joinPublicChannelDesc": "Bárki csatlakozhat ehhez a csatornához.", + "channels_joinHashtagChannel": "Csatlakozzon egy hashtage-os csatornához", + "channels_joinHashtagChannelDesc": "Bárkinek lehet csatlakoznia a hashtagekhez tartozó csatornához.", + "channels_scanQrCode": "Scanned egy QR-kódot", + "channels_scanQrCodeComingSoon": "Hamarosan", + "channels_enterHashtag": "Írja be a hashtaget", + "channels_hashtagHint": "pl. #csapat", + "chat_noMessages": "Még nincs üzenet.", + "chat_sendMessageToStart": "Küldj egy üzenetet, hogy elindulj!", + "chat_originalMessageNotFound": "A eredeti üzenet nem található.", + "chat_replyingTo": "Replying to {name}", + "@chat_replyingTo": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "chat_replyTo": "Reply to {name}", + "@chat_replyTo": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "chat_location": "Helyszín", + "chat_sendMessageTo": "Küldj üzenetet {contactName}-nek", + "@chat_sendMessageTo": { + "placeholders": { + "contactName": { + "type": "String" + } + } + }, + "chat_typeMessage": "Írjon üzenetet...", + "chat_messageTooLong": "A üzenet túl hosszú (a maximális {maxBytes} bájt).", + "@chat_messageTooLong": { + "placeholders": { + "maxBytes": { + "type": "int" + } + } + }, + "chat_messageCopied": "Üzenet másolva", + "chat_messageDeleted": "Üzenet törölve", + "chat_retryingMessage": "Újrapróbálási üzenet", + "chat_retryCount": "Újrapróbál {current}/{max}", + "@chat_retryCount": { + "placeholders": { + "current": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "chat_sendGif": "Küldj GIF-ot", + "chat_reply": "Válasz", + "chat_addReaction": "Hozzon létre reakciót", + "chat_me": "Én", + "emojiCategorySmileys": "Emoji", + "emojiCategoryGestures": "Testmozgások", + "emojiCategoryHearts": "Szívak", + "emojiCategoryObjects": "Tárgyak", + "gifPicker_title": "Válasszon egy GIF-et", + "gifPicker_searchHint": "GIF-ek keresése...", + "gifPicker_poweredBy": "Forrás: GIPHY", + "gifPicker_noGifsFound": "Nincsenek GIF-ek megtalálva.", + "gifPicker_failedLoad": "Nem sikerült betölteni a GIF-fájlokat.", + "gifPicker_failedSearch": "Nem sikerült a GIF-eket megtalálni.", + "gifPicker_noInternet": "Nincs internetkapcsolat.", + "debugLog_appTitle": "App-debug log", + "debugLog_bleTitle": "BLE hibajelentő napló", + "debugLog_copyLog": "Másolat napló", + "debugLog_clearLog": "Jelzett napló", + "debugLog_copied": "Hibajelentő napló másolva", + "debugLog_bleCopied": "BLE-log másolva", + "debugLog_noEntries": "Jelenleg még nem léteznek hibaelhárítási naplókat.", + "debugLog_enableInSettings": "Engedje be az alkalmazás hibaelhárítási naplózását a beállítások menüben.", + "debugLog_frames": "Keretek", + "debugLog_rawLogRx": "Az eredeti Log-RX", + "debugLog_noBleActivity": "Jelenleg nincs BLE-hez kapcsolódó tevékenység.", + "debugFrame_length": "Keret hossza: {count} bájt", + "@debugFrame_length": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "debugFrame_command": "Parancs: 0x{value}", + "@debugFrame_command": { + "placeholders": { + "value": { + "type": "String" + } + } + }, + "debugFrame_textMessageHeader": "Címzett:", + "debugFrame_destinationPubKey": "- Célhely: {pubKey}", + "@debugFrame_destinationPubKey": { + "placeholders": { + "pubKey": { + "type": "String" + } + } + }, + "debugFrame_timestamp": "- Időbélyeg: {timestamp}", + "@debugFrame_timestamp": { + "placeholders": { + "timestamp": { + "type": "int" + } + } + }, + "debugFrame_flags": "- Jelvények: 0x{value}", + "@debugFrame_flags": { + "placeholders": { + "value": { + "type": "String" + } + } + }, + "debugFrame_textType": "- Tartalom típusa: {type} ({label})", + "@debugFrame_textType": { + "placeholders": { + "type": { + "type": "int" + }, + "label": { + "type": "String" + } + } + }, + "debugFrame_textTypeCli": "Parancssori felület (CLI)", + "debugFrame_textTypePlain": "Egyszerű, alap, hagyományos", + "debugFrame_text": "- Tartalom: \"{text}\"", + "@debugFrame_text": { + "placeholders": { + "text": { + "type": "String" + } + } + }, + "debugFrame_hexDump": "Hex-dump:", + "chat_pathManagement": "Útvonal-kezelés", + "chat_ShowAllPaths": "Mutasson meg minden útvonalat", + "chat_routingMode": "Útvonal-kezelési mód", + "chat_autoUseSavedPath": "Automatikus (az eddigi útvonal használata)", + "chat_forceFloodMode": "Erőforrás-alapú áramlás mód", + "chat_recentAckPaths": "Legutóbbi használt útvonalak (gombra kattintva):", + "chat_pathHistoryFull": "Az előző lépések listája teljes. Törölj ki a bejegyzéseket, hogy újokat hozzáadhatsd.", + "chat_hopSingular": "ugor", + "chat_hopPlural": "babér", + "chat_hopsCount": "{count} {count, plural, =1{ugrás} other{ugrások}}", + "@chat_hopsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "chat_successes": "sikerek", + "chat_removePath": "Törölje a elérési útvonalat", + "chat_noPathHistoryYet": "Még nincs útvonal-történet.\nKüldjön egy üzenetet, hogy megtudja a lehetséges útvonalakat.", + "chat_pathActions": "Céltúrások:", + "chat_setCustomPath": "Beállítsd a saját útvonalat", + "chat_setCustomPathSubtitle": "Kézzel megadott útvonal", + "chat_clearPath": "Egyértelmű út", + "chat_clearPathSubtitle": "A parancs új küldéskor újra kell aktivizálnia.", + "chat_pathCleared": "Útvonal cleared. A következő üzenet újból feltérképezheti az útvonalat.", + "chat_floodModeSubtitle": "Használja a \"útvonal\" kapcsolót az alkalmazás sávjában.", + "chat_floodModeEnabled": "Árvízvédelmi mód bekapcsolva. A visszaállítás a alkalmazásban található útvonal ikon segítségével.", + "chat_fullPath": "Teljes elérési út", + "chat_pathDetailsNotAvailable": "Az útvonal részletei még nem elérhetők. Próbálja meg küldeni egy üzenetet, hogy frissítse az információkat.", + "chat_pathSetHops": "Path set: {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}", + "@chat_pathSetHops": { + "placeholders": { + "hopCount": { + "type": "int" + }, + "status": { + "type": "String" + } + } + }, + "chat_pathSavedLocally": "Helyileg mentve. Kapcsolódjon a szinkronizáláshoz.", + "chat_pathDeviceConfirmed": "A készülék megvan.", + "chat_pathDeviceNotConfirmed": "A készülék még nem bizonyított.", + "chat_type": "Típus", + "chat_path": "Út", + "chat_publicKey": "Nyelvkönyv", + "chat_compressOutgoingMessages": "A küldött üzenetek tömörítése", + "chat_floodForced": "Áradás (kényszerített)", + "chat_directForced": "Közvetlen (erélyes)", + "chat_hopsForced": "{count} ánusz (erővel)", + "@chat_hopsForced": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "chat_floodAuto": "Vízosztás (autó)", + "chat_direct": "Közvetlen", + "chat_poiShared": "Közös erőforrás", + "chat_unread": "Olvasatlan: {count}", + "@chat_unread": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "chat_openLink": "Nyisd meg a linket?", + "chat_openLinkConfirmation": "Szeretnéd megnyitni ezt a linket a böngésződben?", + "chat_open": "Nyitott", + "chat_couldNotOpenLink": "Nem sikerült megnyitni a hivat: {url}", + "@chat_couldNotOpenLink": { + "placeholders": { + "url": { + "type": "String" + } + } + }, + "chat_invalidLink": "Érvénytelen hivatkozás formátum", + "map_title": "Grafikus ábrázás", + "map_lineOfSight": "Látási vonal", + "map_losScreenTitle": "Látási vonal", + "map_noNodesWithLocation": "Nincs olyan adatpont, amelyhez helyszín-információk tartoznak.", + "map_nodesNeedGps": "A pontoknak meg kell osztaniuk GPS koordinátáikat, hogy megjelenjenek a térképen.", + "map_nodesCount": "Csúcsok: {count}", + "@map_nodesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "map_pinsCount": "Csapok: {count}", + "@map_pinsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "map_chat": "Csevegés", + "map_repeater": "Ismétlő", + "map_room": "szoba", + "map_sensor": "Érzékelő", + "map_pinDm": "Jel (DM)", + "map_pinPrivate": "Titkos (privát)", + "map_pinPublic": "Jelmez (nyilvános)", + "map_lastSeen": "Utoljára látva", + "map_disconnectConfirm": "Biztosan szeretné kiírni ezt a készüléket?", + "map_from": "Attól", + "map_source": "Forrás", + "map_flags": "Zászló", + "map_shareMarkerHere": "Osztja ezt a tartalmat itt", + "map_setAsMyLocation": "Állítsa be a jelenlegi helyzetemként", + "map_pinLabel": "Címkét ragasztani", + "map_label": "Címke", + "map_pointOfInterest": "Érdekes hely", + "map_sendToContact": "Kapcsolatfelvételi űrlap", + "map_sendToChannel": "Küldés a csatornán", + "map_noChannelsAvailable": "Nincs elérhető csatorna.", + "map_publicLocationShare": "Térköz, nyilvános hely", + "map_publicLocationShareConfirm": "Most egy helyszínt megosztasz a {channelLabel} csatornán. Ez a csatorna nyilvános, és bárki, aki rendelkezik a PSK-val, megtekintheti.", + "@map_publicLocationShareConfirm": { + "placeholders": { + "channelLabel": { + "type": "String" + } + } + }, + "map_connectToShareMarkers": "Kapcsolódjon egy eszközhöz, hogy megoszthassa a vonalzókat.", + "map_filterNodes": "Szűrési pontok", + "map_nodeTypes": "Vonalak típusai", + "map_chatNodes": "Csevegési pontok", + "map_repeaters": "Újraküldők", + "map_otherNodes": "Egyéb csomópontok", + "map_keyPrefix": "Kulcsfontosságú előtag", + "map_filterByKeyPrefix": "Szűrj a kulcsos előtér szerint", + "map_publicKeyPrefix": "Névfelhasználó kulc-prefix", + "map_markers": "Jelölők", + "map_showSharedMarkers": "Mutassa meg a közös jeleket", + "map_showGuessedLocations": "Megjelenítsa a megjósolt csomópontok helyét", + "map_showDiscoveryContacts": "Megjelenítse a Discovery-nál elérhet kontaktokat", + "map_guessedLocation": "Tippolt hely", + "map_lastSeenTime": "Utoljára megjelent idő", + "map_sharedPin": "Gemeinsames PIN-kód", + "map_joinRoom": "Csatlakozás a szobához", + "map_manageRepeater": "Ellenőriző eszköz kezelése", + "map_tapToAdd": "Nyomj meg a csomópontokhoz, hogy hozzáadd őket az útvonalhoz.", + "map_runTrace": "Útvonal követés", + "map_removeLast": "Törölj utolsó", + "map_pathTraceCancelled": "Az útvonal követés megszakadt.", + "mapCache_title": "Offline térkép tárolás", + "mapCache_selectAreaFirst": "Válasszon egy területet, amelyet először cache-oljon.", + "mapCache_noTilesToDownload": "Nincsenek letölthető tile-ok ebben a területben.", + "mapCache_downloadTilesTitle": "Letöltsd a tile-okat", + "mapCache_downloadTilesPrompt": "Töltse le {count} darab tile-t offline használatra?", + "@mapCache_downloadTilesPrompt": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "mapCache_downloadAction": "Letöltés", + "mapCache_cachedTiles": "Tárolt {count} darab", + "@mapCache_cachedTiles": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "mapCache_cachedTilesWithFailed": "Cached {downloaded} tiles ({failed} failed)", + "@mapCache_cachedTilesWithFailed": { + "placeholders": { + "downloaded": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "mapCache_clearOfflineCacheTitle": "Tiszta offline tárhely", + "mapCache_clearOfflineCachePrompt": "Távolítsa el az összes tárolt térképmegjelenítőt?", + "mapCache_offlineCacheCleared": "A helyi memóriát töröltük.", + "mapCache_noAreaSelected": "Nincs kiválasztott terület.", + "mapCache_cacheArea": "Tároló terület", + "mapCache_useCurrentView": "Használja a jelenlegi nézetet", + "mapCache_zoomRange": "Zoom tartomány", + "mapCache_estimatedTiles": "Becsült kerámiák: {count}", + "@mapCache_estimatedTiles": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "mapCache_downloadedTiles": "Letöltve {completed} / {total}", + "@mapCache_downloadedTiles": { + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "mapCache_downloadTilesButton": "Letöltsd a tile-okat", + "mapCache_clearCacheButton": "Ósztótt adatokat", + "mapCache_failedDownloads": "Sikertelen letöltések: {count}", + "@mapCache_failedDownloads": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "mapCache_boundsLabel": "N {north}, S {south}, E {east}, W {west}", + "@mapCache_boundsLabel": { + "placeholders": { + "north": { + "type": "String" + }, + "south": { + "type": "String" + }, + "east": { + "type": "String" + }, + "west": { + "type": "String" + } + } + }, + "time_justNow": "Most", + "time_minutesAgo": "{minutes} perckel ezelőtt", + "@time_minutesAgo": { + "placeholders": { + "minutes": { + "type": "int" + } + } + }, + "time_hoursAgo": "{hours} óva", + "@time_hoursAgo": { + "placeholders": { + "hours": { + "type": "int" + } + } + }, + "time_daysAgo": "{days}d ago", + "@time_daysAgo": { + "placeholders": { + "days": { + "type": "int" + } + } + }, + "time_hour": "óra", + "time_hours": "órák", + "time_day": "nap", + "time_days": "napok", + "time_week": "het", + "time_weeks": "het, hetek", + "time_month": "hónap", + "time_months": "hónapok", + "time_minutes": "percek", + "time_allTime": "Bármely időpont", + "dialog_disconnect": "Csatlakozást megszakasztani", + "dialog_disconnectConfirm": "Biztosan szeretné kiírni ezt a készüléket?", + "login_repeaterLogin": "Ismételt bejelentkezés", + "login_roomLogin": "Szoba szerverbe való bejelentkezés", + "login_password": "Jelszó", + "login_enterPassword": "Adja meg a jelszót", + "login_savePassword": "Mentse el a jelszót", + "login_savePasswordSubtitle": "A jelszó biztonságosan tárolódik ezen a készüléken.", + "login_repeaterDescription": "Adja meg a repeater (ismétítő) jelszót, hogy hozzáférhessen a beállításokhoz és az állapot információkhoz.", + "login_roomDescription": "Adja meg a belépési kódot, hogy hozzáférhessen a beállításokhoz és az állapot információkhoz.", + "login_routing": "Útvonal meghatározás", + "login_routingMode": "Útvonal-kezelési mód", + "login_autoUseSavedPath": "Automatikus (az eddigi útvonal használata)", + "login_forceFloodMode": "Erőforrás-alapú áramlás mód", + "login_managePaths": "Útvonalak kezelése", + "login_login": "Bejelentkezés", + "login_attempt": "Megpróbálás {current}/{max}-adik", + "@login_attempt": { + "placeholders": { + "current": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "login_failed": "Belépés sikertelen: {error}", + "@login_failed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "login_failedMessage": "Belépés sikertelen. Vagy a jelszó helytelen, vagy a hálózati kapcsolat nem létesül.", + "common_reload": "Újra töltés", + "common_clear": "Egyértelmű", + "path_currentPath": "Jelenlegi útvonal: {path}", + "@path_currentPath": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "path_usingHopsPath": "{count} {count, plural, =1{ugrás} other{ugrások}} útvonal használata", + "@path_usingHopsPath": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "path_enterCustomPath": "Adja meg a saját elérési útvonalat", + "path_currentPathLabel": "Jelenlegi útvonal", + "path_hexPrefixInstructions": "Adja meg a 2 karakteres hexadecimális előtagokat minden lépéshez, tagolva kommával.", + "path_hexPrefixExample": "Példa: A1, F2, 3C (minden csomó az első részét használja a nyilvános kulcsából)", + "path_labelHexPrefixes": "Út (hex-prefixek)", + "path_helperMaxHops": "A maximális hossz 64 karakter. Minden előző rész 2 hatos számjegyből áll (1 bájt).", + "path_selectFromContacts": "Válasszon a kontaktlista elembek közül:", + "path_noRepeatersFound": "Nincs megtalálva semmilyen ismétlődő vagy helyiség-szolgáltató szervert.", + "path_customPathsRequire": "Az egyedi útvonalaknak szükségük van átjáró pontokra, amelyek képesek üzeneteket továbbítani.", + "path_invalidHexPrefixes": "Érvénytelen hexadecimális előtagok: {prefixes}", + "@path_invalidHexPrefixes": { + "placeholders": { + "prefixes": { + "type": "String" + } + } + }, + "path_tooLong": "Az út túl hosszú. A maximális engedélyezett lépések száma 64.", + "path_setPath": "Útvonal meghatározása", + "repeater_management": "Adatkapcsolás kezelése", + "room_management": "Szoba-szerver kezelés", + "repeater_managementTools": "Menedzsmentes eszközök", + "repeater_status": "Állapot", + "repeater_statusSubtitle": "Megtekintheted a repeater állapotát, statisztikáit és a környező eszközök adatait.", + "repeater_telemetry": "Adatvisszaadás", + "repeater_telemetrySubtitle": "Tekintsük a szenzorok és a rendszer állapotának adatát", + "repeater_cli": "Parancssori felület (CLI)", + "repeater_cliSubtitle": "Küldj parancsokat a repeaternek.", + "repeater_neighbors": "Szomszédok", + "repeater_neighborsSubtitle": "Tekintsük a nullás lépésű szomszédokat.", + "repeater_settings": "Beállítások", + "repeater_settingsSubtitle": "Állítsa be a repeater paramétereket", + "repeater_statusTitle": "Adatkapcsolódás állapot", + "repeater_routingMode": "Útvonal-kezelési mód", + "repeater_autoUseSavedPath": "Automatikus (az eddigi útvonal használata)", + "repeater_forceFloodMode": "Erőforrás-alapú áramlás mód", + "repeater_pathManagement": "Útvonal-kezelés", + "repeater_refresh": "Újrafriszol", + "repeater_statusRequestTimeout": "Az állapotkérés időtúlt.", + "repeater_errorLoadingStatus": "Hiba a státusz betöltés közben: {error}", + "@repeater_errorLoadingStatus": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "repeater_systemInformation": "Rendszerinformációk", + "repeater_battery": "Akku", + "repeater_clockAtLogin": "Óra (bejelentkezéskor)", + "repeater_uptime": "A rendszer elérhetősége", + "repeater_queueLength": "Várakozási sor hossza", + "repeater_debugFlags": "Hibakeresési beállítások", + "repeater_radioStatistics": "Rádió statisztika", + "repeater_lastRssi": "Utolsó RSSI érték", + "repeater_lastSnr": "Utolsó SNR", + "repeater_noiseFloor": "Háttérzaj szint", + "repeater_txAirtime": "TX Airtime", + "repeater_rxAirtime": "RX Airtime", + "repeater_packetStatistics": "Csomagok statisztikája", + "repeater_sent": "Elküldve", + "repeater_received": "Megérkezett", + "repeater_duplicates": "Duplák", + "repeater_daysHoursMinsSecs": "{days} days {hours}h {minutes}m {seconds}s", + "@repeater_daysHoursMinsSecs": { + "placeholders": { + "days": { + "type": "int" + }, + "hours": { + "type": "int" + }, + "minutes": { + "type": "int" + }, + "seconds": { + "type": "int" + } + } + }, + "repeater_packetTxTotal": "Összesen: {total}, Árvíz: {flood}, Közvetlen: {direct}", + "@repeater_packetTxTotal": { + "placeholders": { + "total": { + "type": "int" + }, + "flood": { + "type": "String" + }, + "direct": { + "type": "String" + } + } + }, + "repeater_packetRxTotal": "Összesen: {total}, Árvíz: {flood}, Közvetlen: {direct}", + "@repeater_packetRxTotal": { + "placeholders": { + "total": { + "type": "int" + }, + "flood": { + "type": "String" + }, + "direct": { + "type": "String" + } + } + }, + "repeater_duplicatesFloodDirect": "Áradás: {flood}, Közvetlen: {direct}", + "@repeater_duplicatesFloodDirect": { + "placeholders": { + "flood": { + "type": "String" + }, + "direct": { + "type": "String" + } + } + }, + "repeater_duplicatesTotal": "Összesen: {total}", + "@repeater_duplicatesTotal": { + "placeholders": { + "total": { + "type": "int" + } + } + }, + "repeater_settingsTitle": "Adatátvisszaadási beállítások", + "repeater_basicSettings": "Alapbeállítások", + "repeater_repeaterName": "Adóállomás neve", + "repeater_repeaterNameHelper": "Ez a repeater neve", + "repeater_adminPassword": "Adminisztrátori jelszó", + "repeater_adminPasswordHelper": "Teljes jogosultságú jelszó", + "repeater_guestPassword": "Vendég felhasználói név/jelszó", + "repeater_guestPasswordHelper": "Csak olvasási jogosítást biztosító jelszó", + "repeater_radioSettings": "Rádióbeállítások", + "repeater_frequencyMhz": "Frekvencia (MHz)", + "repeater_frequencyHelper": "300–2500 MHz", + "repeater_txPower": "TX Power", + "repeater_txPowerHelper": "1-30 dBm", + "repeater_bandwidth": "Adatkapacitás", + "repeater_spreadingFactor": "Terjesztési tényező", + "repeater_codingRate": "Kódolási sebesség", + "repeater_locationSettings": "Helyszínbeállítások", + "repeater_latitude": "Nyugat–keleti szélesség", + "repeater_latitudeHelper": "Desztes fokok (pl. 37,7749)", + "repeater_longitude": "hosszúság", + "repeater_longitudeHelper": "Desztes fokok (pl. -122.4194)", + "repeater_features": "Jellemzők", + "repeater_packetForwarding": "Csomagok továbbítás", + "repeater_packetForwardingSubtitle": "Engedje, hogy a repeater továbbítsa a csomagokat.", + "repeater_guestAccess": "Vendégek számára elérhető", + "repeater_guestAccessSubtitle": "Engedje meg a vendégek számára, hogy csak olvassák a tartalmat", + "repeater_privacyMode": "Adatvédelem mód", + "repeater_privacyModeSubtitle": "Elrejtse a nevét/a helyszínt az űrlapon", + "repeater_advertisementSettings": "Reklámbeállítások", + "repeater_localAdvertInterval": "Helyi hirdetés időtartama", + "repeater_localAdvertIntervalMinutes": "{minutes} perc", + "@repeater_localAdvertIntervalMinutes": { + "placeholders": { + "minutes": { + "type": "int" + } + } + }, + "repeater_floodAdvertInterval": "Vízosztály-hirdetés időtartama", + "repeater_floodAdvertIntervalHours": "{hours} óra", + "@repeater_floodAdvertIntervalHours": { + "placeholders": { + "hours": { + "type": "int" + } + } + }, + "repeater_encryptedAdvertInterval": "Kódolt hirdetés-szünet", + "repeater_dangerZone": "Veszélyzóna", + "repeater_rebootRepeater": "Újraindítás", + "repeater_rebootRepeaterSubtitle": "Indítsa újra a repeater-t.", + "repeater_rebootRepeaterConfirm": "Biztosan szeretné újraindítani ezt a repeatert?", + "repeater_regenerateIdentityKey": "Újra generálja az azonosító kulcsot", + "repeater_regenerateIdentityKeySubtitle": "Új nyilvános/személyes kulcs-párt generáljon", + "repeater_regenerateIdentityKeyConfirm": "Ez új azonosítást fog létrehozni a repeater számára. Folytatni?", + "repeater_eraseFileSystem": "Törölje a fájlrendszert", + "repeater_eraseFileSystemSubtitle": "Formázza a duplázó fájlrendszert.", + "repeater_eraseFileSystemConfirm": "FIGYELEM: Ez törli az összes adatot a repeater-en. Ez nem visszafordítható!", + "repeater_eraseSerialOnly": "Az Erase funkció csak a soros konzolon érhető el.", + "repeater_commandSent": "Parancs elküldve: {command}", + "@repeater_commandSent": { + "placeholders": { + "command": { + "type": "String" + } + } + }, + "repeater_errorSendingCommand": "Hibás parancs küldés: {error}", + "@repeater_errorSendingCommand": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "repeater_confirm": "Beküldve", + "repeater_settingsSaved": "Beállítások sikeresen mentve", + "repeater_errorSavingSettings": "Hibás beállítások mentése: {error}", + "@repeater_errorSavingSettings": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "repeater_refreshBasicSettings": "Visszaállítás az alapértékekre", + "repeater_refreshRadioSettings": "Frissítse a rádió beállításait", + "repeater_refreshTxPower": "Újraindítás TX-támogatással", + "repeater_refreshLocationSettings": "Újraindítás helyszín beállításokkal", + "repeater_refreshPacketForwarding": "Csomagok továbbításának frissítése", + "repeater_refreshGuestAccess": "Újraindítás vendégHozzáférés", + "repeater_refreshPrivacyMode": "Visszaállítás a magánéletvédő módra", + "repeater_refreshAdvertisementSettings": "Újraindítás hirdetés beállítások", + "repeater_refreshed": "{label} frissítve", + "@repeater_refreshed": { + "placeholders": { + "label": { + "type": "String" + } + } + }, + "repeater_errorRefreshing": "Hiba a {label} frissítés közben", + "@repeater_errorRefreshing": { + "placeholders": { + "label": { + "type": "String" + } + } + }, + "repeater_cliTitle": "CLI (parancssori felület)", + "repeater_debugNextCommand": "Hibakeresés, következő parancs", + "repeater_commandHelp": "Segítség", + "repeater_clearHistory": "Egyértelmű történet", + "repeater_noCommandsSent": "Még egyik parancsot sem küldtünk.", + "repeater_typeCommandOrUseQuick": "Írja be a parancsot alább, vagy használja a gyors parancsokat.", + "repeater_enterCommandHint": "Írja be a parancsot...", + "repeater_previousCommand": "Előző parancs", + "repeater_nextCommand": "Következő parancs", + "repeater_enterCommandFirst": "Add meg először egy parancsot", + "repeater_cliCommandFrameTitle": "CLI parancssor felépítése", + "repeater_cliCommandError": "Hiba: {error}", + "@repeater_cliCommandError": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "repeater_cliQuickGetName": "Kapcsold össze a nevet", + "repeater_cliQuickGetRadio": "Szerezd a rádiót", + "repeater_cliQuickGetTx": "Szerezd a TX-t", + "repeater_cliQuickNeighbors": "Szomszédok", + "repeater_cliQuickVersion": "Verzió", + "repeater_cliQuickAdvertise": "Hirdetés", + "repeater_cliQuickClock": "óra", + "repeater_cliHelpAdvert": "Elküldi egy hirdetési csomagot", + "repeater_cliHelpReboot": "Újraindítja a készüléket. (Kérjük, vegye figyelembe, hogy valószínűleg \"Időhiba\" üzenetet fog kapni, ami normális)", + "repeater_cliHelpClock": "A jelenlegi időt mutatja az egyes eszközök karórája alapján.", + "repeater_cliHelpPassword": "Új adminisztrációs jelszót állít be a eszköz számára.", + "repeater_cliHelpVersion": "Megjeleníti a készülék verzióját és a szoftver verziószámát.", + "repeater_cliHelpClearStats": "Visszaállítja a különböző statisztikai mérőszámokat a nullára.", + "repeater_cliHelpSetAf": "Beállítja az idő-szabályozási tényezőt.", + "repeater_cliHelpSetTx": "Beállítja a LoRa átviteli teljesítményt dBm-ben (a rendszer újraindításával alkalmazható).", + "repeater_cliHelpSetRepeat": "Engedélyezi vagy tiltja meg a repeater szerepet ezen a csomón.", + "repeater_cliHelpSetAllowReadOnly": "(Szoba szerver) Ha \"igen\", akkor üres jelszóval történő bejelentkezés engedélyezett lesz, de nem lehet üzeneteket küldeni a szobában. (Csak olvasási funkció)", + "repeater_cliHelpSetFloodMax": "Beállítja a bejövő adatcsomagok maximális számát (ha ez a érték nagyobb vagy egyenlő a maximális értékkel, a csomag nem továbbítódik).", + "repeater_cliHelpSetIntThresh": "Beállítja az interferencia határértéket (dB-ben). Az alapérték 14. Ha 0-ra állítja, kiküntheti a csatornák közötti interferencia detektálást.", + "repeater_cliHelpSetAgcResetInterval": "Beállítja az intervallumot, amely a \"Automatikus gain\" szabályozó újraindításához szükséges. Beállítás értéke 0, ha a funkciót le kell tiltani.", + "repeater_cliHelpSetMultiAcks": "Engedélyezi vagy kikapcsolja a „dupla visszaigazolás” funkciót.", + "repeater_cliHelpSetAdvertInterval": "Beállítja az időzítő intervallumot percenként, hogy egy helyi (nincs átjáró) hirdetési csomagot küldjen. Beállítás értéke 0, ha a funkciót le szeretné tiltani.", + "repeater_cliHelpSetFloodAdvertInterval": "Beállítja az időzítő intervallumot órában, hogy egy \"áramló\" hirdetési üzenetet küldjön. Beállítás értéke 0, ha a funkciót kikapcsolni kell.", + "repeater_cliHelpSetGuestPassword": "Beállítja/frissíti a vendég felhasználói fiókot. (Ez lehetővé teszi a visszatérő felhasználók számára, hogy a \"Statistika lekérdezése\" kérést elküldjék)", + "repeater_cliHelpSetName": "Megadja az űrlap neve.", + "repeater_cliHelpSetLat": "Beállítja az hirdetés térképen megjelenő pont koordinátájának (tizedes fokokban) a latitude-ját.", + "repeater_cliHelpSetLon": "Beállítja az hirdetés térképen megjelenő hosszúság koordinátát (tizedes fokokban).", + "repeater_cliHelpSetRadio": "Teljesen új rádióparamétereket állít be, és azokat a beállításokba menti. Az alkalmazásához \"újraindítás\" parancs szükséges.", + "repeater_cliHelpSetRxDelay": "Beállítások (kísérleti): Alapérték (legalább 1 értékre kell állítani, hogy hatás legyen), amely alapján a fogadott csomagokhoz enyhe késést alkalmazunk, a jelet ereje/pontszám alapján. 0-ra állítva a funkciót lekapcsoljuk.", + "repeater_cliHelpSetTxDelay": "Beállítja egy tényezőt, amely a légköri idővel szorozva, egy áramlás-üzem módú csomaghoz, valamint egy véletlenszerű slot-rendszerhez, hogy késleltesse a továbbítását. (az ütközések valószínűségének csökkentése érdekében)", + "repeater_cliHelpSetDirectTxDelay": "Hasonló a txdelay-hez, de ebben az esetben egy véletlenszerű késést alkalmazunk a közvetlen módú csomagok továbbításakor.", + "repeater_cliHelpSetBridgeEnabled": "Engedélyez/Tiltás a híd funkciójának.", + "repeater_cliHelpSetBridgeDelay": "Állíts be egy késleztatást a csomagok újbóli továbbításakor.", + "repeater_cliHelpSetBridgeSource": "Döntse el, hogy a híd fogadott vagy elküldött csomagokat fogja-e továbbítani.", + "repeater_cliHelpSetBridgeBaud": "Állítsa be a soros kommunikáció sebességét az RS232 hídok számára.", + "repeater_cliHelpSetBridgeSecret": "Állítsa be a titkos kapcsolatot az ESPNOW hídokhoz.", + "repeater_cliHelpSetAdcMultiplier": "Lehetővé teszi a felhasználónak, hogy egyedi tényezőt állíts be a riportolt akkumulátor feszültségének módosításához (ez csak bizonyos alkatrészeken támogatott).", + "repeater_cliHelpTempRadio": "Időjárás szerinti rádióparamétereket állít be a megadott időtartamra, majd visszaállítja az eredeti beállításokat. (Nem menti a beállításokat a beállítások részben).", + "repeater_cliHelpSetPerm": "A ACL-t módosítja. Ha a \"permissions\" érték 0, akkor eltávolítja a megfelelő bejegyzést (a pubkey előtag alapján). Új bejegyzést hoz létre, ha a pubkey-hex teljes hossza, és jelenleg nem szerepel az ACL-ben. A bejegyzést frissíti a megfelelő pubkey előtag alapján. A engedélyek különbözőek a különböző firmware szerepek között, de az alsó 2 bit a következő értékeket képviseli: 0 (Vendég), 1 (Csak olvasás), 2 (Olvasás és írás), 3 (Adminisztrátor)", + "repeater_cliHelpGetBridgeType": "Kapcsolatok: hid típusú, RS232, ESPNOW", + "repeater_cliHelpLogStart": "Elindítja a csomagok naplózását a fájlrendszerbe.", + "repeater_cliHelpLogStop": "Megállítja a csomagok naplózását a fájlrendszerbe.", + "repeater_cliHelpLogErase": "Törli a fájlrendszerből a csomagok log-fájljait.", + "repeater_cliHelpNeighbors": "Mutat egy listát, amely tartalmazza a más repeater-ek által hallott adatok listáját, amelyek 0-hop hirdetések révén érhetők el. Minden sor az alábbi formát követi: id-prefix-hex:timestamp:snr-times-4", + "repeater_cliHelpNeighborRemove": "Törli az első, a megadott kulcs-prefix (hexadecimális formában) alapján megegyező bejegyzést a szomszédok listájából.", + "repeater_cliHelpRegion": "(sorozat) Lista az összes meghatározott területet és a jelenlegi árvízvédelmi engedélyeket.", + "repeater_cliHelpRegionLoad": "FIGYELEM: ez egy speciális, több parancsot tartalmazó futtatás. Minden következő parancs egy területtel kapcsolatos, amely egyenletes szóközökkel (a szülő-gyermek kapcsolatot jelző) megkülönböztethető. A parancs végrehajtása egy üres sor/parancs küldésével történik.", + "repeater_cliHelpRegionGet": "Keresések egy adott név előtérrel (vagy \"*\" globális hatókörre). Válasz: \"-> region-név (szülő-név) 'F'\"", + "repeater_cliHelpRegionPut": "Hozzáad vagy frissíti egy régió definíciót megadott néven.", + "repeater_cliHelpRegionRemove": "Eltávolítja a megadott nevet használó régió-definíciót. (pontosan meg kell egyeznie, és nem lehet gyermekrégiója)", + "repeater_cliHelpRegionAllowf": "Beállítja a megadott területre vonatkozó \"víz\" jogosultságot. (A globális/régi beállítások esetén a \"*\" jelölő)", + "repeater_cliHelpRegionDenyf": "Eltávolítja a megadott területre vonatkozó \"F\"lood (víz) engedélyt. (FIGYELEM: jelenleg nem javasolt ezt a globális/régi verzióban használni!!)", + "repeater_cliHelpRegionHome": "Visszaállítja a jelenlegi „otthoni” régiót. (Ez a beállítás még nem került alkalmazásra, csak jövőbeli használatra fenyelve)", + "repeater_cliHelpRegionHomeSet": "Beállítja a \"házi\" régiót.", + "repeater_cliHelpRegionSave": "Megőrzi a régió listát/térképet a tárolóban.", + "repeater_cliHelpGps": "Megadja a GPS állapotát. Ha a GPS kikapcsolva van, akkor csak \"ki\" választot ad, ha be van, akkor \"be\", \"állapot\", \"pozíció\", \"satellitok száma\" értékeket ad.", + "repeater_cliHelpGpsOnOff": "Engedi a GPS működés állapotát.", + "repeater_cliHelpGpsSync": "A hálózati időt az GPS óra időjével szinkronizálja.", + "repeater_cliHelpGpsSetLoc": "Beállítja a węsz pozícióját GPS koordináták alapján, és menti a beállításokat.", + "repeater_cliHelpGpsAdvert": "Adja meg a hirdetés konfigurációjának helyszín-információját:\n- none: ne tartalmazza a helyszínt a hirdetésekben\n- share: megosztja a GPS-helyszínt (SensorManager-ből)\n- prefs: hirdeti a beállításokban tárolt helyszínt", + "repeater_cliHelpGpsAdvertSet": "Beállítja a hirdetés helyszín-specifikus beállításait.", + "repeater_commandsListTitle": "Parancsok listája", + "repeater_commandsListNote": "FIGYELEM: a különböző \"set ...\" parancsok mellett létezik egy \"get ...\" parancs is.", + "repeater_general": "Általános", + "repeater_settingsCategory": "Beállítások", + "repeater_bridge": "Híd", + "repeater_logging": "Naplózás", + "repeater_neighborsRepeaterOnly": "Szomszédok (Csak ismétlő funkció)", + "repeater_regionManagementRepeaterOnly": "Regionális menedzsment (Csak egyirányú kommunikáció)", + "repeater_regionNote": "Region-specifikus parancsokat vezettek be a régiók definiálására és a hozzájuk tartozó engedélyek kezelésére.", + "repeater_gpsManagement": "GPS-vezérlés", + "repeater_gpsNote": "Az GPS-al kapcsolatos funkciók lehetővé teszik a helyszín-személyesítéssel kapcsolatos feladatok kezelését.", + "telemetry_receivedData": "Kapott adatokat a szenzorokról", + "telemetry_requestTimeout": "Az adatkapcsolati kérés sikertelen.", + "telemetry_errorLoading": "Hiba az adatok begyűjtésében: {error}", + "@telemetry_errorLoading": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "telemetry_noData": "Nincsenek elérhető telemetriadatok.", + "telemetry_channelTitle": "{channel} csatorna", + "@telemetry_channelTitle": { + "placeholders": { + "channel": { + "type": "int" + } + } + }, + "telemetry_batteryLabel": "Akku", + "telemetry_voltageLabel": "Feszültség", + "telemetry_mcuTemperatureLabel": "MCU hőmérséklet", + "telemetry_temperatureLabel": "Hőmérséklet", + "telemetry_currentLabel": "Jelenlegi", + "telemetry_batteryValue": "{percent}% / {volts}V", + "@telemetry_batteryValue": { + "placeholders": { + "percent": { + "type": "int" + }, + "volts": { + "type": "String" + } + } + }, + "telemetry_voltageValue": "{volts}V", + "@telemetry_voltageValue": { + "placeholders": { + "volts": { + "type": "String" + } + } + }, + "telemetry_currentValue": "{amps}A", + "@telemetry_currentValue": { + "placeholders": { + "amps": { + "type": "String" + } + } + }, + "telemetry_temperatureValue": "{celsius} °C / {fahrenheit} °F", + "@telemetry_temperatureValue": { + "placeholders": { + "celsius": { + "type": "String" + }, + "fahrenheit": { + "type": "String" + } + } + }, + "neighbors_receivedData": "Kapott szomszédok adatait", + "neighbors_requestTimedOut": "A szomszédok kérik, hogy tiltsák le a kamerát.", + "neighbors_errorLoading": "Hiba a szomszédok betöltésében: {error}", + "@neighbors_errorLoading": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "neighbors_repeatersNeighbors": "Ismétlő eszközök, szomszédok", + "neighbors_noData": "Nincsenek elérhető szomszédokról adatok.", + "neighbors_unknownContact": "Tudatlan {pubkey}", + "@neighbors_unknownContact": { + "placeholders": { + "pubkey": { + "type": "String" + } + } + }, + "neighbors_heardAgo": "Értsd: {time} sitten", + "@neighbors_heardAgo": { + "placeholders": { + "time": { + "type": "String" + } + } + }, + "channelPath_title": "Csomagok útvonala", + "channelPath_viewMap": "Megtekinthető térkép", + "channelPath_otherObservedPaths": "Egyéb megfigyelt utak", + "channelPath_repeaterHops": "Adat továbbító lépések", + "channelPath_noHopDetails": "Ez a csomag nem tartalmaz részletes információkat a \"hop\" (vagy más hasonló) szót használó kifejezésekről.", + "channelPath_messageDetails": "Üzenet részletei", + "channelPath_senderLabel": "Megküldő", + "channelPath_timeLabel": "Idő", + "channelPath_repeatsLabel": "Ismétli", + "channelPath_pathLabel": "Útvonal {index}", + "channelPath_observedLabel": "Megfigyelt", + "channelPath_observedPathTitle": "Megfigyelt útvonal: {index} • {hops}", + "@channelPath_observedPathTitle": { + "placeholders": { + "index": { + "type": "int" + }, + "hops": { + "type": "String" + } + } + }, + "channelPath_noLocationData": "Nincs helyszínadat.", + "channelPath_timeWithDate": "{day}/{month} {time}", + "@channelPath_timeWithDate": { + "placeholders": { + "day": { + "type": "int" + }, + "month": { + "type": "int" + }, + "time": { + "type": "String" + } + } + }, + "channelPath_timeOnly": "{time}", + "@channelPath_timeOnly": { + "placeholders": { + "time": { + "type": "String" + } + } + }, + "channelPath_unknownPath": "Megfejt", + "channelPath_floodPath": "Árvíz", + "channelPath_directPath": "Közvetlen", + "channelPath_observedZeroOf": "0-ból {total}", + "@channelPath_observedZeroOf": { + "placeholders": { + "total": { + "type": "int" + } + } + }, + "channelPath_observedSomeOf": "{observed} of {total} hops", + "@channelPath_observedSomeOf": { + "placeholders": { + "observed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "channelPath_mapTitle": "Útvonal térkép", + "channelPath_noRepeaterLocations": "Ez a útvonal nem támogat repeater-t.", + "channelPath_primaryPath": "Útvonal {index} (Elsődleges)", + "@channelPath_primaryPath": { + "placeholders": { + "index": { + "type": "int" + } + } + }, + "@channelPath_pathLabel": { + "placeholders": { + "index": { + "type": "int" + } + } + }, + "channelPath_pathLabelTitle": "Út", + "channelPath_observedPathHeader": "Megfigyelt útvonal", + "channelPath_selectedPathLabel": "{label} • {prefixes}", + "@channelPath_selectedPathLabel": { + "placeholders": { + "label": { + "type": "String" + }, + "prefixes": { + "type": "String" + } + } + }, + "channelPath_noHopDetailsAvailable": "Ez a csomag nem tartalmaz részletes információkat a szállításhoz.", + "channelPath_unknownRepeater": "Tudatlan erősítő", + "community_title": "Helyi közösség", + "community_create": "Teremtsd meg a közösséget", + "community_createDesc": "Légyon létre egy új közösséget, és osszák meg QR-kód segítségével.", + "community_join": "Csatlakozjon", + "community_joinTitle": "Csatlakozzon a közösséghez", + "community_joinConfirmation": "Szeretne csatlakozni a közösséghez, {name}?", + "@community_joinConfirmation": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_scanQr": "QR-kód olvasó a közösség számára", + "community_scanInstructions": "Fordítsa a kamerát egy közösségi QR-kód irányába.", + "community_showQr": "Megjelenítse a QR-kódot", + "community_publicChannel": "Összetartó, közösségi", + "community_hashtagChannel": "Helyi hashtaget", + "community_name": "Helyi közösség neve", + "community_enterName": "Kérjük, a közösség nevét írja be.", + "community_created": "A \"{name}\" nevű közösség létrehozva", + "@community_created": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_joined": "Csatlakozott a {name} közösséghez", + "@community_joined": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_qrTitle": "Osszpontosítás a közösségben", + "community_qrInstructions": "Scanned this QR-kódot, hogy csatlakozhat a {name} csoporthoz.", + "@community_qrInstructions": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_hashtagPrivacyHint": "A közösségi hashtagekhez tartozó csatornák csak a közösség tagjai számára érhetők el.", + "community_invalidQrCode": "Érvénytelen közösségi QR-kód", + "community_alreadyMember": "Már tag vagy", + "community_alreadyMemberMessage": "Már tagja {name}-nek.", + "@community_alreadyMemberMessage": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_addPublicChannel": "Hozzon létre egy közösségi nyilvános csatornát", + "community_addPublicChannelHint": "Automatikusan hozzon létre ezt a csatornát a közösség számára.", + "community_noCommunities": "Még egyik közösség sem csatlakozott.", + "community_scanOrCreate": "Scelle egy QR-kódot, vagy hozzon létre egy közösséget, hogy elinduljon.", + "community_manageCommunities": "Közösségek kezelése", + "community_delete": "Hagyományos közösségi élet", + "community_deleteConfirm": "Hagyom {name}-et?", + "@community_deleteConfirm": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_deleteChannelsWarning": "Ezem törli is {count} csatornát és a hozzá tartozó üzeneteket.", + "@community_deleteChannelsWarning": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "community_deleted": "A közösség, amely {name}", + "@community_deleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_regenerateSecret": "Titkos visszaállítás", + "community_regenerateSecretConfirm": "Újra kell generálni a titkos kulcsot {name} számára? Minden tagnak be kell szkennelnie az új QR-kódot, hogy továbbra is kommunikálhasson.", + "@community_regenerateSecretConfirm": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_regenerate": "Újraalakítás", + "community_secretRegenerated": "Titkos kulcs megújult {name} számára.", + "@community_secretRegenerated": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_updateSecret": "Frissítési titok", + "community_secretUpdated": "Titkos információ frissítve {name} számára", + "@community_secretUpdated": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_scanToUpdateSecret": "Scanned a új QR-kódot, hogy frissítsük a {name} számára megőrzött titkos információt.", + "@community_scanToUpdateSecret": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_addHashtagChannel": "Adjon egy közösségi hashtaget", + "community_addHashtagChannelDesc": "Hozz létre egy hashtage-os csatornát ennek a közösségnek", + "community_selectCommunity": "Válasszon közösséget", + "community_regularHashtag": "Rendszeres hashtag", + "community_regularHashtagDesc": "Önmagas szintű hashtaget (bárki csatlakozhat)", + "community_communityHashtag": "Helyi hashtaget", + "community_communityHashtagDesc": "Csak a közösség tagjai számára", + "community_forCommunity": "{name} számára", + "@community_forCommunity": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "listFilter_tooltip": "Szűrés és rendezés", + "listFilter_sortBy": "Szűrés", + "listFilter_latestMessages": "Legfrissebb üzenetek", + "listFilter_heardRecently": "Úgy hallottam, hogy...", + "listFilter_az": "A-Z", + "listFilter_filters": "Szűrők", + "listFilter_all": "Mind", + "listFilter_favorites": "Kedvencek", + "listFilter_addToFavorites": "Megerősítés kívánságlistára", + "listFilter_removeFromFavorites": "Törölj a kedvencekből", + "listFilter_users": "Felhasználók", + "listFilter_repeaters": "Újraküldők", + "listFilter_roomServers": "Szoba-szolgálatok", + "listFilter_unreadOnly": "Csak olvasatlan", + "listFilter_newGroup": "Új csoport", + "pathTrace_you": "Te", + "pathTrace_failed": "A útvonal követése sikertelen.", + "pathTrace_notAvailable": "Az útvonal követési funkció nem elérhető.", + "pathTrace_refreshTooltip": "Út mentesség frissítése.", + "pathTrace_someHopsNoLocation": "Egy vagy több búzavirág hiányozik a helyszínéről!", + "pathTrace_clearTooltip": "Egyértelmű út.", + "losSelectStartEnd": "Válassza ki a kezdő és a végpontokat a LOS-hoz.", + "losRunFailed": "A látószög ellenőrzése sikertelen: {error}", + "@losRunFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "losClearAllPoints": "Teljesen tisztázzuk az összes pontot", + "losRunToViewElevationProfile": "Használja a LOS-t, hogy megtekinthesse a magasságkülönbségek diagramját.", + "losMenuTitle": "LOS menü", + "losMenuSubtitle": "A térképen található pontok kiválasztására vagy a térképen hosszúra nyomva, hogy egyedi pontokat definiálhassunk.", + "losShowDisplayNodes": "Megjelenítsen a megjelenítési egységeket", + "losCustomPoints": "Egyedi pontok", + "losCustomPointLabel": "Egyedi {index}", + "@losCustomPointLabel": { + "placeholders": { + "index": { + "type": "int" + } + } + }, + "losPointA": "A pont A", + "losPointB": "Pont B", + "losAntennaA": "Antenna A: {value} {unit}", + "@losAntennaA": { + "placeholders": { + "value": { + "type": "String" + }, + "unit": { + "type": "String" + } + } + }, + "losAntennaB": "Antenna B: {value} {unit}", + "@losAntennaB": { + "placeholders": { + "value": { + "type": "String" + }, + "unit": { + "type": "String" + } + } + }, + "losRun": "Futtass a LOS-on", + "losNoElevationData": "Nincsenek emelkedési adatok.", + "losProfileClear": "{distance} {distanceUnit}, clear LOS, min clearance {clearance} {heightUnit}", + "@losProfileClear": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "clearance": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "losProfileBlocked": "{distance} {distanceUnit}, amelyet {obstruction} akadályoz meg {heightUnit}-ban", + "@losProfileBlocked": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "losStatusChecking": "LOS: ellenőrzés...", + "losStatusNoData": "LOS: nincs adat", + "losStatusSummary": "LOS: {clear}/{total} tisztított, {blocked} blokkolt, {unknown} ismeretlen", + "@losStatusSummary": { + "placeholders": { + "clear": { + "type": "int" + }, + "total": { + "type": "int" + }, + "blocked": { + "type": "int" + }, + "unknown": { + "type": "int" + } + } + }, + "losErrorElevationUnavailable": "Az alábbi minták esetében nem áll rendelkezésre magasságadat.", + "losErrorInvalidInput": "Hibás vagy hiányos táblázatok a LOS (Loss of Signal) számításához.", + "losRenameCustomPoint": "Állítsa meg a saját pont nevét", + "losPointName": "Pont neve", + "losShowPanelTooltip": "Megjelenítse a LOS paneelt", + "losHidePanelTooltip": "Rejtse el a LOS paneelt", + "losElevationAttribution": "Magasságadatok: Open-Meteo (CC BY 4.0)", + "losLegendRadioHorizon": "Radio Horizont", + "losLegendLosBeam": "LOS jelzés", + "losLegendTerrain": "Terület", + "losFrequencyLabel": "Hatósság", + "losFrequencyInfoTooltip": "Lásd a számítás részleteit", + "losFrequencyDialogTitle": "A rádióhullámok hatótávolságának kiszámítása", + "losFrequencyDialogDescription": "A {baselineK} értékből kezdve, {baselineFreq} MHz-os frekvencián, a számítás az aktuális {frequencyMHz} MHz-os sávhoz igazítja a k-tényezőt, amely meghatározza a görbös rádióhatótávolság határát.", + "@losFrequencyDialogDescription": { + "description": "Explain how the calculation uses the baseline frequency and derived k-factor.", + "placeholders": { + "baselineK": { + "type": "double" + }, + "baselineFreq": { + "type": "double" + }, + "frequencyMHz": { + "type": "double" + }, + "kFactor": { + "type": "double" + } + } + }, + "contacts_pathTrace": "Útvonal követése", + "contacts_ping": "Ping", + "contacts_repeaterPathTrace": "Az útvonal követése a repeaterig", + "contacts_repeaterPing": "Ping-szinkronizáló", + "contacts_roomPathTrace": "Kapcsolat a szobai szerverrel", + "contacts_roomPing": "Ping-szolgáló szerver", + "contacts_chatTraceRoute": "Útvonal meghatározása", + "contacts_pathTraceTo": "Keresse meg a {name} címét.", + "@contacts_pathTraceTo": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "contacts_clipboardEmpty": "A kiválasztott szöveg üres.", + "contacts_invalidAdvertFormat": "Érvénytelen kontaktinformáció", + "contacts_contactImported": "Kapcsolat létrejött.", + "contacts_contactImportFailed": "Nem sikerült a kapcsolatot importálni.", + "contacts_zeroHopAdvert": "Zero Hop reklám", + "contacts_floodAdvert": "Árvízre vonatkozó hirdetés", + "contacts_copyAdvertToClipboard": "Másolja a hirdetést a kiválasztási ablakba", + "contacts_addContactFromClipboard": "Adjon hozzá egy kapcsolatot a kiválasztott listából", + "contacts_ShareContact": "Másolja a kapcsolatot a kiválasztóba", + "contacts_ShareContactZeroHop": "Ossza meg a kapcsolatot hirdetés segítségével", + "contacts_zeroHopContactAdvertSent": "Kapcsolatot a hirdetésen keresztül.", + "contacts_zeroHopContactAdvertFailed": "Nem sikerült a kapcsolatot elküldeni.", + "contacts_contactAdvertCopied": "A hirdetés másolva a vágólapra.", + "contacts_contactAdvertCopyFailed": "Az hirdetés másolása a vágólapra sikertelen.", + "notification_activityTitle": "MeshCore tevékenységek", + "notification_messagesCount": "{count} {count, plural, =1{üzenet} other{üzenetek}}", + "@notification_messagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notification_channelMessagesCount": "{count} {count, plural, =1{csatornaüzenet} other{csatornaüzenetek}}", + "@notification_channelMessagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notification_newNodesCount": "{count} {count, plural, =1{új csomópont} other{új csomópontok}}", + "@notification_newNodesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notification_newTypeDiscovered": "Új {contactType} megtalálva", + "@notification_newTypeDiscovered": { + "placeholders": { + "contactType": { + "type": "String" + } + } + }, + "notification_receivedNewMessage": "Új üzenetet kaptam", + "settings_gpxExportRepeaters": "Külső eszközök / helyi szerver a GPX formátumba", + "settings_gpxExportRepeatersSubtitle": "Exportálható repeater/szobaterm-szerver, amely egy GPX fájlban tárolja a helyzetet.", + "settings_gpxExportContacts": "GPX export funkciók", + "settings_gpxExportContactsSubtitle": "Az export funkció lehetővé teszi, hogy a GPS fájlban megadott helyszínen is megőrizzük az útvonalat.", + "settings_gpxExportAll": "Exportálja az összes kapcsolatot GPX formátumban.", + "settings_gpxExportAllSubtitle": "Az összes elérhetőséget, amelyekhez egy helyszín tartozik, egy GPX fájlba exportálja.", + "settings_gpxExportSuccess": "A GPX fájl sikeresen exportálva lett.", + "settings_gpxExportNoContacts": "Nincs exportálható kapcsolatok.", + "settings_gpxExportNotAvailable": "Nem támogatott a jelenlegi eszközön/rendszeren.", + "settings_gpxExportError": "Hiba történt az export során.", + "settings_gpxExportRepeatersRoom": "Adatátvisszaadó eszközök és helyiségi szerverek helyei", + "settings_gpxExportChat": "Kapcsolódó helyszínek", + "settings_gpxExportAllContacts": "Az összes kapcsolat helyszíne", + "settings_gpxExportShareText": "A meshcore-open-ból exportált térkéadatumok", + "settings_gpxExportShareSubject": "meshcore-open GPX formátumú térképi adatok export", + "snrIndicator_nearByRepeaters": "Helyszíni erősítők", + "snrIndicator_lastSeen": "Utoljára, amikor látták", + "contactsSettings_title": "Kapcsolatok beállításai", + "contactsSettings_autoAddTitle": "Automatikus felfedezés", + "contactsSettings_otherTitle": "Egyéb kapcsolattal kapcsolatos beállítások", + "contactsSettings_autoAddUsersTitle": "Automatikus felhasználói hozzáadás", + "contactsSettings_autoAddUsersSubtitle": "Engedje, hogy a segítő automatikusan hozzáadja az új felhasználókat.", + "contactsSettings_autoAddRepeatersTitle": "Automatikus visszatöltés", + "contactsSettings_autoAddRepeatersSubtitle": "Engedje, hogy a segítő eszköz automatikusan hozzáadja az új, megtalált jelzőállomásokat.", + "contactsSettings_autoAddRoomServersTitle": "Automatikus szobák szerverek hozzáadása", + "contactsSettings_autoAddRoomServersSubtitle": "Engedje, hogy a segítő automatikusan hozzáadja az új, megtalált hálózati szervereket.", + "contactsSettings_autoAddSensorsTitle": "Automatikus érzékelők hozzáadása", + "contactsSettings_autoAddSensorsSubtitle": "Engedje, hogy a kísérő automatikusan hozzáadja az új, megtalált szenzorokat.", + "contactsSettings_overwriteOldestTitle": "Felülírja a legrégebbet", + "contactsSettings_overwriteOldestSubtitle": "Amikor a névsor telítődik, a legidősebb, de még nem kedvencként jelölt személyt helyettesíti egy újabb.", + "discoveredContacts_Title": "Megtalált kapcsolatok", + "discoveredContacts_noMatching": "Nincs megegyező kapcsolat.", + "discoveredContacts_searchHint": "Keress új kapcsolatokat", + "discoveredContacts_contactAdded": "Kapcsolat hozzáadva", + "discoveredContacts_addContact": "Adjon személyhez", + "discoveredContacts_copyContact": "Másolja a kapcsolatot a vágólapra", + "discoveredContacts_deleteContact": "Törölj a feltalált kapcsolatot", + "discoveredContacts_deleteContactAll": "Törölj minden megtalált kapcsolatot", + "discoveredContacts_deleteContactAllContent": "Biztos, hogy szeretné törölni az összes eddig megtalált kapcsolatot?", + "chat_sendCooldown": "Kérjük, várjon egy pillanatot, mielőtt újra elküldené.", + "appSettings_jumpToOldestUnread": "Jelentkezzen az legörebb, olvasatlan üzenetre", + "appSettings_jumpToOldestUnreadSubtitle": "Amikor egy új csevet indítunk, amelyben vannak olvashatatlan üzenetek, görgessük a listát, hogy a legelső, olvashatatlan üzenet megjelenjen, nem pedig az utolsó.", + "appSettings_languageHu": "Magyar", + "appSettings_languageJa": "Japán", + "appSettings_languageKo": "Koreai", + "radioStats_tooltip": "Rádió és hálózati statisztikák", + "radioStats_screenTitle": "Rádió statisztikák", + "radioStats_notConnected": "Csatlakozzon egy eszközhöz, hogy megtekinthesse a rádió adatok statisztikáit.", + "radioStats_firmwareTooOld": "A rádió statisztikákhoz v8 vagy újabb verziójú szoftver szükséges.", + "radioStats_waiting": "Adatokra vár…", + "radioStats_noiseFloor": "Háttérzaj szint: {noiseDbm} dBm", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "radioStats_lastRssi": "Utolsó RSSI érték: {rssiDbm} dBm", + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "radioStats_lastSnr": "Utolsó SNR: {snr} dB", + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "radioStats_txAir": "TX-es idő (összesen): {seconds} másodperc", + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "radioStats_rxAir": "RX használat időtartama (összesen): {seconds} s", + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "radioStats_chartCaption": "Háttérzaj szint (dBm) a legutóbbi minták alapján.", + "radioStats_stripNoise": "Háttérzaj szint: {noiseDbm} dBm", + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "radioStats_stripWaiting": "Rádió adatok begyűjtése…", + "radioStats_settingsTile": "Rádió statisztikák", + "radioStats_settingsSubtitle": "Háttérzaj, RSSI, zaj-sűrűség, és a használat időtartama", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, + "settings_denyAll": "Elutasítom", + "settings_privacySettingsDescription": "Válassza ki, hogy az eszközének melyik információkat oszt meg másokkal.", + "settings_privacySubtitle": "Ellenőrizd, hogy milyen információkat osztanak meg.", + "settings_privacy": "Adatvédelem beállítások", + "settings_allowByContact": "Lehetővé teszi a kapcsolatok kezelését", + "settings_allowAll": "Engedje meg mindent", + "settings_telemetryBaseMode": "Adatkapcsolati alapállapot", + "settings_telemetryLocationMode": "Adatkapcsolási helyszín mód", + "settings_telemetryEnvironmentMode": "Adatkapcsolati környezeti mód", + "settings_advertLocation": "Reklám megjelenési hely", + "settings_advertLocationSubtitle": "A hirdetés tartalmazza a helyszínt.", + "settings_multiAck": "Többszöri visszaigazolások: {value}", + "settings_telemetryModeUpdated": "A telemetriamód frissítve", + "contact_info": "Kapcsolattartási információk", + "contact_settings": "Kapcsolat beállítások", + "contact_telemetry": "Adatvisszaadás", + "contact_lastSeen": "Utoljára, amikor látták", + "contact_clearChat": "Tiszta beszélgetés", + "contact_teleBase": "Adatgyűjtő központ", + "contact_teleBaseSubtitle": "Engedje meg a akkumulátor töltöttségi szintjének és alapvető adatoknak megosztását.", + "contact_teleLoc": "Adatkapcsolati helyszín", + "contact_teleLocSubtitle": "Engedje meg a helyadatok megosztását", + "contact_teleEnv": "Adatkapcsolati környezet", + "contact_teleEnvSubtitle": "Engedje meg az érzékelő adatok megosztását", + "map_showOverlaps": "Az ismétlő kulcsok ütköznek", + "map_runTraceWithReturnPath": "Visszaforduljon az eredeti úton." +} diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index b168e75..99befbb 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -1943,5 +1943,68 @@ "settings_telemetryModeUpdated": "Modalità telemetria aggiornata", "settings_multiAck": "Multi-ACKs: {value}", "map_showOverlaps": "Sovrapposizioni della chiave ripetitore", - "map_runTraceWithReturnPath": "Tornare indietro sullo stesso percorso" -} \ No newline at end of file + "map_runTraceWithReturnPath": "Tornare indietro sullo stesso percorso", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "appSettings_jumpToOldestUnreadSubtitle": "Quando si apre una chat con messaggi non letti, scorrete verso l'alto fino al primo messaggio non letto, invece che al più recente.", + "chat_sendCooldown": "Si prega di attendere un momento prima di inviare nuovamente.", + "appSettings_jumpToOldestUnread": "Vai al messaggio più vecchio non letto", + "appSettings_languageHu": "Ungherese", + "appSettings_languageJa": "Giapponese", + "appSettings_languageKo": "Coreano", + "radioStats_tooltip": "Statistiche per radio e reti", + "radioStats_screenTitle": "Statistiche radio", + "radioStats_notConnected": "Connettiti a un dispositivo per visualizzare le statistiche radio.", + "radioStats_firmwareTooOld": "Le statistiche radio richiedono il firmware versione 8 o successiva.", + "radioStats_noiseFloor": "Livello di rumore: {noiseDbm} dBm", + "radioStats_waiting": "In attesa dei dati…", + "radioStats_lastRssi": "Ultimo valore RSSI: {rssiDbm} dBm", + "radioStats_lastSnr": "Ultimo SNR: {snr} dB", + "radioStats_txAir": "Tempo di trasmissione in diretta (totale): {seconds} s", + "radioStats_rxAir": "Tempo di trasmissione RX (totale): {seconds} s", + "radioStats_chartCaption": "Livello di rumore (dBm) misurato su campioni recenti.", + "radioStats_stripNoise": "Livello di rumore: {noiseDbm} dBm", + "radioStats_stripWaiting": "Recupero delle statistiche radio…", + "radioStats_settingsTile": "Statistiche radio", + "radioStats_settingsSubtitle": "Livello di rumore, RSSI, rapporto segnale/rumore (SNR) e tempo di trasmissione" +} diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb new file mode 100644 index 0000000..d63834c --- /dev/null +++ b/lib/l10n/app_ja.arb @@ -0,0 +1,2048 @@ +{ + "@@locale": "ja", + "appTitle": "MeshCore Open", + "nav_contacts": "連絡先", + "nav_channels": "チャンネル", + "nav_map": "地図", + "common_cancel": "キャンセル", + "common_ok": "了解", + "common_connect": "接続する", + "common_unknownDevice": "不明なデバイス", + "common_save": "保存", + "common_delete": "削除", + "common_deleteAll": "すべて削除", + "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} V", + "@common_voltageValue": { + "placeholders": { + "volts": { + "type": "String" + } + } + }, + "common_percentValue": "{percent}%", + "@common_percentValue": { + "placeholders": { + "percent": { + "type": "int" + } + } + }, + "scanner_title": "MeshCore オープン", + "connectionChoiceUsbLabel": "USB", + "connectionChoiceBluetoothLabel": "ブルートゥース", + "connectionChoiceTcpLabel": "TCP", + "tcpScreenTitle": "TCP を使用して接続", + "tcpHostLabel": "IPアドレス", + "tcpHostHint": "192.168.40.10", + "tcpPortLabel": "港", + "tcpPortHint": "5000", + "tcpStatus_notConnected": "エンドポイントを入力し、接続する", + "tcpStatus_connectingTo": "{endpoint} への接続中...", + "@tcpStatus_connectingTo": { + "placeholders": { + "endpoint": { + "type": "String" + } + } + }, + "tcpErrorHostRequired": "IPアドレスが必要です。", + "tcpErrorPortInvalid": "ポート番号は1から65535の範囲で指定してください。", + "tcpErrorUnsupported": "このプラットフォームでは、TCP 転送はサポートされていません。", + "tcpErrorTimedOut": "TCP 接続がタイムアウトしました。", + "tcpConnectionFailed": "TCP接続に失敗しました:{error}", + "@tcpConnectionFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "usbScreenTitle": "USB経由で接続", + "usbScreenSubtitle": "検出されたシリアルデバイスを選択し、MeshCoreノードに直接接続してください。", + "usbScreenStatus": "USBデバイスを選択する", + "usbScreenNote": "USBシリアルポートは、サポートされているAndroidデバイスおよびデスクトッププラットフォームで利用可能です。", + "usbScreenEmptyState": "USBデバイスが見つかりませんでした。「別のUSBデバイスを接続して、再度確認してください。」", + "usbErrorPermissionDenied": "USBへのアクセス許可が拒否されました。", + "usbErrorDeviceMissing": "選択されたUSBデバイスは、もう利用できません。", + "usbErrorInvalidPort": "有効なUSBデバイスを選択してください。", + "usbErrorBusy": "別のUSB接続の要求がすでに処理中です。", + "usbErrorNotConnected": "USBデバイスは接続されていません。", + "usbErrorOpenFailed": "選択したUSBデバイスを開くことができません。", + "usbErrorConnectFailed": "選択したUSBデバイスへの接続に失敗しました。", + "usbErrorUnsupported": "このプラットフォームでは、USBシリアル通信はサポートされていません。", + "usbErrorAlreadyActive": "USB接続はすでに確立されています。", + "usbErrorNoDeviceSelected": "USBデバイスは選択されていません。", + "usbErrorPortClosed": "USB接続は確立されていません。", + "usbErrorConnectTimedOut": "接続がタイムアウトしました。デバイスにUSBコンパニオンファームウェアがインストールされていることを確認してください。", + "usbFallbackDeviceName": "ウェブシリアルデバイス", + "usbStatus_notConnected": "USBデバイスを選択する", + "usbStatus_connecting": "USBデバイスへの接続中...", + "usbStatus_searching": "USBデバイスを検索中...", + "usbConnectionFailed": "USB接続に失敗しました:{error}", + "@usbConnectionFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "scanner_scanning": "デバイスをスキャン中...", + "scanner_connecting": "接続中...", + "scanner_disconnecting": "切断...", + "scanner_notConnected": "接続されていない", + "scanner_connectedTo": "{deviceName} に接続", + "@scanner_connectedTo": { + "placeholders": { + "deviceName": { + "type": "String" + } + } + }, + "scanner_searchingDevices": "MeshCoreデバイスの検索", + "scanner_tapToScan": "MeshCore デバイスを検索するには、「スキャン」ボタンをタップしてください。", + "scanner_connectionFailed": "接続に失敗しました:{error}", + "@scanner_connectionFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "scanner_stop": "停止", + "scanner_scan": "スキャン", + "scanner_bluetoothOff": "Bluetooth はオフになっています", + "scanner_bluetoothOffMessage": "Bluetoothを有効にして、デバイスを検索してください。", + "scanner_chromeRequired": "Chrome ブラウザが必須です", + "scanner_chromeRequiredMessage": "このWebアプリケーションは、Bluetooth機能を利用するために、Google ChromeまたはChromiumベースのブラウザが必要です。", + "scanner_enableBluetooth": "Bluetoothを有効にする", + "device_quickSwitch": "素早い切り替え", + "device_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_contactSettings": "連絡設定", + "settings_contactSettingsSubtitle": "連絡先を追加する設定", + "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 {version}版", + "@settings_aboutVersion": { + "placeholders": { + "version": { + "type": "String" + } + } + }, + "settings_aboutLegalese": "2026年のMeshCoreオープンソースプロジェクト", + "settings_aboutDescription": "MeshCore LoRaメッシュネットワークデバイス用の、オープンソースのFlutterクライアント。", + "settings_aboutOpenMeteoAttribution": "LOS 標高データ:Open-Meteo (CC BY 4.0)", + "settings_infoName": "名前", + "settings_infoId": "ID", + "settings_infoStatus": "ステータス", + "settings_infoBattery": "バッテリー", + "settings_infoPublicKey": "公開鍵", + "settings_infoContactsCount": "連絡先数", + "settings_infoChannelCount": "チャンネル数", + "settings_presets": "プリセット", + "settings_frequency": "周波数 (MHz)", + "settings_frequencyHelper": "300.0 - 2500.0", + "settings_frequencyInvalid": "無効な周波数 (300-2500 MHz)", + "settings_bandwidth": "帯域幅", + "settings_spreadingFactor": "伝播係数", + "settings_codingRate": "コーディング速度", + "settings_txPower": "TX 信号電力 (dBm)", + "settings_txPowerHelper": "0 - 22", + "settings_txPowerInvalid": "無効な送信電力 (0-22 dBm)", + "settings_clientRepeat": "オフグリッド(電力網から孤立した状態)の繰り返し", + "settings_clientRepeatSubtitle": "このデバイスが、他のデバイスに対してメッシュパケットを繰り返し送信できるようにする。", + "settings_clientRepeatFreqWarning": "オフグリッドでの再送には、433MHz、869MHz、または918MHzの周波数が必要です。", + "settings_error": "エラー:{message}", + "@settings_error": { + "placeholders": { + "message": { + "type": "String" + } + } + }, + "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_languageUk": "ウクライナ語", + "appSettings_enableMessageTracing": "メッセージ追跡機能を有効にする", + "appSettings_enableMessageTracingSubtitle": "メッセージに関する詳細な経路およびタイミングに関するメタデータを表示する", + "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_maxRouteWeight": "最大ルート重量", + "appSettings_maxRouteWeightSubtitle": "ある経路が、成功裏に配送された場合に、積み上げられる最大重量", + "appSettings_initialRouteWeight": "初期ルートの重み", + "appSettings_initialRouteWeightSubtitle": "新たに発見された経路の初期重量", + "appSettings_routeWeightSuccessIncrement": "成功時の重み増加", + "appSettings_routeWeightSuccessIncrementSubtitle": "配送が成功した場合に、経路に追加される重量", + "appSettings_routeWeightFailureDecrement": "失敗時の重み減少", + "appSettings_routeWeightFailureDecrementSubtitle": "配送に失敗した際に、経路から取り除かれた重量", + "appSettings_maxMessageRetries": "最大メッセージ再試行回数", + "appSettings_maxMessageRetriesSubtitle": "メッセージを「失敗」とマークするまでの、再試行回数", + "path_routeWeight": "{weight}/{max}", + "@path_routeWeight": { + "placeholders": { + "weight": { + "type": "String" + }, + "max": { + "type": "String" + } + } + }, + "appSettings_battery": "バッテリー", + "appSettings_batteryChemistry": "電池の化学", + "appSettings_batteryChemistryPerDevice": "{deviceName} 単位", + "@appSettings_batteryChemistryPerDevice": { + "placeholders": { + "deviceName": { + "type": "String" + } + } + }, + "appSettings_batteryChemistryConnectFirst": "デバイスを選択するために接続する", + "appSettings_batteryNmc": "18650型 NMC (3.0-4.2V)", + "appSettings_batteryLifepo4": "LiFePO4 (2.6-3.65V)", + "appSettings_batteryLipo": "LiPo (3.0-4.2V)", + "appSettings_mapDisplay": "地図の表示", + "appSettings_showRepeaters": "繰り返し再生機能", + "appSettings_showRepeatersSubtitle": "地図上にリピーターノードを表示する", + "appSettings_showChatNodes": "チャットノードの表示", + "appSettings_showChatNodesSubtitle": "地図上にチャットノードを表示する", + "appSettings_showOtherNodes": "他のノードを表示する", + "appSettings_showOtherNodesSubtitle": "地図上に、他のノードの種類を表示する", + "appSettings_timeFilter": "時間フィルター", + "appSettings_timeFilterShowAll": "すべてのノードを表示する", + "appSettings_timeFilterShowLast": "過去 {hours} 時間のノードを表示する", + "@appSettings_timeFilterShowLast": { + "placeholders": { + "hours": { + "type": "int" + } + } + }, + "appSettings_mapTimeFilter": "地図の表示期間を絞り込む", + "appSettings_showNodesDiscoveredWithin": "以下の範囲内で発見されたノードを表示する:", + "appSettings_allTime": "すべての期間", + "appSettings_lastHour": "直前の", + "appSettings_last6Hours": "過去6時間", + "appSettings_last24Hours": "過去24時間", + "appSettings_lastWeek": "先週", + "appSettings_offlineMapCache": "オフライン用地図キャッシュ", + "appSettings_unitsTitle": "単位", + "appSettings_unitsMetric": "メートル (m) / キロメートル (km)", + "appSettings_unitsImperial": "帝国 (フィート / マイル)", + "appSettings_noAreaSelected": "選択されたエリアはありません", + "appSettings_areaSelectedZoom": "選択された範囲(ズームレベル:{minZoom}~{maxZoom})", + "@appSettings_areaSelectedZoom": { + "placeholders": { + "minZoom": { + "type": "int" + }, + "maxZoom": { + "type": "int" + } + } + }, + "appSettings_debugCard": "デバッグ", + "appSettings_appDebugLogging": "アプリケーションのデバッグ用ログ", + "appSettings_appDebugLoggingSubtitle": "ログアプリのデバッグメッセージ(トラブルシューティング用)", + "appSettings_appDebugLoggingEnabled": "アプリケーションのデバッグ用ログ機能が有効になっています。", + "appSettings_appDebugLoggingDisabled": "アプリケーションのデバッグログが無効化されています。", + "contacts_title": "連絡先", + "contacts_noContacts": "現時点では、連絡先はまだありません。", + "contacts_contactsWillAppear": "デバイスが広告を行う際に、連絡先が表示されます。", + "contacts_unread": "未読", + "contacts_searchContactsNoNumber": "連絡先を検索...", + "contacts_searchContacts": "{number}件の{str}に関する連絡先を検索...", + "@contacts_searchContacts": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, + "contacts_searchFavorites": "{number}件の{str}を検索...", + "@contacts_searchFavorites": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, + "contacts_searchUsers": "{number}件の{str}に関するユーザーを検索する...", + "@contacts_searchUsers": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, + "contacts_searchRepeaters": "{number} {str} までの検索...", + "@contacts_searchRepeaters": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, + "contacts_searchRoomServers": "{number} {str} 部屋のサーバーを検索する...", + "@contacts_searchRoomServers": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, + "contacts_noUnreadContacts": "未読の連絡先はありません", + "contacts_noContactsFound": "連絡先またはグループは見つかりませんでした。", + "contacts_deleteContact": "連絡先を削除", + "contacts_removeConfirm": "{contactName} を連絡先から削除しますか?", + "@contacts_removeConfirm": { + "placeholders": { + "contactName": { + "type": "String" + } + } + }, + "contacts_manageRepeater": "リピーターの管理", + "contacts_manageRoom": "ルームサーバーの管理", + "contacts_roomLogin": "ルームサーバーへのログイン", + "contacts_openChat": "自由な会話", + "contacts_editGroup": "編集グループ", + "contacts_deleteGroup": "グループを削除", + "contacts_deleteGroupConfirm": "{groupName} を削除しますか?", + "@contacts_deleteGroupConfirm": { + "placeholders": { + "groupName": { + "type": "String" + } + } + }, + "contacts_newGroup": "新しいグループ", + "contacts_groupName": "グループ名", + "contacts_groupNameRequired": "グループ名が必須です", + "contacts_groupNameReserved": "このグループ名はすでに使用されています。", + "contacts_groupAlreadyExists": "グループ「{name}」はすでに存在しています", + "@contacts_groupAlreadyExists": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "contacts_filterContacts": "連絡先をフィルタリングする…", + "contacts_noContactsMatchFilter": "指定された条件に合致する連絡先は見つかりませんでした。", + "contacts_noMembers": "メンバーはいない", + "contacts_lastSeenNow": "最近", + "contacts_lastSeenMinsAgo": "~{minutes} 分", + "@contacts_lastSeenMinsAgo": { + "placeholders": { + "minutes": { + "type": "int" + } + } + }, + "contacts_lastSeenHourAgo": "約1時間", + "contacts_lastSeenHoursAgo": "~ {hours} 時間", + "@contacts_lastSeenHoursAgo": { + "placeholders": { + "hours": { + "type": "int" + } + } + }, + "contacts_lastSeenDayAgo": "~1日", + "contacts_lastSeenDaysAgo": "~{days}日間", + "@contacts_lastSeenDaysAgo": { + "placeholders": { + "days": { + "type": "int" + } + } + }, + "channels_title": "チャンネル", + "channels_noChannelsConfigured": "設定されたチャンネルがありません", + "channels_addPublicChannel": "パブリックチャンネルを追加する", + "channels_searchChannels": "検索オプション...", + "channels_noChannelsFound": "チャンネルが見つかりませんでした", + "channels_channelIndex": "チャンネル {index}", + "@channels_channelIndex": { + "placeholders": { + "index": { + "type": "int" + } + } + }, + "channels_hashtagChannel": "ハッシュタグチャンネル", + "channels_public": "一般の人々", + "channels_private": "個人の", + "channels_publicChannel": "一般チャンネル", + "channels_privateChannel": "プライベートチャンネル", + "channels_editChannel": "チャンネルを編集する", + "channels_muteChannel": "ミュート機能", + "channels_unmuteChannel": "ミュートを解除する", + "channels_deleteChannel": "チャンネルを削除する", + "channels_deleteChannelConfirm": "{name} を削除しますか? これは取り消すことができません。", + "@channels_deleteChannelConfirm": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "channels_channelDeleteFailed": "チャンネル「{name}」の削除に失敗しました。", + "@channels_channelDeleteFailed": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "channels_channelDeleted": "チャンネル「{name}」が削除されました", + "@channels_channelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "channels_addChannel": "チャンネルを追加", + "channels_channelIndexLabel": "チャンネルインデックス", + "channels_channelName": "チャンネル名", + "channels_usePublicChannel": "パブリックチャンネルを使用する", + "channels_standardPublicPsk": "標準的な公用 PSK", + "channels_pskHex": "PSK (ヘックス)", + "channels_generateRandomPsk": "ランダムなPSK(正交符号分割変調)を生成する", + "channels_enterChannelName": "チャンネル名を入力してください", + "channels_pskMustBe32Hex": "PSKは32桁の16進数で構成されている必要があります。", + "channels_channelAdded": "チャンネル「{name}」を追加", + "@channels_channelAdded": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "channels_editChannelTitle": "チャンネル {index} の編集", + "@channels_editChannelTitle": { + "placeholders": { + "index": { + "type": "int" + } + } + }, + "channels_smazCompression": "SMAZ 圧縮", + "channels_channelUpdated": "チャンネル「{name}」が更新されました", + "@channels_channelUpdated": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "channels_publicChannelAdded": "パブリックチャンネルが追加されました", + "channels_sortBy": "並び替え", + "channels_sortManual": "マニュアル", + "channels_sortAZ": "AからZ", + "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_replyingTo": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "chat_replyTo": "{name}への返信", + "@chat_replyTo": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "chat_location": "場所", + "chat_sendMessageTo": "{contactName} へのメッセージを送信する", + "@chat_sendMessageTo": { + "placeholders": { + "contactName": { + "type": "String" + } + } + }, + "chat_typeMessage": "メッセージを入力してください…", + "chat_messageTooLong": "メッセージが長すぎる({maxBytes} バイトを超える)。", + "@chat_messageTooLong": { + "placeholders": { + "maxBytes": { + "type": "int" + } + } + }, + "chat_messageCopied": "メッセージがコピーされました", + "chat_messageDeleted": "メッセージは削除されました", + "chat_retryingMessage": "再試行メッセージ", + "chat_retryCount": "{current} / {max} 回目", + "@chat_retryCount": { + "placeholders": { + "current": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "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": "生のログ-RX", + "debugLog_noBleActivity": "現時点では、BLE関連の活動は行われていません。", + "debugFrame_length": "フレーム長: {count} バイト", + "@debugFrame_length": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "debugFrame_command": "コマンド: 0x{value}", + "@debugFrame_command": { + "placeholders": { + "value": { + "type": "String" + } + } + }, + "debugFrame_textMessageHeader": "テキストメッセージ用フレーム:", + "debugFrame_destinationPubKey": "- 宛先公開鍵: {pubKey}", + "@debugFrame_destinationPubKey": { + "placeholders": { + "pubKey": { + "type": "String" + } + } + }, + "debugFrame_timestamp": "- タイムスタンプ: {timestamp}", + "@debugFrame_timestamp": { + "placeholders": { + "timestamp": { + "type": "int" + } + } + }, + "debugFrame_flags": "- フラグ: 0x{value}", + "@debugFrame_flags": { + "placeholders": { + "value": { + "type": "String" + } + } + }, + "debugFrame_textType": "- テキストの種類: {type} ({label})", + "@debugFrame_textType": { + "placeholders": { + "type": { + "type": "int" + }, + "label": { + "type": "String" + } + } + }, + "debugFrame_textTypeCli": "CLI(コマンドラインインターフェース)", + "debugFrame_textTypePlain": "シンプルな", + "debugFrame_text": "- テキスト:「{text}」", + "@debugFrame_text": { + "placeholders": { + "text": { + "type": "String" + } + } + }, + "debugFrame_hexDump": "ヘックスダンプ:", + "chat_pathManagement": "経路管理", + "chat_ShowAllPaths": "すべての経路を表示", + "chat_routingMode": "ルーティングモード", + "chat_autoUseSavedPath": "自動 (保存されたパスを使用)", + "chat_forceFloodMode": "強制的に洪水モードを起動", + "chat_recentAckPaths": "最近使用したACKパス(タップして使用):", + "chat_pathHistoryFull": "パスの履歴は完全です。エントリを削除して、新しいものを追加できます。", + "chat_hopSingular": "ジャンプ", + "chat_hopPlural": "ホップ", + "chat_hopsCount": "{count} {count, plural, =1{ホップ} other{ホップ}}", + "@chat_hopsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "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": "Path set: {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}", + "@chat_pathSetHops": { + "placeholders": { + "hopCount": { + "type": "int" + }, + "status": { + "type": "String" + } + } + }, + "chat_pathSavedLocally": "ローカルで保存。同期のために接続する。", + "chat_pathDeviceConfirmed": "デバイスの確認済み。", + "chat_pathDeviceNotConfirmed": "デバイスの確認はまだできていません。", + "chat_type": "種類", + "chat_path": "道", + "chat_publicKey": "公開鍵", + "chat_compressOutgoingMessages": "送信されるメッセージを圧縮する", + "chat_floodForced": "洪水(強制的な)", + "chat_directForced": "直接的な(強制的な)", + "chat_hopsForced": "{count} 本のホップ(強制的に採取)", + "@chat_hopsForced": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "chat_floodAuto": "洪水 (自動)", + "chat_direct": "直接", + "chat_poiShared": "共有されたPOI", + "chat_unread": "未読: {count}", + "@chat_unread": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "chat_openLink": "リンクを開く?", + "chat_openLinkConfirmation": "このリンクをブラウザで開くことはご希望ですか?", + "chat_open": "開く", + "chat_couldNotOpenLink": "リンクを開けられませんでした: {url}", + "@chat_couldNotOpenLink": { + "placeholders": { + "url": { + "type": "String" + } + } + }, + "chat_invalidLink": "無効なリンク形式", + "map_title": "ノードマップ", + "map_lineOfSight": "視界", + "map_losScreenTitle": "視界", + "map_noNodesWithLocation": "位置情報データを持つノードは存在しません", + "map_nodesNeedGps": "ノードは、地図上に表示されるために、GPS座標を共有する必要があります。", + "map_nodesCount": "ノード:{count}", + "@map_nodesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "map_pinsCount": "ピン:{count}個", + "@map_pinsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "map_chat": "チャット", + "map_repeater": "繰り返し送信装置", + "map_room": "部屋", + "map_sensor": "センサー", + "map_pinDm": "ピン(DM)", + "map_pinPrivate": "プライベート(非公開)", + "map_pinPublic": "公開 (一般公開)", + "map_lastSeen": "最後に確認された場所", + "map_disconnectConfirm": "本当にこのデバイスとの接続を解除したいですか?", + "map_from": "~から", + "map_source": "出典", + "map_flags": "旗", + "map_shareMarkerHere": "この場所でシェア", + "map_setAsMyLocation": "現在地として設定", + "map_pinLabel": "ピンラベル", + "map_label": "ラベル", + "map_pointOfInterest": "注目すべき点", + "map_sendToContact": "連絡先へ送信", + "map_sendToChannel": "特定のチャンネルに送信する", + "map_noChannelsAvailable": "利用可能なチャンネルはありません", + "map_publicLocationShare": "公共スペースの共有", + "map_publicLocationShareConfirm": "現在、{channelLabel} で位置情報を共有する準備をしています。このチャンネルは公開されており、PSK を持つ誰でも閲覧できます。", + "@map_publicLocationShareConfirm": { + "placeholders": { + "channelLabel": { + "type": "String" + } + } + }, + "map_connectToShareMarkers": "他のデバイスと接続して、マーカーを共有する", + "map_filterNodes": "フィルタノード", + "map_nodeTypes": "ノードの種類", + "map_chatNodes": "チャットノード", + "map_repeaters": "繰り返し送信装置", + "map_otherNodes": "その他のノード", + "map_keyPrefix": "主要なプレフィックス", + "map_filterByKeyPrefix": "主要なプレフィックスでフィルタリングする", + "map_publicKeyPrefix": "公開鍵のプレフィックス", + "map_markers": "マーカー", + "map_showSharedMarkers": "共有のマーカーを表示する", + "map_showGuessedLocations": "推測されたノードの位置を表示する", + "map_showDiscoveryContacts": "Discovery社の連絡先を表示する", + "map_guessedLocation": "推測された場所", + "map_lastSeenTime": "最後に確認された時間", + "map_sharedPin": "共有パスワード", + "map_joinRoom": "部屋に参加する", + "map_manageRepeater": "リピーターの管理", + "map_tapToAdd": "ノードをクリックして、パスに追加します。", + "map_runTrace": "パスの追跡を実行", + "map_removeLast": "最後のものを削除", + "map_pathTraceCancelled": "パスの追跡は中止。", + "mapCache_title": "オフライン用地図キャッシュ", + "mapCache_selectAreaFirst": "最初にキャッシュする領域を選択してください", + "mapCache_noTilesToDownload": "この地域にはダウンロードできるタイルは存在しません。", + "mapCache_downloadTilesTitle": "タイルをダウンロードする", + "mapCache_downloadTilesPrompt": "オフラインでの使用のために、{count}個のタイルをダウンロードしますか?", + "@mapCache_downloadTilesPrompt": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "mapCache_downloadAction": "ダウンロード", + "mapCache_cachedTiles": "{count} 個のタイルをキャッシュ", + "@mapCache_cachedTiles": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "mapCache_cachedTilesWithFailed": "Cached {downloaded} tiles ({failed} failed)", + "@mapCache_cachedTilesWithFailed": { + "placeholders": { + "downloaded": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "mapCache_clearOfflineCacheTitle": "オフラインキャッシュをクリアする", + "mapCache_clearOfflineCachePrompt": "キャッシュされた地図のタイルをすべて削除しますか?", + "mapCache_offlineCacheCleared": "オフラインキャッシュをクリア", + "mapCache_noAreaSelected": "選択されたエリアはありません", + "mapCache_cacheArea": "キャッシュエリア", + "mapCache_useCurrentView": "現在表示されている内容を保持する", + "mapCache_zoomRange": "ズーム範囲", + "mapCache_estimatedTiles": "推定されるタイル数: {count}", + "@mapCache_estimatedTiles": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "mapCache_downloadedTiles": "Downloaded {completed} / {total}", + "@mapCache_downloadedTiles": { + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "mapCache_downloadTilesButton": "タイルをダウンロードする", + "mapCache_clearCacheButton": "キャッシュをクリアする", + "mapCache_failedDownloads": "失敗したダウンロード: {count}", + "@mapCache_failedDownloads": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "mapCache_boundsLabel": "N {north}, S {south}, E {east}, W {west}", + "@mapCache_boundsLabel": { + "placeholders": { + "north": { + "type": "String" + }, + "south": { + "type": "String" + }, + "east": { + "type": "String" + }, + "west": { + "type": "String" + } + } + }, + "time_justNow": "まさに今", + "time_minutesAgo": "{minutes}分前", + "@time_minutesAgo": { + "placeholders": { + "minutes": { + "type": "int" + } + } + }, + "time_hoursAgo": "{hours}時間前", + "@time_hoursAgo": { + "placeholders": { + "hours": { + "type": "int" + } + } + }, + "time_daysAgo": "{days}日前", + "@time_daysAgo": { + "placeholders": { + "days": { + "type": "int" + } + } + }, + "time_hour": "1時間", + "time_hours": "時間", + "time_day": "一日", + "time_days": "日", + "time_week": "1週間", + "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_attempt": { + "placeholders": { + "current": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "login_failed": "ログインに失敗しました:{error}", + "@login_failed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "login_failedMessage": "ログインに失敗しました。パスワードが間違っているか、または接続が確立されていません。", + "common_reload": "再読み込み", + "common_clear": "明確", + "path_currentPath": "現在のパス: {path}", + "@path_currentPath": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "path_usingHopsPath": "{count} {count, plural, =1{ホップ} other{ホップ}}のパスを使用", + "@path_usingHopsPath": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "path_enterCustomPath": "カスタムパスを入力", + "path_currentPathLabel": "現在の経路", + "path_hexPrefixInstructions": "各ホップに対して、2文字の16進数プレフィックスをカンマで区切って入力してください。", + "path_hexPrefixExample": "例:A1, F2, 3C (各ノードは、自身の公開鍵の最初のバイトを使用)", + "path_labelHexPrefixes": "パス (ヘックスプレフィックス)", + "path_helperMaxHops": "最大64個のホップ。各プレフィックスは2つの16進数文字(1バイト)で構成されています。", + "path_selectFromContacts": "または、連絡先リストから選択してください:", + "path_noRepeatersFound": "繰り返し機能やルームサーバーは見つかりませんでした。", + "path_customPathsRequire": "カスタムパスには、メッセージを中継できる中間地点が必要です。", + "path_invalidHexPrefixes": "無効な16進数プレフィックス: {prefixes}", + "@path_invalidHexPrefixes": { + "placeholders": { + "prefixes": { + "type": "String" + } + } + }, + "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_neighbors": "近隣住民", + "repeater_neighborsSubtitle": "ゼロホップの隣接ノードを表示する。", + "repeater_settings": "設定", + "repeater_settingsSubtitle": "リピーターのパラメータを設定する", + "repeater_statusTitle": "再送ステータス", + "repeater_routingMode": "ルーティングモード", + "repeater_autoUseSavedPath": "自動 (保存されたパスを使用)", + "repeater_forceFloodMode": "強制的に洪水モードを起動", + "repeater_pathManagement": "経路管理", + "repeater_refresh": "リフレッシュ", + "repeater_statusRequestTimeout": "ステータス情報の取得に失敗しました。", + "repeater_errorLoadingStatus": "ステータス読み込みエラー: {error}", + "@repeater_errorLoadingStatus": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "repeater_systemInformation": "システム情報", + "repeater_battery": "バッテリー", + "repeater_clockAtLogin": "ログイン時の時刻表示", + "repeater_uptime": "稼働率", + "repeater_queueLength": "待ち行列の長さ", + "repeater_debugFlags": "デバッグフラグ", + "repeater_radioStatistics": "ラジオに関する統計", + "repeater_lastRssi": "最後のRSSI", + "repeater_lastSnr": "最後のSNR", + "repeater_noiseFloor": "ノイズレベル", + "repeater_txAirtime": "TXの放送時間", + "repeater_rxAirtime": "RX 空き時間", + "repeater_packetStatistics": "パケット統計", + "repeater_sent": "送信", + "repeater_received": "受領", + "repeater_duplicates": "重複", + "repeater_daysHoursMinsSecs": "{days}日 {hours}時間 {minutes}分 {seconds}秒", + "@repeater_daysHoursMinsSecs": { + "placeholders": { + "days": { + "type": "int" + }, + "hours": { + "type": "int" + }, + "minutes": { + "type": "int" + }, + "seconds": { + "type": "int" + } + } + }, + "repeater_packetTxTotal": "合計: {total}, 洪水: {flood}, 直接: {direct}", + "@repeater_packetTxTotal": { + "placeholders": { + "total": { + "type": "int" + }, + "flood": { + "type": "String" + }, + "direct": { + "type": "String" + } + } + }, + "repeater_packetRxTotal": "合計: {total}, 洪水: {flood}, 直接: {direct}", + "@repeater_packetRxTotal": { + "placeholders": { + "total": { + "type": "int" + }, + "flood": { + "type": "String" + }, + "direct": { + "type": "String" + } + } + }, + "repeater_duplicatesFloodDirect": "{flood}: {flood}, 直接: {direct}", + "@repeater_duplicatesFloodDirect": { + "placeholders": { + "flood": { + "type": "String" + }, + "direct": { + "type": "String" + } + } + }, + "repeater_duplicatesTotal": "合計: {total}", + "@repeater_duplicatesTotal": { + "placeholders": { + "total": { + "type": "int" + } + } + }, + "repeater_settingsTitle": "リピーター設定", + "repeater_basicSettings": "基本設定", + "repeater_repeaterName": "送信装置名", + "repeater_repeaterNameHelper": "このリピーターの名前", + "repeater_adminPassword": "管理者パスワード", + "repeater_adminPasswordHelper": "完全アクセス権のパスワード", + "repeater_guestPassword": "ゲスト用のパスワード", + "repeater_guestPasswordHelper": "読み取り専用アクセス用のパスワード", + "repeater_radioSettings": "ラジオ設定", + "repeater_frequencyMhz": "周波数 (MHz)", + "repeater_frequencyHelper": "300~2500 MHz", + "repeater_txPower": "TXパワー", + "repeater_txPowerHelper": "-30~-10 dBm", + "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_localAdvertIntervalMinutes": { + "placeholders": { + "minutes": { + "type": "int" + } + } + }, + "repeater_floodAdvertInterval": "洪水に関する広告の表示間隔", + "repeater_floodAdvertIntervalHours": "{hours} 時間", + "@repeater_floodAdvertIntervalHours": { + "placeholders": { + "hours": { + "type": "int" + } + } + }, + "repeater_encryptedAdvertInterval": "暗号化された広告表示間", + "repeater_dangerZone": "危険区域", + "repeater_rebootRepeater": "リピーターを再起動する", + "repeater_rebootRepeaterSubtitle": "リピーターデバイスを再起動する", + "repeater_rebootRepeaterConfirm": "本当にこのリピーターを再起動したいですか?", + "repeater_regenerateIdentityKey": "IDキーの再生成", + "repeater_regenerateIdentityKeySubtitle": "新しい公開鍵/秘密鍵のペアを生成する", + "repeater_regenerateIdentityKeyConfirm": "これにより、リピーターには新しい識別情報が割り当てられます。続行しますか?", + "repeater_eraseFileSystem": "ファイルシステムを削除する", + "repeater_eraseFileSystemSubtitle": "リピーターファイルシステムをフォーマットする", + "repeater_eraseFileSystemConfirm": "警告:この操作により、リピーター内のすべてのデータが消去されます。この操作は元に戻すことができません!", + "repeater_eraseSerialOnly": "Erase機能は、シリアルコンソール経由でのみ利用可能です。", + "repeater_commandSent": "送信されたコマンド: {command}", + "@repeater_commandSent": { + "placeholders": { + "command": { + "type": "String" + } + } + }, + "repeater_errorSendingCommand": "コマンド送信エラー:{error}", + "@repeater_errorSendingCommand": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "repeater_confirm": "確認", + "repeater_settingsSaved": "設定が正常に保存されました", + "repeater_errorSavingSettings": "設定の保存に失敗しました:{error}", + "@repeater_errorSavingSettings": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "repeater_refreshBasicSettings": "基本設定をリセットする", + "repeater_refreshRadioSettings": "ラジオ設定をリセットする", + "repeater_refreshTxPower": "TX の電力レベルをリセットする", + "repeater_refreshLocationSettings": "場所設定をリセットする", + "repeater_refreshPacketForwarding": "パケット転送の刷新", + "repeater_refreshGuestAccess": "ゲストへのアクセスをリフレッシュする", + "repeater_refreshPrivacyMode": "プライバシーモードをリセットする", + "repeater_refreshAdvertisementSettings": "広告設定のリセット", + "repeater_refreshed": "{label} が更新されました", + "@repeater_refreshed": { + "placeholders": { + "label": { + "type": "String" + } + } + }, + "repeater_errorRefreshing": "{label} の更新に失敗しました", + "@repeater_errorRefreshing": { + "placeholders": { + "label": { + "type": "String" + } + } + }, + "repeater_cliTitle": "リピーターのコマンドラインインターフェース", + "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_cliCommandError": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "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の送信電力をdBmで設定します。(設定変更後、再起動が必要です)", + "repeater_cliHelpSetRepeat": "このノードに対するリピーターの役割を有効化または無効化します。", + "repeater_cliHelpSetAllowReadOnly": "(ルームサーバー設定)「オン」に設定した場合、空白のパスワードでのログインは可能ですが、ルームへの投稿はできません。(閲覧のみ)", + "repeater_cliHelpSetFloodMax": "インバウンドフラッパケットの最大ホップ数を設定します(最大値を超えた場合、パケットは転送されません)。", + "repeater_cliHelpSetIntThresh": "干渉閾値を設定します(dB単位)。デフォルト値は14です。0に設定すると、チャンネル間の干渉を検出する機能を無効にします。", + "repeater_cliHelpSetAgcResetInterval": "オートゲインコントローラーのリセット間隔を設定します。 0 に設定すると無効化されます。", + "repeater_cliHelpSetMultiAcks": "「ダブルACK」機能の有効化または無効化を可能にします。", + "repeater_cliHelpSetAdvertInterval": "ローカル(ホップなし)の広告パケットを送信する間隔を分単位で設定します。 0 に設定すると、機能を無効にします。", + "repeater_cliHelpSetFloodAdvertInterval": "洪水広告の送信間隔を時間単位で設定します。0に設定すると、送信を停止します。", + "repeater_cliHelpSetGuestPassword": "ゲストのパスワードを設定/更新します。(繰り返し利用の場合、ゲストのログインは「統計情報を取得」のリクエストを送信できます)", + "repeater_cliHelpSetName": "広告の名前を設定します。", + "repeater_cliHelpSetLat": "広告表示の地図の緯度を設定します。(度分秒表記)", + "repeater_cliHelpSetLon": "広告表示の地図の経度を設定します。(度数、分)", + "repeater_cliHelpSetRadio": "完全に新しいラジオパラメータを設定し、設定として保存します。適用するには、「再起動」コマンドが必要です。", + "repeater_cliHelpSetRxDelay": "(実験用)遅延時間を設定するためのベース(1以上の値に設定する必要)\n受信パケットに対して、信号強度/スコアに基づいてわずかな遅延を適用します。 0に設定すると無効化されます。", + "repeater_cliHelpSetTxDelay": "時間経過に応じた「フラッシュモード」パケットの送信遅延を設定します。この遅延は、ランダムなスロットシステムと組み合わせて使用され、パケットの衝突を減らすことを目的としています。", + "repeater_cliHelpSetDirectTxDelay": "txdelayと同様ですが、ダイレクトモードのパケット転送にランダムな遅延を適用する場合に使用します。", + "repeater_cliHelpSetBridgeEnabled": "ブリッジを有効化/無効化", + "repeater_cliHelpSetBridgeDelay": "パケットを再送信する前に、遅延を設定する。", + "repeater_cliHelpSetBridgeSource": "橋が受信したパケットを再送信するか、送信したパケットを再送信するかどうかを選択してください。", + "repeater_cliHelpSetBridgeBaud": "RS232 橋渡しに使用するシリアルリンクのボーレートを設定する。", + "repeater_cliHelpSetBridgeSecret": "ESPNow 橋の秘密設定", + "repeater_cliHelpSetAdcMultiplier": "特定のボードでのみサポートされている、報告されるバッテリー電圧を調整するためのカスタムファクタを設定できます。", + "repeater_cliHelpTempRadio": "指定された時間(分単位)に対して、一時的にラジオパラメータを設定し、その後元のラジオパラメータに戻します。(設定を保存しません)。", + "repeater_cliHelpSetPerm": "ACL を変更します。「permissions」が 0 の場合、対応するエントリ(pubkey のプレフィックスで識別)を削除します。pubkey-hex が有効な長さで、かつ ACL に現在存在しない場合に、新しいエントリを追加します。pubkey のプレフィックスと一致するエントリを更新します。権限ビットはファームウェアの役割によって異なり、下位 2 ビットは以下のとおりです:0 (ゲスト)、1 (読み取り専用)、2 (読み書き)、3 (管理者)", + "repeater_cliHelpGetBridgeType": "ブリッジ機能なし、RS232、ESPNow", + "repeater_cliHelpLogStart": "パケットのログ記録を開始し、ファイルシステムに保存する。", + "repeater_cliHelpLogStop": "ファイルシステムへのパケットログの記録を停止する。", + "repeater_cliHelpLogErase": "ファイルシステムからパケットログを削除する。", + "repeater_cliHelpNeighbors": "ゼロホップ広告を通じて受信した他のリピーターノードの一覧を表示します。各行は、IDプレフィックス(16進数)、タイムスタンプ、SNR(シグナル強度)の情報を4つ含みます。", + "repeater_cliHelpNeighborRemove": "隣接リストから、最初に一致するエントリ(pubkeyプレフィックス(16進数)で特定)を削除します。", + "repeater_cliHelpRegion": "(特定のシリーズのみ)定義されたすべての地域と、現在の洪水許可状況を一覧表示します。", + "repeater_cliHelpRegionLoad": "注:これは特殊な複数コマンドの呼び出しです。その後の各コマンドは、地域名であり(スペースを使用して親階層を示し、少なくとも1つのスペースが必要です)、空行/コマンドで終了します。", + "repeater_cliHelpRegionGet": "指定された名前のプレフィックスを持つ地域を検索します(または、グローバルな範囲の場合は「*」)。結果として、「region-name (parent-name) 'F'」と返答します。", + "repeater_cliHelpRegionPut": "指定された名前で、領域の定義を追加または更新します。", + "repeater_cliHelpRegionRemove": "指定された名前を持つ領域の定義を削除します。(正確に一致している必要があり、子領域は存在してはなりません)", + "repeater_cliHelpRegionAllowf": "指定された領域に対して、「洪水」アクセス許可を設定します。 (グローバル/従来のスコープには「*」を使用)", + "repeater_cliHelpRegionDenyf": "指定された領域における「FLOOD」権限を削除します。(注:現時点では、グローバル/従来の範囲での使用は推奨されません!)", + "repeater_cliHelpRegionHome": "現在の「ホーム」地域に返信します。(まだ適用されていない、将来利用を予定)", + "repeater_cliHelpRegionHomeSet": "「ホーム」地域を設定します。", + "repeater_cliHelpRegionSave": "領域リスト/マップをストレージに保存する。", + "repeater_cliHelpGps": "GPSの状態を表示します。GPSがオフの場合、「オフ」と表示します。オンの場合、「オン」、「ステータス」、「位置情報」、「衛星数」と表示します。", + "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_errorLoading": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "telemetry_noData": "テレメトリデータは利用できません。", + "telemetry_channelTitle": "チャンネル {channel}", + "@telemetry_channelTitle": { + "placeholders": { + "channel": { + "type": "int" + } + } + }, + "telemetry_batteryLabel": "バッテリー", + "telemetry_voltageLabel": "電圧", + "telemetry_mcuTemperatureLabel": "MCU の温度", + "telemetry_temperatureLabel": "温度", + "telemetry_currentLabel": "現在", + "telemetry_batteryValue": "{percent}% / {volts}V", + "@telemetry_batteryValue": { + "placeholders": { + "percent": { + "type": "int" + }, + "volts": { + "type": "String" + } + } + }, + "telemetry_voltageValue": "{volts}V", + "@telemetry_voltageValue": { + "placeholders": { + "volts": { + "type": "String" + } + } + }, + "telemetry_currentValue": "{amps}A", + "@telemetry_currentValue": { + "placeholders": { + "amps": { + "type": "String" + } + } + }, + "telemetry_temperatureValue": "{celsius}℃ / {fahrenheit}°F", + "@telemetry_temperatureValue": { + "placeholders": { + "celsius": { + "type": "String" + }, + "fahrenheit": { + "type": "String" + } + } + }, + "neighbors_receivedData": "近隣住民のデータを受信", + "neighbors_requestTimedOut": "近隣住民からの要望:時間制限を設けてください。", + "neighbors_errorLoading": "近隣情報の読み込みに失敗: {error}", + "@neighbors_errorLoading": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "neighbors_repeatersNeighbors": "繰り返し送信する、近隣", + "neighbors_noData": "近隣のデータは利用できません。", + "neighbors_unknownContact": "不明な {pubkey}", + "@neighbors_unknownContact": { + "placeholders": { + "pubkey": { + "type": "String" + } + } + }, + "neighbors_heardAgo": "聞いたのは、{time} くらい前です", + "@neighbors_heardAgo": { + "placeholders": { + "time": { + "type": "String" + } + } + }, + "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_observedPathTitle": { + "placeholders": { + "index": { + "type": "int" + }, + "hops": { + "type": "String" + } + } + }, + "channelPath_noLocationData": "場所に関するデータはありません", + "channelPath_timeWithDate": "{day}/{month} {time}", + "@channelPath_timeWithDate": { + "placeholders": { + "day": { + "type": "int" + }, + "month": { + "type": "int" + }, + "time": { + "type": "String" + } + } + }, + "channelPath_timeOnly": "{time}", + "@channelPath_timeOnly": { + "placeholders": { + "time": { + "type": "String" + } + } + }, + "channelPath_unknownPath": "不明", + "channelPath_floodPath": "洪水", + "channelPath_directPath": "直接", + "channelPath_observedZeroOf": "{total}個のホップ", + "@channelPath_observedZeroOf": { + "placeholders": { + "total": { + "type": "int" + } + } + }, + "channelPath_observedSomeOf": "{observed} of {total} hops", + "@channelPath_observedSomeOf": { + "placeholders": { + "observed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "channelPath_mapTitle": "経路図", + "channelPath_noRepeaterLocations": "この経路には、中継装置の設置場所がありません。", + "channelPath_primaryPath": "{index}番目の経路(主要経路)", + "@channelPath_primaryPath": { + "placeholders": { + "index": { + "type": "int" + } + } + }, + "@channelPath_pathLabel": { + "placeholders": { + "index": { + "type": "int" + } + } + }, + "channelPath_pathLabelTitle": "道", + "channelPath_observedPathHeader": "観察された経路", + "channelPath_selectedPathLabel": "{label} • {prefixes}", + "@channelPath_selectedPathLabel": { + "placeholders": { + "label": { + "type": "String" + }, + "prefixes": { + "type": "String" + } + } + }, + "channelPath_noHopDetailsAvailable": "このパッケージに関する詳細な配送情報は利用できません。", + "channelPath_unknownRepeater": "不明な増幅機", + "community_title": "地域", + "community_create": "コミュニティを構築する", + "community_createDesc": "新しいコミュニティを作成し、QRコードで共有する。", + "community_join": "参加する", + "community_joinTitle": "コミュニティに参加する", + "community_joinConfirmation": "{name}さんのようなコミュニティに参加したいですか?", + "@community_joinConfirmation": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_scanQr": "コミュニティのQRコードをスキャン", + "community_scanInstructions": "カメラを、地域のQRコードを向けて", + "community_showQr": "QRコードを表示する", + "community_publicChannel": "地域住民向け", + "community_hashtagChannel": "コミュニティ用ハッシュタグ", + "community_name": "コミュニティ名", + "community_enterName": "コミュニティ名を入力してください", + "community_created": "コミュニティ「{name}」が作成されました", + "@community_created": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_joined": "{name} のコミュニティに参加", + "@community_joined": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_qrTitle": "コミュニティ共有", + "community_qrInstructions": "このQRコードをスキャンして、{name}に参加してください。", + "@community_qrInstructions": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_hashtagPrivacyHint": "コミュニティハッシュタグのチャンネルは、コミュニティのメンバーのみが参加できます。", + "community_invalidQrCode": "無効なコミュニティQRコード", + "community_alreadyMember": "すでに会員である", + "community_alreadyMemberMessage": "あなたはすでに {name} の会員です。", + "@community_alreadyMemberMessage": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_addPublicChannel": "コミュニティ用の公開チャンネルを追加", + "community_addPublicChannelHint": "このコミュニティの公開チャンネルを自動的に追加する", + "community_noCommunities": "まだコミュニティは形成されていません。", + "community_scanOrCreate": "QRコードをスキャンするか、コミュニティを作成して開始してください。", + "community_manageCommunities": "コミュニティの管理", + "community_delete": "コミュニティからの離脱", + "community_deleteConfirm": "{name}を辞める?", + "@community_deleteConfirm": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_deleteChannelsWarning": "これにより、{count} のチャンネルとそのメッセージも削除されます。", + "@community_deleteChannelsWarning": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "community_deleted": "コミュニティ「{name}」を離れる", + "@community_deleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_regenerateSecret": "秘密の復元", + "community_regenerateSecretConfirm": "{name} の秘密鍵を再生成しますか? 継続的に通信するため、すべてのメンバーは新しいQRコードをスキャンする必要があります。", + "@community_regenerateSecretConfirm": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_regenerate": "再生", + "community_secretRegenerated": "{name} への秘密が再設定されました", + "@community_secretRegenerated": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_updateSecret": "秘密情報の更新", + "community_secretUpdated": "{name} 向けの秘密設定を更新", + "@community_secretUpdated": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_scanToUpdateSecret": "新しいQRコードをスキャンして、{name}の秘密情報を更新してください。", + "@community_scanToUpdateSecret": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_addHashtagChannel": "コミュニティのハッシュタグを追加", + "community_addHashtagChannelDesc": "このコミュニティ用のハッシュタグチャンネルを追加する", + "community_selectCommunity": "コミュニティを選択", + "community_regularHashtag": "定期的なハッシュタグ", + "community_regularHashtagDesc": "一般のハッシュタグ(誰でも参加可能)", + "community_communityHashtag": "コミュニティ用ハッシュタグ", + "community_communityHashtagDesc": "コミュニティメンバーのみへの限定", + "community_forCommunity": "{name} 様", + "@community_forCommunity": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "listFilter_tooltip": "フィルタリングと並べ替え", + "listFilter_sortBy": "並び替え", + "listFilter_latestMessages": "最新のメッセージ", + "listFilter_heardRecently": "最近、聞いた", + "listFilter_az": "AからZ", + "listFilter_filters": "フィルター", + "listFilter_all": "すべて", + "listFilter_favorites": "お気に入り", + "listFilter_addToFavorites": "お気に入りに追加", + "listFilter_removeFromFavorites": "お気に入りから削除", + "listFilter_users": "利用者", + "listFilter_repeaters": "繰り返し送信装置", + "listFilter_roomServers": "ルーム用サーバー", + "listFilter_unreadOnly": "未読のみ", + "listFilter_newGroup": "新しいグループ", + "pathTrace_you": "あなた", + "pathTrace_failed": "パスの追跡に失敗しました。", + "pathTrace_notAvailable": "パスの追跡機能は利用できません。", + "pathTrace_refreshTooltip": "パスの追跡をリフレッシュする。", + "pathTrace_someHopsNoLocation": "ホップの1つまたは複数について、場所が特定されていません。", + "pathTrace_clearTooltip": "明確な道筋。", + "losSelectStartEnd": "LOS の開始ノードと終了ノードを選択してください。", + "losRunFailed": "視界確認に失敗: {error}", + "@losRunFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "losClearAllPoints": "すべての項目をクリア", + "losRunToViewElevationProfile": "LOS(レーザー測距)を使用して、標高プロファイルを表示する", + "losMenuTitle": "LOS メニュー", + "losMenuSubtitle": "特定の場所をタップするか、地図を長押ししてカスタムポイントを作成する。", + "losShowDisplayNodes": "表示ノードを表示する", + "losCustomPoints": "カスタマイズ可能なポイント", + "losCustomPointLabel": "カスタマイズ {index}", + "@losCustomPointLabel": { + "placeholders": { + "index": { + "type": "int" + } + } + }, + "losPointA": "ポイントA", + "losPointB": "ポイントB", + "losAntennaA": "アンテナ A: {value} {unit}", + "@losAntennaA": { + "placeholders": { + "value": { + "type": "String" + }, + "unit": { + "type": "String" + } + } + }, + "losAntennaB": "アンテナ B: {value} {unit}", + "@losAntennaB": { + "placeholders": { + "value": { + "type": "String" + }, + "unit": { + "type": "String" + } + } + }, + "losRun": "LOS(レーティングシステム)を使用する", + "losNoElevationData": "標高データは含まれていません", + "losProfileClear": "{distance} {distanceUnit}, clear LOS, min clearance {clearance} {heightUnit}", + "@losProfileClear": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "clearance": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "losProfileBlocked": "{distance} {distanceUnit}, blocked by {obstruction} {heightUnit}", + "@losProfileBlocked": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "losStatusChecking": "LOS:確認中…", + "losStatusNoData": "LOS: データの欠如", + "losStatusSummary": "LOS: {clear}/{total} clear, {blocked} blocked, {unknown} unknown", + "@losStatusSummary": { + "placeholders": { + "clear": { + "type": "int" + }, + "total": { + "type": "int" + }, + "blocked": { + "type": "int" + }, + "unknown": { + "type": "int" + } + } + }, + "losErrorElevationUnavailable": "あるサンプルまたは複数のサンプルについて、標高データが利用できません。", + "losErrorInvalidInput": "LOS(レーダー)計算に必要な、無効な点/標高データ。", + "losRenameCustomPoint": "カスタムポイントの名前を変更する", + "losPointName": "項目名", + "losShowPanelTooltip": "LOSパネルを表示する", + "losHidePanelTooltip": "LOSパネルを隠す", + "losElevationAttribution": "標高データ:Open-Meteo (CC BY 4.0)", + "losLegendRadioHorizon": "ラジオ・ホライゾン", + "losLegendLosBeam": "LOS ビーミング", + "losLegendTerrain": "地形", + "losFrequencyLabel": "周波数", + "losFrequencyInfoTooltip": "計算の詳細を見る", + "losFrequencyDialogTitle": "ラジオによる水平線計算", + "losFrequencyDialogDescription": "k={baselineK} ( {baselineFreq} MHz) から開始し、現在の {frequencyMHz} MHz の帯域に対して k の値を調整します。これにより、曲面状の無線通信範囲の限界が定義されます。", + "@losFrequencyDialogDescription": { + "description": "Explain how the calculation uses the baseline frequency and derived k-factor.", + "placeholders": { + "baselineK": { + "type": "double" + }, + "baselineFreq": { + "type": "double" + }, + "frequencyMHz": { + "type": "double" + }, + "kFactor": { + "type": "double" + } + } + }, + "contacts_pathTrace": "経路追跡", + "contacts_ping": "パング", + "contacts_repeaterPathTrace": "リピーターまでの経路を追跡する", + "contacts_repeaterPing": "PING 繰り返し", + "contacts_roomPathTrace": "部屋のサーバーへの経路を追跡する", + "contacts_roomPing": "ピンルーム用サーバー", + "contacts_chatTraceRoute": "経路の追跡ルート", + "contacts_pathTraceTo": "{name} への経路を追跡する", + "@contacts_pathTraceTo": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "contacts_clipboardEmpty": "クリップボードは空です。", + "contacts_invalidAdvertFormat": "無効な連絡先情報", + "contacts_contactImported": "連絡先が登録されました。", + "contacts_contactImportFailed": "連絡先のインポートに失敗しました。", + "contacts_zeroHopAdvert": "ゼロホップ広告", + "contacts_floodAdvert": "洪水に関する広告", + "contacts_copyAdvertToClipboard": "広告をクリップボードにコピー", + "contacts_addContactFromClipboard": "クリップボードから連絡先を追加する", + "contacts_ShareContact": "連絡先をクリップボードにコピー", + "contacts_ShareContactZeroHop": "広告を通じて連絡先を共有する", + "contacts_zeroHopContactAdvertSent": "広告を通じて連絡先を得た。", + "contacts_zeroHopContactAdvertFailed": "連絡を送信できませんでした。", + "contacts_contactAdvertCopied": "広告がクリップボードにコピーされました。", + "contacts_contactAdvertCopyFailed": "広告のコピーがクリップボードにコピーできませんでした。", + "notification_activityTitle": "メッシュコアの活動", + "notification_messagesCount": "{count} {count, plural, =1{message} other{messages}}", + "@notification_messagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notification_channelMessagesCount": "{count} {count, plural, =1{チャンネルメッセージ} other{チャンネルメッセージ}}", + "@notification_channelMessagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notification_newNodesCount": "{count} {count, plural, =1{新しいノード} other{新しいノード}}", + "@notification_newNodesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notification_newTypeDiscovered": "新たに {contactType} が発見されました", + "@notification_newTypeDiscovered": { + "placeholders": { + "contactType": { + "type": "String" + } + } + }, + "notification_receivedNewMessage": "新しいメッセージを受信", + "settings_gpxExportRepeaters": "GPX へのエクスポート用リピーター/ルームサーバー", + "settings_gpxExportRepeatersSubtitle": "GPXファイルに場所情報を付加した、レピーター/ルームサーバーのエクスポート", + "settings_gpxExportContacts": "GPX 形式へのエクスポート", + "settings_gpxExportContactsSubtitle": "GPXファイルに位置情報を保存して、他の人と共有する。", + "settings_gpxExportAll": "すべての連絡先をGPX形式でエクスポートする", + "settings_gpxExportAllSubtitle": "すべての連絡先を、場所情報付きのGPXファイルにエクスポートする。", + "settings_gpxExportSuccess": "GPXファイルの正常なエクスポートが完了しました。", + "settings_gpxExportNoContacts": "エクスポートする連絡先は存在しません。", + "settings_gpxExportNotAvailable": "このデバイス/OSではサポートされていません", + "settings_gpxExportError": "エクスポート時にエラーが発生しました。", + "settings_gpxExportRepeatersRoom": "中継装置およびルームサーバーの設置場所", + "settings_gpxExportChat": "関連施設", + "settings_gpxExportAllContacts": "すべての連絡先場所", + "settings_gpxExportShareText": "meshcore-openからエクスポートされた地図データ", + "settings_gpxExportShareSubject": "meshcore-open GPX形式の地図データのエクスポート", + "snrIndicator_nearByRepeaters": "近くの電波中継局", + "snrIndicator_lastSeen": "最後に確認された場所", + "contactsSettings_title": "連絡先設定", + "contactsSettings_autoAddTitle": "自動検出", + "contactsSettings_otherTitle": "その他の連絡に関する設定", + "contactsSettings_autoAddUsersTitle": "自動でユーザーを追加する", + "contactsSettings_autoAddUsersSubtitle": "利用者が自動的に発見したユーザーを追加できるようにする。", + "contactsSettings_autoAddRepeatersTitle": "自動で繰り返し設定", + "contactsSettings_autoAddRepeatersSubtitle": "発見した中継局を、自動的に追加できるようにする。", + "contactsSettings_autoAddRoomServersTitle": "自動でルームサーバーを追加", + "contactsSettings_autoAddRoomServersSubtitle": "利用者が、発見した部屋のサーバーを自動的に追加できるようにする。", + "contactsSettings_autoAddSensorsTitle": "自動でセンサーを追加", + "contactsSettings_autoAddSensorsSubtitle": "利用者が、発見したセンサーを自動的に追加できるようにする。", + "contactsSettings_overwriteOldestTitle": "最も古いものを上書きする", + "contactsSettings_overwriteOldestSubtitle": "連絡先リストが満杯になった場合、最も古いかつ「お気に入り」ではない連絡先が削除されます。", + "discoveredContacts_Title": "連絡先が見つかった", + "discoveredContacts_noMatching": "一致する連絡先が見つかりませんでした", + "discoveredContacts_searchHint": "発見された連絡先を検索する", + "discoveredContacts_contactAdded": "連絡先を追加", + "discoveredContacts_addContact": "連絡先を追加", + "discoveredContacts_copyContact": "連絡先をクリップボードにコピー", + "discoveredContacts_deleteContact": "発見された連絡先を削除", + "discoveredContacts_deleteContactAll": "発見されたすべての連絡先を削除", + "discoveredContacts_deleteContactAllContent": "本当に、見つけたすべての連絡先を削除してもよろしいですか?", + "chat_sendCooldown": "再度送信する前に、しばらくお待ちください。", + "appSettings_jumpToOldestUnread": "最も古い未読のメッセージへ移動", + "appSettings_jumpToOldestUnreadSubtitle": "未読メッセージがあるチャットを開く際、「最新のメッセージ」ではなく、最初に未読のメッセージまでスクロールしてください。", + "appSettings_languageHu": "ハンガリー語", + "appSettings_languageJa": "日本語", + "appSettings_languageKo": "韓国語", + "radioStats_tooltip": "ラジオおよびメッシュに関する統計", + "radioStats_screenTitle": "ラジオの統計", + "radioStats_notConnected": "ラジオの統計情報を表示するために、デバイスに接続してください。", + "radioStats_firmwareTooOld": "ラジオの統計機能を使用するには、v8またはそれ以降のファームウェアが必要です。", + "radioStats_waiting": "データ待ち…", + "radioStats_noiseFloor": "ノイズレベル: {noiseDbm} dBm", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "radioStats_lastRssi": "最後のRSSI: {rssiDbm} dBm", + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "radioStats_lastSnr": "最終SNR: {snr} dB", + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "radioStats_txAir": "TX 放送時間(合計):{seconds} 秒", + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "radioStats_rxAir": "RX 放送時間(合計):{seconds} 秒", + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "radioStats_chartCaption": "最近のサンプルのノイズレベル(dBm)。", + "radioStats_stripNoise": "ノイズレベル: {noiseDbm} dBm", + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "radioStats_stripWaiting": "ラジオの統計情報を取得中…", + "radioStats_settingsTile": "ラジオの統計", + "radioStats_settingsSubtitle": "ノイズレベル、RSSI、SNR、および通信時間", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, + "settings_privacy": "プライバシー設定", + "settings_privacySubtitle": "共有する情報の内容を管理する。", + "settings_denyAll": "すべてを否定", + "settings_privacySettingsDescription": "自分のデバイスが他の人に共有する情報を選択してください。", + "settings_allowByContact": "連絡先を明示するオプション", + "settings_allowAll": "すべて許可", + "settings_telemetryBaseMode": "テレメトリ基本モード", + "settings_telemetryLocationMode": "テレメトリ位置特定モード", + "settings_telemetryEnvironmentMode": "テレメトリ環境モード", + "settings_advertLocation": "広告掲載場所", + "settings_advertLocationSubtitle": "広告に場所を記載してください。", + "settings_multiAck": "複数のACK:{value}", + "settings_telemetryModeUpdated": "テレメトリモードが更新されました", + "contact_info": "連絡先", + "contact_settings": "連絡設定", + "contact_telemetry": "テレメトリー", + "contact_lastSeen": "最後に確認された場所", + "contact_clearChat": "チャットのクリア", + "contact_teleBase": "テレメトリ基地", + "contact_teleBaseSubtitle": "バッテリー残量と基本的なテレメトリーの共有を許可する", + "contact_teleLoc": "テレメトリの場所", + "contact_teleLocSubtitle": "位置情報共有を許可する", + "contact_teleEnv": "テレメトリ環境", + "contact_teleEnvSubtitle": "環境センサーのデータを共有することを許可する", + "map_showOverlaps": "リピーターキーの重複", + "map_runTraceWithReturnPath": "元の経路に戻る。" +} diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb new file mode 100644 index 0000000..c2215b9 --- /dev/null +++ b/lib/l10n/app_ko.arb @@ -0,0 +1,2048 @@ +{ + "@@locale": "ko", + "appTitle": "MeshCore Open", + "nav_contacts": "연락처", + "nav_channels": "채널", + "nav_map": "지도", + "common_cancel": "취소", + "common_ok": "알겠습니다", + "common_connect": "연결", + "common_unknownDevice": "알 수 없는 장치", + "common_save": "저장", + "common_delete": "삭제", + "common_deleteAll": "모두 삭제", + "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} V", + "@common_voltageValue": { + "placeholders": { + "volts": { + "type": "String" + } + } + }, + "common_percentValue": "{percent}%", + "@common_percentValue": { + "placeholders": { + "percent": { + "type": "int" + } + } + }, + "scanner_title": "MeshCore 공개", + "connectionChoiceUsbLabel": "USB", + "connectionChoiceBluetoothLabel": "블루투스", + "connectionChoiceTcpLabel": "TCP", + "tcpScreenTitle": "TCP를 통해 연결", + "tcpHostLabel": "IP 주소", + "tcpHostHint": "192.168.40.10", + "tcpPortLabel": "항", + "tcpPortHint": "5000", + "tcpStatus_notConnected": "목적지 주소 입력 후 연결", + "tcpStatus_connectingTo": "{endpoint}에 연결 중...", + "@tcpStatus_connectingTo": { + "placeholders": { + "endpoint": { + "type": "String" + } + } + }, + "tcpErrorHostRequired": "IP 주소가 필요합니다.", + "tcpErrorPortInvalid": "포트 번호는 1에서 65535 사이여야 합니다.", + "tcpErrorUnsupported": "이 플랫폼에서는 TCP 트랜스포트를 지원하지 않습니다.", + "tcpErrorTimedOut": "TCP 연결이 시간 초과되었습니다.", + "tcpConnectionFailed": "TCP 연결 실패: {error}", + "@tcpConnectionFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "usbScreenTitle": "USB를 통해 연결", + "usbScreenSubtitle": "감지된 시리얼 장치를 선택하고 MeshCore 노드에 직접 연결하십시오.", + "usbScreenStatus": "USB 장치를 선택합니다.", + "usbScreenNote": "USB 직렬 통신은 지원되는 안드로이드 장치 및 데스크톱 플랫폼에서 활성화됩니다.", + "usbScreenEmptyState": "USB 장치가 탐지되지 않았습니다. USB 장치를 연결하고 다시 시도해 보세요.", + "usbErrorPermissionDenied": "USB 접근 권한이 거부되었습니다.", + "usbErrorDeviceMissing": "선택한 USB 장치는 더 이상 사용 불가능합니다.", + "usbErrorInvalidPort": "유효한 USB 장치를 선택하세요.", + "usbErrorBusy": "또 다른 USB 연결 요청이 이미 진행 중입니다.", + "usbErrorNotConnected": "USB 장치가 연결되지 않았습니다.", + "usbErrorOpenFailed": "선택한 USB 장치를 열 수 없습니다.", + "usbErrorConnectFailed": "선택한 USB 장치에 연결에 실패했습니다.", + "usbErrorUnsupported": "이 플랫폼에서는 USB 직렬 통신을 지원하지 않습니다.", + "usbErrorAlreadyActive": "USB 연결이 이미 활성화되어 있습니다.", + "usbErrorNoDeviceSelected": "USB 장치가 선택되지 않았습니다.", + "usbErrorPortClosed": "USB 연결이 활성화되지 않았습니다.", + "usbErrorConnectTimedOut": "연결이 시간 초과되었습니다. 장치가 USB Companion 펌웨어를 가지고 있는지 확인해 주세요.", + "usbFallbackDeviceName": "웹 시리얼 장치", + "usbStatus_notConnected": "USB 장치를 선택합니다.", + "usbStatus_connecting": "USB 장치에 연결 중...", + "usbStatus_searching": "USB 장치 검색 중...", + "usbConnectionFailed": "USB 연결 실패: {error}", + "@usbConnectionFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "scanner_scanning": "장치 검색 중...", + "scanner_connecting": "연결 중...", + "scanner_disconnecting": "연결 해제 중...", + "scanner_notConnected": "연결되지 않음", + "scanner_connectedTo": "{deviceName}에 연결됨", + "@scanner_connectedTo": { + "placeholders": { + "deviceName": { + "type": "String" + } + } + }, + "scanner_searchingDevices": "MeshCore 장치를 검색 중...", + "scanner_tapToScan": "MeshCore 장치를 찾기 위해 스캔 버튼을 누르세요.", + "scanner_connectionFailed": "연결 실패: {error}", + "@scanner_connectionFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "scanner_stop": "멈춰", + "scanner_scan": "스캔", + "scanner_bluetoothOff": "블루투스는 꺼져 있습니다.", + "scanner_bluetoothOffMessage": "블루투스를 켜서 장치를 검색해주세요.", + "scanner_chromeRequired": "크롬 브라우저 필요", + "scanner_chromeRequiredMessage": "이 웹 애플리케이션은 블루투드 지원을 위해 Google Chrome 또는 Chromium 기반 브라우저가 필요합니다.", + "scanner_enableBluetooth": "블루투스 활성화", + "device_quickSwitch": "빠른 전환", + "device_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_contactSettings": "연락처 설정", + "settings_contactSettingsSubtitle": "연락처 추가 방식 설정", + "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 {version} 버전", + "@settings_aboutVersion": { + "placeholders": { + "version": { + "type": "String" + } + } + }, + "settings_aboutLegalese": "2026년 MeshCore 오픈 소스 프로젝트", + "settings_aboutDescription": "MeshCore LoRa 메시 네트워크 장치를 위한 오픈 소스 Flutter 클라이언트.", + "settings_aboutOpenMeteoAttribution": "LOS 고도 데이터: Open-Meteo (CC BY 4.0)", + "settings_infoName": "이름", + "settings_infoId": "ID", + "settings_infoStatus": "상태", + "settings_infoBattery": "배터리", + "settings_infoPublicKey": "공개 키", + "settings_infoContactsCount": "연락처 수", + "settings_infoChannelCount": "채널 수", + "settings_presets": "기본 설정", + "settings_frequency": "주파수 (MHz)", + "settings_frequencyHelper": "300.0 - 2500.0", + "settings_frequencyInvalid": "유효하지 않은 주파수 (300-2500 MHz)", + "settings_bandwidth": "대역폭", + "settings_spreadingFactor": "분산 계수", + "settings_codingRate": "코딩 속도", + "settings_txPower": "TX 전력 (dBm)", + "settings_txPowerHelper": "0 - 22", + "settings_txPowerInvalid": "유효하지 않은 TX 전력 (0-22 dBm)", + "settings_clientRepeat": "오프그리드 반복", + "settings_clientRepeatSubtitle": "이 장치가 다른 사람들을 위해 메시 패킷을 반복하도록 허용합니다.", + "settings_clientRepeatFreqWarning": "오프그리드(무전력) 시스템 재연결에는 433MHz, 869MHz, 또는 918MHz 주파수가 필요합니다.", + "settings_error": "오류: {message}", + "@settings_error": { + "placeholders": { + "message": { + "type": "String" + } + } + }, + "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_languageUk": "우크라이나", + "appSettings_enableMessageTracing": "메시지 추적 기능 활성화", + "appSettings_enableMessageTracingSubtitle": "메시지에 대한 상세한 경로 및 시간 정보를 표시", + "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": "Max 재시도 시 경로 명확하게 설정", + "appSettings_clearPathOnMaxRetrySubtitle": "5번의 전송 시도가 실패하면 연락 경로를 재설정", + "appSettings_pathsWillBeCleared": "5번의 시도 실패 후, 해당 경로가 확보될 것입니다.", + "appSettings_pathsWillNotBeCleared": "경로는 자동으로 정리되지 않습니다.", + "appSettings_autoRouteRotation": "자동 경로 순환", + "appSettings_autoRouteRotationSubtitle": "최적 경로와 방수 모드 사이를 전환", + "appSettings_autoRouteRotationEnabled": "자동 경로 순환 기능 활성화", + "appSettings_autoRouteRotationDisabled": "자동 경로 순환 기능 비활성화", + "appSettings_maxRouteWeight": "최대 경로 무게", + "appSettings_maxRouteWeightSubtitle": "한 경로가 성공적인 배송을 통해 누적할 수 있는 최대 무게", + "appSettings_initialRouteWeight": "초기 경로 가중치", + "appSettings_initialRouteWeightSubtitle": "새롭게 발견된 경로의 초기 무게", + "appSettings_routeWeightSuccessIncrement": "성공 횟수 증가", + "appSettings_routeWeightSuccessIncrementSubtitle": "성공적으로 배송된 경로에 추가된 무게", + "appSettings_routeWeightFailureDecrement": "오류 가중치 감소", + "appSettings_routeWeightFailureDecrementSubtitle": "배송 실패 후 경로에서 제거된 무게", + "appSettings_maxMessageRetries": "최대 메시지 재시도 횟수", + "appSettings_maxMessageRetriesSubtitle": "메시지를 실패로 처리하기 전 시도 횟수", + "path_routeWeight": "{weight}/{max}", + "@path_routeWeight": { + "placeholders": { + "weight": { + "type": "String" + }, + "max": { + "type": "String" + } + } + }, + "appSettings_battery": "배터리", + "appSettings_batteryChemistry": "배터리 화학", + "appSettings_batteryChemistryPerDevice": "{deviceName} 당분간", + "@appSettings_batteryChemistryPerDevice": { + "placeholders": { + "deviceName": { + "type": "String" + } + } + }, + "appSettings_batteryChemistryConnectFirst": "장치를 선택하기 위해 연결", + "appSettings_batteryNmc": "18650 NMC (3.0-4.2V)", + "appSettings_batteryLifepo4": "LiFePO4 (2.6-3.65V)", + "appSettings_batteryLipo": "리튬 폴리머 (3.0-4.2V)", + "appSettings_mapDisplay": "지도 표시", + "appSettings_showRepeaters": "반복 기능 표시", + "appSettings_showRepeatersSubtitle": "지도에 반복자 노드를 표시", + "appSettings_showChatNodes": "채팅 노드 표시", + "appSettings_showChatNodesSubtitle": "지도에 채팅 노드를 표시", + "appSettings_showOtherNodes": "다른 노드 표시", + "appSettings_showOtherNodesSubtitle": "지도에서 다른 노드 유형을 표시", + "appSettings_timeFilter": "시간 필터", + "appSettings_timeFilterShowAll": "모든 노드 표시", + "appSettings_timeFilterShowLast": "지난 {hours} 시간 동안의 노드 표시", + "@appSettings_timeFilterShowLast": { + "placeholders": { + "hours": { + "type": "int" + } + } + }, + "appSettings_mapTimeFilter": "지도 필터", + "appSettings_showNodesDiscoveredWithin": "다음 내역에서 발견된 노드 표시:", + "appSettings_allTime": "모든 시간", + "appSettings_lastHour": "지난 시간", + "appSettings_last6Hours": "지난 6시간", + "appSettings_last24Hours": "지난 24시간", + "appSettings_lastWeek": "지난 주", + "appSettings_offlineMapCache": "오프라인 지도 캐시", + "appSettings_unitsTitle": "단위", + "appSettings_unitsMetric": "단위 (m / km)", + "appSettings_unitsImperial": "제국 (피트/마일)", + "appSettings_noAreaSelected": "선택된 영역 없음", + "appSettings_areaSelectedZoom": "선택된 영역 (줌 레벨: {minZoom} - {maxZoom})", + "@appSettings_areaSelectedZoom": { + "placeholders": { + "minZoom": { + "type": "int" + }, + "maxZoom": { + "type": "int" + } + } + }, + "appSettings_debugCard": "디버깅", + "appSettings_appDebugLogging": "앱 디버깅 로깅", + "appSettings_appDebugLoggingSubtitle": "로그 앱 디버깅 메시지 (문제 해결을 위한)", + "appSettings_appDebugLoggingEnabled": "앱 디버깅 로깅 활성화", + "appSettings_appDebugLoggingDisabled": "앱 디버깅 로깅 비활성화", + "contacts_title": "연락처", + "contacts_noContacts": "아직 연락처는 없습니다.", + "contacts_contactsWillAppear": "장치가 광고를 할 때, 연락처 정보가 표시됩니다.", + "contacts_unread": "읽지 않음", + "contacts_searchContactsNoNumber": "연락처 검색...", + "contacts_searchContacts": "{number} {str} 연락처 검색...", + "@contacts_searchContacts": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, + "contacts_searchFavorites": "{number} {str} 검색 결과 보기...", + "@contacts_searchFavorites": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, + "contacts_searchUsers": "{number} {str} 사용자 검색...", + "@contacts_searchUsers": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, + "contacts_searchRepeaters": "{number} {str} 검색 결과 반복기 검색", + "@contacts_searchRepeaters": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, + "contacts_searchRoomServers": "{number} {str} 방 서버 검색", + "@contacts_searchRoomServers": { + "placeholders": { + "number": { + "type": "int" + }, + "str": { + "type": "String" + } + } + }, + "contacts_noUnreadContacts": "읽지 않은 연락처가 없습니다.", + "contacts_noContactsFound": "연락처 또는 그룹이 검색되지 않았습니다.", + "contacts_deleteContact": "연락처 삭제", + "contacts_removeConfirm": "{contactName}를 연락처 목록에서 제거하시겠습니까?", + "@contacts_removeConfirm": { + "placeholders": { + "contactName": { + "type": "String" + } + } + }, + "contacts_manageRepeater": "리피터 관리", + "contacts_manageRoom": "방 서버 관리", + "contacts_roomLogin": "방 서버 로그인", + "contacts_openChat": "자유로운 대화", + "contacts_editGroup": "편집 그룹", + "contacts_deleteGroup": "그룹 삭제", + "contacts_deleteGroupConfirm": "{groupName} 삭제?", + "@contacts_deleteGroupConfirm": { + "placeholders": { + "groupName": { + "type": "String" + } + } + }, + "contacts_newGroup": "새로운 그룹", + "contacts_groupName": "그룹 이름", + "contacts_groupNameRequired": "그룹 이름이 필요합니다", + "contacts_groupNameReserved": "이 그룹 이름은 이미 사용 중입니다.", + "contacts_groupAlreadyExists": "그룹 \"{name}\"은 이미 존재합니다.", + "@contacts_groupAlreadyExists": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "contacts_filterContacts": "연락처 필터링...", + "contacts_noContactsMatchFilter": "입력하신 검색 조건과 일치하는 연락처가 없습니다.", + "contacts_noMembers": "회원 없음", + "contacts_lastSeenNow": "최근", + "contacts_lastSeenMinsAgo": "~ {minutes} min.", + "@contacts_lastSeenMinsAgo": { + "placeholders": { + "minutes": { + "type": "int" + } + } + }, + "contacts_lastSeenHourAgo": "약 1시간", + "contacts_lastSeenHoursAgo": "~ {hours} hours", + "@contacts_lastSeenHoursAgo": { + "placeholders": { + "hours": { + "type": "int" + } + } + }, + "contacts_lastSeenDayAgo": "~ 1일", + "contacts_lastSeenDaysAgo": "~ {days}일", + "@contacts_lastSeenDaysAgo": { + "placeholders": { + "days": { + "type": "int" + } + } + }, + "channels_title": "채널", + "channels_noChannelsConfigured": "구성된 채널이 없습니다.", + "channels_addPublicChannel": "공개 채널 추가", + "channels_searchChannels": "검색 채널...", + "channels_noChannelsFound": "채널을 찾을 수 없습니다.", + "channels_channelIndex": "채널 {index}", + "@channels_channelIndex": { + "placeholders": { + "index": { + "type": "int" + } + } + }, + "channels_hashtagChannel": "해시태그 채널", + "channels_public": "대중의", + "channels_private": "사립", + "channels_publicChannel": "공개 채널", + "channels_privateChannel": "개인 채널", + "channels_editChannel": "채널 편집", + "channels_muteChannel": "음소거 채널", + "channels_unmuteChannel": "채널 음소거 해제", + "channels_deleteChannel": "채널 삭제", + "channels_deleteChannelConfirm": "{name} 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.", + "@channels_deleteChannelConfirm": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "channels_channelDeleteFailed": "채널 \"{name}\" 삭제에 실패했습니다.", + "@channels_channelDeleteFailed": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "channels_channelDeleted": "채널 \"{name}\" 삭제", + "@channels_channelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "channels_addChannel": "채널 추가", + "channels_channelIndexLabel": "채널 인덱스", + "channels_channelName": "채널 이름", + "channels_usePublicChannel": "공개 채널 사용", + "channels_standardPublicPsk": "표준 공공 PSK", + "channels_pskHex": "PSK (헥스)", + "channels_generateRandomPsk": "임의의 PSK 생성", + "channels_enterChannelName": "채널 이름을 입력해 주세요.", + "channels_pskMustBe32Hex": "PSK(개인식별키)는 32자리 16진수 문자여야 합니다.", + "channels_channelAdded": "채널 \"{name}\" 추가", + "@channels_channelAdded": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "channels_editChannelTitle": "채널 {index} 편집", + "@channels_editChannelTitle": { + "placeholders": { + "index": { + "type": "int" + } + } + }, + "channels_smazCompression": "SMAZ 압축", + "channels_channelUpdated": "채널 \"{name}\"이 업데이트되었습니다.", + "@channels_channelUpdated": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "channels_publicChannelAdded": "공개 채널 추가", + "channels_sortBy": "정렬 기준 선택", + "channels_sortManual": "사용 설명서", + "channels_sortAZ": "A부터 Z까지", + "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_replyingTo": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "chat_replyTo": "{name}님께 회신", + "@chat_replyTo": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "chat_location": "위치", + "chat_sendMessageTo": "{contactName}에게 메시지를 보내", + "@chat_sendMessageTo": { + "placeholders": { + "contactName": { + "type": "String" + } + } + }, + "chat_typeMessage": "메시지를 입력하세요...", + "chat_messageTooLong": "메시지가 너무 길어서 (최대 {maxBytes} 바이트).", + "@chat_messageTooLong": { + "placeholders": { + "maxBytes": { + "type": "int" + } + } + }, + "chat_messageCopied": "메시지가 복사되었습니다", + "chat_messageDeleted": "메시지가 삭제되었습니다.", + "chat_retryingMessage": "재시도 메시지", + "chat_retryCount": "{current}/{max} 시도", + "@chat_retryCount": { + "placeholders": { + "current": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "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": "원시 로그-RX", + "debugLog_noBleActivity": "현재 BLE 관련 활동은 없습니다.", + "debugFrame_length": "프레임 길이: {count} 바이트", + "@debugFrame_length": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "debugFrame_command": "명령: 0x{value}", + "@debugFrame_command": { + "placeholders": { + "value": { + "type": "String" + } + } + }, + "debugFrame_textMessageHeader": "텍스트 메시지 프레임:", + "debugFrame_destinationPubKey": "- 목적지 공개 키: {pubKey}", + "@debugFrame_destinationPubKey": { + "placeholders": { + "pubKey": { + "type": "String" + } + } + }, + "debugFrame_timestamp": "- 시간: {timestamp}", + "@debugFrame_timestamp": { + "placeholders": { + "timestamp": { + "type": "int" + } + } + }, + "debugFrame_flags": "- 플래그: 0x{value}", + "@debugFrame_flags": { + "placeholders": { + "value": { + "type": "String" + } + } + }, + "debugFrame_textType": "- 텍스트 유형: {type} ({label})", + "@debugFrame_textType": { + "placeholders": { + "type": { + "type": "int" + }, + "label": { + "type": "String" + } + } + }, + "debugFrame_textTypeCli": "명령줄 인터페이스 (CLI)", + "debugFrame_textTypePlain": "단순한", + "debugFrame_text": "- 텍스트: \"{text}\"", + "@debugFrame_text": { + "placeholders": { + "text": { + "type": "String" + } + } + }, + "debugFrame_hexDump": "헥스 덤프:", + "chat_pathManagement": "경로 관리", + "chat_ShowAllPaths": "모든 경로 표시", + "chat_routingMode": "라우팅 방식", + "chat_autoUseSavedPath": "자동 (저장된 경로 사용)", + "chat_forceFloodMode": "강수 모드 활성화", + "chat_recentAckPaths": "최근 사용한 ACK 경로 (사용하려면 탭):", + "chat_pathHistoryFull": "이력 기록은 이미 가득 차 있습니다. 항목을 삭제하여 새로운 항목을 추가할 수 있습니다.", + "chat_hopSingular": "점프", + "chat_hopPlural": "홉", + "chat_hopsCount": "{count} {count, plural, =1{홉} other{홉}}", + "@chat_hopsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "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": "Path set: {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}", + "@chat_pathSetHops": { + "placeholders": { + "hopCount": { + "type": "int" + }, + "status": { + "type": "String" + } + } + }, + "chat_pathSavedLocally": "로컬에 저장. 동기화 연결", + "chat_pathDeviceConfirmed": "장치 확인 완료.", + "chat_pathDeviceNotConfirmed": "기기가 아직 확인되지 않았습니다.", + "chat_type": "종류", + "chat_path": "경로", + "chat_publicKey": "공개 키", + "chat_compressOutgoingMessages": "전송되는 메시지 압축", + "chat_floodForced": "홍수 (강제)", + "chat_directForced": "직접적인 (강제적인)", + "chat_hopsForced": "{count}번 띄우기 (강제)", + "@chat_hopsForced": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "chat_floodAuto": "홍수 (자동)", + "chat_direct": "직접", + "chat_poiShared": "공유된 POI", + "chat_unread": "읽지 않음: {count}", + "@chat_unread": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "chat_openLink": "링크를 열기?", + "chat_openLinkConfirmation": "이 링크를 브라우저에서 열고 싶으신가요?", + "chat_open": "열기", + "chat_couldNotOpenLink": "링크를 열 수 없습니다: {url}", + "@chat_couldNotOpenLink": { + "placeholders": { + "url": { + "type": "String" + } + } + }, + "chat_invalidLink": "유효하지 않은 링크 형식", + "map_title": "노드 매핑", + "map_lineOfSight": "시야", + "map_losScreenTitle": "시야", + "map_noNodesWithLocation": "위치 정보가 있는 노드가 없습니다.", + "map_nodesNeedGps": "노드는 지도에 표시되려면 GPS 좌표를 공유해야 합니다.", + "map_nodesCount": "노드: {count}", + "@map_nodesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "map_pinsCount": "핀: {count}", + "@map_pinsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "map_chat": "채팅", + "map_repeater": "반복기", + "map_room": "방", + "map_sensor": "센서", + "map_pinDm": "핀 (DM)", + "map_pinPrivate": "개인 계정", + "map_pinPublic": "공개 (일반 공개)", + "map_lastSeen": "마지막으로 목격", + "map_disconnectConfirm": "이 장치와의 연결을 해제하시겠습니까?", + "map_from": "~부터", + "map_source": "출처", + "map_flags": "깃발", + "map_shareMarkerHere": "여기에서 마커 공유", + "map_setAsMyLocation": "내 위치로 설정", + "map_pinLabel": "핀 라벨", + "map_label": "레이블", + "map_pointOfInterest": "관심 지점", + "map_sendToContact": "연락처로 보내기", + "map_sendToChannel": "채널로 전송", + "map_noChannelsAvailable": "사용 가능한 채널이 없습니다.", + "map_publicLocationShare": "공개 장소 공유", + "map_publicLocationShareConfirm": "현재 {channelLabel} 채널에서 위치 정보를 공유하려고 합니다. 이 채널은 공개되어 있으며, PSK를 가진 모든 사용자가 이 위치 정보를 볼 수 있습니다.", + "@map_publicLocationShareConfirm": { + "placeholders": { + "channelLabel": { + "type": "String" + } + } + }, + "map_connectToShareMarkers": "장치를 연결하여 마커를 공유", + "map_filterNodes": "필터 노드", + "map_nodeTypes": "노드 유형", + "map_chatNodes": "채팅 노드", + "map_repeaters": "다시 보내는 장치", + "map_otherNodes": "다른 노드", + "map_keyPrefix": "핵심 접두사", + "map_filterByKeyPrefix": "주요 접두사 기준으로 필터링", + "map_publicKeyPrefix": "공개 키 접두사", + "map_markers": "마커", + "map_showSharedMarkers": "공통 마커 표시", + "map_showGuessedLocations": "추정된 노드 위치 표시", + "map_showDiscoveryContacts": "디스커버리 담당자 연락처 보기", + "map_guessedLocation": "추측된 위치", + "map_lastSeenTime": "마지막으로 확인된 시간", + "map_sharedPin": "공유 비밀번호", + "map_joinRoom": "방에 참여", + "map_manageRepeater": "리피터 관리", + "map_tapToAdd": "노드에 클릭하여 경로에 추가합니다.", + "map_runTrace": "경로 추적", + "map_removeLast": "마지막 항목 삭제", + "map_pathTraceCancelled": "경로 추적 기능이 취소되었습니다.", + "mapCache_title": "오프라인 지도 캐시", + "mapCache_selectAreaFirst": "캐시할 영역을 먼저 선택하세요", + "mapCache_noTilesToDownload": "이 지역에 다운로드할 타일이 없습니다.", + "mapCache_downloadTilesTitle": "타일 다운로드", + "mapCache_downloadTilesPrompt": "{count}개의 타일을 오프라인 사용을 위해 다운로드하시겠습니까?", + "@mapCache_downloadTilesPrompt": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "mapCache_downloadAction": "다운로드", + "mapCache_cachedTiles": "{count} 개의 타일 캐시", + "@mapCache_cachedTiles": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "mapCache_cachedTilesWithFailed": "Cached {downloaded} tiles ({failed} failed)", + "@mapCache_cachedTilesWithFailed": { + "placeholders": { + "downloaded": { + "type": "int" + }, + "failed": { + "type": "int" + } + } + }, + "mapCache_clearOfflineCacheTitle": "오프라인 캐시 삭제", + "mapCache_clearOfflineCachePrompt": "모든 캐시된 지도 템플릿을 삭제하시겠습니까?", + "mapCache_offlineCacheCleared": "오프라인 캐시 삭제", + "mapCache_noAreaSelected": "선택된 영역 없음", + "mapCache_cacheArea": "캐시 영역", + "mapCache_useCurrentView": "현재 보기 유지", + "mapCache_zoomRange": "줌 기능 범위", + "mapCache_estimatedTiles": "예상되는 타일 개수: {count}", + "@mapCache_estimatedTiles": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "mapCache_downloadedTiles": "Downloaded {completed} / {total}", + "@mapCache_downloadedTiles": { + "placeholders": { + "completed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "mapCache_downloadTilesButton": "타일 다운로드", + "mapCache_clearCacheButton": "캐시 삭제", + "mapCache_failedDownloads": "실패한 다운로드: {count}", + "@mapCache_failedDownloads": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "mapCache_boundsLabel": "N {north}, S {south}, E {east}, W {west}", + "@mapCache_boundsLabel": { + "placeholders": { + "north": { + "type": "String" + }, + "south": { + "type": "String" + }, + "east": { + "type": "String" + }, + "west": { + "type": "String" + } + } + }, + "time_justNow": "방금", + "time_minutesAgo": "{minutes}분 전", + "@time_minutesAgo": { + "placeholders": { + "minutes": { + "type": "int" + } + } + }, + "time_hoursAgo": "{hours}h ago", + "@time_hoursAgo": { + "placeholders": { + "hours": { + "type": "int" + } + } + }, + "time_daysAgo": "{days}일 전", + "@time_daysAgo": { + "placeholders": { + "days": { + "type": "int" + } + } + }, + "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_attempt": { + "placeholders": { + "current": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, + "login_failed": "로그인 실패: {error}", + "@login_failed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "login_failedMessage": "로그인에 실패했습니다. 비밀번호가 잘못되었거나, 연결이 되지 않는 것 같습니다.", + "common_reload": "다시 로드", + "common_clear": "명확하게", + "path_currentPath": "현재 경로: {path}", + "@path_currentPath": { + "placeholders": { + "path": { + "type": "String" + } + } + }, + "path_usingHopsPath": "Using {count} {count, plural, =1{hop} other{hops}} path", + "@path_usingHopsPath": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "path_enterCustomPath": "사용자 지정 경로 입력", + "path_currentPathLabel": "현재 경로", + "path_hexPrefixInstructions": "각 단계에 대한 2자리 헥사데진 접두사를 쉼표로 구분하여 입력하세요.", + "path_hexPrefixExample": "예시: A1, F2, 3C (각 노드는 자신의 공개 키의 첫 번째 바이트를 사용)", + "path_labelHexPrefixes": "경로 (헥스 접두사)", + "path_helperMaxHops": "최대 64개의 홉. 각 접두사는 2개의 16진수 문자(1바이트)로 구성됩니다.", + "path_selectFromContacts": "또 연락처 목록에서 선택:", + "path_noRepeatersFound": "반복 장치 또는 서버는 찾을 수 없습니다.", + "path_customPathsRequire": "사용자 정의 경로에는 메시지를 전달할 수 있는 중간 경로가 필요합니다.", + "path_invalidHexPrefixes": "유효하지 않은 16진수 접두사: {prefixes}", + "@path_invalidHexPrefixes": { + "placeholders": { + "prefixes": { + "type": "String" + } + } + }, + "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_neighbors": "이웃", + "repeater_neighborsSubtitle": "0홉 이웃 노드를 확인합니다.", + "repeater_settings": "설정", + "repeater_settingsSubtitle": "리피터 파라미터 설정", + "repeater_statusTitle": "반복 장치 상태", + "repeater_routingMode": "라우팅 방식", + "repeater_autoUseSavedPath": "자동 (저장된 경로 사용)", + "repeater_forceFloodMode": "강수 모드 활성화", + "repeater_pathManagement": "경로 관리", + "repeater_refresh": "새롭게", + "repeater_statusRequestTimeout": "상태 확인 요청이 시간 초과되었습니다.", + "repeater_errorLoadingStatus": "상태 로딩 오류: {error}", + "@repeater_errorLoadingStatus": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "repeater_systemInformation": "시스템 정보", + "repeater_battery": "배터리", + "repeater_clockAtLogin": "로그인 시 시간 표시", + "repeater_uptime": "가동 시간", + "repeater_queueLength": "대기 줄의 길이", + "repeater_debugFlags": "디버깅 플래그", + "repeater_radioStatistics": "라디오 통계", + "repeater_lastRssi": "마지막 RSSI 값", + "repeater_lastSnr": "마지막 SNR", + "repeater_noiseFloor": "잡음 수준", + "repeater_txAirtime": "TX 에어타임", + "repeater_rxAirtime": "RX 에어타임", + "repeater_packetStatistics": "패킷 통계", + "repeater_sent": "발송", + "repeater_received": "수신", + "repeater_duplicates": "중복", + "repeater_daysHoursMinsSecs": "{days}일 {hours}시간 {minutes}분 {seconds}초", + "@repeater_daysHoursMinsSecs": { + "placeholders": { + "days": { + "type": "int" + }, + "hours": { + "type": "int" + }, + "minutes": { + "type": "int" + }, + "seconds": { + "type": "int" + } + } + }, + "repeater_packetTxTotal": "총: {total}, 홍수: {flood}, 직접: {direct}", + "@repeater_packetTxTotal": { + "placeholders": { + "total": { + "type": "int" + }, + "flood": { + "type": "String" + }, + "direct": { + "type": "String" + } + } + }, + "repeater_packetRxTotal": "총: {total}, 홍수: {flood}, 직접: {direct}", + "@repeater_packetRxTotal": { + "placeholders": { + "total": { + "type": "int" + }, + "flood": { + "type": "String" + }, + "direct": { + "type": "String" + } + } + }, + "repeater_duplicatesFloodDirect": "홍수: {flood}, 직접: {direct}", + "@repeater_duplicatesFloodDirect": { + "placeholders": { + "flood": { + "type": "String" + }, + "direct": { + "type": "String" + } + } + }, + "repeater_duplicatesTotal": "총: {total}", + "@repeater_duplicatesTotal": { + "placeholders": { + "total": { + "type": "int" + } + } + }, + "repeater_settingsTitle": "리피터 설정", + "repeater_basicSettings": "기본 설정", + "repeater_repeaterName": "반복 장비 이름", + "repeater_repeaterNameHelper": "이 반복기용 표시 이름", + "repeater_adminPassword": "관리자 비밀번호", + "repeater_adminPasswordHelper": "전체 접근 권한 비밀번호", + "repeater_guestPassword": "게스트 비밀번호", + "repeater_guestPasswordHelper": "읽기 전용 접근 비밀번호", + "repeater_radioSettings": "라디오 설정", + "repeater_frequencyMhz": "주파수 (MHz)", + "repeater_frequencyHelper": "300-2500 MHz", + "repeater_txPower": "TX 파워", + "repeater_txPowerHelper": "1~30 dBm", + "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_localAdvertIntervalMinutes": { + "placeholders": { + "minutes": { + "type": "int" + } + } + }, + "repeater_floodAdvertInterval": "홍수 광고 간격", + "repeater_floodAdvertIntervalHours": "{hours} 시간", + "@repeater_floodAdvertIntervalHours": { + "placeholders": { + "hours": { + "type": "int" + } + } + }, + "repeater_encryptedAdvertInterval": "암호화된 광고 간격", + "repeater_dangerZone": "위험 구역", + "repeater_rebootRepeater": "리부트 반복", + "repeater_rebootRepeaterSubtitle": "리피터 장치를 재시작하세요.", + "repeater_rebootRepeaterConfirm": "반복기를 재부팅하시려는 것이 맞으신가요?", + "repeater_regenerateIdentityKey": "아이디 키 재 생성", + "repeater_regenerateIdentityKeySubtitle": "새로운 공개/개인 키 쌍 생성", + "repeater_regenerateIdentityKeyConfirm": "이를 통해 리피터에 새로운 식별자를 할당합니다. 계속 진행하시겠습니까?", + "repeater_eraseFileSystem": "파일 시스템 삭제", + "repeater_eraseFileSystemSubtitle": "리피터 파일 시스템을 포맷합니다.", + "repeater_eraseFileSystemConfirm": "경고: 이 작업은 리피터에 있는 모든 데이터를 삭제합니다. 이 작업을 되돌릴 수 없습니다!", + "repeater_eraseSerialOnly": "'Erase' 기능은 시리얼 콘솔을 통해서만 사용할 수 있습니다.", + "repeater_commandSent": "명령 전송: {command}", + "@repeater_commandSent": { + "placeholders": { + "command": { + "type": "String" + } + } + }, + "repeater_errorSendingCommand": "명령 전송 오류: {error}", + "@repeater_errorSendingCommand": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "repeater_confirm": "확인", + "repeater_settingsSaved": "설정이 성공적으로 저장되었습니다.", + "repeater_errorSavingSettings": "설정 저장 오류: {error}", + "@repeater_errorSavingSettings": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "repeater_refreshBasicSettings": "기본 설정 초기화", + "repeater_refreshRadioSettings": "라디오 설정 초기화", + "repeater_refreshTxPower": "TX 전원 재설정", + "repeater_refreshLocationSettings": "위치 설정 초기화", + "repeater_refreshPacketForwarding": "패킷 전송 재시작", + "repeater_refreshGuestAccess": "게스트 접근 권한 갱신", + "repeater_refreshPrivacyMode": "개인 정보 보호 모드 재설정", + "repeater_refreshAdvertisementSettings": "광고 설정 재설정", + "repeater_refreshed": "{label}가 갱신됨", + "@repeater_refreshed": { + "placeholders": { + "label": { + "type": "String" + } + } + }, + "repeater_errorRefreshing": "{label}를 새로 고침 중 오류 발생", + "@repeater_errorRefreshing": { + "placeholders": { + "label": { + "type": "String" + } + } + }, + "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_cliCommandError": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "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": "다양한 통계 지표를 0으로 초기화합니다.", + "repeater_cliHelpSetAf": "에어 타임 요소를 설정합니다.", + "repeater_cliHelpSetTx": "LoRa 전송 전력을 dBm 단위로 설정합니다. (설정을 적용하려면 재부팅 필요)", + "repeater_cliHelpSetRepeat": "이 노드에 대한 리피터 역할을 활성화하거나 비활성화합니다.", + "repeater_cliHelpSetAllowReadOnly": "(방 서버) '켜짐' 상태인 경우, 빈 비밀번호로 로그인할 수 있지만, 방에 게시할 수는 없습니다 (단, 읽기만 가능).", + "repeater_cliHelpSetFloodMax": "들어오는 플러드 패킷의 최대 홉 수를 설정합니다 (최대 홉 수보다 크거나 같으면 패킷은 전달되지 않습니다).", + "repeater_cliHelpSetIntThresh": "간섭 임계값을 설정합니다 (dB 단위). 기본값은 14입니다. 0으로 설정하면 채널 간섭 감지 기능을 비활성화합니다.", + "repeater_cliHelpSetAgcResetInterval": "자동 게인 제어기를 재설정하는 간격을 설정합니다. 0으로 설정하면 비활성화됩니다.", + "repeater_cliHelpSetMultiAcks": "'더블 ACK' 기능을 활성화하거나 비활성화할 수 있습니다.", + "repeater_cliHelpSetAdvertInterval": "로컬 (제로 홉) 광고 패킷을 전송하는 간격 (분 단위)을 설정합니다. 0으로 설정하면 비활성화됩니다.", + "repeater_cliHelpSetFloodAdvertInterval": "시간 단위로 광고 패킷을 전송하는 간격을 설정합니다. 0으로 설정하면 비활성화됩니다.", + "repeater_cliHelpSetGuestPassword": "게스트 비밀번호를 설정하거나 업데이트합니다. (반복 사용자, 게스트 로그인 시 \"통계 가져오기\" 요청을 보낼 수 있음)", + "repeater_cliHelpSetName": "광고 이름을 설정합니다.", + "repeater_cliHelpSetLat": "광고 지도의 위도를 설정합니다. (십진법 단위)", + "repeater_cliHelpSetLon": "광고 지도의 경도를 설정합니다. (십진도)", + "repeater_cliHelpSetRadio": "완전히 새로운 라디오 파라미터를 설정하고, 선호 사항에 저장합니다. 적용하려면 \"재부팅\" 명령이 필요합니다.", + "repeater_cliHelpSetRxDelay": "(실험용) 기본 설정 (최소 1이어야 함)으로, 수신된 패킷에 약간의 지연을 적용하며, 신호 강도/점수를 기준으로 설정합니다. 0으로 설정하면 비활성화됩니다.", + "repeater_cliHelpSetTxDelay": "공통 패킷의 전송 지연 시간을 설정하며, 시간-공기 시간과 무작위 슬롯 시스템을 곱하여 충돌 가능성을 줄입니다.", + "repeater_cliHelpSetDirectTxDelay": "txdelay와 동일하게, 하지만 직접 모드 패킷 전송 시 무작위 지연을 적용하는 경우", + "repeater_cliHelpSetBridgeEnabled": "브리지 활성화/비활성화", + "repeater_cliHelpSetBridgeDelay": "패킷 재전송 전에 지연 시간을 설정합니다.", + "repeater_cliHelpSetBridgeSource": "브리지가 수신된 패킷을 다시 전송할지, 아니면 전송된 패킷을 다시 전송할지 선택하십시오.", + "repeater_cliHelpSetBridgeBaud": "rs232 브리지에 대한 직렬 통신 속도(baud rate)를 설정합니다.", + "repeater_cliHelpSetBridgeSecret": "ESPNow 브리지에 대한 비밀 설정", + "repeater_cliHelpSetAdcMultiplier": "특정 보드에서만 지원되는 방식으로, 보고되는 배터리 전압을 조정하기 위한 사용자 정의 요소를 설정할 수 있습니다.", + "repeater_cliHelpTempRadio": "주어진 시간(분) 동안 임시 라디오 파라미터를 설정하고, 이후 원래 라디오 파라미터로 되돌립니다. (설정을 저장하지 않습니다).", + "repeater_cliHelpSetPerm": "ACL을 수정합니다. \"permissions\" 값이 0인 경우, 일치하는 항목(pubkey 접두사)을 제거합니다. pubkey-hex 길이가 완전하고 현재 ACL에 없는 경우 새로운 항목을 추가합니다. pubkey 접두사를 기준으로 항목을 업데이트합니다. 권한 비트는 펌웨어 역할에 따라 다르지만, 하위 2비트는 다음과 같습니다: 0 (게스트), 1 (읽기 전용), 2 (읽기/쓰기), 3 (관리자)", + "repeater_cliHelpGetBridgeType": "브리지형, RS232, ESPNOW 지원", + "repeater_cliHelpLogStart": "패킷 로깅을 파일 시스템으로 시작합니다.", + "repeater_cliHelpLogStop": "패킷 로깅을 파일 시스템으로 저장하는 것을 중단합니다.", + "repeater_cliHelpLogErase": "파일 시스템에서 패킷 로그를 삭제합니다.", + "repeater_cliHelpNeighbors": "제로 홉 광고를 통해 수신된 다른 리피터 노드 목록을 보여줍니다. 각 줄은 ID-프리픽스-16진수:타임스탬프:SNR-횟수-4 형식입니다.", + "repeater_cliHelpNeighborRemove": "이 함수는 지정된 pubkey 접두사(16진수)와 일치하는 첫 번째 항목을 이웃 목록에서 제거합니다.", + "repeater_cliHelpRegion": "(단일 시리즈) 정의된 모든 지역과 현재 홍수 허가 정보를 나열합니다.", + "repeater_cliHelpRegionLoad": "참고: 이는 여러 명령을 한 번에 실행하는 특별한 방식입니다. 각 후속 명령은 영역 이름이며 (부모 계층 구조를 나타내기 위해 공백으로 들여쓰기하며, 최소 1개의 공백을 사용) 공백으로 끝나는 줄 또는 명령을 보내어 종료합니다.", + "repeater_cliHelpRegionGet": "주어진 이름 접두사(또는 전역 검색을 위한 \"\\*\" 사용)를 사용하여 특정 지역을 검색합니다. 결과를 \"-> 지역 이름 (상위 지역 이름) 'F'\" 형태로 반환합니다.", + "repeater_cliHelpRegionPut": "주어진 이름으로 지역 정의를 추가하거나 업데이트합니다.", + "repeater_cliHelpRegionRemove": "지정된 이름으로 특정 영역 정의를 제거합니다. (정확히 일치해야 하며, 하위 영역은 존재하지 않아야 합니다)", + "repeater_cliHelpRegionAllowf": "지정된 영역에 대한 '물' 접근 권한을 설정합니다. ('*'는 전역/기존 범위에 해당)", + "repeater_cliHelpRegionDenyf": "지정된 영역에 대해 'Flood' 권한을 제거합니다. (참고: 현재 단계에서는 전역/기존 범위에서 이 기능을 사용하지 않는 것이 좋습니다!!)", + "repeater_cliHelpRegionHome": "현재 '홈' 지역으로 응답합니다. (아직 적용되지 않았으며, 향후 사용을 위해 예약됨)", + "repeater_cliHelpRegionHomeSet": "'홈' 지역을 설정합니다.", + "repeater_cliHelpRegionSave": "지역 목록/지도를 저장에 유지합니다.", + "repeater_cliHelpGps": "GPS 상태를 표시합니다. GPS가 꺼져 있으면 \"꺼짐\"이라고 표시하고, 켜져 있으면 \"켜짐\", 상태, 위치 정보, 위성 수 등을 표시합니다.", + "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_errorLoading": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "telemetry_noData": "텔레메트리 데이터는 제공되지 않습니다.", + "telemetry_channelTitle": "채널 {channel}", + "@telemetry_channelTitle": { + "placeholders": { + "channel": { + "type": "int" + } + } + }, + "telemetry_batteryLabel": "배터리", + "telemetry_voltageLabel": "전압", + "telemetry_mcuTemperatureLabel": "MCU의 온도", + "telemetry_temperatureLabel": "온도", + "telemetry_currentLabel": "현재", + "telemetry_batteryValue": "{percent}% / {volts}V", + "@telemetry_batteryValue": { + "placeholders": { + "percent": { + "type": "int" + }, + "volts": { + "type": "String" + } + } + }, + "telemetry_voltageValue": "{volts}V", + "@telemetry_voltageValue": { + "placeholders": { + "volts": { + "type": "String" + } + } + }, + "telemetry_currentValue": "{amps}A", + "@telemetry_currentValue": { + "placeholders": { + "amps": { + "type": "String" + } + } + }, + "telemetry_temperatureValue": "{celsius}°C / {fahrenheit}°F", + "@telemetry_temperatureValue": { + "placeholders": { + "celsius": { + "type": "String" + }, + "fahrenheit": { + "type": "String" + } + } + }, + "neighbors_receivedData": "이웃 정보 수집", + "neighbors_requestTimedOut": "이웃들이 시간 제한을 요청하고 있습니다.", + "neighbors_errorLoading": "이웃 정보 로딩 중 오류: {error}", + "@neighbors_errorLoading": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "neighbors_repeatersNeighbors": "반복기, 이웃", + "neighbors_noData": "이웃 정보는 없습니다.", + "neighbors_unknownContact": "알 수 없는 {pubkey}", + "@neighbors_unknownContact": { + "placeholders": { + "pubkey": { + "type": "String" + } + } + }, + "neighbors_heardAgo": "Heard: {time} ago", + "@neighbors_heardAgo": { + "placeholders": { + "time": { + "type": "String" + } + } + }, + "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_observedPathTitle": { + "placeholders": { + "index": { + "type": "int" + }, + "hops": { + "type": "String" + } + } + }, + "channelPath_noLocationData": "위치 정보 없음", + "channelPath_timeWithDate": "{day}/{month} {time}", + "@channelPath_timeWithDate": { + "placeholders": { + "day": { + "type": "int" + }, + "month": { + "type": "int" + }, + "time": { + "type": "String" + } + } + }, + "channelPath_timeOnly": "{time}", + "@channelPath_timeOnly": { + "placeholders": { + "time": { + "type": "String" + } + } + }, + "channelPath_unknownPath": "알 수 없음", + "channelPath_floodPath": "홍수", + "channelPath_directPath": "직접", + "channelPath_observedZeroOf": "{total} 중 0개", + "@channelPath_observedZeroOf": { + "placeholders": { + "total": { + "type": "int" + } + } + }, + "channelPath_observedSomeOf": "{observed} of {total} hops", + "@channelPath_observedSomeOf": { + "placeholders": { + "observed": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, + "channelPath_mapTitle": "경로 지도", + "channelPath_noRepeaterLocations": "이 경로에 대한 중계기 설치 위치는 없습니다.", + "channelPath_primaryPath": "경로 {index} (주 경로)", + "@channelPath_primaryPath": { + "placeholders": { + "index": { + "type": "int" + } + } + }, + "@channelPath_pathLabel": { + "placeholders": { + "index": { + "type": "int" + } + } + }, + "channelPath_pathLabelTitle": "경로", + "channelPath_observedPathHeader": "관찰된 경로", + "channelPath_selectedPathLabel": "{label} • {prefixes}", + "@channelPath_selectedPathLabel": { + "placeholders": { + "label": { + "type": "String" + }, + "prefixes": { + "type": "String" + } + } + }, + "channelPath_noHopDetailsAvailable": "이 패킷에 대한 이동 정보는 제공되지 않습니다.", + "channelPath_unknownRepeater": "알 수 없는 중계기", + "community_title": "지역 사회", + "community_create": "커뮤니티 만들기", + "community_createDesc": "새로운 커뮤니티를 만들고 QR 코드를 통해 공유하세요.", + "community_join": "참여하기", + "community_joinTitle": "커뮤니티에 참여하기", + "community_joinConfirmation": "{name}님, 커뮤니티에 참여하고 싶으신가요?", + "@community_joinConfirmation": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_scanQr": "커뮤니티 QR 스캔", + "community_scanInstructions": "카메라를 커뮤니티 QR 코드 방향으로 향하게 하세요.", + "community_showQr": "QR 코드 표시", + "community_publicChannel": "지역 사회 대상", + "community_hashtagChannel": "커뮤니티 해시태그", + "community_name": "지역 이름", + "community_enterName": "커뮤니티 이름을 입력하세요", + "community_created": "커뮤니티 \"{name}\"이 생성되었습니다.", + "@community_created": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_joined": "\"{name}\" 커뮤니티에 가입", + "@community_joined": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_qrTitle": "커뮤니티 공유", + "community_qrInstructions": "이 QR 코드를 스캔하여 \"{name}\"에 가입하세요.", + "@community_qrInstructions": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_hashtagPrivacyHint": "커뮤니티 해시태그 채널은 커뮤니티 구성원만 가입할 수 있습니다.", + "community_invalidQrCode": "유효하지 않은 커뮤니티 QR 코드", + "community_alreadyMember": "이미 회원인 경우", + "community_alreadyMemberMessage": "이미 {name}의 회원입니다.", + "@community_alreadyMemberMessage": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_addPublicChannel": "커뮤니티 공개 채널 추가", + "community_addPublicChannelHint": "이 커뮤니티에 공개 채널을 자동으로 추가합니다.", + "community_noCommunities": "아직 어느 커뮤니티도 가입하지 않았습니다.", + "community_scanOrCreate": "QR 코드를 스캔하거나 커뮤니티를 만들어 시작하세요.", + "community_manageCommunities": "커뮤니티 관리", + "community_delete": "커뮤니티 떠나기", + "community_deleteConfirm": "{name}을 묻어두나요?", + "@community_deleteConfirm": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_deleteChannelsWarning": "또한, 이 기능은 {count}개의 채널과 그에 해당하는 메시지를 삭제합니다.", + "@community_deleteChannelsWarning": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "community_deleted": "지역 커뮤니티 \"{name}\"", + "@community_deleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_regenerateSecret": "비밀 복원", + "community_regenerateSecretConfirm": "{name}의 비밀 키를 재생성하시겠습니까? 모든 회원은 계속 통신을 위해 새로운 QR 코드를 스캔해야 합니다.", + "@community_regenerateSecretConfirm": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_regenerate": "재생", + "community_secretRegenerated": "{name}을 위한 비밀 정보가 복원되었습니다.", + "@community_secretRegenerated": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_updateSecret": "비밀 업데이트", + "community_secretUpdated": "{name}을 위한 비밀 정보 업데이트", + "@community_secretUpdated": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_scanToUpdateSecret": "새로운 QR 코드를 스캔하여 {name}의 비밀번호를 업데이트하세요.", + "@community_scanToUpdateSecret": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "community_addHashtagChannel": "커뮤니티 해시태그 추가", + "community_addHashtagChannelDesc": "이 커뮤니티를 위한 해시태그 채널을 추가하세요.", + "community_selectCommunity": "커뮤니티 선택", + "community_regularHashtag": "일반 해시태그", + "community_regularHashtagDesc": "공개 해시태그 (누구나 참여 가능)", + "community_communityHashtag": "커뮤니티 해시태그", + "community_communityHashtagDesc": "지역 주민을 위한", + "community_forCommunity": "{name} 님께", + "@community_forCommunity": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "listFilter_tooltip": "필터링 및 정렬", + "listFilter_sortBy": "정렬 기준 선택", + "listFilter_latestMessages": "최신 메시지", + "listFilter_heardRecently": "최근에 들었습니다", + "listFilter_az": "A부터 Z까지", + "listFilter_filters": "필터", + "listFilter_all": "모든", + "listFilter_favorites": "관심 목록", + "listFilter_addToFavorites": "즐겨찾으로 추가", + "listFilter_removeFromFavorites": "즐겨찾에서 제거", + "listFilter_users": "사용자", + "listFilter_repeaters": "다시 보내는 장치", + "listFilter_roomServers": "방 내 서버", + "listFilter_unreadOnly": "읽지 않은 항목만", + "listFilter_newGroup": "새로운 그룹", + "pathTrace_you": "당신", + "pathTrace_failed": "경로 추적 실패.", + "pathTrace_notAvailable": "경로 추적 기능은 제공되지 않습니다.", + "pathTrace_refreshTooltip": "경로 추적 재시작", + "pathTrace_someHopsNoLocation": "홉 중 하나 또는 여러 개에 위치 정보가 누락되었습니다!", + "pathTrace_clearTooltip": "명확한 경로.", + "losSelectStartEnd": "LOS(최소 거리 경로)의 시작 및 종료 노드를 선택합니다.", + "losRunFailed": "시야 확인 실패: {error}", + "@losRunFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "losClearAllPoints": "모든 사항을 명확히 합니다.", + "losRunToViewElevationProfile": "LOS(Line of Sight)를 사용하여 고도 프로필을 확인합니다.", + "losMenuTitle": "LOS 메뉴", + "losMenuSubtitle": "사용자 지정 지점을 추가하려면, 노드를 탭하거나 맵을 길게 눌러 주세요.", + "losShowDisplayNodes": "노드 표시", + "losCustomPoints": "사용자 지정 포인트", + "losCustomPointLabel": "맞춤형 {index}", + "@losCustomPointLabel": { + "placeholders": { + "index": { + "type": "int" + } + } + }, + "losPointA": "A 지점", + "losPointB": "점 B", + "losAntennaA": "안테나 A: {value} {unit}", + "@losAntennaA": { + "placeholders": { + "value": { + "type": "String" + }, + "unit": { + "type": "String" + } + } + }, + "losAntennaB": "Antenna B: {value} {unit}", + "@losAntennaB": { + "placeholders": { + "value": { + "type": "String" + }, + "unit": { + "type": "String" + } + } + }, + "losRun": "LOS (Loss of Signal) 상태로 전환", + "losNoElevationData": "고도 정보 없음", + "losProfileClear": "{distance} {distanceUnit}, clear LOS, min clearance {clearance} {heightUnit}", + "@losProfileClear": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "clearance": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "losProfileBlocked": "{distance} {distanceUnit}, blocked by {obstruction} {heightUnit}", + "@losProfileBlocked": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "losStatusChecking": "LOS: 확인 중...", + "losStatusNoData": "LOS: 데이터 없음", + "losStatusSummary": "LOS: {clear}/{total} 개, {blocked} 개, {unknown} 개", + "@losStatusSummary": { + "placeholders": { + "clear": { + "type": "int" + }, + "total": { + "type": "int" + }, + "blocked": { + "type": "int" + }, + "unknown": { + "type": "int" + } + } + }, + "losErrorElevationUnavailable": "샘플 중 하나 이상에 대한 고도 데이터가 없습니다.", + "losErrorInvalidInput": "LOS 계산에 사용되는 부정확한 지점/고도 데이터.", + "losRenameCustomPoint": "사용자 지정된 지점의 이름을 변경", + "losPointName": "항목 이름", + "losShowPanelTooltip": "LOS 패널 표시", + "losHidePanelTooltip": "LOS 패널 숨기기", + "losElevationAttribution": "고도 데이터: Open-Meteo (CC BY 4.0)", + "losLegendRadioHorizon": "라디오 호라이즌", + "losLegendLosBeam": "LOS 빔", + "losLegendTerrain": "지형", + "losFrequencyLabel": "빈도", + "losFrequencyInfoTooltip": "계산 내역 보기", + "losFrequencyDialogTitle": "라디오 수신 가능 범위 계산", + "losFrequencyDialogDescription": "{baselineK}에서 시작하여 {baselineFreq} MHz의 주파수에서 계산을 시작하면, 현재 {frequencyMHz} MHz 대역에 대한 k-값을 조정하여, 이는 곡선형 라디오 지평선 상한선을 정의합니다.", + "@losFrequencyDialogDescription": { + "description": "Explain how the calculation uses the baseline frequency and derived k-factor.", + "placeholders": { + "baselineK": { + "type": "double" + }, + "baselineFreq": { + "type": "double" + }, + "frequencyMHz": { + "type": "double" + }, + "kFactor": { + "type": "double" + } + } + }, + "contacts_pathTrace": "경로 추적", + "contacts_ping": "핑", + "contacts_repeaterPathTrace": "리피터로 가는 경로", + "contacts_repeaterPing": "핑 반복", + "contacts_roomPathTrace": "방 서버로의 경로 추적", + "contacts_roomPing": "피нг 룸 서버", + "contacts_chatTraceRoute": "경로 추적 경로", + "contacts_pathTraceTo": "{name}까지의 경로 추적", + "@contacts_pathTraceTo": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "contacts_clipboardEmpty": "클립보드가 비어 있습니다.", + "contacts_invalidAdvertFormat": "유효하지 않은 연락 정보", + "contacts_contactImported": "연락이 수신되었습니다.", + "contacts_contactImportFailed": "연락처를 가져오지 못했습니다.", + "contacts_zeroHopAdvert": "제로 홉 광고", + "contacts_floodAdvert": "홍수 광고", + "contacts_copyAdvertToClipboard": "광고 텍스트를 클립보드에 복사", + "contacts_addContactFromClipboard": "복사본에서 연락처 추가", + "contacts_ShareContact": "연락처를 복사", + "contacts_ShareContactZeroHop": "광고를 통해 연락처 공유", + "contacts_zeroHopContactAdvertSent": "광고를 통해 연락처를 받았습니다.", + "contacts_zeroHopContactAdvertFailed": "연락처 전송에 실패했습니다.", + "contacts_contactAdvertCopied": "광고 내용이 복사되었습니다.", + "contacts_contactAdvertCopyFailed": "광고를 클립보드에 복사하는 데 실패했습니다.", + "notification_activityTitle": "메쉬코어 활동", + "notification_messagesCount": "{count} {count, plural, =1{메시지} other{메시지들}}", + "@notification_messagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notification_channelMessagesCount": "{count} {count, plural, =1{채널 메시지} other{채널 메시지}}", + "@notification_channelMessagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notification_newNodesCount": "{count} {count, plural, =1{새 노드} other{새 노드들}}", + "@notification_newNodesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "notification_newTypeDiscovered": "새로운 {contactType} 발견", + "@notification_newTypeDiscovered": { + "placeholders": { + "contactType": { + "type": "String" + } + } + }, + "notification_receivedNewMessage": "새로운 메시지를 받았습니다", + "settings_gpxExportRepeaters": "GPX로 전송/방 관리 서버", + "settings_gpxExportRepeatersSubtitle": "GPX 파일에 위치 정보를 포함하여 반복자/룸 서버를 내보냅니다.", + "settings_gpxExportContacts": "GPX 형식으로 내보내기", + "settings_gpxExportContactsSubtitle": "GPX 파일에 위치 정보를 포함하여 동행하는 기능을 내보냅니다.", + "settings_gpxExportAll": "모든 연락처를 GPX 형식으로 내보내기", + "settings_gpxExportAllSubtitle": "위치 정보가 있는 모든 연락처를 GPX 파일로 내보냅니다.", + "settings_gpxExportSuccess": "GPX 파일이 성공적으로 내보내졌습니다.", + "settings_gpxExportNoContacts": "수출할 연락처가 없습니다.", + "settings_gpxExportNotAvailable": "귀하의 장치/운영체제에서는 지원되지 않습니다.", + "settings_gpxExportError": "데이터 내보내기 과정에서 오류가 발생했습니다.", + "settings_gpxExportRepeatersRoom": "중계 장치 및 서버 위치", + "settings_gpxExportChat": "함께 방문할 장소", + "settings_gpxExportAllContacts": "모든 연락처 위치", + "settings_gpxExportShareText": "meshcore-open에서 추출한 지도 데이터", + "settings_gpxExportShareSubject": "meshcore-open GPX 지도 데이터 내보내기", + "snrIndicator_nearByRepeaters": "주변의 중계기", + "snrIndicator_lastSeen": "마지막으로 목격", + "contactsSettings_title": "연락처 설정", + "contactsSettings_autoAddTitle": "자동 검색", + "contactsSettings_otherTitle": "다른 연락 관련 설정", + "contactsSettings_autoAddUsersTitle": "자동으로 사용자 추가", + "contactsSettings_autoAddUsersSubtitle": "동반자가 자동으로 발견한 사용자를 추가할 수 있도록 합니다.", + "contactsSettings_autoAddRepeatersTitle": "자동으로 중계기 추가", + "contactsSettings_autoAddRepeatersSubtitle": "애완동물이 발견한 무선 라디오를 자동으로 추가할 수 있도록 설정합니다.", + "contactsSettings_autoAddRoomServersTitle": "자동으로 방 서버 추가", + "contactsSettings_autoAddRoomServersSubtitle": "애완동물이 발견한 방 서버를 자동으로 추가할 수 있도록 설정합니다.", + "contactsSettings_autoAddSensorsTitle": "자동으로 센서 추가", + "contactsSettings_autoAddSensorsSubtitle": "애완동물이 발견한 센서를 자동으로 추가할 수 있도록 설정합니다.", + "contactsSettings_overwriteOldestTitle": "가장 오래된 것을 덮어쓰기", + "contactsSettings_overwriteOldestSubtitle": "연락처 목록이 가득 차면, 가장 오래된 (선호하지 않은) 연락처가 대체됩니다.", + "discoveredContacts_Title": "연락처 찾기", + "discoveredContacts_noMatching": "일치하는 연락처가 없습니다.", + "discoveredContacts_searchHint": "발견된 연락처 검색", + "discoveredContacts_contactAdded": "연락처 추가", + "discoveredContacts_addContact": "연락처 추가", + "discoveredContacts_copyContact": "복사", + "discoveredContacts_deleteContact": "발견된 연락처 삭제", + "discoveredContacts_deleteContactAll": "발견된 모든 연락처 삭제", + "discoveredContacts_deleteContactAllContent": "정말로 모든 검색된 연락처를 삭제하시겠습니까?", + "chat_sendCooldown": "다시 보내기 전에 잠시 기다려 주시기 바랍니다.", + "appSettings_jumpToOldestUnread": "가장 오래된, 아직 읽지 않은 항목으로 이동", + "appSettings_jumpToOldestUnreadSubtitle": "새로운 메시지가 없는 채팅을 열 때, 최신 메시지가 아닌 첫 번째 읽지 않은 메시지로 스크롤하세요.", + "appSettings_languageHu": "헝가리", + "appSettings_languageJa": "일본어", + "appSettings_languageKo": "한국어", + "radioStats_tooltip": "라디오 및 메시 통계", + "radioStats_screenTitle": "라디오 통계", + "radioStats_notConnected": "라디오 통계를 확인하기 위해 장치에 연결합니다.", + "radioStats_firmwareTooOld": "무선 통계 기능을 사용하려면 v8 또는 그 이상의 호환 펌웨어가 필요합니다.", + "radioStats_waiting": "데이터를 기다리는 중…", + "radioStats_noiseFloor": "잡음 수준: {noiseDbm} dBm", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "radioStats_lastRssi": "마지막 RSSI: {rssiDbm} dBm", + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "radioStats_lastSnr": "마지막 SNR: {snr} dB", + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "radioStats_txAir": "TX 방송 시간 (총): {seconds} 초", + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "radioStats_rxAir": "RX 사용 시간 (총): {seconds} 초", + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "radioStats_chartCaption": "최근 샘플의 잡음 수준 (dBm)", + "radioStats_stripNoise": "잡음 수준: {noiseDbm} dBm", + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "radioStats_stripWaiting": "라디오 통계 가져오기…", + "radioStats_settingsTile": "라디오 통계", + "radioStats_settingsSubtitle": "잡음 수준, RSSI, 신호 대 잡음비, 통신 시간", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, + "settings_privacy": "개인 정보 설정", + "settings_privacySubtitle": "어떤 정보를 공유할지 통제하세요.", + "settings_privacySettingsDescription": "어떤 정보를 기기가 다른 사람들과 공유할지 선택하세요.", + "settings_denyAll": "모든 것을 부정", + "settings_allowByContact": "연락처 표시 기능 활성화", + "settings_allowAll": "모든 것을 허용", + "settings_telemetryBaseMode": "원격 모니터링 기본 설정", + "settings_telemetryLocationMode": "텔레메트리 위치 모드", + "settings_telemetryEnvironmentMode": "텔레메트리 환경 모드", + "settings_advertLocation": "광고 위치", + "settings_advertLocationSubtitle": "광고에 위치 정보를 포함하세요.", + "settings_multiAck": "다중 ACK: {value}", + "settings_telemetryModeUpdated": "텔레메트리 모드 업데이트 완료", + "contact_info": "연락처", + "contact_settings": "연락처 설정", + "contact_telemetry": "텔레메트리", + "contact_lastSeen": "마지막으로 목격", + "contact_clearChat": "명확한 대화", + "contact_teleBase": "텔레메트리 기반", + "contact_teleBaseSubtitle": "배터리 잔량 및 기본적인 통신 데이터를 공유할 수 있도록 허용", + "contact_teleLoc": "텔레메트리 위치", + "contact_teleLocSubtitle": "위치 정보 공유 허용", + "contact_teleEnv": "텔레메트리 환경", + "contact_teleEnvSubtitle": "환경 센서 데이터를 공유하도록 허용", + "map_showOverlaps": "반복 키 중복", + "map_runTraceWithReturnPath": "원래 경로로 돌아가세요." +} diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index af57fc2..6e9c0de 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -3494,7 +3494,7 @@ class AppLocalizationsEs extends AppLocalizations { @override String get appSettings_jumpToOldestUnread => - 'Ve a el mensaje más antiguo sin leer'; + 'Salta a los mensajes más antiguos sin leer'; @override String get appSettings_jumpToOldestUnreadSubtitle => diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart new file mode 100644 index 0000000..7a0bf11 --- /dev/null +++ b/lib/l10n/app_localizations_hu.dart @@ -0,0 +1,3589 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Hungarian (`hu`). +class AppLocalizationsHu extends AppLocalizations { + AppLocalizationsHu([String locale = 'hu']) : super(locale); + + @override + String get appTitle => 'MeshCore Open'; + + @override + String get nav_contacts => 'Kapcsolatok'; + + @override + String get nav_channels => 'Csatornák'; + + @override + String get nav_map => 'Térkép'; + + @override + String get common_cancel => 'Át kell venni'; + + @override + String get common_ok => 'Rendben'; + + @override + String get common_connect => 'Kapcsolódj'; + + @override + String get common_unknownDevice => 'Tudatlan eszköz'; + + @override + String get common_save => 'Mentés'; + + @override + String get common_delete => 'Töröl'; + + @override + String get common_deleteAll => 'Minden törlés'; + + @override + String get common_close => 'Bezárás'; + + @override + String get common_edit => 'Szerkesztés'; + + @override + String get common_add => 'Hozzáad'; + + @override + String get common_settings => 'Beállítások'; + + @override + String get common_disconnect => 'Csatlakozást megszakasztani'; + + @override + String get common_connected => 'Kapcsolódó'; + + @override + String get common_disconnected => 'Elválasztva'; + + @override + String get common_create => 'Készítsd'; + + @override + String get common_continue => 'Folytatás'; + + @override + String get common_share => 'Ossza meg'; + + @override + String get common_copy => 'Másolat'; + + @override + String get common_retry => 'Újrapróbálja'; + + @override + String get common_hide => 'Elrejt'; + + @override + String get common_remove => 'Eltávolít'; + + @override + String get common_enable => 'Engedélyezés'; + + @override + String get common_disable => 'Leteteszt'; + + @override + String get common_reboot => 'Újraindítás'; + + @override + String get common_loading => 'Betöltés...'; + + @override + String get common_notAvailable => '—'; + + @override + String common_voltageValue(String volts) { + return '$volts V'; + } + + @override + String common_percentValue(int percent) { + return '$percent%'; + } + + @override + String get scanner_title => 'MeshCore nyitott'; + + @override + String get connectionChoiceUsbLabel => 'USB'; + + @override + String get connectionChoiceBluetoothLabel => 'Bluetooth'; + + @override + String get connectionChoiceTcpLabel => 'TCP'; + + @override + String get tcpScreenTitle => 'TCP-n keresztül kapcsolódjon'; + + @override + String get tcpHostLabel => 'IP-cím'; + + @override + String get tcpHostHint => '192.168.40.10'; + + @override + String get tcpPortLabel => 'Múzeum'; + + @override + String get tcpPortHint => '5000'; + + @override + String get tcpStatus_notConnected => + 'Adja meg a célpontot, majd kapcsolja össze.'; + + @override + String tcpStatus_connectingTo(String endpoint) { + return 'Kapcsolat a $endpoint-hez...'; + } + + @override + String get tcpErrorHostRequired => 'Az IP-címet meg kell adni.'; + + @override + String get tcpErrorPortInvalid => 'Az érték 1 és 65535 között kell lennie.'; + + @override + String get tcpErrorUnsupported => + 'A TCP-protokoll nem támogatott ez a platformon.'; + + @override + String get tcpErrorTimedOut => 'A TCP-kapcsolat időtúllépett.'; + + @override + String tcpConnectionFailed(String error) { + return 'A TCP-kapcsolat sikertelen: $error'; + } + + @override + String get usbScreenTitle => 'USB-en keresztül csatlakoztassuk'; + + @override + String get usbScreenSubtitle => + 'Válasszon egy azonosított soros eszközt, és közvetlenül csatlakoztassa a MeshCore-hoz.'; + + @override + String get usbScreenStatus => 'Válasszon egy USB-es eszközt'; + + @override + String get usbScreenNote => + 'Az USB-es soros kommunikáció a támogatott Android eszközökön és asztali rendszereken is elérhető.'; + + @override + String get usbScreenEmptyState => + 'Nincs USB eszköz megtalálva. Csatlakoztasson egyet, majd frissítse a rendszert.'; + + @override + String get usbErrorPermissionDenied => 'A USB-es hozzáférés megtagadva.'; + + @override + String get usbErrorDeviceMissing => + 'Az kiválasztott USB eszköz már nem elérhető.'; + + @override + String get usbErrorInvalidPort => 'Válasszon egy érvényes USB-eszközt.'; + + @override + String get usbErrorBusy => + 'Egy másik USB-csatlakozás kérése már folyamatban van.'; + + @override + String get usbErrorNotConnected => 'Nincs csatlakoztatott USB eszköz.'; + + @override + String get usbErrorOpenFailed => + 'Nem sikerült megnyitni a kiválasztott USB-eszközöt.'; + + @override + String get usbErrorConnectFailed => + 'Nem sikerült kapcsolatot létesíteni a kiválasztott USB-eszközhöz.'; + + @override + String get usbErrorUnsupported => + 'Ez a platform nem támogat USB-es soros kommunikációt.'; + + @override + String get usbErrorAlreadyActive => 'Az USB-kapcsolat már be van állítva.'; + + @override + String get usbErrorNoDeviceSelected => 'Nincs kiválasztva USB eszköz.'; + + @override + String get usbErrorPortClosed => 'Az USB-kapcsolat nem aktív.'; + + @override + String get usbErrorConnectTimedOut => + 'Kapcsolódás sikertelen. Ellenőrizze, hogy a eszköz rendelkezik-e USB-hez tartozó firmware-rel.'; + + @override + String get usbFallbackDeviceName => 'Web-szériás eszköz'; + + @override + String get usbStatus_notConnected => 'Válasszon egy USB-es eszközt'; + + @override + String get usbStatus_connecting => 'USB eszközhez való csatlakozás...'; + + @override + String get usbStatus_searching => 'USB eszközök keresése...'; + + @override + String usbConnectionFailed(String error) { + return 'USB-kapcsolat sikertelen: $error'; + } + + @override + String get scanner_scanning => 'Készülékek keresése...'; + + @override + String get scanner_connecting => 'Kapcsolódás...'; + + @override + String get scanner_disconnecting => 'Kapcsolat megszakad...'; + + @override + String get scanner_notConnected => 'Nem csatlakozva'; + + @override + String scanner_connectedTo(String deviceName) { + return 'Kapcsolódik a $deviceName-hez'; + } + + @override + String get scanner_searchingDevices => 'MeshCore eszközök keresése...'; + + @override + String get scanner_tapToScan => + 'A Tap Scan funkció segítségével kereshet MeshCore eszközöket.'; + + @override + String scanner_connectionFailed(String error) { + return 'Kapcsolódás sikertelen: $error'; + } + + @override + String get scanner_stop => 'Megállj'; + + @override + String get scanner_scan => 'Szkenálás'; + + @override + String get scanner_bluetoothOff => 'A Bluetooth kikapcsolva'; + + @override + String get scanner_bluetoothOffMessage => + 'Kérjük, kapcsolja be a Bluetooth-ot, hogy eszközök keresése lehessen.'; + + @override + String get scanner_chromeRequired => 'Chrome böngésző szükséges'; + + @override + String get scanner_chromeRequiredMessage => + 'Ez az alkalmazás a Bluetooth funkcióhoz Google Chrome-ot vagy Chromium alapú böngészőt igényel.'; + + @override + String get scanner_enableBluetooth => 'Engedje be a Bluetooth funkciót'; + + @override + String get device_quickSwitch => 'Gyors váltás'; + + @override + String get device_meshcore => 'MeshCore'; + + @override + String get settings_title => 'Beállítások'; + + @override + String get settings_deviceInfo => 'A készülék információi'; + + @override + String get settings_appSettings => 'Alkalmazási beállítások'; + + @override + String get settings_appSettingsSubtitle => + 'Értesítések, üzenetküldés és térképi beállítások'; + + @override + String get settings_nodeSettings => 'Műközép beállítások'; + + @override + String get settings_nodeName => 'Vonal neve'; + + @override + String get settings_nodeNameNotSet => 'Nem megállapított'; + + @override + String get settings_nodeNameHint => 'Adja meg a csomópont nevét'; + + @override + String get settings_nodeNameUpdated => 'Neve frissítve'; + + @override + String get settings_radioSettings => 'Rádióbeállítások'; + + @override + String get settings_radioSettingsSubtitle => + 'Frekvencia, teljesítmény, szélesítési tényező'; + + @override + String get settings_radioSettingsUpdated => 'A rádió beállítások frissítve'; + + @override + String get settings_location => 'Helyszín'; + + @override + String get settings_locationSubtitle => 'GPS koordináták'; + + @override + String get settings_locationUpdated => + 'A helyzet és a GPS beállítások frissítve'; + + @override + String get settings_locationBothRequired => + 'Kérjük, adja meg a földrajzi szélességet és hosszúságot.'; + + @override + String get settings_locationInvalid => + 'Érvénytelen szélesszög vagy hosszszög.'; + + @override + String get settings_locationGPSEnable => 'GPS engedélyezve'; + + @override + String get settings_locationGPSEnableSubtitle => + 'Lehetővé teszi, hogy a GPS automatikusan frissítse a helyzetet.'; + + @override + String get settings_locationIntervalSec => + 'GPS-számolási intervallum (másodpercek)'; + + @override + String get settings_locationIntervalInvalid => + 'Az intervallum legalább 60 másodpercnek, de legfeljebb 86400 másodpercnak kell lennie.'; + + @override + String get settings_latitude => 'Nyugat-–––––––––––––––––––––––––––––––'; + + @override + String get settings_longitude => 'hosszúság'; + + @override + String get settings_contactSettings => 'Kapcsolat beállítások'; + + @override + String get settings_contactSettingsSubtitle => + 'Beállítások, amelyek meghatározzák, hogyan lehet új kapcsolatokat hozzáadni.'; + + @override + String get settings_privacyMode => 'Adatvédelem mód'; + + @override + String get settings_privacyModeSubtitle => + 'Elrejtsük a nevét/a helyszínt az űrianyagokban'; + + @override + String get settings_privacyModeToggle => + 'Engedje be a privát üzemmódot, hogy elrejtse a nevét és a helyét az online hirdetésekben.'; + + @override + String get settings_privacyModeEnabled => 'Adatvédelem mód beállítva'; + + @override + String get settings_privacyModeDisabled => 'Adatvédelem mód kikapcsolva'; + + @override + String get settings_privacy => 'Adatvédelem beállítások'; + + @override + String get settings_privacySubtitle => + 'Ellenőrizd, hogy milyen információkat osztanak meg.'; + + @override + String get settings_privacySettingsDescription => + 'Válassza ki, hogy az eszközének melyik információkat oszt meg másokkal.'; + + @override + String get settings_denyAll => 'Elutasítom'; + + @override + String get settings_allowByContact => + 'Lehetővé teszi a kapcsolatok kezelését'; + + @override + String get settings_allowAll => 'Engedje meg mindent'; + + @override + String get settings_telemetryBaseMode => 'Adatkapcsolati alapállapot'; + + @override + String get settings_telemetryLocationMode => 'Adatkapcsolási helyszín mód'; + + @override + String get settings_telemetryEnvironmentMode => + 'Adatkapcsolati környezeti mód'; + + @override + String get settings_advertLocation => 'Reklám megjelenési hely'; + + @override + String get settings_advertLocationSubtitle => + 'A hirdetés tartalmazza a helyszínt.'; + + @override + String settings_multiAck(String value) { + return 'Többszöri visszaigazolások: $value'; + } + + @override + String get settings_telemetryModeUpdated => 'A telemetriamód frissítve'; + + @override + String get settings_actions => 'Tevékenységek'; + + @override + String get settings_sendAdvertisement => 'Hirdetés küldése'; + + @override + String get settings_sendAdvertisementSubtitle => 'A nyilvános megjelenés'; + + @override + String get settings_advertisementSent => 'Hirdetés elküldve'; + + @override + String get settings_syncTime => 'Szinkronizációs idő'; + + @override + String get settings_syncTimeSubtitle => + 'Állítsa a készülék időzítését a telefon időjére'; + + @override + String get settings_timeSynchronized => 'Időben szinkronizált'; + + @override + String get settings_refreshContacts => 'Újraindítsd a kapcsolatok listát'; + + @override + String get settings_refreshContactsSubtitle => + 'Újra töltse a kontaktlista-adatokat a készülékről'; + + @override + String get settings_rebootDevice => 'Újraindítás'; + + @override + String get settings_rebootDeviceSubtitle => + 'Indítsa újra a MeshCore eszközt.'; + + @override + String get settings_rebootDeviceConfirm => + 'Biztosan szeretné újraindítani a készüléket? Ebben az esetben a kapcsolat megszűnik.'; + + @override + String get settings_debug => 'Hibakeresés'; + + @override + String get settings_bleDebugLog => 'BLE hibaelhárítási napló'; + + @override + String get settings_bleDebugLogSubtitle => + 'BLE parancsok, válaszok és alapvető adatok'; + + @override + String get settings_appDebugLog => 'App-debug log'; + + @override + String get settings_appDebugLogSubtitle => 'Programozási hibajelzések'; + + @override + String get settings_about => 'Ról'; + + @override + String settings_aboutVersion(String version) { + return 'MeshCore Open $version verzió'; + } + + @override + String get settings_aboutLegalese => + '2026-os MeshCore nyílt forráskódú projekt'; + + @override + String get settings_aboutDescription => + 'Egy nyílt forráskódú Flutter kliens a MeshCore LoRa hálózati eszközök számára.'; + + @override + String get settings_aboutOpenMeteoAttribution => + 'LOS magassági adatok: Open-Meteo (CC BY 4.0)'; + + @override + String get settings_infoName => 'Név'; + + @override + String get settings_infoId => 'Az azonosító'; + + @override + String get settings_infoStatus => 'Állapot'; + + @override + String get settings_infoBattery => 'Akku'; + + @override + String get settings_infoPublicKey => 'Nyelvkönyv'; + + @override + String get settings_infoContactsCount => 'Kapcsolatok száma'; + + @override + String get settings_infoChannelCount => 'Csatorna száma'; + + @override + String get settings_presets => 'Előre beállított beállítások'; + + @override + String get settings_frequency => 'Frekvencia (MHz)'; + + @override + String get settings_frequencyHelper => '300,0 – 2500,0'; + + @override + String get settings_frequencyInvalid => + 'Érvénytelen frekvencia (300-2500 MHz)'; + + @override + String get settings_bandwidth => 'Kapacitás'; + + @override + String get settings_spreadingFactor => 'Terjesztési tényező'; + + @override + String get settings_codingRate => 'Kódolási sebesség'; + + @override + String get settings_txPower => 'TX teljesítmény (dBm)'; + + @override + String get settings_txPowerHelper => '0 – 22'; + + @override + String get settings_txPowerInvalid => + 'Érvénytelen TX teljesítmény (0-22 dBm)'; + + @override + String get settings_clientRepeat => 'Autonóm rendszer újra'; + + @override + String get settings_clientRepeatSubtitle => + 'Engedje, hogy ez a eszköz mások számára is ismételje a hálózati csomagokat.'; + + @override + String get settings_clientRepeatFreqWarning => + 'A hálózat nélküli kommunikációhoz 433, 869 vagy 918 MHz frekvenciát igényel.'; + + @override + String settings_error(String message) { + return 'Hiba: $message'; + } + + @override + String get appSettings_title => 'Alkalmazási beállítások'; + + @override + String get appSettings_appearance => 'Megjelenés'; + + @override + String get appSettings_theme => 'Téma'; + + @override + String get appSettings_themeSystem => 'Alapértékek'; + + @override + String get appSettings_themeLight => 'Világítás'; + + @override + String get appSettings_themeDark => 'Sötét'; + + @override + String get appSettings_language => 'Nyelv'; + + @override + String get appSettings_languageSystem => 'Alapértékek'; + + @override + String get appSettings_languageEn => 'Angol'; + + @override + String get appSettings_languageFr => 'Francia'; + + @override + String get appSettings_languageEs => 'Spanyol'; + + @override + String get appSettings_languageDe => 'Német'; + + @override + String get appSettings_languagePl => 'Lengyel'; + + @override + String get appSettings_languageSl => 'szlovén nyelv'; + + @override + String get appSettings_languagePt => 'Portugál'; + + @override + String get appSettings_languageIt => 'Olasz'; + + @override + String get appSettings_languageZh => 'Kínai'; + + @override + String get appSettings_languageSv => 'Svéd'; + + @override + String get appSettings_languageNl => 'Hollandi'; + + @override + String get appSettings_languageSk => 'Szlovén nyelvre fordítás'; + + @override + String get appSettings_languageBg => 'Bulgár'; + + @override + String get appSettings_languageRu => 'Orosz'; + + @override + String get appSettings_languageUk => 'Украинский'; + + @override + String get appSettings_enableMessageTracing => + 'Engedje meg a üzenetek nyomon követését'; + + @override + String get appSettings_enableMessageTracingSubtitle => + 'Adja meg a üzenetek részletes útvonal- és időzítési adatokat.'; + + @override + String get appSettings_notifications => 'Értesítések'; + + @override + String get appSettings_enableNotifications => 'Engedélyezze az értesítéseket'; + + @override + String get appSettings_enableNotificationsSubtitle => + 'Kapjon értesítéseket üzenetekről és hirdetésekről.'; + + @override + String get appSettings_notificationPermissionDenied => + 'A értesítési engedély megtagadva'; + + @override + String get appSettings_notificationsEnabled => + 'A figyelmeztetések engedélyezve'; + + @override + String get appSettings_notificationsDisabled => + 'A figyelmeztetések kikapcsolva'; + + @override + String get appSettings_messageNotifications => 'Üzenet értesítések'; + + @override + String get appSettings_messageNotificationsSubtitle => + 'A figyelmeztetést megjelenítve, amikor új üzenet érkezik'; + + @override + String get appSettings_channelMessageNotifications => + 'Csatorna-üzenetek értesítése'; + + @override + String get appSettings_channelMessageNotificationsSubtitle => + 'A figyelmeztetést megjelenítve, amikor új üzenet érkezik a csatornáról'; + + @override + String get appSettings_advertisementNotifications => 'Reklám értesítések'; + + @override + String get appSettings_advertisementNotificationsSubtitle => + 'A figyelmeztetést megjelenítve, amikor új csomópontok kerülnek felfedezésre.'; + + @override + String get appSettings_messaging => 'Üzenetek küldése'; + + @override + String get appSettings_clearPathOnMaxRetry => + 'Egyértelmű út a Max Retry funkció használatával'; + + @override + String get appSettings_clearPathOnMaxRetrySubtitle => + 'A kapcsolat visszaállítás 5 sikertelen továbbítás után'; + + @override + String get appSettings_pathsWillBeCleared => + 'Ha 5-szer sikertelenül próbálunk, a útvonalat automatikusan tisztítjuk.'; + + @override + String get appSettings_pathsWillNotBeCleared => + 'A utak automatikusan nem tisztítódnak.'; + + @override + String get appSettings_autoRouteRotation => 'Autóútok forgása'; + + @override + String get appSettings_autoRouteRotationSubtitle => + 'Válasszon a legjobb útvonalak között, vagy válassza a vízözön-módot.'; + + @override + String get appSettings_autoRouteRotationEnabled => + 'Az automatikus útvonalváltás engedélyezve'; + + @override + String get appSettings_autoRouteRotationDisabled => + 'Az automatikus útvonal-választás funkció kikapcsolva.'; + + @override + String get appSettings_maxRouteWeight => 'Maximális útvonal súly'; + + @override + String get appSettings_maxRouteWeightSubtitle => + 'A lehető legnagyobb súly, amit egy útvonal sikeres szállítmányok során összegyűjthet.'; + + @override + String get appSettings_initialRouteWeight => 'A kezdeti útvonal súlya'; + + @override + String get appSettings_initialRouteWeightSubtitle => + 'Az új, felfedezett útvonalakhoz tartozó kezdeti súly'; + + @override + String get appSettings_routeWeightSuccessIncrement => + 'Sikerhez vezető növelés'; + + @override + String get appSettings_routeWeightSuccessIncrementSubtitle => + 'A sikeresen teljesített útvonalhoz hozzáadott súly.'; + + @override + String get appSettings_routeWeightFailureDecrement => 'Hibás súly csökkenése'; + + @override + String get appSettings_routeWeightFailureDecrementSubtitle => + 'A jártatásból eltávolított súly, ami a sikertelen szállítás következménye.'; + + @override + String get appSettings_maxMessageRetries => + 'Maximális üzenetek újraküldési próbálkozások'; + + @override + String get appSettings_maxMessageRetriesSubtitle => + 'A próbálkozások száma, mielőtt egy üzenetet hibásnak jelölünk.'; + + @override + String path_routeWeight(String weight, String max) { + return '$weight/$max'; + } + + @override + String get appSettings_battery => 'Akku'; + + @override + String get appSettings_batteryChemistry => 'Aakkum töltés kémia'; + + @override + String appSettings_batteryChemistryPerDevice(String deviceName) { + return 'Beállítások $deviceName-hez'; + } + + @override + String get appSettings_batteryChemistryConnectFirst => + 'Csatlakozzon egy eszközhez, hogy kiválassza'; + + @override + String get appSettings_batteryNmc => '18650 NMC (3,0-4,2 V)'; + + @override + String get appSettings_batteryLifepo4 => 'LiFePO4 (2,6–3,65 V)'; + + @override + String get appSettings_batteryLipo => 'LiPo (3,0-4,2 V)'; + + @override + String get appSettings_mapDisplay => 'Térkép megjelenítése'; + + @override + String get appSettings_showRepeaters => 'Megismétlés'; + + @override + String get appSettings_showRepeatersSubtitle => + 'A térképen megjelenítsük a repeater-eket.'; + + @override + String get appSettings_showChatNodes => 'Megjeleníts kommunikációs pontokat'; + + @override + String get appSettings_showChatNodesSubtitle => + 'A chat-szobákat megjelenítsük a térképen'; + + @override + String get appSettings_showOtherNodes => 'Mutasson további csomópontokat'; + + @override + String get appSettings_showOtherNodesSubtitle => + 'Mutassa meg a többi hálózati elemet a térképen'; + + @override + String get appSettings_timeFilter => 'Időbeli szűrés'; + + @override + String get appSettings_timeFilterShowAll => + 'Mutassa meg az összes csomópontot'; + + @override + String appSettings_timeFilterShowLast(int hours) { + return 'Mutasson az utolsó $hours órából származó adatokat.'; + } + + @override + String get appSettings_mapTimeFilter => 'Térkép időszűrő'; + + @override + String get appSettings_showNodesDiscoveredWithin => + 'Megjeleníts olyan węzveket, amelyek a következő területen lettek felfedezve:'; + + @override + String get appSettings_allTime => 'Minden időpont'; + + @override + String get appSettings_lastHour => 'Az utolsó óra'; + + @override + String get appSettings_last6Hours => 'Az utóban 6 óra'; + + @override + String get appSettings_last24Hours => 'Az utóbbi 24 óra'; + + @override + String get appSettings_lastWeek => 'A múlt héten'; + + @override + String get appSettings_offlineMapCache => 'Offline térkép tárolás'; + + @override + String get appSettings_unitsTitle => 'Egységek'; + + @override + String get appSettings_unitsMetric => 'Méter (m / kilométer)'; + + @override + String get appSettings_unitsImperial => 'Királyi (láb / mérföld)'; + + @override + String get appSettings_noAreaSelected => 'Nincs kiválasztott terület.'; + + @override + String appSettings_areaSelectedZoom(int minZoom, int maxZoom) { + return 'Kiválasztott terület (zoom: $minZoom-$maxZoom)'; + } + + @override + String get appSettings_debugCard => 'Hibakeresés'; + + @override + String get appSettings_appDebugLogging => + 'App-ban történő hibakereséshez használt naplózás'; + + @override + String get appSettings_appDebugLoggingSubtitle => + 'Log alkalmazás hibaelhárítási üzenetek'; + + @override + String get appSettings_appDebugLoggingEnabled => + 'Az alkalmazás hibaelhárítási naplózás engedélyezve'; + + @override + String get appSettings_appDebugLoggingDisabled => + 'Az alkalmazás hibaelhárítási naplózatának bekapcsolása kiküszöbölve'; + + @override + String get contacts_title => 'Kapcsolatok'; + + @override + String get contacts_noContacts => 'Jelenleg még nincs kapcsolat.'; + + @override + String get contacts_contactsWillAppear => + 'A kapcsolatok megjelennek, amikor a eszközök hirdetnek.'; + + @override + String get contacts_unread => 'Olvasatlan'; + + @override + String get contacts_searchContactsNoNumber => 'Kapcsolatok keresése...'; + + @override + String contacts_searchContacts(int number, String str) { + return 'Keresés $number-ban $str…'; + } + + @override + String contacts_searchFavorites(int number, String str) { + return 'Keresés $number$str... Kedvencek'; + } + + @override + String contacts_searchUsers(int number, String str) { + return 'Search $number$str Users...'; + } + + @override + String contacts_searchRepeaters(int number, String str) { + return 'Keresés $number-on, $str típusú adóállomások között...'; + } + + @override + String contacts_searchRoomServers(int number, String str) { + return 'Keresés $number-ban $str...'; + } + + @override + String get contacts_noUnreadContacts => 'Nincs olvasatlan üzenetek'; + + @override + String get contacts_noContactsFound => + 'Nincs megtalálva semmilyen kapcsolat vagy csoport.'; + + @override + String get contacts_deleteContact => 'Kapcsolattól töröl'; + + @override + String contacts_removeConfirm(String contactName) { + return 'Hogy töröljem a $contactName nevű személyt a kontaktlistából?'; + } + + @override + String get contacts_manageRepeater => 'Ellenőriző eszköz kezelése'; + + @override + String get contacts_manageRoom => 'A szobai szerver kezelése'; + + @override + String get contacts_roomLogin => 'Szoba szerverbe való bejelentkezés'; + + @override + String get contacts_openChat => 'Nyitott beszélgetés'; + + @override + String get contacts_editGroup => 'Edit csoport'; + + @override + String get contacts_deleteGroup => 'Csoport törlése'; + + @override + String contacts_deleteGroupConfirm(String groupName) { + return 'Hogy töröljem a \"$groupName\"-t?'; + } + + @override + String get contacts_newGroup => 'Új csoport'; + + @override + String get contacts_groupName => 'Csoport neve'; + + @override + String get contacts_groupNameRequired => + 'A csoportnak meg kell adni a nevét.'; + + @override + String get contacts_groupNameReserved => 'Ez a csoportnév foglalt'; + + @override + String contacts_groupAlreadyExists(String name) { + return 'A \"$name\" nevű csoport már létezik.'; + } + + @override + String get contacts_filterContacts => 'Szűrj kontaktokat...'; + + @override + String get contacts_noContactsMatchFilter => + 'Nincs találat a megadott szűrés alapján.'; + + @override + String get contacts_noMembers => 'Nincsenek tagok'; + + @override + String get contacts_lastSeenNow => 'utóbbi időben'; + + @override + String contacts_lastSeenMinsAgo(int minutes) { + return '~ $minutes perc'; + } + + @override + String get contacts_lastSeenHourAgo => 'Kb. 1 óra'; + + @override + String contacts_lastSeenHoursAgo(int hours) { + return '~ $hours óra'; + } + + @override + String get contacts_lastSeenDayAgo => 'Kb. 1 nap'; + + @override + String contacts_lastSeenDaysAgo(int days) { + return '~ $days days'; + } + + @override + String get contact_info => 'Kapcsolattartási információk'; + + @override + String get contact_settings => 'Kapcsolat beállítások'; + + @override + String get contact_telemetry => 'Adatvisszaadás'; + + @override + String get contact_lastSeen => 'Utoljára, amikor látták'; + + @override + String get contact_clearChat => 'Tiszta beszélgetés'; + + @override + String get contact_teleBase => 'Adatgyűjtő központ'; + + @override + String get contact_teleBaseSubtitle => + 'Engedje meg a akkumulátor töltöttségi szintjének és alapvető adatoknak megosztását.'; + + @override + String get contact_teleLoc => 'Adatkapcsolati helyszín'; + + @override + String get contact_teleLocSubtitle => 'Engedje meg a helyadatok megosztását'; + + @override + String get contact_teleEnv => 'Adatkapcsolati környezet'; + + @override + String get contact_teleEnvSubtitle => + 'Engedje meg az érzékelő adatok megosztását'; + + @override + String get channels_title => 'Csatornák'; + + @override + String get channels_noChannelsConfigured => 'Nincs konfigurált csatorna.'; + + @override + String get channels_addPublicChannel => 'Hozzon létre nyilvános csatornát'; + + @override + String get channels_searchChannels => 'Keresési opciók...'; + + @override + String get channels_noChannelsFound => 'Nincs megtalálható csatorna'; + + @override + String channels_channelIndex(int index) { + return '$index-os csatorna'; + } + + @override + String get channels_hashtagChannel => 'Hashtag-ok közössége'; + + @override + String get channels_public => 'A nyilvánosság számára'; + + @override + String get channels_private => 'Személyes'; + + @override + String get channels_publicChannel => 'Össztávos csatorna'; + + @override + String get channels_privateChannel => 'Személyes csatorna'; + + @override + String get channels_editChannel => 'Csatorna szerkesztése'; + + @override + String get channels_muteChannel => 'Csendes csatorna'; + + @override + String get channels_unmuteChannel => 'Engedje be a hangot'; + + @override + String get channels_deleteChannel => 'Mozdony törlése'; + + @override + String channels_deleteChannelConfirm(String name) { + return 'Törlés $name? Ez nem visszafordítható.'; + } + + @override + String channels_channelDeleteFailed(String name) { + return 'Nem sikerült törölni a \"$name\" nevű csatornát.'; + } + + @override + String channels_channelDeleted(String name) { + return 'A \"$name\" nevű csatorna törölve'; + } + + @override + String get channels_addChannel => 'Csatorna hozzáadása'; + + @override + String get channels_channelIndexLabel => 'Csatorna index'; + + @override + String get channels_channelName => 'Csatorna neve'; + + @override + String get channels_usePublicChannel => 'Használja a nyilvános csatornát'; + + @override + String get channels_standardPublicPsk => + 'Általános, állami által finanszírozott PSK'; + + @override + String get channels_pskHex => 'PSK (Hexadecimális kód)'; + + @override + String get channels_generateRandomPsk => 'Véletlenszerűen generáljon PSK-t'; + + @override + String get channels_enterChannelName => 'Kérjük, adja meg egy csatorna nevét'; + + @override + String get channels_pskMustBe32Hex => + 'A PSK 32-bázisú hexadecimális karakterből áll.'; + + @override + String channels_channelAdded(String name) { + return 'A \"$name\" csatorna hozzáadva'; + } + + @override + String channels_editChannelTitle(int index) { + return 'Módosítsd a csatornát $index'; + } + + @override + String get channels_smazCompression => 'SMAZ kompresszió'; + + @override + String channels_channelUpdated(String name) { + return 'A $name csatorna frissítve'; + } + + @override + String get channels_publicChannelAdded => 'A nyilvános csatorna hozzáadva'; + + @override + String get channels_sortBy => 'Szűrés'; + + @override + String get channels_sortManual => 'Használati útmutató'; + + @override + String get channels_sortAZ => 'A-Z'; + + @override + String get channels_sortLatestMessages => 'Legfrissebb üzenetek'; + + @override + String get channels_sortUnread => 'Olvasatlan'; + + @override + String get channels_createPrivateChannel => 'Létrehoz egy privát csatornát'; + + @override + String get channels_createPrivateChannelDesc => + 'Titkos kulcs segítségével védelem.'; + + @override + String get channels_joinPrivateChannel => + 'Csatlakozzon egy privát csatornához'; + + @override + String get channels_joinPrivateChannelDesc => + 'Kézzel adja meg a titkos kulcsot.'; + + @override + String get channels_joinPublicChannel => + 'Csatlakozzon a nyilvános csatornához'; + + @override + String get channels_joinPublicChannelDesc => + 'Bárki csatlakozhat ehhez a csatornához.'; + + @override + String get channels_joinHashtagChannel => + 'Csatlakozzon egy hashtage-os csatornához'; + + @override + String get channels_joinHashtagChannelDesc => + 'Bárkinek lehet csatlakoznia a hashtagekhez tartozó csatornához.'; + + @override + String get channels_scanQrCode => 'Scanned egy QR-kódot'; + + @override + String get channels_scanQrCodeComingSoon => 'Hamarosan'; + + @override + String get channels_enterHashtag => 'Írja be a hashtaget'; + + @override + String get channels_hashtagHint => 'pl. #csapat'; + + @override + String get chat_noMessages => 'Még nincs üzenet.'; + + @override + String get chat_sendMessageToStart => 'Küldj egy üzenetet, hogy elindulj!'; + + @override + String get chat_originalMessageNotFound => 'A eredeti üzenet nem található.'; + + @override + String chat_replyingTo(String name) { + return 'Replying to $name'; + } + + @override + String chat_replyTo(String name) { + return 'Reply to $name'; + } + + @override + String get chat_location => 'Helyszín'; + + @override + String chat_sendMessageTo(String contactName) { + return 'Küldj üzenetet $contactName-nek'; + } + + @override + String get chat_typeMessage => 'Írjon üzenetet...'; + + @override + String chat_messageTooLong(int maxBytes) { + return 'A üzenet túl hosszú (a maximális $maxBytes bájt).'; + } + + @override + String get chat_messageCopied => 'Üzenet másolva'; + + @override + String get chat_messageDeleted => 'Üzenet törölve'; + + @override + String get chat_retryingMessage => 'Újrapróbálási üzenet'; + + @override + String chat_retryCount(int current, int max) { + return 'Újrapróbál $current/$max'; + } + + @override + String get chat_sendGif => 'Küldj GIF-ot'; + + @override + String get chat_reply => 'Válasz'; + + @override + String get chat_addReaction => 'Hozzon létre reakciót'; + + @override + String get chat_me => 'Én'; + + @override + String get emojiCategorySmileys => 'Emoji'; + + @override + String get emojiCategoryGestures => 'Testmozgások'; + + @override + String get emojiCategoryHearts => 'Szívak'; + + @override + String get emojiCategoryObjects => 'Tárgyak'; + + @override + String get gifPicker_title => 'Válasszon egy GIF-et'; + + @override + String get gifPicker_searchHint => 'GIF-ek keresése...'; + + @override + String get gifPicker_poweredBy => 'Forrás: GIPHY'; + + @override + String get gifPicker_noGifsFound => 'Nincsenek GIF-ek megtalálva.'; + + @override + String get gifPicker_failedLoad => 'Nem sikerült betölteni a GIF-fájlokat.'; + + @override + String get gifPicker_failedSearch => 'Nem sikerült a GIF-eket megtalálni.'; + + @override + String get gifPicker_noInternet => 'Nincs internetkapcsolat.'; + + @override + String get debugLog_appTitle => 'App-debug log'; + + @override + String get debugLog_bleTitle => 'BLE hibajelentő napló'; + + @override + String get debugLog_copyLog => 'Másolat napló'; + + @override + String get debugLog_clearLog => 'Jelzett napló'; + + @override + String get debugLog_copied => 'Hibajelentő napló másolva'; + + @override + String get debugLog_bleCopied => 'BLE-log másolva'; + + @override + String get debugLog_noEntries => + 'Jelenleg még nem léteznek hibaelhárítási naplókat.'; + + @override + String get debugLog_enableInSettings => + 'Engedje be az alkalmazás hibaelhárítási naplózását a beállítások menüben.'; + + @override + String get debugLog_frames => 'Keretek'; + + @override + String get debugLog_rawLogRx => 'Az eredeti Log-RX'; + + @override + String get debugLog_noBleActivity => + 'Jelenleg nincs BLE-hez kapcsolódó tevékenység.'; + + @override + String debugFrame_length(int count) { + return 'Keret hossza: $count bájt'; + } + + @override + String debugFrame_command(String value) { + return 'Parancs: 0x$value'; + } + + @override + String get debugFrame_textMessageHeader => 'Címzett:'; + + @override + String debugFrame_destinationPubKey(String pubKey) { + return '- Célhely: $pubKey'; + } + + @override + String debugFrame_timestamp(int timestamp) { + return '- Időbélyeg: $timestamp'; + } + + @override + String debugFrame_flags(String value) { + return '- Jelvények: 0x$value'; + } + + @override + String debugFrame_textType(int type, String label) { + return '- Tartalom típusa: $type ($label)'; + } + + @override + String get debugFrame_textTypeCli => 'Parancssori felület (CLI)'; + + @override + String get debugFrame_textTypePlain => 'Egyszerű, alap, hagyományos'; + + @override + String debugFrame_text(String text) { + return '- Tartalom: \"$text\"'; + } + + @override + String get debugFrame_hexDump => 'Hex-dump:'; + + @override + String get chat_pathManagement => 'Útvonal-kezelés'; + + @override + String get chat_ShowAllPaths => 'Mutasson meg minden útvonalat'; + + @override + String get chat_routingMode => 'Útvonal-kezelési mód'; + + @override + String get chat_autoUseSavedPath => + 'Automatikus (az eddigi útvonal használata)'; + + @override + String get chat_forceFloodMode => 'Erőforrás-alapú áramlás mód'; + + @override + String get chat_recentAckPaths => + 'Legutóbbi használt útvonalak (gombra kattintva):'; + + @override + String get chat_pathHistoryFull => + 'Az előző lépések listája teljes. Törölj ki a bejegyzéseket, hogy újokat hozzáadhatsd.'; + + @override + String get chat_hopSingular => 'ugor'; + + @override + String get chat_hopPlural => 'babér'; + + @override + String chat_hopsCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'ugrások', + one: 'ugrás', + ); + return '$count $_temp0'; + } + + @override + String get chat_successes => 'sikerek'; + + @override + String get chat_removePath => 'Törölje a elérési útvonalat'; + + @override + String get chat_noPathHistoryYet => + 'Még nincs útvonal-történet.\nKüldjön egy üzenetet, hogy megtudja a lehetséges útvonalakat.'; + + @override + String get chat_pathActions => 'Céltúrások:'; + + @override + String get chat_setCustomPath => 'Beállítsd a saját útvonalat'; + + @override + String get chat_setCustomPathSubtitle => 'Kézzel megadott útvonal'; + + @override + String get chat_clearPath => 'Egyértelmű út'; + + @override + String get chat_clearPathSubtitle => + 'A parancs új küldéskor újra kell aktivizálnia.'; + + @override + String get chat_pathCleared => + 'Útvonal cleared. A következő üzenet újból feltérképezheti az útvonalat.'; + + @override + String get chat_floodModeSubtitle => + 'Használja a \"útvonal\" kapcsolót az alkalmazás sávjában.'; + + @override + String get chat_floodModeEnabled => + 'Árvízvédelmi mód bekapcsolva. A visszaállítás a alkalmazásban található útvonal ikon segítségével.'; + + @override + String get chat_fullPath => 'Teljes elérési út'; + + @override + String get chat_pathDetailsNotAvailable => + 'Az útvonal részletei még nem elérhetők. Próbálja meg küldeni egy üzenetet, hogy frissítse az információkat.'; + + @override + String chat_pathSetHops(int hopCount, String status) { + String _temp0 = intl.Intl.pluralLogic( + hopCount, + locale: localeName, + other: 'hops', + one: 'hop', + ); + return 'Path set: $hopCount $_temp0 - $status'; + } + + @override + String get chat_pathSavedLocally => + 'Helyileg mentve. Kapcsolódjon a szinkronizáláshoz.'; + + @override + String get chat_pathDeviceConfirmed => 'A készülék megvan.'; + + @override + String get chat_pathDeviceNotConfirmed => 'A készülék még nem bizonyított.'; + + @override + String get chat_type => 'Típus'; + + @override + String get chat_path => 'Út'; + + @override + String get chat_publicKey => 'Nyelvkönyv'; + + @override + String get chat_compressOutgoingMessages => 'A küldött üzenetek tömörítése'; + + @override + String get chat_floodForced => 'Áradás (kényszerített)'; + + @override + String get chat_directForced => 'Közvetlen (erélyes)'; + + @override + String chat_hopsForced(int count) { + return '$count ánusz (erővel)'; + } + + @override + String get chat_floodAuto => 'Vízosztás (autó)'; + + @override + String get chat_direct => 'Közvetlen'; + + @override + String get chat_poiShared => 'Közös erőforrás'; + + @override + String chat_unread(int count) { + return 'Olvasatlan: $count'; + } + + @override + String get chat_openLink => 'Nyisd meg a linket?'; + + @override + String get chat_openLinkConfirmation => + 'Szeretnéd megnyitni ezt a linket a böngésződben?'; + + @override + String get chat_open => 'Nyitott'; + + @override + String chat_couldNotOpenLink(String url) { + return 'Nem sikerült megnyitni a hivat: $url'; + } + + @override + String get chat_invalidLink => 'Érvénytelen hivatkozás formátum'; + + @override + String get map_title => 'Grafikus ábrázás'; + + @override + String get map_lineOfSight => 'Látási vonal'; + + @override + String get map_losScreenTitle => 'Látási vonal'; + + @override + String get map_noNodesWithLocation => + 'Nincs olyan adatpont, amelyhez helyszín-információk tartoznak.'; + + @override + String get map_nodesNeedGps => + 'A pontoknak meg kell osztaniuk GPS koordinátáikat, hogy megjelenjenek a térképen.'; + + @override + String map_nodesCount(int count) { + return 'Csúcsok: $count'; + } + + @override + String map_pinsCount(int count) { + return 'Csapok: $count'; + } + + @override + String get map_chat => 'Csevegés'; + + @override + String get map_repeater => 'Ismétlő'; + + @override + String get map_room => 'szoba'; + + @override + String get map_sensor => 'Érzékelő'; + + @override + String get map_pinDm => 'Jel (DM)'; + + @override + String get map_pinPrivate => 'Titkos (privát)'; + + @override + String get map_pinPublic => 'Jelmez (nyilvános)'; + + @override + String get map_lastSeen => 'Utoljára látva'; + + @override + String get map_disconnectConfirm => + 'Biztosan szeretné kiírni ezt a készüléket?'; + + @override + String get map_from => 'Attól'; + + @override + String get map_source => 'Forrás'; + + @override + String get map_flags => 'Zászló'; + + @override + String get map_shareMarkerHere => 'Osztja ezt a tartalmat itt'; + + @override + String get map_setAsMyLocation => 'Állítsa be a jelenlegi helyzetemként'; + + @override + String get map_pinLabel => 'Címkét ragasztani'; + + @override + String get map_label => 'Címke'; + + @override + String get map_pointOfInterest => 'Érdekes hely'; + + @override + String get map_sendToContact => 'Kapcsolatfelvételi űrlap'; + + @override + String get map_sendToChannel => 'Küldés a csatornán'; + + @override + String get map_noChannelsAvailable => 'Nincs elérhető csatorna.'; + + @override + String get map_publicLocationShare => 'Térköz, nyilvános hely'; + + @override + String map_publicLocationShareConfirm(String channelLabel) { + return 'Most egy helyszínt megosztasz a $channelLabel csatornán. Ez a csatorna nyilvános, és bárki, aki rendelkezik a PSK-val, megtekintheti.'; + } + + @override + String get map_connectToShareMarkers => + 'Kapcsolódjon egy eszközhöz, hogy megoszthassa a vonalzókat.'; + + @override + String get map_filterNodes => 'Szűrési pontok'; + + @override + String get map_nodeTypes => 'Vonalak típusai'; + + @override + String get map_chatNodes => 'Csevegési pontok'; + + @override + String get map_repeaters => 'Újraküldők'; + + @override + String get map_otherNodes => 'Egyéb csomópontok'; + + @override + String get map_showOverlaps => 'Az ismétlő kulcsok ütköznek'; + + @override + String get map_keyPrefix => 'Kulcsfontosságú előtag'; + + @override + String get map_filterByKeyPrefix => 'Szűrj a kulcsos előtér szerint'; + + @override + String get map_publicKeyPrefix => 'Névfelhasználó kulc-prefix'; + + @override + String get map_markers => 'Jelölők'; + + @override + String get map_showSharedMarkers => 'Mutassa meg a közös jeleket'; + + @override + String get map_showGuessedLocations => + 'Megjelenítsa a megjósolt csomópontok helyét'; + + @override + String get map_showDiscoveryContacts => + 'Megjelenítse a Discovery-nál elérhet kontaktokat'; + + @override + String get map_guessedLocation => 'Tippolt hely'; + + @override + String get map_lastSeenTime => 'Utoljára megjelent idő'; + + @override + String get map_sharedPin => 'Gemeinsames PIN-kód'; + + @override + String get map_joinRoom => 'Csatlakozás a szobához'; + + @override + String get map_manageRepeater => 'Ellenőriző eszköz kezelése'; + + @override + String get map_tapToAdd => + 'Nyomj meg a csomópontokhoz, hogy hozzáadd őket az útvonalhoz.'; + + @override + String get map_runTrace => 'Útvonal követés'; + + @override + String get map_runTraceWithReturnPath => 'Visszaforduljon az eredeti úton.'; + + @override + String get map_removeLast => 'Törölj utolsó'; + + @override + String get map_pathTraceCancelled => 'Az útvonal követés megszakadt.'; + + @override + String get mapCache_title => 'Offline térkép tárolás'; + + @override + String get mapCache_selectAreaFirst => + 'Válasszon egy területet, amelyet először cache-oljon.'; + + @override + String get mapCache_noTilesToDownload => + 'Nincsenek letölthető tile-ok ebben a területben.'; + + @override + String get mapCache_downloadTilesTitle => 'Letöltsd a tile-okat'; + + @override + String mapCache_downloadTilesPrompt(int count) { + return 'Töltse le $count darab tile-t offline használatra?'; + } + + @override + String get mapCache_downloadAction => 'Letöltés'; + + @override + String mapCache_cachedTiles(int count) { + return 'Tárolt $count darab'; + } + + @override + String mapCache_cachedTilesWithFailed(int downloaded, int failed) { + return 'Cached $downloaded tiles ($failed failed)'; + } + + @override + String get mapCache_clearOfflineCacheTitle => 'Tiszta offline tárhely'; + + @override + String get mapCache_clearOfflineCachePrompt => + 'Távolítsa el az összes tárolt térképmegjelenítőt?'; + + @override + String get mapCache_offlineCacheCleared => 'A helyi memóriát töröltük.'; + + @override + String get mapCache_noAreaSelected => 'Nincs kiválasztott terület.'; + + @override + String get mapCache_cacheArea => 'Tároló terület'; + + @override + String get mapCache_useCurrentView => 'Használja a jelenlegi nézetet'; + + @override + String get mapCache_zoomRange => 'Zoom tartomány'; + + @override + String mapCache_estimatedTiles(int count) { + return 'Becsült kerámiák: $count'; + } + + @override + String mapCache_downloadedTiles(int completed, int total) { + return 'Letöltve $completed / $total'; + } + + @override + String get mapCache_downloadTilesButton => 'Letöltsd a tile-okat'; + + @override + String get mapCache_clearCacheButton => 'Ósztótt adatokat'; + + @override + String mapCache_failedDownloads(int count) { + return 'Sikertelen letöltések: $count'; + } + + @override + String mapCache_boundsLabel( + String north, + String south, + String east, + String west, + ) { + return 'N $north, S $south, E $east, W $west'; + } + + @override + String get time_justNow => 'Most'; + + @override + String time_minutesAgo(int minutes) { + return '$minutes perckel ezelőtt'; + } + + @override + String time_hoursAgo(int hours) { + return '$hours óva'; + } + + @override + String time_daysAgo(int days) { + return '${days}d ago'; + } + + @override + String get time_hour => 'óra'; + + @override + String get time_hours => 'órák'; + + @override + String get time_day => 'nap'; + + @override + String get time_days => 'napok'; + + @override + String get time_week => 'het'; + + @override + String get time_weeks => 'het, hetek'; + + @override + String get time_month => 'hónap'; + + @override + String get time_months => 'hónapok'; + + @override + String get time_minutes => 'percek'; + + @override + String get time_allTime => 'Bármely időpont'; + + @override + String get dialog_disconnect => 'Csatlakozást megszakasztani'; + + @override + String get dialog_disconnectConfirm => + 'Biztosan szeretné kiírni ezt a készüléket?'; + + @override + String get login_repeaterLogin => 'Ismételt bejelentkezés'; + + @override + String get login_roomLogin => 'Szoba szerverbe való bejelentkezés'; + + @override + String get login_password => 'Jelszó'; + + @override + String get login_enterPassword => 'Adja meg a jelszót'; + + @override + String get login_savePassword => 'Mentse el a jelszót'; + + @override + String get login_savePasswordSubtitle => + 'A jelszó biztonságosan tárolódik ezen a készüléken.'; + + @override + String get login_repeaterDescription => + 'Adja meg a repeater (ismétítő) jelszót, hogy hozzáférhessen a beállításokhoz és az állapot információkhoz.'; + + @override + String get login_roomDescription => + 'Adja meg a belépési kódot, hogy hozzáférhessen a beállításokhoz és az állapot információkhoz.'; + + @override + String get login_routing => 'Útvonal meghatározás'; + + @override + String get login_routingMode => 'Útvonal-kezelési mód'; + + @override + String get login_autoUseSavedPath => + 'Automatikus (az eddigi útvonal használata)'; + + @override + String get login_forceFloodMode => 'Erőforrás-alapú áramlás mód'; + + @override + String get login_managePaths => 'Útvonalak kezelése'; + + @override + String get login_login => 'Bejelentkezés'; + + @override + String login_attempt(int current, int max) { + return 'Megpróbálás $current/$max-adik'; + } + + @override + String login_failed(String error) { + return 'Belépés sikertelen: $error'; + } + + @override + String get login_failedMessage => + 'Belépés sikertelen. Vagy a jelszó helytelen, vagy a hálózati kapcsolat nem létesül.'; + + @override + String get common_reload => 'Újra töltés'; + + @override + String get common_clear => 'Egyértelmű'; + + @override + String path_currentPath(String path) { + return 'Jelenlegi útvonal: $path'; + } + + @override + String path_usingHopsPath(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'ugrások', + one: 'ugrás', + ); + return '$count $_temp0 útvonal használata'; + } + + @override + String get path_enterCustomPath => 'Adja meg a saját elérési útvonalat'; + + @override + String get path_currentPathLabel => 'Jelenlegi útvonal'; + + @override + String get path_hexPrefixInstructions => + 'Adja meg a 2 karakteres hexadecimális előtagokat minden lépéshez, tagolva kommával.'; + + @override + String get path_hexPrefixExample => + 'Példa: A1, F2, 3C (minden csomó az első részét használja a nyilvános kulcsából)'; + + @override + String get path_labelHexPrefixes => 'Út (hex-prefixek)'; + + @override + String get path_helperMaxHops => + 'A maximális hossz 64 karakter. Minden előző rész 2 hatos számjegyből áll (1 bájt).'; + + @override + String get path_selectFromContacts => + 'Válasszon a kontaktlista elembek közül:'; + + @override + String get path_noRepeatersFound => + 'Nincs megtalálva semmilyen ismétlődő vagy helyiség-szolgáltató szervert.'; + + @override + String get path_customPathsRequire => + 'Az egyedi útvonalaknak szükségük van átjáró pontokra, amelyek képesek üzeneteket továbbítani.'; + + @override + String path_invalidHexPrefixes(String prefixes) { + return 'Érvénytelen hexadecimális előtagok: $prefixes'; + } + + @override + String get path_tooLong => + 'Az út túl hosszú. A maximális engedélyezett lépések száma 64.'; + + @override + String get path_setPath => 'Útvonal meghatározása'; + + @override + String get repeater_management => 'Adatkapcsolás kezelése'; + + @override + String get room_management => 'Szoba-szerver kezelés'; + + @override + String get repeater_managementTools => 'Menedzsmentes eszközök'; + + @override + String get repeater_status => 'Állapot'; + + @override + String get repeater_statusSubtitle => + 'Megtekintheted a repeater állapotát, statisztikáit és a környező eszközök adatait.'; + + @override + String get repeater_telemetry => 'Adatvisszaadás'; + + @override + String get repeater_telemetrySubtitle => + 'Tekintsük a szenzorok és a rendszer állapotának adatát'; + + @override + String get repeater_cli => 'Parancssori felület (CLI)'; + + @override + String get repeater_cliSubtitle => 'Küldj parancsokat a repeaternek.'; + + @override + String get repeater_neighbors => 'Szomszédok'; + + @override + String get repeater_neighborsSubtitle => + 'Tekintsük a nullás lépésű szomszédokat.'; + + @override + String get repeater_settings => 'Beállítások'; + + @override + String get repeater_settingsSubtitle => 'Állítsa be a repeater paramétereket'; + + @override + String get repeater_statusTitle => 'Adatkapcsolódás állapot'; + + @override + String get repeater_routingMode => 'Útvonal-kezelési mód'; + + @override + String get repeater_autoUseSavedPath => + 'Automatikus (az eddigi útvonal használata)'; + + @override + String get repeater_forceFloodMode => 'Erőforrás-alapú áramlás mód'; + + @override + String get repeater_pathManagement => 'Útvonal-kezelés'; + + @override + String get repeater_refresh => 'Újrafriszol'; + + @override + String get repeater_statusRequestTimeout => 'Az állapotkérés időtúlt.'; + + @override + String repeater_errorLoadingStatus(String error) { + return 'Hiba a státusz betöltés közben: $error'; + } + + @override + String get repeater_systemInformation => 'Rendszerinformációk'; + + @override + String get repeater_battery => 'Akku'; + + @override + String get repeater_clockAtLogin => 'Óra (bejelentkezéskor)'; + + @override + String get repeater_uptime => 'A rendszer elérhetősége'; + + @override + String get repeater_queueLength => 'Várakozási sor hossza'; + + @override + String get repeater_debugFlags => 'Hibakeresési beállítások'; + + @override + String get repeater_radioStatistics => 'Rádió statisztika'; + + @override + String get repeater_lastRssi => 'Utolsó RSSI érték'; + + @override + String get repeater_lastSnr => 'Utolsó SNR'; + + @override + String get repeater_noiseFloor => 'Háttérzaj szint'; + + @override + String get repeater_txAirtime => 'TX Airtime'; + + @override + String get repeater_rxAirtime => 'RX Airtime'; + + @override + String get repeater_packetStatistics => 'Csomagok statisztikája'; + + @override + String get repeater_sent => 'Elküldve'; + + @override + String get repeater_received => 'Megérkezett'; + + @override + String get repeater_duplicates => 'Duplák'; + + @override + String repeater_daysHoursMinsSecs( + int days, + int hours, + int minutes, + int seconds, + ) { + return '$days days ${hours}h ${minutes}m ${seconds}s'; + } + + @override + String repeater_packetTxTotal(int total, String flood, String direct) { + return 'Összesen: $total, Árvíz: $flood, Közvetlen: $direct'; + } + + @override + String repeater_packetRxTotal(int total, String flood, String direct) { + return 'Összesen: $total, Árvíz: $flood, Közvetlen: $direct'; + } + + @override + String repeater_duplicatesFloodDirect(String flood, String direct) { + return 'Áradás: $flood, Közvetlen: $direct'; + } + + @override + String repeater_duplicatesTotal(int total) { + return 'Összesen: $total'; + } + + @override + String get repeater_settingsTitle => 'Adatátvisszaadási beállítások'; + + @override + String get repeater_basicSettings => 'Alapbeállítások'; + + @override + String get repeater_repeaterName => 'Adóállomás neve'; + + @override + String get repeater_repeaterNameHelper => 'Ez a repeater neve'; + + @override + String get repeater_adminPassword => 'Adminisztrátori jelszó'; + + @override + String get repeater_adminPasswordHelper => 'Teljes jogosultságú jelszó'; + + @override + String get repeater_guestPassword => 'Vendég felhasználói név/jelszó'; + + @override + String get repeater_guestPasswordHelper => + 'Csak olvasási jogosítást biztosító jelszó'; + + @override + String get repeater_radioSettings => 'Rádióbeállítások'; + + @override + String get repeater_frequencyMhz => 'Frekvencia (MHz)'; + + @override + String get repeater_frequencyHelper => '300–2500 MHz'; + + @override + String get repeater_txPower => 'TX Power'; + + @override + String get repeater_txPowerHelper => '1-30 dBm'; + + @override + String get repeater_bandwidth => 'Adatkapacitás'; + + @override + String get repeater_spreadingFactor => 'Terjesztési tényező'; + + @override + String get repeater_codingRate => 'Kódolási sebesség'; + + @override + String get repeater_locationSettings => 'Helyszínbeállítások'; + + @override + String get repeater_latitude => 'Nyugat–keleti szélesség'; + + @override + String get repeater_latitudeHelper => 'Desztes fokok (pl. 37,7749)'; + + @override + String get repeater_longitude => 'hosszúság'; + + @override + String get repeater_longitudeHelper => 'Desztes fokok (pl. -122.4194)'; + + @override + String get repeater_features => 'Jellemzők'; + + @override + String get repeater_packetForwarding => 'Csomagok továbbítás'; + + @override + String get repeater_packetForwardingSubtitle => + 'Engedje, hogy a repeater továbbítsa a csomagokat.'; + + @override + String get repeater_guestAccess => 'Vendégek számára elérhető'; + + @override + String get repeater_guestAccessSubtitle => + 'Engedje meg a vendégek számára, hogy csak olvassák a tartalmat'; + + @override + String get repeater_privacyMode => 'Adatvédelem mód'; + + @override + String get repeater_privacyModeSubtitle => + 'Elrejtse a nevét/a helyszínt az űrlapon'; + + @override + String get repeater_advertisementSettings => 'Reklámbeállítások'; + + @override + String get repeater_localAdvertInterval => 'Helyi hirdetés időtartama'; + + @override + String repeater_localAdvertIntervalMinutes(int minutes) { + return '$minutes perc'; + } + + @override + String get repeater_floodAdvertInterval => 'Vízosztály-hirdetés időtartama'; + + @override + String repeater_floodAdvertIntervalHours(int hours) { + return '$hours óra'; + } + + @override + String get repeater_encryptedAdvertInterval => 'Kódolt hirdetés-szünet'; + + @override + String get repeater_dangerZone => 'Veszélyzóna'; + + @override + String get repeater_rebootRepeater => 'Újraindítás'; + + @override + String get repeater_rebootRepeaterSubtitle => 'Indítsa újra a repeater-t.'; + + @override + String get repeater_rebootRepeaterConfirm => + 'Biztosan szeretné újraindítani ezt a repeatert?'; + + @override + String get repeater_regenerateIdentityKey => + 'Újra generálja az azonosító kulcsot'; + + @override + String get repeater_regenerateIdentityKeySubtitle => + 'Új nyilvános/személyes kulcs-párt generáljon'; + + @override + String get repeater_regenerateIdentityKeyConfirm => + 'Ez új azonosítást fog létrehozni a repeater számára. Folytatni?'; + + @override + String get repeater_eraseFileSystem => 'Törölje a fájlrendszert'; + + @override + String get repeater_eraseFileSystemSubtitle => + 'Formázza a duplázó fájlrendszert.'; + + @override + String get repeater_eraseFileSystemConfirm => + 'FIGYELEM: Ez törli az összes adatot a repeater-en. Ez nem visszafordítható!'; + + @override + String get repeater_eraseSerialOnly => + 'Az Erase funkció csak a soros konzolon érhető el.'; + + @override + String repeater_commandSent(String command) { + return 'Parancs elküldve: $command'; + } + + @override + String repeater_errorSendingCommand(String error) { + return 'Hibás parancs küldés: $error'; + } + + @override + String get repeater_confirm => 'Beküldve'; + + @override + String get repeater_settingsSaved => 'Beállítások sikeresen mentve'; + + @override + String repeater_errorSavingSettings(String error) { + return 'Hibás beállítások mentése: $error'; + } + + @override + String get repeater_refreshBasicSettings => 'Visszaállítás az alapértékekre'; + + @override + String get repeater_refreshRadioSettings => 'Frissítse a rádió beállításait'; + + @override + String get repeater_refreshTxPower => 'Újraindítás TX-támogatással'; + + @override + String get repeater_refreshLocationSettings => + 'Újraindítás helyszín beállításokkal'; + + @override + String get repeater_refreshPacketForwarding => + 'Csomagok továbbításának frissítése'; + + @override + String get repeater_refreshGuestAccess => 'Újraindítás vendégHozzáférés'; + + @override + String get repeater_refreshPrivacyMode => + 'Visszaállítás a magánéletvédő módra'; + + @override + String get repeater_refreshAdvertisementSettings => + 'Újraindítás hirdetés beállítások'; + + @override + String repeater_refreshed(String label) { + return '$label frissítve'; + } + + @override + String repeater_errorRefreshing(String label) { + return 'Hiba a $label frissítés közben'; + } + + @override + String get repeater_cliTitle => 'CLI (parancssori felület)'; + + @override + String get repeater_debugNextCommand => 'Hibakeresés, következő parancs'; + + @override + String get repeater_commandHelp => 'Segítség'; + + @override + String get repeater_clearHistory => 'Egyértelmű történet'; + + @override + String get repeater_noCommandsSent => 'Még egyik parancsot sem küldtünk.'; + + @override + String get repeater_typeCommandOrUseQuick => + 'Írja be a parancsot alább, vagy használja a gyors parancsokat.'; + + @override + String get repeater_enterCommandHint => 'Írja be a parancsot...'; + + @override + String get repeater_previousCommand => 'Előző parancs'; + + @override + String get repeater_nextCommand => 'Következő parancs'; + + @override + String get repeater_enterCommandFirst => 'Add meg először egy parancsot'; + + @override + String get repeater_cliCommandFrameTitle => 'CLI parancssor felépítése'; + + @override + String repeater_cliCommandError(String error) { + return 'Hiba: $error'; + } + + @override + String get repeater_cliQuickGetName => 'Kapcsold össze a nevet'; + + @override + String get repeater_cliQuickGetRadio => 'Szerezd a rádiót'; + + @override + String get repeater_cliQuickGetTx => 'Szerezd a TX-t'; + + @override + String get repeater_cliQuickNeighbors => 'Szomszédok'; + + @override + String get repeater_cliQuickVersion => 'Verzió'; + + @override + String get repeater_cliQuickAdvertise => 'Hirdetés'; + + @override + String get repeater_cliQuickClock => 'óra'; + + @override + String get repeater_cliHelpAdvert => 'Elküldi egy hirdetési csomagot'; + + @override + String get repeater_cliHelpReboot => + 'Újraindítja a készüléket. (Kérjük, vegye figyelembe, hogy valószínűleg \"Időhiba\" üzenetet fog kapni, ami normális)'; + + @override + String get repeater_cliHelpClock => + 'A jelenlegi időt mutatja az egyes eszközök karórája alapján.'; + + @override + String get repeater_cliHelpPassword => + 'Új adminisztrációs jelszót állít be a eszköz számára.'; + + @override + String get repeater_cliHelpVersion => + 'Megjeleníti a készülék verzióját és a szoftver verziószámát.'; + + @override + String get repeater_cliHelpClearStats => + 'Visszaállítja a különböző statisztikai mérőszámokat a nullára.'; + + @override + String get repeater_cliHelpSetAf => 'Beállítja az idő-szabályozási tényezőt.'; + + @override + String get repeater_cliHelpSetTx => + 'Beállítja a LoRa átviteli teljesítményt dBm-ben (a rendszer újraindításával alkalmazható).'; + + @override + String get repeater_cliHelpSetRepeat => + 'Engedélyezi vagy tiltja meg a repeater szerepet ezen a csomón.'; + + @override + String get repeater_cliHelpSetAllowReadOnly => + '(Szoba szerver) Ha \"igen\", akkor üres jelszóval történő bejelentkezés engedélyezett lesz, de nem lehet üzeneteket küldeni a szobában. (Csak olvasási funkció)'; + + @override + String get repeater_cliHelpSetFloodMax => + 'Beállítja a bejövő adatcsomagok maximális számát (ha ez a érték nagyobb vagy egyenlő a maximális értékkel, a csomag nem továbbítódik).'; + + @override + String get repeater_cliHelpSetIntThresh => + 'Beállítja az interferencia határértéket (dB-ben). Az alapérték 14. Ha 0-ra állítja, kiküntheti a csatornák közötti interferencia detektálást.'; + + @override + String get repeater_cliHelpSetAgcResetInterval => + 'Beállítja az intervallumot, amely a \"Automatikus gain\" szabályozó újraindításához szükséges. Beállítás értéke 0, ha a funkciót le kell tiltani.'; + + @override + String get repeater_cliHelpSetMultiAcks => + 'Engedélyezi vagy kikapcsolja a „dupla visszaigazolás” funkciót.'; + + @override + String get repeater_cliHelpSetAdvertInterval => + 'Beállítja az időzítő intervallumot percenként, hogy egy helyi (nincs átjáró) hirdetési csomagot küldjen. Beállítás értéke 0, ha a funkciót le szeretné tiltani.'; + + @override + String get repeater_cliHelpSetFloodAdvertInterval => + 'Beállítja az időzítő intervallumot órában, hogy egy \"áramló\" hirdetési üzenetet küldjön. Beállítás értéke 0, ha a funkciót kikapcsolni kell.'; + + @override + String get repeater_cliHelpSetGuestPassword => + 'Beállítja/frissíti a vendég felhasználói fiókot. (Ez lehetővé teszi a visszatérő felhasználók számára, hogy a \"Statistika lekérdezése\" kérést elküldjék)'; + + @override + String get repeater_cliHelpSetName => 'Megadja az űrlap neve.'; + + @override + String get repeater_cliHelpSetLat => + 'Beállítja az hirdetés térképen megjelenő pont koordinátájának (tizedes fokokban) a latitude-ját.'; + + @override + String get repeater_cliHelpSetLon => + 'Beállítja az hirdetés térképen megjelenő hosszúság koordinátát (tizedes fokokban).'; + + @override + String get repeater_cliHelpSetRadio => + 'Teljesen új rádióparamétereket állít be, és azokat a beállításokba menti. Az alkalmazásához \"újraindítás\" parancs szükséges.'; + + @override + String get repeater_cliHelpSetRxDelay => + 'Beállítások (kísérleti): Alapérték (legalább 1 értékre kell állítani, hogy hatás legyen), amely alapján a fogadott csomagokhoz enyhe késést alkalmazunk, a jelet ereje/pontszám alapján. 0-ra állítva a funkciót lekapcsoljuk.'; + + @override + String get repeater_cliHelpSetTxDelay => + 'Beállítja egy tényezőt, amely a légköri idővel szorozva, egy áramlás-üzem módú csomaghoz, valamint egy véletlenszerű slot-rendszerhez, hogy késleltesse a továbbítását. (az ütközések valószínűségének csökkentése érdekében)'; + + @override + String get repeater_cliHelpSetDirectTxDelay => + 'Hasonló a txdelay-hez, de ebben az esetben egy véletlenszerű késést alkalmazunk a közvetlen módú csomagok továbbításakor.'; + + @override + String get repeater_cliHelpSetBridgeEnabled => + 'Engedélyez/Tiltás a híd funkciójának.'; + + @override + String get repeater_cliHelpSetBridgeDelay => + 'Állíts be egy késleztatást a csomagok újbóli továbbításakor.'; + + @override + String get repeater_cliHelpSetBridgeSource => + 'Döntse el, hogy a híd fogadott vagy elküldött csomagokat fogja-e továbbítani.'; + + @override + String get repeater_cliHelpSetBridgeBaud => + 'Állítsa be a soros kommunikáció sebességét az RS232 hídok számára.'; + + @override + String get repeater_cliHelpSetBridgeSecret => + 'Állítsa be a titkos kapcsolatot az ESPNOW hídokhoz.'; + + @override + String get repeater_cliHelpSetAdcMultiplier => + 'Lehetővé teszi a felhasználónak, hogy egyedi tényezőt állíts be a riportolt akkumulátor feszültségének módosításához (ez csak bizonyos alkatrészeken támogatott).'; + + @override + String get repeater_cliHelpTempRadio => + 'Időjárás szerinti rádióparamétereket állít be a megadott időtartamra, majd visszaállítja az eredeti beállításokat. (Nem menti a beállításokat a beállítások részben).'; + + @override + String get repeater_cliHelpSetPerm => + 'A ACL-t módosítja. Ha a \"permissions\" érték 0, akkor eltávolítja a megfelelő bejegyzést (a pubkey előtag alapján). Új bejegyzést hoz létre, ha a pubkey-hex teljes hossza, és jelenleg nem szerepel az ACL-ben. A bejegyzést frissíti a megfelelő pubkey előtag alapján. A engedélyek különbözőek a különböző firmware szerepek között, de az alsó 2 bit a következő értékeket képviseli: 0 (Vendég), 1 (Csak olvasás), 2 (Olvasás és írás), 3 (Adminisztrátor)'; + + @override + String get repeater_cliHelpGetBridgeType => + 'Kapcsolatok: hid típusú, RS232, ESPNOW'; + + @override + String get repeater_cliHelpLogStart => + 'Elindítja a csomagok naplózását a fájlrendszerbe.'; + + @override + String get repeater_cliHelpLogStop => + 'Megállítja a csomagok naplózását a fájlrendszerbe.'; + + @override + String get repeater_cliHelpLogErase => + 'Törli a fájlrendszerből a csomagok log-fájljait.'; + + @override + String get repeater_cliHelpNeighbors => + 'Mutat egy listát, amely tartalmazza a más repeater-ek által hallott adatok listáját, amelyek 0-hop hirdetések révén érhetők el. Minden sor az alábbi formát követi: id-prefix-hex:timestamp:snr-times-4'; + + @override + String get repeater_cliHelpNeighborRemove => + 'Törli az első, a megadott kulcs-prefix (hexadecimális formában) alapján megegyező bejegyzést a szomszédok listájából.'; + + @override + String get repeater_cliHelpRegion => + '(sorozat) Lista az összes meghatározott területet és a jelenlegi árvízvédelmi engedélyeket.'; + + @override + String get repeater_cliHelpRegionLoad => + 'FIGYELEM: ez egy speciális, több parancsot tartalmazó futtatás. Minden következő parancs egy területtel kapcsolatos, amely egyenletes szóközökkel (a szülő-gyermek kapcsolatot jelző) megkülönböztethető. A parancs végrehajtása egy üres sor/parancs küldésével történik.'; + + @override + String get repeater_cliHelpRegionGet => + 'Keresések egy adott név előtérrel (vagy \"*\" globális hatókörre). Válasz: \"-> region-név (szülő-név) \'F\'\"'; + + @override + String get repeater_cliHelpRegionPut => + 'Hozzáad vagy frissíti egy régió definíciót megadott néven.'; + + @override + String get repeater_cliHelpRegionRemove => + 'Eltávolítja a megadott nevet használó régió-definíciót. (pontosan meg kell egyeznie, és nem lehet gyermekrégiója)'; + + @override + String get repeater_cliHelpRegionAllowf => + 'Beállítja a megadott területre vonatkozó \"víz\" jogosultságot. (A globális/régi beállítások esetén a \"*\" jelölő)'; + + @override + String get repeater_cliHelpRegionDenyf => + 'Eltávolítja a megadott területre vonatkozó \"F\"lood (víz) engedélyt. (FIGYELEM: jelenleg nem javasolt ezt a globális/régi verzióban használni!!)'; + + @override + String get repeater_cliHelpRegionHome => + 'Visszaállítja a jelenlegi „otthoni” régiót. (Ez a beállítás még nem került alkalmazásra, csak jövőbeli használatra fenyelve)'; + + @override + String get repeater_cliHelpRegionHomeSet => 'Beállítja a \"házi\" régiót.'; + + @override + String get repeater_cliHelpRegionSave => + 'Megőrzi a régió listát/térképet a tárolóban.'; + + @override + String get repeater_cliHelpGps => + 'Megadja a GPS állapotát. Ha a GPS kikapcsolva van, akkor csak \"ki\" választot ad, ha be van, akkor \"be\", \"állapot\", \"pozíció\", \"satellitok száma\" értékeket ad.'; + + @override + String get repeater_cliHelpGpsOnOff => 'Engedi a GPS működés állapotát.'; + + @override + String get repeater_cliHelpGpsSync => + 'A hálózati időt az GPS óra időjével szinkronizálja.'; + + @override + String get repeater_cliHelpGpsSetLoc => + 'Beállítja a węsz pozícióját GPS koordináták alapján, és menti a beállításokat.'; + + @override + String get repeater_cliHelpGpsAdvert => + 'Adja meg a hirdetés konfigurációjának helyszín-információját:\n- none: ne tartalmazza a helyszínt a hirdetésekben\n- share: megosztja a GPS-helyszínt (SensorManager-ből)\n- prefs: hirdeti a beállításokban tárolt helyszínt'; + + @override + String get repeater_cliHelpGpsAdvertSet => + 'Beállítja a hirdetés helyszín-specifikus beállításait.'; + + @override + String get repeater_commandsListTitle => 'Parancsok listája'; + + @override + String get repeater_commandsListNote => + 'FIGYELEM: a különböző \"set ...\" parancsok mellett létezik egy \"get ...\" parancs is.'; + + @override + String get repeater_general => 'Általános'; + + @override + String get repeater_settingsCategory => 'Beállítások'; + + @override + String get repeater_bridge => 'Híd'; + + @override + String get repeater_logging => 'Naplózás'; + + @override + String get repeater_neighborsRepeaterOnly => + 'Szomszédok (Csak ismétlő funkció)'; + + @override + String get repeater_regionManagementRepeaterOnly => + 'Regionális menedzsment (Csak egyirányú kommunikáció)'; + + @override + String get repeater_regionNote => + 'Region-specifikus parancsokat vezettek be a régiók definiálására és a hozzájuk tartozó engedélyek kezelésére.'; + + @override + String get repeater_gpsManagement => 'GPS-vezérlés'; + + @override + String get repeater_gpsNote => + 'Az GPS-al kapcsolatos funkciók lehetővé teszik a helyszín-személyesítéssel kapcsolatos feladatok kezelését.'; + + @override + String get telemetry_receivedData => 'Kapott adatokat a szenzorokról'; + + @override + String get telemetry_requestTimeout => 'Az adatkapcsolati kérés sikertelen.'; + + @override + String telemetry_errorLoading(String error) { + return 'Hiba az adatok begyűjtésében: $error'; + } + + @override + String get telemetry_noData => 'Nincsenek elérhető telemetriadatok.'; + + @override + String telemetry_channelTitle(int channel) { + return '$channel csatorna'; + } + + @override + String get telemetry_batteryLabel => 'Akku'; + + @override + String get telemetry_voltageLabel => 'Feszültség'; + + @override + String get telemetry_mcuTemperatureLabel => 'MCU hőmérséklet'; + + @override + String get telemetry_temperatureLabel => 'Hőmérséklet'; + + @override + String get telemetry_currentLabel => 'Jelenlegi'; + + @override + String telemetry_batteryValue(int percent, String volts) { + return '$percent% / ${volts}V'; + } + + @override + String telemetry_voltageValue(String volts) { + return '${volts}V'; + } + + @override + String telemetry_currentValue(String amps) { + return '${amps}A'; + } + + @override + String telemetry_temperatureValue(String celsius, String fahrenheit) { + return '$celsius °C / $fahrenheit °F'; + } + + @override + String get neighbors_receivedData => 'Kapott szomszédok adatait'; + + @override + String get neighbors_requestTimedOut => + 'A szomszédok kérik, hogy tiltsák le a kamerát.'; + + @override + String neighbors_errorLoading(String error) { + return 'Hiba a szomszédok betöltésében: $error'; + } + + @override + String get neighbors_repeatersNeighbors => 'Ismétlő eszközök, szomszédok'; + + @override + String get neighbors_noData => 'Nincsenek elérhető szomszédokról adatok.'; + + @override + String neighbors_unknownContact(String pubkey) { + return 'Tudatlan $pubkey'; + } + + @override + String neighbors_heardAgo(String time) { + return 'Értsd: $time sitten'; + } + + @override + String get channelPath_title => 'Csomagok útvonala'; + + @override + String get channelPath_viewMap => 'Megtekinthető térkép'; + + @override + String get channelPath_otherObservedPaths => 'Egyéb megfigyelt utak'; + + @override + String get channelPath_repeaterHops => 'Adat továbbító lépések'; + + @override + String get channelPath_noHopDetails => + 'Ez a csomag nem tartalmaz részletes információkat a \"hop\" (vagy más hasonló) szót használó kifejezésekről.'; + + @override + String get channelPath_messageDetails => 'Üzenet részletei'; + + @override + String get channelPath_senderLabel => 'Megküldő'; + + @override + String get channelPath_timeLabel => 'Idő'; + + @override + String get channelPath_repeatsLabel => 'Ismétli'; + + @override + String channelPath_pathLabel(int index) { + return 'Útvonal $index'; + } + + @override + String get channelPath_observedLabel => 'Megfigyelt'; + + @override + String channelPath_observedPathTitle(int index, String hops) { + return 'Megfigyelt útvonal: $index • $hops'; + } + + @override + String get channelPath_noLocationData => 'Nincs helyszínadat.'; + + @override + String channelPath_timeWithDate(int day, int month, String time) { + return '$day/$month $time'; + } + + @override + String channelPath_timeOnly(String time) { + return '$time'; + } + + @override + String get channelPath_unknownPath => 'Megfejt'; + + @override + String get channelPath_floodPath => 'Árvíz'; + + @override + String get channelPath_directPath => 'Közvetlen'; + + @override + String channelPath_observedZeroOf(int total) { + return '0-ból $total'; + } + + @override + String channelPath_observedSomeOf(int observed, int total) { + return '$observed of $total hops'; + } + + @override + String get channelPath_mapTitle => 'Útvonal térkép'; + + @override + String get channelPath_noRepeaterLocations => + 'Ez a útvonal nem támogat repeater-t.'; + + @override + String channelPath_primaryPath(int index) { + return 'Útvonal $index (Elsődleges)'; + } + + @override + String get channelPath_pathLabelTitle => 'Út'; + + @override + String get channelPath_observedPathHeader => 'Megfigyelt útvonal'; + + @override + String channelPath_selectedPathLabel(String label, String prefixes) { + return '$label • $prefixes'; + } + + @override + String get channelPath_noHopDetailsAvailable => + 'Ez a csomag nem tartalmaz részletes információkat a szállításhoz.'; + + @override + String get channelPath_unknownRepeater => 'Tudatlan erősítő'; + + @override + String get community_title => 'Helyi közösség'; + + @override + String get community_create => 'Teremtsd meg a közösséget'; + + @override + String get community_createDesc => + 'Légyon létre egy új közösséget, és osszák meg QR-kód segítségével.'; + + @override + String get community_join => 'Csatlakozjon'; + + @override + String get community_joinTitle => 'Csatlakozzon a közösséghez'; + + @override + String community_joinConfirmation(String name) { + return 'Szeretne csatlakozni a közösséghez, $name?'; + } + + @override + String get community_scanQr => 'QR-kód olvasó a közösség számára'; + + @override + String get community_scanInstructions => + 'Fordítsa a kamerát egy közösségi QR-kód irányába.'; + + @override + String get community_showQr => 'Megjelenítse a QR-kódot'; + + @override + String get community_publicChannel => 'Összetartó, közösségi'; + + @override + String get community_hashtagChannel => 'Helyi hashtaget'; + + @override + String get community_name => 'Helyi közösség neve'; + + @override + String get community_enterName => 'Kérjük, a közösség nevét írja be.'; + + @override + String community_created(String name) { + return 'A \"$name\" nevű közösség létrehozva'; + } + + @override + String community_joined(String name) { + return 'Csatlakozott a $name közösséghez'; + } + + @override + String get community_qrTitle => 'Osszpontosítás a közösségben'; + + @override + String community_qrInstructions(String name) { + return 'Scanned this QR-kódot, hogy csatlakozhat a $name csoporthoz.'; + } + + @override + String get community_hashtagPrivacyHint => + 'A közösségi hashtagekhez tartozó csatornák csak a közösség tagjai számára érhetők el.'; + + @override + String get community_invalidQrCode => 'Érvénytelen közösségi QR-kód'; + + @override + String get community_alreadyMember => 'Már tag vagy'; + + @override + String community_alreadyMemberMessage(String name) { + return 'Már tagja $name-nek.'; + } + + @override + String get community_addPublicChannel => + 'Hozzon létre egy közösségi nyilvános csatornát'; + + @override + String get community_addPublicChannelHint => + 'Automatikusan hozzon létre ezt a csatornát a közösség számára.'; + + @override + String get community_noCommunities => 'Még egyik közösség sem csatlakozott.'; + + @override + String get community_scanOrCreate => + 'Scelle egy QR-kódot, vagy hozzon létre egy közösséget, hogy elinduljon.'; + + @override + String get community_manageCommunities => 'Közösségek kezelése'; + + @override + String get community_delete => 'Hagyományos közösségi élet'; + + @override + String community_deleteConfirm(String name) { + return 'Hagyom $name-et?'; + } + + @override + String community_deleteChannelsWarning(int count) { + return 'Ezem törli is $count csatornát és a hozzá tartozó üzeneteket.'; + } + + @override + String community_deleted(String name) { + return 'A közösség, amely $name'; + } + + @override + String get community_regenerateSecret => 'Titkos visszaállítás'; + + @override + String community_regenerateSecretConfirm(String name) { + return 'Újra kell generálni a titkos kulcsot $name számára? Minden tagnak be kell szkennelnie az új QR-kódot, hogy továbbra is kommunikálhasson.'; + } + + @override + String get community_regenerate => 'Újraalakítás'; + + @override + String community_secretRegenerated(String name) { + return 'Titkos kulcs megújult $name számára.'; + } + + @override + String get community_updateSecret => 'Frissítési titok'; + + @override + String community_secretUpdated(String name) { + return 'Titkos információ frissítve $name számára'; + } + + @override + String community_scanToUpdateSecret(String name) { + return 'Scanned a új QR-kódot, hogy frissítsük a $name számára megőrzött titkos információt.'; + } + + @override + String get community_addHashtagChannel => 'Adjon egy közösségi hashtaget'; + + @override + String get community_addHashtagChannelDesc => + 'Hozz létre egy hashtage-os csatornát ennek a közösségnek'; + + @override + String get community_selectCommunity => 'Válasszon közösséget'; + + @override + String get community_regularHashtag => 'Rendszeres hashtag'; + + @override + String get community_regularHashtagDesc => + 'Önmagas szintű hashtaget (bárki csatlakozhat)'; + + @override + String get community_communityHashtag => 'Helyi hashtaget'; + + @override + String get community_communityHashtagDesc => 'Csak a közösség tagjai számára'; + + @override + String community_forCommunity(String name) { + return '$name számára'; + } + + @override + String get listFilter_tooltip => 'Szűrés és rendezés'; + + @override + String get listFilter_sortBy => 'Szűrés'; + + @override + String get listFilter_latestMessages => 'Legfrissebb üzenetek'; + + @override + String get listFilter_heardRecently => 'Úgy hallottam, hogy...'; + + @override + String get listFilter_az => 'A-Z'; + + @override + String get listFilter_filters => 'Szűrők'; + + @override + String get listFilter_all => 'Mind'; + + @override + String get listFilter_favorites => 'Kedvencek'; + + @override + String get listFilter_addToFavorites => 'Megerősítés kívánságlistára'; + + @override + String get listFilter_removeFromFavorites => 'Törölj a kedvencekből'; + + @override + String get listFilter_users => 'Felhasználók'; + + @override + String get listFilter_repeaters => 'Újraküldők'; + + @override + String get listFilter_roomServers => 'Szoba-szolgálatok'; + + @override + String get listFilter_unreadOnly => 'Csak olvasatlan'; + + @override + String get listFilter_newGroup => 'Új csoport'; + + @override + String get pathTrace_you => 'Te'; + + @override + String get pathTrace_failed => 'A útvonal követése sikertelen.'; + + @override + String get pathTrace_notAvailable => + 'Az útvonal követési funkció nem elérhető.'; + + @override + String get pathTrace_refreshTooltip => 'Út mentesség frissítése.'; + + @override + String get pathTrace_someHopsNoLocation => + 'Egy vagy több búzavirág hiányozik a helyszínéről!'; + + @override + String get pathTrace_clearTooltip => 'Egyértelmű út.'; + + @override + String get losSelectStartEnd => + 'Válassza ki a kezdő és a végpontokat a LOS-hoz.'; + + @override + String losRunFailed(String error) { + return 'A látószög ellenőrzése sikertelen: $error'; + } + + @override + String get losClearAllPoints => 'Teljesen tisztázzuk az összes pontot'; + + @override + String get losRunToViewElevationProfile => + 'Használja a LOS-t, hogy megtekinthesse a magasságkülönbségek diagramját.'; + + @override + String get losMenuTitle => 'LOS menü'; + + @override + String get losMenuSubtitle => + 'A térképen található pontok kiválasztására vagy a térképen hosszúra nyomva, hogy egyedi pontokat definiálhassunk.'; + + @override + String get losShowDisplayNodes => 'Megjelenítsen a megjelenítési egységeket'; + + @override + String get losCustomPoints => 'Egyedi pontok'; + + @override + String losCustomPointLabel(int index) { + return 'Egyedi $index'; + } + + @override + String get losPointA => 'A pont A'; + + @override + String get losPointB => 'Pont B'; + + @override + String losAntennaA(String value, String unit) { + return 'Antenna A: $value $unit'; + } + + @override + String losAntennaB(String value, String unit) { + return 'Antenna B: $value $unit'; + } + + @override + String get losRun => 'Futtass a LOS-on'; + + @override + String get losNoElevationData => 'Nincsenek emelkedési adatok.'; + + @override + String losProfileClear( + String distance, + String distanceUnit, + String clearance, + String heightUnit, + ) { + return '$distance $distanceUnit, clear LOS, min clearance $clearance $heightUnit'; + } + + @override + String losProfileBlocked( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit, amelyet $obstruction akadályoz meg $heightUnit-ban'; + } + + @override + String get losStatusChecking => 'LOS: ellenőrzés...'; + + @override + String get losStatusNoData => 'LOS: nincs adat'; + + @override + String losStatusSummary(int clear, int total, int blocked, int unknown) { + return 'LOS: $clear/$total tisztított, $blocked blokkolt, $unknown ismeretlen'; + } + + @override + String get losErrorElevationUnavailable => + 'Az alábbi minták esetében nem áll rendelkezésre magasságadat.'; + + @override + String get losErrorInvalidInput => + 'Hibás vagy hiányos táblázatok a LOS (Loss of Signal) számításához.'; + + @override + String get losRenameCustomPoint => 'Állítsa meg a saját pont nevét'; + + @override + String get losPointName => 'Pont neve'; + + @override + String get losShowPanelTooltip => 'Megjelenítse a LOS paneelt'; + + @override + String get losHidePanelTooltip => 'Rejtse el a LOS paneelt'; + + @override + String get losElevationAttribution => + 'Magasságadatok: Open-Meteo (CC BY 4.0)'; + + @override + String get losLegendRadioHorizon => 'Radio Horizont'; + + @override + String get losLegendLosBeam => 'LOS jelzés'; + + @override + String get losLegendTerrain => 'Terület'; + + @override + String get losFrequencyLabel => 'Hatósság'; + + @override + String get losFrequencyInfoTooltip => 'Lásd a számítás részleteit'; + + @override + String get losFrequencyDialogTitle => + 'A rádióhullámok hatótávolságának kiszámítása'; + + @override + String losFrequencyDialogDescription( + double baselineK, + double baselineFreq, + double frequencyMHz, + double kFactor, + ) { + return 'A $baselineK értékből kezdve, $baselineFreq MHz-os frekvencián, a számítás az aktuális $frequencyMHz MHz-os sávhoz igazítja a k-tényezőt, amely meghatározza a görbös rádióhatótávolság határát.'; + } + + @override + String get contacts_pathTrace => 'Útvonal követése'; + + @override + String get contacts_ping => 'Ping'; + + @override + String get contacts_repeaterPathTrace => 'Az útvonal követése a repeaterig'; + + @override + String get contacts_repeaterPing => 'Ping-szinkronizáló'; + + @override + String get contacts_roomPathTrace => 'Kapcsolat a szobai szerverrel'; + + @override + String get contacts_roomPing => 'Ping-szolgáló szerver'; + + @override + String get contacts_chatTraceRoute => 'Útvonal meghatározása'; + + @override + String contacts_pathTraceTo(String name) { + return 'Keresse meg a $name címét.'; + } + + @override + String get contacts_clipboardEmpty => 'A kiválasztott szöveg üres.'; + + @override + String get contacts_invalidAdvertFormat => 'Érvénytelen kontaktinformáció'; + + @override + String get contacts_contactImported => 'Kapcsolat létrejött.'; + + @override + String get contacts_contactImportFailed => + 'Nem sikerült a kapcsolatot importálni.'; + + @override + String get contacts_zeroHopAdvert => 'Zero Hop reklám'; + + @override + String get contacts_floodAdvert => 'Árvízre vonatkozó hirdetés'; + + @override + String get contacts_copyAdvertToClipboard => + 'Másolja a hirdetést a kiválasztási ablakba'; + + @override + String get contacts_addContactFromClipboard => + 'Adjon hozzá egy kapcsolatot a kiválasztott listából'; + + @override + String get contacts_ShareContact => 'Másolja a kapcsolatot a kiválasztóba'; + + @override + String get contacts_ShareContactZeroHop => + 'Ossza meg a kapcsolatot hirdetés segítségével'; + + @override + String get contacts_zeroHopContactAdvertSent => + 'Kapcsolatot a hirdetésen keresztül.'; + + @override + String get contacts_zeroHopContactAdvertFailed => + 'Nem sikerült a kapcsolatot elküldeni.'; + + @override + String get contacts_contactAdvertCopied => 'A hirdetés másolva a vágólapra.'; + + @override + String get contacts_contactAdvertCopyFailed => + 'Az hirdetés másolása a vágólapra sikertelen.'; + + @override + String get notification_activityTitle => 'MeshCore tevékenységek'; + + @override + String notification_messagesCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'üzenetek', + one: 'üzenet', + ); + return '$count $_temp0'; + } + + @override + String notification_channelMessagesCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'csatornaüzenetek', + one: 'csatornaüzenet', + ); + return '$count $_temp0'; + } + + @override + String notification_newNodesCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'új csomópontok', + one: 'új csomópont', + ); + return '$count $_temp0'; + } + + @override + String notification_newTypeDiscovered(String contactType) { + return 'Új $contactType megtalálva'; + } + + @override + String get notification_receivedNewMessage => 'Új üzenetet kaptam'; + + @override + String get settings_gpxExportRepeaters => + 'Külső eszközök / helyi szerver a GPX formátumba'; + + @override + String get settings_gpxExportRepeatersSubtitle => + 'Exportálható repeater/szobaterm-szerver, amely egy GPX fájlban tárolja a helyzetet.'; + + @override + String get settings_gpxExportContacts => 'GPX export funkciók'; + + @override + String get settings_gpxExportContactsSubtitle => + 'Az export funkció lehetővé teszi, hogy a GPS fájlban megadott helyszínen is megőrizzük az útvonalat.'; + + @override + String get settings_gpxExportAll => + 'Exportálja az összes kapcsolatot GPX formátumban.'; + + @override + String get settings_gpxExportAllSubtitle => + 'Az összes elérhetőséget, amelyekhez egy helyszín tartozik, egy GPX fájlba exportálja.'; + + @override + String get settings_gpxExportSuccess => + 'A GPX fájl sikeresen exportálva lett.'; + + @override + String get settings_gpxExportNoContacts => 'Nincs exportálható kapcsolatok.'; + + @override + String get settings_gpxExportNotAvailable => + 'Nem támogatott a jelenlegi eszközön/rendszeren.'; + + @override + String get settings_gpxExportError => 'Hiba történt az export során.'; + + @override + String get settings_gpxExportRepeatersRoom => + 'Adatátvisszaadó eszközök és helyiségi szerverek helyei'; + + @override + String get settings_gpxExportChat => 'Kapcsolódó helyszínek'; + + @override + String get settings_gpxExportAllContacts => 'Az összes kapcsolat helyszíne'; + + @override + String get settings_gpxExportShareText => + 'A meshcore-open-ból exportált térkéadatumok'; + + @override + String get settings_gpxExportShareSubject => + 'meshcore-open GPX formátumú térképi adatok export'; + + @override + String get snrIndicator_nearByRepeaters => 'Helyszíni erősítők'; + + @override + String get snrIndicator_lastSeen => 'Utoljára, amikor látták'; + + @override + String get contactsSettings_title => 'Kapcsolatok beállításai'; + + @override + String get contactsSettings_autoAddTitle => 'Automatikus felfedezés'; + + @override + String get contactsSettings_otherTitle => + 'Egyéb kapcsolattal kapcsolatos beállítások'; + + @override + String get contactsSettings_autoAddUsersTitle => + 'Automatikus felhasználói hozzáadás'; + + @override + String get contactsSettings_autoAddUsersSubtitle => + 'Engedje, hogy a segítő automatikusan hozzáadja az új felhasználókat.'; + + @override + String get contactsSettings_autoAddRepeatersTitle => + 'Automatikus visszatöltés'; + + @override + String get contactsSettings_autoAddRepeatersSubtitle => + 'Engedje, hogy a segítő eszköz automatikusan hozzáadja az új, megtalált jelzőállomásokat.'; + + @override + String get contactsSettings_autoAddRoomServersTitle => + 'Automatikus szobák szerverek hozzáadása'; + + @override + String get contactsSettings_autoAddRoomServersSubtitle => + 'Engedje, hogy a segítő automatikusan hozzáadja az új, megtalált hálózati szervereket.'; + + @override + String get contactsSettings_autoAddSensorsTitle => + 'Automatikus érzékelők hozzáadása'; + + @override + String get contactsSettings_autoAddSensorsSubtitle => + 'Engedje, hogy a kísérő automatikusan hozzáadja az új, megtalált szenzorokat.'; + + @override + String get contactsSettings_overwriteOldestTitle => 'Felülírja a legrégebbet'; + + @override + String get contactsSettings_overwriteOldestSubtitle => + 'Amikor a névsor telítődik, a legidősebb, de még nem kedvencként jelölt személyt helyettesíti egy újabb.'; + + @override + String get discoveredContacts_Title => 'Megtalált kapcsolatok'; + + @override + String get discoveredContacts_noMatching => 'Nincs megegyező kapcsolat.'; + + @override + String get discoveredContacts_searchHint => 'Keress új kapcsolatokat'; + + @override + String get discoveredContacts_contactAdded => 'Kapcsolat hozzáadva'; + + @override + String get discoveredContacts_addContact => 'Adjon személyhez'; + + @override + String get discoveredContacts_copyContact => + 'Másolja a kapcsolatot a vágólapra'; + + @override + String get discoveredContacts_deleteContact => + 'Törölj a feltalált kapcsolatot'; + + @override + String get discoveredContacts_deleteContactAll => + 'Törölj minden megtalált kapcsolatot'; + + @override + String get discoveredContacts_deleteContactAllContent => + 'Biztos, hogy szeretné törölni az összes eddig megtalált kapcsolatot?'; + + @override + String get chat_sendCooldown => + 'Kérjük, várjon egy pillanatot, mielőtt újra elküldené.'; + + @override + String get appSettings_jumpToOldestUnread => + 'Jelentkezzen az legörebb, olvasatlan üzenetre'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + 'Amikor egy új csevet indítunk, amelyben vannak olvashatatlan üzenetek, görgessük a listát, hogy a legelső, olvashatatlan üzenet megjelenjen, nem pedig az utolsó.'; + + @override + String get appSettings_languageHu => 'Magyar'; + + @override + String get appSettings_languageJa => 'Japán'; + + @override + String get appSettings_languageKo => 'Koreai'; + + @override + String get radioStats_tooltip => 'Rádió és hálózati statisztikák'; + + @override + String get radioStats_screenTitle => 'Rádió statisztikák'; + + @override + String get radioStats_notConnected => + 'Csatlakozzon egy eszközhöz, hogy megtekinthesse a rádió adatok statisztikáit.'; + + @override + String get radioStats_firmwareTooOld => + 'A rádió statisztikákhoz v8 vagy újabb verziójú szoftver szükséges.'; + + @override + String get radioStats_waiting => 'Adatokra vár…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'Háttérzaj szint: $noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return 'Utolsó RSSI érték: $rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return 'Utolsó SNR: $snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'TX-es idő (összesen): $seconds másodperc'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'RX használat időtartama (összesen): $seconds s'; + } + + @override + String get radioStats_chartCaption => + 'Háttérzaj szint (dBm) a legutóbbi minták alapján.'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'Háttérzaj szint: $noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => 'Rádió adatok begyűjtése…'; + + @override + String get radioStats_settingsTile => 'Rádió statisztikák'; + + @override + String get radioStats_settingsSubtitle => + 'Háttérzaj, RSSI, zaj-sűrűség, és a használat időtartama'; +} diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart new file mode 100644 index 0000000..12f7bb7 --- /dev/null +++ b/lib/l10n/app_localizations_ja.dart @@ -0,0 +1,3404 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Japanese (`ja`). +class AppLocalizationsJa extends AppLocalizations { + AppLocalizationsJa([String locale = 'ja']) : super(locale); + + @override + String get appTitle => 'MeshCore Open'; + + @override + String get nav_contacts => '連絡先'; + + @override + String get nav_channels => 'チャンネル'; + + @override + String get nav_map => '地図'; + + @override + String get common_cancel => 'キャンセル'; + + @override + String get common_ok => '了解'; + + @override + String get common_connect => '接続する'; + + @override + String get common_unknownDevice => '不明なデバイス'; + + @override + String get common_save => '保存'; + + @override + String get common_delete => '削除'; + + @override + String get common_deleteAll => 'すべて削除'; + + @override + String get common_close => '閉じる'; + + @override + String get common_edit => '編集'; + + @override + String get common_add => '追加'; + + @override + String get common_settings => '設定'; + + @override + String get common_disconnect => '切断する'; + + @override + String get common_connected => '接続されている'; + + @override + String get common_disconnected => '切断'; + + @override + String get common_create => '作成する'; + + @override + String get common_continue => '続き'; + + @override + String get common_share => '共有する'; + + @override + String get common_copy => 'コピー'; + + @override + String get common_retry => '再試'; + + @override + String get common_hide => '隠す'; + + @override + String get common_remove => '削除'; + + @override + String get common_enable => '有効化する'; + + @override + String get common_disable => '無効化する'; + + @override + String get common_reboot => '再起動'; + + @override + String get common_loading => '読み込み中...'; + + @override + String get common_notAvailable => '—'; + + @override + String common_voltageValue(String volts) { + return '$volts V'; + } + + @override + String common_percentValue(int percent) { + return '$percent%'; + } + + @override + String get scanner_title => 'MeshCore オープン'; + + @override + String get connectionChoiceUsbLabel => 'USB'; + + @override + String get connectionChoiceBluetoothLabel => 'ブルートゥース'; + + @override + String get connectionChoiceTcpLabel => 'TCP'; + + @override + String get tcpScreenTitle => 'TCP を使用して接続'; + + @override + String get tcpHostLabel => 'IPアドレス'; + + @override + String get tcpHostHint => '192.168.40.10'; + + @override + String get tcpPortLabel => '港'; + + @override + String get tcpPortHint => '5000'; + + @override + String get tcpStatus_notConnected => 'エンドポイントを入力し、接続する'; + + @override + String tcpStatus_connectingTo(String endpoint) { + return '$endpoint への接続中...'; + } + + @override + String get tcpErrorHostRequired => 'IPアドレスが必要です。'; + + @override + String get tcpErrorPortInvalid => 'ポート番号は1から65535の範囲で指定してください。'; + + @override + String get tcpErrorUnsupported => 'このプラットフォームでは、TCP 転送はサポートされていません。'; + + @override + String get tcpErrorTimedOut => 'TCP 接続がタイムアウトしました。'; + + @override + String tcpConnectionFailed(String error) { + return 'TCP接続に失敗しました:$error'; + } + + @override + String get usbScreenTitle => 'USB経由で接続'; + + @override + String get usbScreenSubtitle => '検出されたシリアルデバイスを選択し、MeshCoreノードに直接接続してください。'; + + @override + String get usbScreenStatus => 'USBデバイスを選択する'; + + @override + String get usbScreenNote => + 'USBシリアルポートは、サポートされているAndroidデバイスおよびデスクトッププラットフォームで利用可能です。'; + + @override + String get usbScreenEmptyState => + 'USBデバイスが見つかりませんでした。「別のUSBデバイスを接続して、再度確認してください。」'; + + @override + String get usbErrorPermissionDenied => 'USBへのアクセス許可が拒否されました。'; + + @override + String get usbErrorDeviceMissing => '選択されたUSBデバイスは、もう利用できません。'; + + @override + String get usbErrorInvalidPort => '有効なUSBデバイスを選択してください。'; + + @override + String get usbErrorBusy => '別のUSB接続の要求がすでに処理中です。'; + + @override + String get usbErrorNotConnected => 'USBデバイスは接続されていません。'; + + @override + String get usbErrorOpenFailed => '選択したUSBデバイスを開くことができません。'; + + @override + String get usbErrorConnectFailed => '選択したUSBデバイスへの接続に失敗しました。'; + + @override + String get usbErrorUnsupported => 'このプラットフォームでは、USBシリアル通信はサポートされていません。'; + + @override + String get usbErrorAlreadyActive => 'USB接続はすでに確立されています。'; + + @override + String get usbErrorNoDeviceSelected => 'USBデバイスは選択されていません。'; + + @override + String get usbErrorPortClosed => 'USB接続は確立されていません。'; + + @override + String get usbErrorConnectTimedOut => + '接続がタイムアウトしました。デバイスにUSBコンパニオンファームウェアがインストールされていることを確認してください。'; + + @override + String get usbFallbackDeviceName => 'ウェブシリアルデバイス'; + + @override + String get usbStatus_notConnected => 'USBデバイスを選択する'; + + @override + String get usbStatus_connecting => 'USBデバイスへの接続中...'; + + @override + String get usbStatus_searching => 'USBデバイスを検索中...'; + + @override + String usbConnectionFailed(String error) { + return 'USB接続に失敗しました:$error'; + } + + @override + String get scanner_scanning => 'デバイスをスキャン中...'; + + @override + String get scanner_connecting => '接続中...'; + + @override + String get scanner_disconnecting => '切断...'; + + @override + String get scanner_notConnected => '接続されていない'; + + @override + String scanner_connectedTo(String deviceName) { + return '$deviceName に接続'; + } + + @override + String get scanner_searchingDevices => 'MeshCoreデバイスの検索'; + + @override + String get scanner_tapToScan => 'MeshCore デバイスを検索するには、「スキャン」ボタンをタップしてください。'; + + @override + String scanner_connectionFailed(String error) { + return '接続に失敗しました:$error'; + } + + @override + String get scanner_stop => '停止'; + + @override + String get scanner_scan => 'スキャン'; + + @override + String get scanner_bluetoothOff => 'Bluetooth はオフになっています'; + + @override + String get scanner_bluetoothOffMessage => 'Bluetoothを有効にして、デバイスを検索してください。'; + + @override + String get scanner_chromeRequired => 'Chrome ブラウザが必須です'; + + @override + String get scanner_chromeRequiredMessage => + 'このWebアプリケーションは、Bluetooth機能を利用するために、Google ChromeまたはChromiumベースのブラウザが必要です。'; + + @override + String get scanner_enableBluetooth => 'Bluetoothを有効にする'; + + @override + String get device_quickSwitch => '素早い切り替え'; + + @override + String get device_meshcore => 'メッシュコア'; + + @override + String get settings_title => '設定'; + + @override + String get settings_deviceInfo => 'デバイス情報'; + + @override + String get settings_appSettings => 'アプリ設定'; + + @override + String get settings_appSettingsSubtitle => '通知、メッセージング、および地図の表示設定'; + + @override + String get settings_nodeSettings => 'ノード設定'; + + @override + String get settings_nodeName => 'ノード名'; + + @override + String get settings_nodeNameNotSet => '設定されていない'; + + @override + String get settings_nodeNameHint => 'ノード名を入力してください'; + + @override + String get settings_nodeNameUpdated => '氏名変更'; + + @override + String get settings_radioSettings => 'ラジオ設定'; + + @override + String get settings_radioSettingsSubtitle => '周波数、電力、スプレッドファクター'; + + @override + String get settings_radioSettingsUpdated => 'ラジオの設定が更新されました'; + + @override + String get settings_location => '場所'; + + @override + String get settings_locationSubtitle => 'GPS 座標'; + + @override + String get settings_locationUpdated => '場所とGPS設定が更新されました'; + + @override + String get settings_locationBothRequired => '緯度と経度をそれぞれ入力してください。'; + + @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 => '緯度'; + + @override + String get settings_longitude => '経度'; + + @override + String get settings_contactSettings => '連絡設定'; + + @override + String get settings_contactSettingsSubtitle => '連絡先を追加する設定'; + + @override + String get settings_privacyMode => 'プライバシーモード'; + + @override + String get settings_privacyModeSubtitle => '広告に名前/場所を記載しない'; + + @override + String get settings_privacyModeToggle => + 'プライバシーモードをオンにして、広告に表示される名前や場所を非表示にします。'; + + @override + String get settings_privacyModeEnabled => 'プライバシーモードが有効になっています'; + + @override + String get settings_privacyModeDisabled => 'プライバシーモードは無効化されています'; + + @override + String get settings_privacy => 'プライバシー設定'; + + @override + String get settings_privacySubtitle => '共有する情報の内容を管理する。'; + + @override + String get settings_privacySettingsDescription => + '自分のデバイスが他の人に共有する情報を選択してください。'; + + @override + String get settings_denyAll => 'すべてを否定'; + + @override + String get settings_allowByContact => '連絡先を明示するオプション'; + + @override + String get settings_allowAll => 'すべて許可'; + + @override + String get settings_telemetryBaseMode => 'テレメトリ基本モード'; + + @override + String get settings_telemetryLocationMode => 'テレメトリ位置特定モード'; + + @override + String get settings_telemetryEnvironmentMode => 'テレメトリ環境モード'; + + @override + String get settings_advertLocation => '広告掲載場所'; + + @override + String get settings_advertLocationSubtitle => '広告に場所を記載してください。'; + + @override + String settings_multiAck(String value) { + return '複数のACK:$value'; + } + + @override + String get settings_telemetryModeUpdated => 'テレメトリモードが更新されました'; + + @override + String get settings_actions => '行動'; + + @override + String get settings_sendAdvertisement => '広告を送信する'; + + @override + String get settings_sendAdvertisementSubtitle => '現在、放送での活動'; + + @override + String get settings_advertisementSent => '広告が送信されました'; + + @override + String get settings_syncTime => '同期時間'; + + @override + String get settings_syncTimeSubtitle => 'デバイスの時刻を、携帯電話の時刻に合わせる'; + + @override + String get settings_timeSynchronized => '時間同期'; + + @override + String get settings_refreshContacts => '連絡先を更新する'; + + @override + String get settings_refreshContactsSubtitle => 'デバイスから連絡先リストを再読み込みする'; + + @override + String get settings_rebootDevice => 'デバイスを再起動する'; + + @override + String get settings_rebootDeviceSubtitle => 'MeshCore デバイスを再起動する'; + + @override + String get settings_rebootDeviceConfirm => + '本当にデバイスを再起動したいですか? その場合、接続が切断されます。'; + + @override + String get settings_debug => 'デバッグ'; + + @override + String get settings_bleDebugLog => 'BLE デバッグログ'; + + @override + String get settings_bleDebugLogSubtitle => 'BLEコマンド、応答、および生のデータ'; + + @override + String get settings_appDebugLog => 'アプリケーションのデバッグログ'; + + @override + String get settings_appDebugLogSubtitle => 'アプリケーションのデバッグメッセージ'; + + @override + String get settings_about => '概要'; + + @override + String settings_aboutVersion(String version) { + return 'MeshCore Open $version版'; + } + + @override + String get settings_aboutLegalese => '2026年のMeshCoreオープンソースプロジェクト'; + + @override + String get settings_aboutDescription => + 'MeshCore LoRaメッシュネットワークデバイス用の、オープンソースのFlutterクライアント。'; + + @override + String get settings_aboutOpenMeteoAttribution => + 'LOS 標高データ:Open-Meteo (CC BY 4.0)'; + + @override + String get settings_infoName => '名前'; + + @override + String get settings_infoId => 'ID'; + + @override + String get settings_infoStatus => 'ステータス'; + + @override + String get settings_infoBattery => 'バッテリー'; + + @override + String get settings_infoPublicKey => '公開鍵'; + + @override + String get settings_infoContactsCount => '連絡先数'; + + @override + String get settings_infoChannelCount => 'チャンネル数'; + + @override + String get settings_presets => 'プリセット'; + + @override + String get settings_frequency => '周波数 (MHz)'; + + @override + String get settings_frequencyHelper => '300.0 - 2500.0'; + + @override + String get settings_frequencyInvalid => '無効な周波数 (300-2500 MHz)'; + + @override + String get settings_bandwidth => '帯域幅'; + + @override + String get settings_spreadingFactor => '伝播係数'; + + @override + String get settings_codingRate => 'コーディング速度'; + + @override + String get settings_txPower => 'TX 信号電力 (dBm)'; + + @override + String get settings_txPowerHelper => '0 - 22'; + + @override + String get settings_txPowerInvalid => '無効な送信電力 (0-22 dBm)'; + + @override + String get settings_clientRepeat => 'オフグリッド(電力網から孤立した状態)の繰り返し'; + + @override + String get settings_clientRepeatSubtitle => + 'このデバイスが、他のデバイスに対してメッシュパケットを繰り返し送信できるようにする。'; + + @override + String get settings_clientRepeatFreqWarning => + 'オフグリッドでの再送には、433MHz、869MHz、または918MHzの周波数が必要です。'; + + @override + String settings_error(String message) { + return 'エラー:$message'; + } + + @override + String get appSettings_title => 'アプリ設定'; + + @override + String get appSettings_appearance => '外観'; + + @override + String get appSettings_theme => 'テーマ'; + + @override + String get appSettings_themeSystem => 'システムデフォルト'; + + @override + String get appSettings_themeLight => '光'; + + @override + String get appSettings_themeDark => '暗い'; + + @override + String get appSettings_language => '言語'; + + @override + String get appSettings_languageSystem => 'システムデフォルト'; + + @override + String get appSettings_languageEn => '英語'; + + @override + String get appSettings_languageFr => 'フランス語'; + + @override + String get appSettings_languageEs => 'スペイン語'; + + @override + String get appSettings_languageDe => 'ドイツ語'; + + @override + String get appSettings_languagePl => 'ポーランド語'; + + @override + String get appSettings_languageSl => 'スロベニア語'; + + @override + String get appSettings_languagePt => 'ポルトガル語'; + + @override + String get appSettings_languageIt => 'イタリア語'; + + @override + String get appSettings_languageZh => '中国語'; + + @override + String get appSettings_languageSv => 'スウェーデン語'; + + @override + String get appSettings_languageNl => 'オランダ語'; + + @override + String get appSettings_languageSk => 'スロベニア語'; + + @override + String get appSettings_languageBg => 'ブルガリア語'; + + @override + String get appSettings_languageRu => 'ロシア語'; + + @override + String get appSettings_languageUk => 'ウクライナ語'; + + @override + String get appSettings_enableMessageTracing => 'メッセージ追跡機能を有効にする'; + + @override + String get appSettings_enableMessageTracingSubtitle => + 'メッセージに関する詳細な経路およびタイミングに関するメタデータを表示する'; + + @override + String get appSettings_notifications => '通知'; + + @override + String get appSettings_enableNotifications => '通知を有効にする'; + + @override + String get appSettings_enableNotificationsSubtitle => 'メッセージや広告に関する通知を受け取る'; + + @override + String get appSettings_notificationPermissionDenied => '通知の許可が拒否されました'; + + @override + String get appSettings_notificationsEnabled => '通知機能が有効になっています'; + + @override + String get appSettings_notificationsDisabled => '通知が無効化されています'; + + @override + String get appSettings_messageNotifications => 'メッセージ通知'; + + @override + String get appSettings_messageNotificationsSubtitle => + '新しいメッセージを受信した際に、通知を表示する'; + + @override + String get appSettings_channelMessageNotifications => 'チャネルメッセージの通知'; + + @override + String get appSettings_channelMessageNotificationsSubtitle => + 'チャンネルからのメッセージを受信した際に、通知を表示する'; + + @override + String get appSettings_advertisementNotifications => '広告通知'; + + @override + String get appSettings_advertisementNotificationsSubtitle => + '新しいノードが発見された場合に通知を表示する'; + + @override + String get appSettings_messaging => 'メッセージング'; + + @override + String get appSettings_clearPathOnMaxRetry => 'マックスリトライでの明確な手順'; + + @override + String get appSettings_clearPathOnMaxRetrySubtitle => + '5回送信に失敗した場合、連絡経路をリセットする'; + + @override + String get appSettings_pathsWillBeCleared => '5回失敗した後、経路が再開されます。'; + + @override + String get appSettings_pathsWillNotBeCleared => 'パスは自動で削除されません。'; + + @override + String get appSettings_autoRouteRotation => '自動ルートの切り替え'; + + @override + String get appSettings_autoRouteRotationSubtitle => '最適なルートと、洪水モードを切り替える'; + + @override + String get appSettings_autoRouteRotationEnabled => '自動ルートの切り替え機能が有効になっています'; + + @override + String get appSettings_autoRouteRotationDisabled => '自動ルートの変更機能が無効になっています。'; + + @override + String get appSettings_maxRouteWeight => '最大ルート重量'; + + @override + String get appSettings_maxRouteWeightSubtitle => + 'ある経路が、成功裏に配送された場合に、積み上げられる最大重量'; + + @override + String get appSettings_initialRouteWeight => '初期ルートの重み'; + + @override + String get appSettings_initialRouteWeightSubtitle => '新たに発見された経路の初期重量'; + + @override + String get appSettings_routeWeightSuccessIncrement => '成功時の重み増加'; + + @override + String get appSettings_routeWeightSuccessIncrementSubtitle => + '配送が成功した場合に、経路に追加される重量'; + + @override + String get appSettings_routeWeightFailureDecrement => '失敗時の重み減少'; + + @override + String get appSettings_routeWeightFailureDecrementSubtitle => + '配送に失敗した際に、経路から取り除かれた重量'; + + @override + String get appSettings_maxMessageRetries => '最大メッセージ再試行回数'; + + @override + String get appSettings_maxMessageRetriesSubtitle => + 'メッセージを「失敗」とマークするまでの、再試行回数'; + + @override + String path_routeWeight(String weight, String max) { + return '$weight/$max'; + } + + @override + String get appSettings_battery => 'バッテリー'; + + @override + String get appSettings_batteryChemistry => '電池の化学'; + + @override + String appSettings_batteryChemistryPerDevice(String deviceName) { + return '$deviceName 単位'; + } + + @override + String get appSettings_batteryChemistryConnectFirst => 'デバイスを選択するために接続する'; + + @override + String get appSettings_batteryNmc => '18650型 NMC (3.0-4.2V)'; + + @override + String get appSettings_batteryLifepo4 => 'LiFePO4 (2.6-3.65V)'; + + @override + String get appSettings_batteryLipo => 'LiPo (3.0-4.2V)'; + + @override + String get appSettings_mapDisplay => '地図の表示'; + + @override + String get appSettings_showRepeaters => '繰り返し再生機能'; + + @override + String get appSettings_showRepeatersSubtitle => '地図上にリピーターノードを表示する'; + + @override + String get appSettings_showChatNodes => 'チャットノードの表示'; + + @override + String get appSettings_showChatNodesSubtitle => '地図上にチャットノードを表示する'; + + @override + String get appSettings_showOtherNodes => '他のノードを表示する'; + + @override + String get appSettings_showOtherNodesSubtitle => '地図上に、他のノードの種類を表示する'; + + @override + String get appSettings_timeFilter => '時間フィルター'; + + @override + String get appSettings_timeFilterShowAll => 'すべてのノードを表示する'; + + @override + String appSettings_timeFilterShowLast(int hours) { + return '過去 $hours 時間のノードを表示する'; + } + + @override + String get appSettings_mapTimeFilter => '地図の表示期間を絞り込む'; + + @override + String get appSettings_showNodesDiscoveredWithin => '以下の範囲内で発見されたノードを表示する:'; + + @override + String get appSettings_allTime => 'すべての期間'; + + @override + String get appSettings_lastHour => '直前の'; + + @override + String get appSettings_last6Hours => '過去6時間'; + + @override + String get appSettings_last24Hours => '過去24時間'; + + @override + String get appSettings_lastWeek => '先週'; + + @override + String get appSettings_offlineMapCache => 'オフライン用地図キャッシュ'; + + @override + String get appSettings_unitsTitle => '単位'; + + @override + String get appSettings_unitsMetric => 'メートル (m) / キロメートル (km)'; + + @override + String get appSettings_unitsImperial => '帝国 (フィート / マイル)'; + + @override + String get appSettings_noAreaSelected => '選択されたエリアはありません'; + + @override + String appSettings_areaSelectedZoom(int minZoom, int maxZoom) { + return '選択された範囲(ズームレベル:$minZoom~$maxZoom)'; + } + + @override + String get appSettings_debugCard => 'デバッグ'; + + @override + String get appSettings_appDebugLogging => 'アプリケーションのデバッグ用ログ'; + + @override + String get appSettings_appDebugLoggingSubtitle => + 'ログアプリのデバッグメッセージ(トラブルシューティング用)'; + + @override + String get appSettings_appDebugLoggingEnabled => + 'アプリケーションのデバッグ用ログ機能が有効になっています。'; + + @override + String get appSettings_appDebugLoggingDisabled => + 'アプリケーションのデバッグログが無効化されています。'; + + @override + String get contacts_title => '連絡先'; + + @override + String get contacts_noContacts => '現時点では、連絡先はまだありません。'; + + @override + String get contacts_contactsWillAppear => 'デバイスが広告を行う際に、連絡先が表示されます。'; + + @override + String get contacts_unread => '未読'; + + @override + String get contacts_searchContactsNoNumber => '連絡先を検索...'; + + @override + String contacts_searchContacts(int number, String str) { + return '$number件の$strに関する連絡先を検索...'; + } + + @override + String contacts_searchFavorites(int number, String str) { + return '$number件の$strを検索...'; + } + + @override + String contacts_searchUsers(int number, String str) { + return '$number件の$strに関するユーザーを検索する...'; + } + + @override + String contacts_searchRepeaters(int number, String str) { + return '$number $str までの検索...'; + } + + @override + String contacts_searchRoomServers(int number, String str) { + return '$number $str 部屋のサーバーを検索する...'; + } + + @override + String get contacts_noUnreadContacts => '未読の連絡先はありません'; + + @override + String get contacts_noContactsFound => '連絡先またはグループは見つかりませんでした。'; + + @override + String get contacts_deleteContact => '連絡先を削除'; + + @override + String contacts_removeConfirm(String contactName) { + return '$contactName を連絡先から削除しますか?'; + } + + @override + String get contacts_manageRepeater => 'リピーターの管理'; + + @override + String get contacts_manageRoom => 'ルームサーバーの管理'; + + @override + String get contacts_roomLogin => 'ルームサーバーへのログイン'; + + @override + String get contacts_openChat => '自由な会話'; + + @override + String get contacts_editGroup => '編集グループ'; + + @override + String get contacts_deleteGroup => 'グループを削除'; + + @override + String contacts_deleteGroupConfirm(String groupName) { + return '$groupName を削除しますか?'; + } + + @override + String get contacts_newGroup => '新しいグループ'; + + @override + String get contacts_groupName => 'グループ名'; + + @override + String get contacts_groupNameRequired => 'グループ名が必須です'; + + @override + String get contacts_groupNameReserved => 'このグループ名はすでに使用されています。'; + + @override + String contacts_groupAlreadyExists(String name) { + return 'グループ「$name」はすでに存在しています'; + } + + @override + String get contacts_filterContacts => '連絡先をフィルタリングする…'; + + @override + String get contacts_noContactsMatchFilter => '指定された条件に合致する連絡先は見つかりませんでした。'; + + @override + String get contacts_noMembers => 'メンバーはいない'; + + @override + String get contacts_lastSeenNow => '最近'; + + @override + String contacts_lastSeenMinsAgo(int minutes) { + return '~$minutes 分'; + } + + @override + String get contacts_lastSeenHourAgo => '約1時間'; + + @override + String contacts_lastSeenHoursAgo(int hours) { + return '~ $hours 時間'; + } + + @override + String get contacts_lastSeenDayAgo => '~1日'; + + @override + String contacts_lastSeenDaysAgo(int days) { + return '~$days日間'; + } + + @override + String get contact_info => '連絡先'; + + @override + String get contact_settings => '連絡設定'; + + @override + String get contact_telemetry => 'テレメトリー'; + + @override + String get contact_lastSeen => '最後に確認された場所'; + + @override + String get contact_clearChat => 'チャットのクリア'; + + @override + String get contact_teleBase => 'テレメトリ基地'; + + @override + String get contact_teleBaseSubtitle => 'バッテリー残量と基本的なテレメトリーの共有を許可する'; + + @override + String get contact_teleLoc => 'テレメトリの場所'; + + @override + String get contact_teleLocSubtitle => '位置情報共有を許可する'; + + @override + String get contact_teleEnv => 'テレメトリ環境'; + + @override + String get contact_teleEnvSubtitle => '環境センサーのデータを共有することを許可する'; + + @override + String get channels_title => 'チャンネル'; + + @override + String get channels_noChannelsConfigured => '設定されたチャンネルがありません'; + + @override + String get channels_addPublicChannel => 'パブリックチャンネルを追加する'; + + @override + String get channels_searchChannels => '検索オプション...'; + + @override + String get channels_noChannelsFound => 'チャンネルが見つかりませんでした'; + + @override + String channels_channelIndex(int index) { + return 'チャンネル $index'; + } + + @override + String get channels_hashtagChannel => 'ハッシュタグチャンネル'; + + @override + String get channels_public => '一般の人々'; + + @override + String get channels_private => '個人の'; + + @override + String get channels_publicChannel => '一般チャンネル'; + + @override + String get channels_privateChannel => 'プライベートチャンネル'; + + @override + String get channels_editChannel => 'チャンネルを編集する'; + + @override + String get channels_muteChannel => 'ミュート機能'; + + @override + String get channels_unmuteChannel => 'ミュートを解除する'; + + @override + String get channels_deleteChannel => 'チャンネルを削除する'; + + @override + String channels_deleteChannelConfirm(String name) { + return '$name を削除しますか? これは取り消すことができません。'; + } + + @override + String channels_channelDeleteFailed(String name) { + return 'チャンネル「$name」の削除に失敗しました。'; + } + + @override + String channels_channelDeleted(String name) { + return 'チャンネル「$name」が削除されました'; + } + + @override + String get channels_addChannel => 'チャンネルを追加'; + + @override + String get channels_channelIndexLabel => 'チャンネルインデックス'; + + @override + String get channels_channelName => 'チャンネル名'; + + @override + String get channels_usePublicChannel => 'パブリックチャンネルを使用する'; + + @override + String get channels_standardPublicPsk => '標準的な公用 PSK'; + + @override + String get channels_pskHex => 'PSK (ヘックス)'; + + @override + String get channels_generateRandomPsk => 'ランダムなPSK(正交符号分割変調)を生成する'; + + @override + String get channels_enterChannelName => 'チャンネル名を入力してください'; + + @override + String get channels_pskMustBe32Hex => 'PSKは32桁の16進数で構成されている必要があります。'; + + @override + String channels_channelAdded(String name) { + return 'チャンネル「$name」を追加'; + } + + @override + String channels_editChannelTitle(int index) { + return 'チャンネル $index の編集'; + } + + @override + String get channels_smazCompression => 'SMAZ 圧縮'; + + @override + String channels_channelUpdated(String name) { + return 'チャンネル「$name」が更新されました'; + } + + @override + String get channels_publicChannelAdded => 'パブリックチャンネルが追加されました'; + + @override + String get channels_sortBy => '並び替え'; + + @override + String get channels_sortManual => 'マニュアル'; + + @override + String get channels_sortAZ => 'AからZ'; + + @override + String get channels_sortLatestMessages => '最新のメッセージ'; + + @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 => 'まだメッセージは届いていません'; + + @override + String get chat_sendMessageToStart => '開始するためにメッセージを送信してください'; + + @override + String get chat_originalMessageNotFound => '元のメッセージが見つかりませんでした'; + + @override + String chat_replyingTo(String name) { + return '$name への返信'; + } + + @override + String chat_replyTo(String name) { + return '$nameへの返信'; + } + + @override + String get chat_location => '場所'; + + @override + String chat_sendMessageTo(String contactName) { + return '$contactName へのメッセージを送信する'; + } + + @override + String get chat_typeMessage => 'メッセージを入力してください…'; + + @override + String chat_messageTooLong(int maxBytes) { + return 'メッセージが長すぎる($maxBytes バイトを超える)。'; + } + + @override + String get chat_messageCopied => 'メッセージがコピーされました'; + + @override + String get chat_messageDeleted => 'メッセージは削除されました'; + + @override + String get chat_retryingMessage => '再試行メッセージ'; + + @override + String chat_retryCount(int current, int max) { + return '$current / $max 回目'; + } + + @override + String get chat_sendGif => 'GIFを送信する'; + + @override + String get chat_reply => '返信'; + + @override + String get chat_addReaction => '反応を追加'; + + @override + String get chat_me => '私'; + + @override + String get emojiCategorySmileys => '笑顔の絵文字'; + + @override + String get emojiCategoryGestures => '身振り、動作'; + + @override + String get emojiCategoryHearts => '心'; + + @override + String get emojiCategoryObjects => '対象物'; + + @override + String get gifPicker_title => 'GIF を選択してください'; + + @override + String get gifPicker_searchHint => 'GIFの検索...'; + + @override + String get gifPicker_poweredBy => 'GIPHYによる提供'; + + @override + String get gifPicker_noGifsFound => 'GIF形式のファイルは見つかりませんでした'; + + @override + String get gifPicker_failedLoad => 'GIFファイルの読み込みに失敗しました'; + + @override + String get gifPicker_failedSearch => 'GIFファイルの検索に失敗しました'; + + @override + String get gifPicker_noInternet => 'インターネット接続なし'; + + @override + String get debugLog_appTitle => 'アプリケーションのデバッグログ'; + + @override + String get debugLog_bleTitle => 'BLE デバッグログ'; + + @override + String get debugLog_copyLog => '記録'; + + @override + String get debugLog_clearLog => '詳細なログ'; + + @override + String get debugLog_copied => 'デバッグログをコピー'; + + @override + String get debugLog_bleCopied => 'BLEログのコピー'; + + @override + String get debugLog_noEntries => 'デバッグログはまだ生成されていません'; + + @override + String get debugLog_enableInSettings => 'アプリのデバッグログを有効にするには、設定から操作してください。'; + + @override + String get debugLog_frames => 'フレーム'; + + @override + String get debugLog_rawLogRx => '生のログ-RX'; + + @override + String get debugLog_noBleActivity => '現時点では、BLE関連の活動は行われていません。'; + + @override + String debugFrame_length(int count) { + return 'フレーム長: $count バイト'; + } + + @override + String debugFrame_command(String value) { + return 'コマンド: 0x$value'; + } + + @override + String get debugFrame_textMessageHeader => 'テキストメッセージ用フレーム:'; + + @override + String debugFrame_destinationPubKey(String pubKey) { + return '- 宛先公開鍵: $pubKey'; + } + + @override + String debugFrame_timestamp(int timestamp) { + return '- タイムスタンプ: $timestamp'; + } + + @override + String debugFrame_flags(String value) { + return '- フラグ: 0x$value'; + } + + @override + String debugFrame_textType(int type, String label) { + return '- テキストの種類: $type ($label)'; + } + + @override + String get debugFrame_textTypeCli => 'CLI(コマンドラインインターフェース)'; + + @override + String get debugFrame_textTypePlain => 'シンプルな'; + + @override + String debugFrame_text(String text) { + return '- テキスト:「$text」'; + } + + @override + String get debugFrame_hexDump => 'ヘックスダンプ:'; + + @override + String get chat_pathManagement => '経路管理'; + + @override + String get chat_ShowAllPaths => 'すべての経路を表示'; + + @override + String get chat_routingMode => 'ルーティングモード'; + + @override + String get chat_autoUseSavedPath => '自動 (保存されたパスを使用)'; + + @override + String get chat_forceFloodMode => '強制的に洪水モードを起動'; + + @override + String get chat_recentAckPaths => '最近使用したACKパス(タップして使用):'; + + @override + String get chat_pathHistoryFull => 'パスの履歴は完全です。エントリを削除して、新しいものを追加できます。'; + + @override + String get chat_hopSingular => 'ジャンプ'; + + @override + String get chat_hopPlural => 'ホップ'; + + @override + String chat_hopsCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'ホップ', + one: 'ホップ', + ); + return '$count $_temp0'; + } + + @override + String get chat_successes => '成功事例'; + + @override + String get chat_removePath => 'パスを削除する'; + + @override + String get chat_noPathHistoryYet => 'まだ履歴はありません。\nパスを特定するためにメッセージを送信してください。'; + + @override + String get chat_pathActions => 'パスの操作:'; + + @override + String get chat_setCustomPath => 'カスタムパスを設定'; + + @override + String get chat_setCustomPathSubtitle => '手動で経路を指定する'; + + @override + String get chat_clearPath => '明確な道'; + + @override + String get chat_clearPathSubtitle => '次回送信時に、以前の情報を再取得する'; + + @override + String get chat_pathCleared => '経路が確保されました。次のメッセージでルートを再確認します。'; + + @override + String get chat_floodModeSubtitle => 'アプリのバーにあるルーティング切り替え機能を使用する'; + + @override + String get chat_floodModeEnabled => + '洪水モードが有効になっています。アプリのメニューバーにあるルートアイコンを使用して、モードを切り替えることができます。'; + + @override + String get chat_fullPath => 'フルパス'; + + @override + String get chat_pathDetailsNotAvailable => + '経路の詳細については、まだ情報がありません。「リフレッシュ」ボタンを押して、再度お試しください。'; + + @override + String chat_pathSetHops(int hopCount, String status) { + String _temp0 = intl.Intl.pluralLogic( + hopCount, + locale: localeName, + other: 'hops', + one: 'hop', + ); + return 'Path set: $hopCount $_temp0 - $status'; + } + + @override + String get chat_pathSavedLocally => 'ローカルで保存。同期のために接続する。'; + + @override + String get chat_pathDeviceConfirmed => 'デバイスの確認済み。'; + + @override + String get chat_pathDeviceNotConfirmed => 'デバイスの確認はまだできていません。'; + + @override + String get chat_type => '種類'; + + @override + String get chat_path => '道'; + + @override + String get chat_publicKey => '公開鍵'; + + @override + String get chat_compressOutgoingMessages => '送信されるメッセージを圧縮する'; + + @override + String get chat_floodForced => '洪水(強制的な)'; + + @override + String get chat_directForced => '直接的な(強制的な)'; + + @override + String chat_hopsForced(int count) { + return '$count 本のホップ(強制的に採取)'; + } + + @override + String get chat_floodAuto => '洪水 (自動)'; + + @override + String get chat_direct => '直接'; + + @override + String get chat_poiShared => '共有されたPOI'; + + @override + String chat_unread(int count) { + 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 => 'ノードマップ'; + + @override + String get map_lineOfSight => '視界'; + + @override + String get map_losScreenTitle => '視界'; + + @override + String get map_noNodesWithLocation => '位置情報データを持つノードは存在しません'; + + @override + String get map_nodesNeedGps => 'ノードは、地図上に表示されるために、GPS座標を共有する必要があります。'; + + @override + String map_nodesCount(int count) { + return 'ノード:$count'; + } + + @override + String map_pinsCount(int count) { + return 'ピン:$count個'; + } + + @override + String get map_chat => 'チャット'; + + @override + String get map_repeater => '繰り返し送信装置'; + + @override + String get map_room => '部屋'; + + @override + String get map_sensor => 'センサー'; + + @override + String get map_pinDm => 'ピン(DM)'; + + @override + String get map_pinPrivate => 'プライベート(非公開)'; + + @override + String get map_pinPublic => '公開 (一般公開)'; + + @override + String get map_lastSeen => '最後に確認された場所'; + + @override + String get map_disconnectConfirm => '本当にこのデバイスとの接続を解除したいですか?'; + + @override + String get map_from => '~から'; + + @override + String get map_source => '出典'; + + @override + String get map_flags => '旗'; + + @override + String get map_shareMarkerHere => 'この場所でシェア'; + + @override + String get map_setAsMyLocation => '現在地として設定'; + + @override + String get map_pinLabel => 'ピンラベル'; + + @override + String get map_label => 'ラベル'; + + @override + String get map_pointOfInterest => '注目すべき点'; + + @override + String get map_sendToContact => '連絡先へ送信'; + + @override + String get map_sendToChannel => '特定のチャンネルに送信する'; + + @override + String get map_noChannelsAvailable => '利用可能なチャンネルはありません'; + + @override + String get map_publicLocationShare => '公共スペースの共有'; + + @override + String map_publicLocationShareConfirm(String channelLabel) { + return '現在、$channelLabel で位置情報を共有する準備をしています。このチャンネルは公開されており、PSK を持つ誰でも閲覧できます。'; + } + + @override + String get map_connectToShareMarkers => '他のデバイスと接続して、マーカーを共有する'; + + @override + String get map_filterNodes => 'フィルタノード'; + + @override + String get map_nodeTypes => 'ノードの種類'; + + @override + String get map_chatNodes => 'チャットノード'; + + @override + String get map_repeaters => '繰り返し送信装置'; + + @override + String get map_otherNodes => 'その他のノード'; + + @override + String get map_showOverlaps => 'リピーターキーの重複'; + + @override + String get map_keyPrefix => '主要なプレフィックス'; + + @override + String get map_filterByKeyPrefix => '主要なプレフィックスでフィルタリングする'; + + @override + String get map_publicKeyPrefix => '公開鍵のプレフィックス'; + + @override + String get map_markers => 'マーカー'; + + @override + String get map_showSharedMarkers => '共有のマーカーを表示する'; + + @override + String get map_showGuessedLocations => '推測されたノードの位置を表示する'; + + @override + String get map_showDiscoveryContacts => 'Discovery社の連絡先を表示する'; + + @override + String get map_guessedLocation => '推測された場所'; + + @override + String get map_lastSeenTime => '最後に確認された時間'; + + @override + String get map_sharedPin => '共有パスワード'; + + @override + String get map_joinRoom => '部屋に参加する'; + + @override + String get map_manageRepeater => 'リピーターの管理'; + + @override + String get map_tapToAdd => 'ノードをクリックして、パスに追加します。'; + + @override + String get map_runTrace => 'パスの追跡を実行'; + + @override + String get map_runTraceWithReturnPath => '元の経路に戻る。'; + + @override + String get map_removeLast => '最後のものを削除'; + + @override + String get map_pathTraceCancelled => 'パスの追跡は中止。'; + + @override + String get mapCache_title => 'オフライン用地図キャッシュ'; + + @override + String get mapCache_selectAreaFirst => '最初にキャッシュする領域を選択してください'; + + @override + String get mapCache_noTilesToDownload => 'この地域にはダウンロードできるタイルは存在しません。'; + + @override + String get mapCache_downloadTilesTitle => 'タイルをダウンロードする'; + + @override + String mapCache_downloadTilesPrompt(int count) { + return 'オフラインでの使用のために、$count個のタイルをダウンロードしますか?'; + } + + @override + String get mapCache_downloadAction => 'ダウンロード'; + + @override + String mapCache_cachedTiles(int count) { + return '$count 個のタイルをキャッシュ'; + } + + @override + String mapCache_cachedTilesWithFailed(int downloaded, int failed) { + return 'Cached $downloaded tiles ($failed failed)'; + } + + @override + String get mapCache_clearOfflineCacheTitle => 'オフラインキャッシュをクリアする'; + + @override + String get mapCache_clearOfflineCachePrompt => 'キャッシュされた地図のタイルをすべて削除しますか?'; + + @override + String get mapCache_offlineCacheCleared => 'オフラインキャッシュをクリア'; + + @override + String get mapCache_noAreaSelected => '選択されたエリアはありません'; + + @override + String get mapCache_cacheArea => 'キャッシュエリア'; + + @override + String get mapCache_useCurrentView => '現在表示されている内容を保持する'; + + @override + String get mapCache_zoomRange => 'ズーム範囲'; + + @override + String mapCache_estimatedTiles(int count) { + return '推定されるタイル数: $count'; + } + + @override + String mapCache_downloadedTiles(int completed, int total) { + return 'Downloaded $completed / $total'; + } + + @override + String get mapCache_downloadTilesButton => 'タイルをダウンロードする'; + + @override + String get mapCache_clearCacheButton => 'キャッシュをクリアする'; + + @override + String mapCache_failedDownloads(int count) { + return '失敗したダウンロード: $count'; + } + + @override + String mapCache_boundsLabel( + String north, + String south, + String east, + String west, + ) { + return 'N $north, S $south, E $east, W $west'; + } + + @override + String get time_justNow => 'まさに今'; + + @override + String time_minutesAgo(int minutes) { + return '$minutes分前'; + } + + @override + String time_hoursAgo(int hours) { + return '$hours時間前'; + } + + @override + String time_daysAgo(int days) { + return '$days日前'; + } + + @override + String get time_hour => '1時間'; + + @override + String get time_hours => '時間'; + + @override + String get time_day => '一日'; + + @override + String get time_days => '日'; + + @override + String get time_week => '1週間'; + + @override + String get time_weeks => '週'; + + @override + String get time_month => '月'; + + @override + String get time_months => '月'; + + @override + String get time_minutes => '分'; + + @override + String get time_allTime => '全期間'; + + @override + String get dialog_disconnect => '切断する'; + + @override + String get dialog_disconnectConfirm => '本当にこのデバイスとの接続を解除したいですか?'; + + @override + String get login_repeaterLogin => '再ログイン'; + + @override + String get login_roomLogin => 'ルームサーバーへのログイン'; + + @override + String get login_password => 'パスワード'; + + @override + String get login_enterPassword => 'パスワードを入力してください'; + + @override + String get login_savePassword => 'パスワードを保存する'; + + @override + String get login_savePasswordSubtitle => 'パスワードは、このデバイスに安全に保存されます。'; + + @override + String get login_repeaterDescription => + '設定やステータスにアクセスするために、リピーターのパスワードを入力してください。'; + + @override + String get login_roomDescription => '設定やステータスへのアクセスには、部屋のパスワードを入力してください。'; + + @override + String get login_routing => '経路設定'; + + @override + String get login_routingMode => 'ルーティングモード'; + + @override + String get login_autoUseSavedPath => '自動 (保存されたパスを使用)'; + + @override + String get login_forceFloodMode => '強制的に洪水モードを起動'; + + @override + String get login_managePaths => 'パスの管理'; + + @override + String get login_login => 'ログイン'; + + @override + String login_attempt(int current, int max) { + return '試行回数:$current/$max'; + } + + @override + String login_failed(String error) { + return 'ログインに失敗しました:$error'; + } + + @override + String get login_failedMessage => + 'ログインに失敗しました。パスワードが間違っているか、または接続が確立されていません。'; + + @override + String get common_reload => '再読み込み'; + + @override + String get common_clear => '明確'; + + @override + String path_currentPath(String path) { + return '現在のパス: $path'; + } + + @override + String path_usingHopsPath(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'ホップ', + one: 'ホップ', + ); + return '$count $_temp0のパスを使用'; + } + + @override + String get path_enterCustomPath => 'カスタムパスを入力'; + + @override + String get path_currentPathLabel => '現在の経路'; + + @override + String get path_hexPrefixInstructions => + '各ホップに対して、2文字の16進数プレフィックスをカンマで区切って入力してください。'; + + @override + String get path_hexPrefixExample => '例:A1, F2, 3C (各ノードは、自身の公開鍵の最初のバイトを使用)'; + + @override + String get path_labelHexPrefixes => 'パス (ヘックスプレフィックス)'; + + @override + String get path_helperMaxHops => + '最大64個のホップ。各プレフィックスは2つの16進数文字(1バイト)で構成されています。'; + + @override + String get path_selectFromContacts => 'または、連絡先リストから選択してください:'; + + @override + String get path_noRepeatersFound => '繰り返し機能やルームサーバーは見つかりませんでした。'; + + @override + String get path_customPathsRequire => 'カスタムパスには、メッセージを中継できる中間地点が必要です。'; + + @override + String path_invalidHexPrefixes(String prefixes) { + return '無効な16進数プレフィックス: $prefixes'; + } + + @override + String get path_tooLong => '経路が長すぎる。最大64回のジャンプのみ許可。'; + + @override + String get path_setPath => 'パスを設定'; + + @override + String get repeater_management => 'リピーター管理'; + + @override + String get room_management => 'ルームサーバーの管理'; + + @override + String get repeater_managementTools => '管理ツール'; + + @override + String get repeater_status => 'ステータス'; + + @override + String get repeater_statusSubtitle => 'リピーターの状態、統計情報、および隣接するネットワークの情報を表示する'; + + @override + String get repeater_telemetry => 'テレメトリー'; + + @override + String get repeater_telemetrySubtitle => 'センサーおよびシステムの状態に関するテレメトリの表示'; + + @override + String get repeater_cli => 'CLI(コマンドラインインターフェース)'; + + @override + String get repeater_cliSubtitle => 'リピーターへのコマンドを送信する'; + + @override + String get repeater_neighbors => '近隣住民'; + + @override + String get repeater_neighborsSubtitle => 'ゼロホップの隣接ノードを表示する。'; + + @override + String get repeater_settings => '設定'; + + @override + String get repeater_settingsSubtitle => 'リピーターのパラメータを設定する'; + + @override + String get repeater_statusTitle => '再送ステータス'; + + @override + String get repeater_routingMode => 'ルーティングモード'; + + @override + String get repeater_autoUseSavedPath => '自動 (保存されたパスを使用)'; + + @override + String get repeater_forceFloodMode => '強制的に洪水モードを起動'; + + @override + String get repeater_pathManagement => '経路管理'; + + @override + String get repeater_refresh => 'リフレッシュ'; + + @override + String get repeater_statusRequestTimeout => 'ステータス情報の取得に失敗しました。'; + + @override + String repeater_errorLoadingStatus(String error) { + return 'ステータス読み込みエラー: $error'; + } + + @override + String get repeater_systemInformation => 'システム情報'; + + @override + String get repeater_battery => 'バッテリー'; + + @override + String get repeater_clockAtLogin => 'ログイン時の時刻表示'; + + @override + String get repeater_uptime => '稼働率'; + + @override + String get repeater_queueLength => '待ち行列の長さ'; + + @override + String get repeater_debugFlags => 'デバッグフラグ'; + + @override + String get repeater_radioStatistics => 'ラジオに関する統計'; + + @override + String get repeater_lastRssi => '最後のRSSI'; + + @override + String get repeater_lastSnr => '最後のSNR'; + + @override + String get repeater_noiseFloor => 'ノイズレベル'; + + @override + String get repeater_txAirtime => 'TXの放送時間'; + + @override + String get repeater_rxAirtime => 'RX 空き時間'; + + @override + String get repeater_packetStatistics => 'パケット統計'; + + @override + String get repeater_sent => '送信'; + + @override + String get repeater_received => '受領'; + + @override + String get repeater_duplicates => '重複'; + + @override + String repeater_daysHoursMinsSecs( + int days, + int hours, + int minutes, + int seconds, + ) { + return '$days日 $hours時間 $minutes分 $seconds秒'; + } + + @override + String repeater_packetTxTotal(int total, String flood, String direct) { + return '合計: $total, 洪水: $flood, 直接: $direct'; + } + + @override + String repeater_packetRxTotal(int total, String flood, String direct) { + return '合計: $total, 洪水: $flood, 直接: $direct'; + } + + @override + String repeater_duplicatesFloodDirect(String flood, String direct) { + return '$flood: $flood, 直接: $direct'; + } + + @override + String repeater_duplicatesTotal(int total) { + return '合計: $total'; + } + + @override + String get repeater_settingsTitle => 'リピーター設定'; + + @override + String get repeater_basicSettings => '基本設定'; + + @override + String get repeater_repeaterName => '送信装置名'; + + @override + String get repeater_repeaterNameHelper => 'このリピーターの名前'; + + @override + String get repeater_adminPassword => '管理者パスワード'; + + @override + String get repeater_adminPasswordHelper => '完全アクセス権のパスワード'; + + @override + String get repeater_guestPassword => 'ゲスト用のパスワード'; + + @override + String get repeater_guestPasswordHelper => '読み取り専用アクセス用のパスワード'; + + @override + String get repeater_radioSettings => 'ラジオ設定'; + + @override + String get repeater_frequencyMhz => '周波数 (MHz)'; + + @override + String get repeater_frequencyHelper => '300~2500 MHz'; + + @override + String get repeater_txPower => 'TXパワー'; + + @override + String get repeater_txPowerHelper => '-30~-10 dBm'; + + @override + String get repeater_bandwidth => '帯域幅'; + + @override + String get repeater_spreadingFactor => '伝播係数'; + + @override + String get repeater_codingRate => 'コーディング速度'; + + @override + String get repeater_locationSettings => '場所設定'; + + @override + String get repeater_latitude => '緯度'; + + @override + String get repeater_latitudeHelper => '度分表記(例:37.7749)'; + + @override + String get repeater_longitude => '経度'; + + @override + String get repeater_longitudeHelper => '度分表記(例:-122.4194)'; + + @override + String get repeater_features => '特徴'; + + @override + String get repeater_packetForwarding => 'パケット転送'; + + @override + String get repeater_packetForwardingSubtitle => 'リピーターがパケットを転送できるように設定する'; + + @override + String get repeater_guestAccess => 'ゲストへのアクセス'; + + @override + String get repeater_guestAccessSubtitle => 'ゲストへの読み取り専用アクセスを許可する'; + + @override + String get repeater_privacyMode => 'プライバシーモード'; + + @override + String get repeater_privacyModeSubtitle => '広告に名前/場所を記載しない'; + + @override + String get repeater_advertisementSettings => '広告設定'; + + @override + String get repeater_localAdvertInterval => '地域広告掲載期間'; + + @override + String repeater_localAdvertIntervalMinutes(int minutes) { + return '$minutes 分'; + } + + @override + String get repeater_floodAdvertInterval => '洪水に関する広告の表示間隔'; + + @override + String repeater_floodAdvertIntervalHours(int hours) { + return '$hours 時間'; + } + + @override + String get repeater_encryptedAdvertInterval => '暗号化された広告表示間'; + + @override + String get repeater_dangerZone => '危険区域'; + + @override + String get repeater_rebootRepeater => 'リピーターを再起動する'; + + @override + String get repeater_rebootRepeaterSubtitle => 'リピーターデバイスを再起動する'; + + @override + String get repeater_rebootRepeaterConfirm => '本当にこのリピーターを再起動したいですか?'; + + @override + String get repeater_regenerateIdentityKey => 'IDキーの再生成'; + + @override + String get repeater_regenerateIdentityKeySubtitle => '新しい公開鍵/秘密鍵のペアを生成する'; + + @override + String get repeater_regenerateIdentityKeyConfirm => + 'これにより、リピーターには新しい識別情報が割り当てられます。続行しますか?'; + + @override + String get repeater_eraseFileSystem => 'ファイルシステムを削除する'; + + @override + String get repeater_eraseFileSystemSubtitle => 'リピーターファイルシステムをフォーマットする'; + + @override + String get repeater_eraseFileSystemConfirm => + '警告:この操作により、リピーター内のすべてのデータが消去されます。この操作は元に戻すことができません!'; + + @override + String get repeater_eraseSerialOnly => 'Erase機能は、シリアルコンソール経由でのみ利用可能です。'; + + @override + String repeater_commandSent(String command) { + return '送信されたコマンド: $command'; + } + + @override + String repeater_errorSendingCommand(String error) { + return 'コマンド送信エラー:$error'; + } + + @override + String get repeater_confirm => '確認'; + + @override + String get repeater_settingsSaved => '設定が正常に保存されました'; + + @override + String repeater_errorSavingSettings(String error) { + return '設定の保存に失敗しました:$error'; + } + + @override + String get repeater_refreshBasicSettings => '基本設定をリセットする'; + + @override + String get repeater_refreshRadioSettings => 'ラジオ設定をリセットする'; + + @override + String get repeater_refreshTxPower => 'TX の電力レベルをリセットする'; + + @override + String get repeater_refreshLocationSettings => '場所設定をリセットする'; + + @override + String get repeater_refreshPacketForwarding => 'パケット転送の刷新'; + + @override + String get repeater_refreshGuestAccess => 'ゲストへのアクセスをリフレッシュする'; + + @override + String get repeater_refreshPrivacyMode => 'プライバシーモードをリセットする'; + + @override + String get repeater_refreshAdvertisementSettings => '広告設定のリセット'; + + @override + String repeater_refreshed(String label) { + return '$label が更新されました'; + } + + @override + String repeater_errorRefreshing(String label) { + return '$label の更新に失敗しました'; + } + + @override + String get repeater_cliTitle => 'リピーターのコマンドラインインターフェース'; + + @override + String get repeater_debugNextCommand => '次のコマンドのデバッグ'; + + @override + String get repeater_commandHelp => 'コマンドヘルプ'; + + @override + String get repeater_clearHistory => '明確な歴史'; + + @override + String get repeater_noCommandsSent => 'まだコマンドは送信されていません'; + + @override + String get repeater_typeCommandOrUseQuick => + '以下のコマンドを入力するか、クイックコマンドを使用してください。'; + + @override + String get repeater_enterCommandHint => 'コマンドを入力してください...'; + + @override + String get repeater_previousCommand => '直前の指示'; + + @override + String get repeater_nextCommand => '次の指示'; + + @override + String get repeater_enterCommandFirst => 'まず、コマンドを入力してください。'; + + @override + String get repeater_cliCommandFrameTitle => 'CLI コマンドフレーム'; + + @override + String repeater_cliCommandError(String error) { + return 'エラー:$error'; + } + + @override + String get repeater_cliQuickGetName => '名前を取得する'; + + @override + String get repeater_cliQuickGetRadio => 'ラジオを聴く'; + + @override + String get repeater_cliQuickGetTx => 'TXを入手する'; + + @override + String get repeater_cliQuickNeighbors => '近隣住民'; + + @override + String get repeater_cliQuickVersion => 'バージョン'; + + @override + String get repeater_cliQuickAdvertise => '広告'; + + @override + String get repeater_cliQuickClock => '時計'; + + @override + String get repeater_cliHelpAdvert => '広告用資料を送る'; + + @override + String get repeater_cliHelpReboot => + 'デバイスを再起動します。(注:通常は「タイムアウト」が表示されますが、これは正常です)'; + + @override + String get repeater_cliHelpClock => '各デバイスの時計で現在の時刻を表示します。'; + + @override + String get repeater_cliHelpPassword => 'デバイス用の新しい管理者パスワードを設定します。'; + + @override + String get repeater_cliHelpVersion => 'デバイスのバージョンとファームウェアのビルド日を表示します。'; + + @override + String get repeater_cliHelpClearStats => 'さまざまな統計カウンターをゼロにリセットする。'; + + @override + String get repeater_cliHelpSetAf => '空き時間係数を設定します。'; + + @override + String get repeater_cliHelpSetTx => 'LoRaの送信電力をdBmで設定します。(設定変更後、再起動が必要です)'; + + @override + String get repeater_cliHelpSetRepeat => 'このノードに対するリピーターの役割を有効化または無効化します。'; + + @override + String get repeater_cliHelpSetAllowReadOnly => + '(ルームサーバー設定)「オン」に設定した場合、空白のパスワードでのログインは可能ですが、ルームへの投稿はできません。(閲覧のみ)'; + + @override + String get repeater_cliHelpSetFloodMax => + 'インバウンドフラッパケットの最大ホップ数を設定します(最大値を超えた場合、パケットは転送されません)。'; + + @override + String get repeater_cliHelpSetIntThresh => + '干渉閾値を設定します(dB単位)。デフォルト値は14です。0に設定すると、チャンネル間の干渉を検出する機能を無効にします。'; + + @override + String get repeater_cliHelpSetAgcResetInterval => + 'オートゲインコントローラーのリセット間隔を設定します。 0 に設定すると無効化されます。'; + + @override + String get repeater_cliHelpSetMultiAcks => '「ダブルACK」機能の有効化または無効化を可能にします。'; + + @override + String get repeater_cliHelpSetAdvertInterval => + 'ローカル(ホップなし)の広告パケットを送信する間隔を分単位で設定します。 0 に設定すると、機能を無効にします。'; + + @override + String get repeater_cliHelpSetFloodAdvertInterval => + '洪水広告の送信間隔を時間単位で設定します。0に設定すると、送信を停止します。'; + + @override + String get repeater_cliHelpSetGuestPassword => + 'ゲストのパスワードを設定/更新します。(繰り返し利用の場合、ゲストのログインは「統計情報を取得」のリクエストを送信できます)'; + + @override + String get repeater_cliHelpSetName => '広告の名前を設定します。'; + + @override + String get repeater_cliHelpSetLat => '広告表示の地図の緯度を設定します。(度分秒表記)'; + + @override + String get repeater_cliHelpSetLon => '広告表示の地図の経度を設定します。(度数、分)'; + + @override + String get repeater_cliHelpSetRadio => + '完全に新しいラジオパラメータを設定し、設定として保存します。適用するには、「再起動」コマンドが必要です。'; + + @override + String get repeater_cliHelpSetRxDelay => + '(実験用)遅延時間を設定するためのベース(1以上の値に設定する必要)\n受信パケットに対して、信号強度/スコアに基づいてわずかな遅延を適用します。 0に設定すると無効化されます。'; + + @override + String get repeater_cliHelpSetTxDelay => + '時間経過に応じた「フラッシュモード」パケットの送信遅延を設定します。この遅延は、ランダムなスロットシステムと組み合わせて使用され、パケットの衝突を減らすことを目的としています。'; + + @override + String get repeater_cliHelpSetDirectTxDelay => + 'txdelayと同様ですが、ダイレクトモードのパケット転送にランダムな遅延を適用する場合に使用します。'; + + @override + String get repeater_cliHelpSetBridgeEnabled => 'ブリッジを有効化/無効化'; + + @override + String get repeater_cliHelpSetBridgeDelay => 'パケットを再送信する前に、遅延を設定する。'; + + @override + String get repeater_cliHelpSetBridgeSource => + '橋が受信したパケットを再送信するか、送信したパケットを再送信するかどうかを選択してください。'; + + @override + String get repeater_cliHelpSetBridgeBaud => + 'RS232 橋渡しに使用するシリアルリンクのボーレートを設定する。'; + + @override + String get repeater_cliHelpSetBridgeSecret => 'ESPNow 橋の秘密設定'; + + @override + String get repeater_cliHelpSetAdcMultiplier => + '特定のボードでのみサポートされている、報告されるバッテリー電圧を調整するためのカスタムファクタを設定できます。'; + + @override + String get repeater_cliHelpTempRadio => + '指定された時間(分単位)に対して、一時的にラジオパラメータを設定し、その後元のラジオパラメータに戻します。(設定を保存しません)。'; + + @override + String get repeater_cliHelpSetPerm => + 'ACL を変更します。「permissions」が 0 の場合、対応するエントリ(pubkey のプレフィックスで識別)を削除します。pubkey-hex が有効な長さで、かつ ACL に現在存在しない場合に、新しいエントリを追加します。pubkey のプレフィックスと一致するエントリを更新します。権限ビットはファームウェアの役割によって異なり、下位 2 ビットは以下のとおりです:0 (ゲスト)、1 (読み取り専用)、2 (読み書き)、3 (管理者)'; + + @override + String get repeater_cliHelpGetBridgeType => 'ブリッジ機能なし、RS232、ESPNow'; + + @override + String get repeater_cliHelpLogStart => 'パケットのログ記録を開始し、ファイルシステムに保存する。'; + + @override + String get repeater_cliHelpLogStop => 'ファイルシステムへのパケットログの記録を停止する。'; + + @override + String get repeater_cliHelpLogErase => 'ファイルシステムからパケットログを削除する。'; + + @override + String get repeater_cliHelpNeighbors => + 'ゼロホップ広告を通じて受信した他のリピーターノードの一覧を表示します。各行は、IDプレフィックス(16進数)、タイムスタンプ、SNR(シグナル強度)の情報を4つ含みます。'; + + @override + String get repeater_cliHelpNeighborRemove => + '隣接リストから、最初に一致するエントリ(pubkeyプレフィックス(16進数)で特定)を削除します。'; + + @override + String get repeater_cliHelpRegion => + '(特定のシリーズのみ)定義されたすべての地域と、現在の洪水許可状況を一覧表示します。'; + + @override + String get repeater_cliHelpRegionLoad => + '注:これは特殊な複数コマンドの呼び出しです。その後の各コマンドは、地域名であり(スペースを使用して親階層を示し、少なくとも1つのスペースが必要です)、空行/コマンドで終了します。'; + + @override + String get repeater_cliHelpRegionGet => + '指定された名前のプレフィックスを持つ地域を検索します(または、グローバルな範囲の場合は「*」)。結果として、「region-name (parent-name) \'F\'」と返答します。'; + + @override + String get repeater_cliHelpRegionPut => '指定された名前で、領域の定義を追加または更新します。'; + + @override + String get repeater_cliHelpRegionRemove => + '指定された名前を持つ領域の定義を削除します。(正確に一致している必要があり、子領域は存在してはなりません)'; + + @override + String get repeater_cliHelpRegionAllowf => + '指定された領域に対して、「洪水」アクセス許可を設定します。 (グローバル/従来のスコープには「*」を使用)'; + + @override + String get repeater_cliHelpRegionDenyf => + '指定された領域における「FLOOD」権限を削除します。(注:現時点では、グローバル/従来の範囲での使用は推奨されません!)'; + + @override + String get repeater_cliHelpRegionHome => + '現在の「ホーム」地域に返信します。(まだ適用されていない、将来利用を予定)'; + + @override + String get repeater_cliHelpRegionHomeSet => '「ホーム」地域を設定します。'; + + @override + String get repeater_cliHelpRegionSave => '領域リスト/マップをストレージに保存する。'; + + @override + String get repeater_cliHelpGps => + 'GPSの状態を表示します。GPSがオフの場合、「オフ」と表示します。オンの場合、「オン」、「ステータス」、「位置情報」、「衛星数」と表示します。'; + + @override + String get repeater_cliHelpGpsOnOff => 'GPS の電源状態を切り替えます。'; + + @override + String get repeater_cliHelpGpsSync => 'ノードの時刻をGPSクロックと同期する。'; + + @override + String get repeater_cliHelpGpsSetLoc => 'ノードの位置をGPS座標に設定し、設定を保存する。'; + + @override + String get repeater_cliHelpGpsAdvert => + 'ノードの広告設定における場所情報の指定:\n- none: 広告に場所情報を含まない\n- share: GPS位置情報を共有 (SensorManagerから取得)\n- prefs: プリファレンスに保存された場所情報を広告'; + + @override + String get repeater_cliHelpGpsAdvertSet => '場所に関する広告設定を行います。'; + + @override + String get repeater_commandsListTitle => 'コマンド一覧'; + + @override + String get repeater_commandsListNote => + '注:さまざまな「set ...」コマンドには、「get ...」コマンドも存在します。'; + + @override + String get repeater_general => '一般的な'; + + @override + String get repeater_settingsCategory => '設定'; + + @override + String get repeater_bridge => '橋'; + + @override + String get repeater_logging => 'ログ記録'; + + @override + String get repeater_neighborsRepeaterOnly => '近隣住民(リピーターのみ)'; + + @override + String get repeater_regionManagementRepeaterOnly => '地域管理(ブロードキャスト用のみ)'; + + @override + String get repeater_regionNote => '地域レベルでの管理のため、地域定義と権限の管理を行うための機能が導入されました。'; + + @override + String get repeater_gpsManagement => 'GPS管理'; + + @override + String get repeater_gpsNote => 'GPSコマンドは、位置情報に関連するタスクを管理するために導入されました。'; + + @override + String get telemetry_receivedData => '受信したテレメトリーデータ'; + + @override + String get telemetry_requestTimeout => 'テレメトリの要求タイムアウトしました。'; + + @override + String telemetry_errorLoading(String error) { + return 'テレメトリの読み込みに失敗しました: $error'; + } + + @override + String get telemetry_noData => 'テレメトリデータは利用できません。'; + + @override + String telemetry_channelTitle(int channel) { + return 'チャンネル $channel'; + } + + @override + String get telemetry_batteryLabel => 'バッテリー'; + + @override + String get telemetry_voltageLabel => '電圧'; + + @override + String get telemetry_mcuTemperatureLabel => 'MCU の温度'; + + @override + String get telemetry_temperatureLabel => '温度'; + + @override + String get telemetry_currentLabel => '現在'; + + @override + String telemetry_batteryValue(int percent, String volts) { + return '$percent% / ${volts}V'; + } + + @override + String telemetry_voltageValue(String volts) { + return '${volts}V'; + } + + @override + String telemetry_currentValue(String amps) { + return '${amps}A'; + } + + @override + String telemetry_temperatureValue(String celsius, String fahrenheit) { + return '$celsius℃ / $fahrenheit°F'; + } + + @override + String get neighbors_receivedData => '近隣住民のデータを受信'; + + @override + String get neighbors_requestTimedOut => '近隣住民からの要望:時間制限を設けてください。'; + + @override + String neighbors_errorLoading(String error) { + return '近隣情報の読み込みに失敗: $error'; + } + + @override + String get neighbors_repeatersNeighbors => '繰り返し送信する、近隣'; + + @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 => 'パケットパス'; + + @override + String get channelPath_viewMap => '地図を表示する'; + + @override + String get channelPath_otherObservedPaths => '観察されたその他の経路'; + + @override + String get channelPath_repeaterHops => 'ホップの繰り返し'; + + @override + String get channelPath_noHopDetails => 'このパッケージに関する詳細な情報は提供されていません。'; + + @override + String get channelPath_messageDetails => 'メッセージの詳細'; + + @override + String get channelPath_senderLabel => '送信者'; + + @override + String get channelPath_timeLabel => '時間'; + + @override + String get channelPath_repeatsLabel => '繰り返し'; + + @override + String channelPath_pathLabel(int index) { + return '$index 番目の経路'; + } + + @override + String get channelPath_observedLabel => '観察'; + + @override + String channelPath_observedPathTitle(int index, String hops) { + return '観察された経路 $index • $hops'; + } + + @override + String get channelPath_noLocationData => '場所に関するデータはありません'; + + @override + String channelPath_timeWithDate(int day, int month, String time) { + return '$day/$month $time'; + } + + @override + String channelPath_timeOnly(String time) { + return '$time'; + } + + @override + String get channelPath_unknownPath => '不明'; + + @override + String get channelPath_floodPath => '洪水'; + + @override + String get channelPath_directPath => '直接'; + + @override + String channelPath_observedZeroOf(int total) { + return '$total個のホップ'; + } + + @override + String channelPath_observedSomeOf(int observed, int total) { + return '$observed of $total hops'; + } + + @override + String get channelPath_mapTitle => '経路図'; + + @override + String get channelPath_noRepeaterLocations => 'この経路には、中継装置の設置場所がありません。'; + + @override + String channelPath_primaryPath(int index) { + return '$index番目の経路(主要経路)'; + } + + @override + String get channelPath_pathLabelTitle => '道'; + + @override + String get channelPath_observedPathHeader => '観察された経路'; + + @override + String channelPath_selectedPathLabel(String label, String prefixes) { + return '$label • $prefixes'; + } + + @override + String get channelPath_noHopDetailsAvailable => 'このパッケージに関する詳細な配送情報は利用できません。'; + + @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 => 'フィルタリングと並べ替え'; + + @override + String get listFilter_sortBy => '並び替え'; + + @override + String get listFilter_latestMessages => '最新のメッセージ'; + + @override + String get listFilter_heardRecently => '最近、聞いた'; + + @override + String get listFilter_az => 'AからZ'; + + @override + String get listFilter_filters => 'フィルター'; + + @override + String get listFilter_all => 'すべて'; + + @override + String get listFilter_favorites => 'お気に入り'; + + @override + String get listFilter_addToFavorites => 'お気に入りに追加'; + + @override + String get listFilter_removeFromFavorites => 'お気に入りから削除'; + + @override + String get listFilter_users => '利用者'; + + @override + String get listFilter_repeaters => '繰り返し送信装置'; + + @override + String get listFilter_roomServers => 'ルーム用サーバー'; + + @override + String get listFilter_unreadOnly => '未読のみ'; + + @override + String get listFilter_newGroup => '新しいグループ'; + + @override + String get pathTrace_you => 'あなた'; + + @override + String get pathTrace_failed => 'パスの追跡に失敗しました。'; + + @override + String get pathTrace_notAvailable => 'パスの追跡機能は利用できません。'; + + @override + String get pathTrace_refreshTooltip => 'パスの追跡をリフレッシュする。'; + + @override + String get pathTrace_someHopsNoLocation => 'ホップの1つまたは複数について、場所が特定されていません。'; + + @override + String get pathTrace_clearTooltip => '明確な道筋。'; + + @override + String get losSelectStartEnd => 'LOS の開始ノードと終了ノードを選択してください。'; + + @override + String losRunFailed(String error) { + return '視界確認に失敗: $error'; + } + + @override + String get losClearAllPoints => 'すべての項目をクリア'; + + @override + String get losRunToViewElevationProfile => 'LOS(レーザー測距)を使用して、標高プロファイルを表示する'; + + @override + String get losMenuTitle => 'LOS メニュー'; + + @override + String get losMenuSubtitle => '特定の場所をタップするか、地図を長押ししてカスタムポイントを作成する。'; + + @override + String get losShowDisplayNodes => '表示ノードを表示する'; + + @override + String get losCustomPoints => 'カスタマイズ可能なポイント'; + + @override + String losCustomPointLabel(int index) { + return 'カスタマイズ $index'; + } + + @override + String get losPointA => 'ポイントA'; + + @override + String get losPointB => 'ポイントB'; + + @override + String losAntennaA(String value, String unit) { + return 'アンテナ A: $value $unit'; + } + + @override + String losAntennaB(String value, String unit) { + return 'アンテナ B: $value $unit'; + } + + @override + String get losRun => 'LOS(レーティングシステム)を使用する'; + + @override + String get losNoElevationData => '標高データは含まれていません'; + + @override + String losProfileClear( + String distance, + String distanceUnit, + String clearance, + String heightUnit, + ) { + return '$distance $distanceUnit, clear LOS, min clearance $clearance $heightUnit'; + } + + @override + String losProfileBlocked( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit, blocked by $obstruction $heightUnit'; + } + + @override + String get losStatusChecking => 'LOS:確認中…'; + + @override + String get losStatusNoData => 'LOS: データの欠如'; + + @override + String losStatusSummary(int clear, int total, int blocked, int unknown) { + return 'LOS: $clear/$total clear, $blocked blocked, $unknown unknown'; + } + + @override + String get losErrorElevationUnavailable => + 'あるサンプルまたは複数のサンプルについて、標高データが利用できません。'; + + @override + String get losErrorInvalidInput => 'LOS(レーダー)計算に必要な、無効な点/標高データ。'; + + @override + String get losRenameCustomPoint => 'カスタムポイントの名前を変更する'; + + @override + String get losPointName => '項目名'; + + @override + String get losShowPanelTooltip => 'LOSパネルを表示する'; + + @override + String get losHidePanelTooltip => 'LOSパネルを隠す'; + + @override + String get losElevationAttribution => '標高データ:Open-Meteo (CC BY 4.0)'; + + @override + String get losLegendRadioHorizon => 'ラジオ・ホライゾン'; + + @override + String get losLegendLosBeam => 'LOS ビーミング'; + + @override + String get losLegendTerrain => '地形'; + + @override + String get losFrequencyLabel => '周波数'; + + @override + String get losFrequencyInfoTooltip => '計算の詳細を見る'; + + @override + String get losFrequencyDialogTitle => 'ラジオによる水平線計算'; + + @override + String losFrequencyDialogDescription( + double baselineK, + double baselineFreq, + double frequencyMHz, + double kFactor, + ) { + return 'k=$baselineK ( $baselineFreq MHz) から開始し、現在の $frequencyMHz MHz の帯域に対して k の値を調整します。これにより、曲面状の無線通信範囲の限界が定義されます。'; + } + + @override + String get contacts_pathTrace => '経路追跡'; + + @override + String get contacts_ping => 'パング'; + + @override + String get contacts_repeaterPathTrace => 'リピーターまでの経路を追跡する'; + + @override + String get contacts_repeaterPing => 'PING 繰り返し'; + + @override + String get contacts_roomPathTrace => '部屋のサーバーへの経路を追跡する'; + + @override + String get contacts_roomPing => 'ピンルーム用サーバー'; + + @override + String get contacts_chatTraceRoute => '経路の追跡ルート'; + + @override + String contacts_pathTraceTo(String name) { + return '$name への経路を追跡する'; + } + + @override + String get contacts_clipboardEmpty => 'クリップボードは空です。'; + + @override + String get contacts_invalidAdvertFormat => '無効な連絡先情報'; + + @override + String get contacts_contactImported => '連絡先が登録されました。'; + + @override + String get contacts_contactImportFailed => '連絡先のインポートに失敗しました。'; + + @override + String get contacts_zeroHopAdvert => 'ゼロホップ広告'; + + @override + String get contacts_floodAdvert => '洪水に関する広告'; + + @override + String get contacts_copyAdvertToClipboard => '広告をクリップボードにコピー'; + + @override + String get contacts_addContactFromClipboard => 'クリップボードから連絡先を追加する'; + + @override + String get contacts_ShareContact => '連絡先をクリップボードにコピー'; + + @override + String get contacts_ShareContactZeroHop => '広告を通じて連絡先を共有する'; + + @override + String get contacts_zeroHopContactAdvertSent => '広告を通じて連絡先を得た。'; + + @override + String get contacts_zeroHopContactAdvertFailed => '連絡を送信できませんでした。'; + + @override + String get contacts_contactAdvertCopied => '広告がクリップボードにコピーされました。'; + + @override + String get contacts_contactAdvertCopyFailed => '広告のコピーがクリップボードにコピーできませんでした。'; + + @override + String get notification_activityTitle => 'メッシュコアの活動'; + + @override + String notification_messagesCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'messages', + one: 'message', + ); + return '$count $_temp0'; + } + + @override + String notification_channelMessagesCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'チャンネルメッセージ', + one: 'チャンネルメッセージ', + ); + return '$count $_temp0'; + } + + @override + String notification_newNodesCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '新しいノード', + one: '新しいノード', + ); + return '$count $_temp0'; + } + + @override + String notification_newTypeDiscovered(String contactType) { + return '新たに $contactType が発見されました'; + } + + @override + String get notification_receivedNewMessage => '新しいメッセージを受信'; + + @override + String get settings_gpxExportRepeaters => 'GPX へのエクスポート用リピーター/ルームサーバー'; + + @override + String get settings_gpxExportRepeatersSubtitle => + 'GPXファイルに場所情報を付加した、レピーター/ルームサーバーのエクスポート'; + + @override + String get settings_gpxExportContacts => 'GPX 形式へのエクスポート'; + + @override + String get settings_gpxExportContactsSubtitle => + 'GPXファイルに位置情報を保存して、他の人と共有する。'; + + @override + String get settings_gpxExportAll => 'すべての連絡先をGPX形式でエクスポートする'; + + @override + String get settings_gpxExportAllSubtitle => + 'すべての連絡先を、場所情報付きのGPXファイルにエクスポートする。'; + + @override + String get settings_gpxExportSuccess => 'GPXファイルの正常なエクスポートが完了しました。'; + + @override + String get settings_gpxExportNoContacts => 'エクスポートする連絡先は存在しません。'; + + @override + String get settings_gpxExportNotAvailable => 'このデバイス/OSではサポートされていません'; + + @override + String get settings_gpxExportError => 'エクスポート時にエラーが発生しました。'; + + @override + String get settings_gpxExportRepeatersRoom => '中継装置およびルームサーバーの設置場所'; + + @override + String get settings_gpxExportChat => '関連施設'; + + @override + String get settings_gpxExportAllContacts => 'すべての連絡先場所'; + + @override + String get settings_gpxExportShareText => 'meshcore-openからエクスポートされた地図データ'; + + @override + String get settings_gpxExportShareSubject => + 'meshcore-open GPX形式の地図データのエクスポート'; + + @override + String get snrIndicator_nearByRepeaters => '近くの電波中継局'; + + @override + String get snrIndicator_lastSeen => '最後に確認された場所'; + + @override + String get contactsSettings_title => '連絡先設定'; + + @override + String get contactsSettings_autoAddTitle => '自動検出'; + + @override + String get contactsSettings_otherTitle => 'その他の連絡に関する設定'; + + @override + String get contactsSettings_autoAddUsersTitle => '自動でユーザーを追加する'; + + @override + String get contactsSettings_autoAddUsersSubtitle => + '利用者が自動的に発見したユーザーを追加できるようにする。'; + + @override + String get contactsSettings_autoAddRepeatersTitle => '自動で繰り返し設定'; + + @override + String get contactsSettings_autoAddRepeatersSubtitle => + '発見した中継局を、自動的に追加できるようにする。'; + + @override + String get contactsSettings_autoAddRoomServersTitle => '自動でルームサーバーを追加'; + + @override + String get contactsSettings_autoAddRoomServersSubtitle => + '利用者が、発見した部屋のサーバーを自動的に追加できるようにする。'; + + @override + String get contactsSettings_autoAddSensorsTitle => '自動でセンサーを追加'; + + @override + String get contactsSettings_autoAddSensorsSubtitle => + '利用者が、発見したセンサーを自動的に追加できるようにする。'; + + @override + String get contactsSettings_overwriteOldestTitle => '最も古いものを上書きする'; + + @override + String get contactsSettings_overwriteOldestSubtitle => + '連絡先リストが満杯になった場合、最も古いかつ「お気に入り」ではない連絡先が削除されます。'; + + @override + String get discoveredContacts_Title => '連絡先が見つかった'; + + @override + String get discoveredContacts_noMatching => '一致する連絡先が見つかりませんでした'; + + @override + String get discoveredContacts_searchHint => '発見された連絡先を検索する'; + + @override + String get discoveredContacts_contactAdded => '連絡先を追加'; + + @override + String get discoveredContacts_addContact => '連絡先を追加'; + + @override + String get discoveredContacts_copyContact => '連絡先をクリップボードにコピー'; + + @override + String get discoveredContacts_deleteContact => '発見された連絡先を削除'; + + @override + String get discoveredContacts_deleteContactAll => '発見されたすべての連絡先を削除'; + + @override + String get discoveredContacts_deleteContactAllContent => + '本当に、見つけたすべての連絡先を削除してもよろしいですか?'; + + @override + String get chat_sendCooldown => '再度送信する前に、しばらくお待ちください。'; + + @override + String get appSettings_jumpToOldestUnread => '最も古い未読のメッセージへ移動'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + '未読メッセージがあるチャットを開く際、「最新のメッセージ」ではなく、最初に未読のメッセージまでスクロールしてください。'; + + @override + String get appSettings_languageHu => 'ハンガリー語'; + + @override + String get appSettings_languageJa => '日本語'; + + @override + String get appSettings_languageKo => '韓国語'; + + @override + String get radioStats_tooltip => 'ラジオおよびメッシュに関する統計'; + + @override + String get radioStats_screenTitle => 'ラジオの統計'; + + @override + String get radioStats_notConnected => 'ラジオの統計情報を表示するために、デバイスに接続してください。'; + + @override + String get radioStats_firmwareTooOld => + 'ラジオの統計機能を使用するには、v8またはそれ以降のファームウェアが必要です。'; + + @override + String get radioStats_waiting => 'データ待ち…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return 'ノイズレベル: $noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return '最後のRSSI: $rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return '最終SNR: $snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'TX 放送時間(合計):$seconds 秒'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'RX 放送時間(合計):$seconds 秒'; + } + + @override + String get radioStats_chartCaption => '最近のサンプルのノイズレベル(dBm)。'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return 'ノイズレベル: $noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => 'ラジオの統計情報を取得中…'; + + @override + String get radioStats_settingsTile => 'ラジオの統計'; + + @override + String get radioStats_settingsSubtitle => 'ノイズレベル、RSSI、SNR、および通信時間'; +} diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart new file mode 100644 index 0000000..57ee40a --- /dev/null +++ b/lib/l10n/app_localizations_ko.dart @@ -0,0 +1,3403 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Korean (`ko`). +class AppLocalizationsKo extends AppLocalizations { + AppLocalizationsKo([String locale = 'ko']) : super(locale); + + @override + String get appTitle => 'MeshCore Open'; + + @override + String get nav_contacts => '연락처'; + + @override + String get nav_channels => '채널'; + + @override + String get nav_map => '지도'; + + @override + String get common_cancel => '취소'; + + @override + String get common_ok => '알겠습니다'; + + @override + String get common_connect => '연결'; + + @override + String get common_unknownDevice => '알 수 없는 장치'; + + @override + String get common_save => '저장'; + + @override + String get common_delete => '삭제'; + + @override + String get common_deleteAll => '모두 삭제'; + + @override + String get common_close => '닫기'; + + @override + String get common_edit => '수정'; + + @override + String get common_add => '추가'; + + @override + String get common_settings => '설정'; + + @override + String get common_disconnect => '연결 해제'; + + @override + String get common_connected => '연결된'; + + @override + String get common_disconnected => '단절'; + + @override + String get common_create => '만들다'; + + @override + String get common_continue => '계속'; + + @override + String get common_share => '공유'; + + @override + String get common_copy => '복사'; + + @override + String get common_retry => '다시 시도'; + + @override + String get common_hide => '숨기다'; + + @override + String get common_remove => '제거'; + + @override + String get common_enable => '활성화'; + + @override + String get common_disable => '비활성화'; + + @override + String get common_reboot => '재부팅'; + + @override + String get common_loading => '로딩 중...'; + + @override + String get common_notAvailable => '—'; + + @override + String common_voltageValue(String volts) { + return '$volts V'; + } + + @override + String common_percentValue(int percent) { + return '$percent%'; + } + + @override + String get scanner_title => 'MeshCore 공개'; + + @override + String get connectionChoiceUsbLabel => 'USB'; + + @override + String get connectionChoiceBluetoothLabel => '블루투스'; + + @override + String get connectionChoiceTcpLabel => 'TCP'; + + @override + String get tcpScreenTitle => 'TCP를 통해 연결'; + + @override + String get tcpHostLabel => 'IP 주소'; + + @override + String get tcpHostHint => '192.168.40.10'; + + @override + String get tcpPortLabel => '항'; + + @override + String get tcpPortHint => '5000'; + + @override + String get tcpStatus_notConnected => '목적지 주소 입력 후 연결'; + + @override + String tcpStatus_connectingTo(String endpoint) { + return '$endpoint에 연결 중...'; + } + + @override + String get tcpErrorHostRequired => 'IP 주소가 필요합니다.'; + + @override + String get tcpErrorPortInvalid => '포트 번호는 1에서 65535 사이여야 합니다.'; + + @override + String get tcpErrorUnsupported => '이 플랫폼에서는 TCP 트랜스포트를 지원하지 않습니다.'; + + @override + String get tcpErrorTimedOut => 'TCP 연결이 시간 초과되었습니다.'; + + @override + String tcpConnectionFailed(String error) { + return 'TCP 연결 실패: $error'; + } + + @override + String get usbScreenTitle => 'USB를 통해 연결'; + + @override + String get usbScreenSubtitle => '감지된 시리얼 장치를 선택하고 MeshCore 노드에 직접 연결하십시오.'; + + @override + String get usbScreenStatus => 'USB 장치를 선택합니다.'; + + @override + String get usbScreenNote => 'USB 직렬 통신은 지원되는 안드로이드 장치 및 데스크톱 플랫폼에서 활성화됩니다.'; + + @override + String get usbScreenEmptyState => + 'USB 장치가 탐지되지 않았습니다. USB 장치를 연결하고 다시 시도해 보세요.'; + + @override + String get usbErrorPermissionDenied => 'USB 접근 권한이 거부되었습니다.'; + + @override + String get usbErrorDeviceMissing => '선택한 USB 장치는 더 이상 사용 불가능합니다.'; + + @override + String get usbErrorInvalidPort => '유효한 USB 장치를 선택하세요.'; + + @override + String get usbErrorBusy => '또 다른 USB 연결 요청이 이미 진행 중입니다.'; + + @override + String get usbErrorNotConnected => 'USB 장치가 연결되지 않았습니다.'; + + @override + String get usbErrorOpenFailed => '선택한 USB 장치를 열 수 없습니다.'; + + @override + String get usbErrorConnectFailed => '선택한 USB 장치에 연결에 실패했습니다.'; + + @override + String get usbErrorUnsupported => '이 플랫폼에서는 USB 직렬 통신을 지원하지 않습니다.'; + + @override + String get usbErrorAlreadyActive => 'USB 연결이 이미 활성화되어 있습니다.'; + + @override + String get usbErrorNoDeviceSelected => 'USB 장치가 선택되지 않았습니다.'; + + @override + String get usbErrorPortClosed => 'USB 연결이 활성화되지 않았습니다.'; + + @override + String get usbErrorConnectTimedOut => + '연결이 시간 초과되었습니다. 장치가 USB Companion 펌웨어를 가지고 있는지 확인해 주세요.'; + + @override + String get usbFallbackDeviceName => '웹 시리얼 장치'; + + @override + String get usbStatus_notConnected => 'USB 장치를 선택합니다.'; + + @override + String get usbStatus_connecting => 'USB 장치에 연결 중...'; + + @override + String get usbStatus_searching => 'USB 장치 검색 중...'; + + @override + String usbConnectionFailed(String error) { + return 'USB 연결 실패: $error'; + } + + @override + String get scanner_scanning => '장치 검색 중...'; + + @override + String get scanner_connecting => '연결 중...'; + + @override + String get scanner_disconnecting => '연결 해제 중...'; + + @override + String get scanner_notConnected => '연결되지 않음'; + + @override + String scanner_connectedTo(String deviceName) { + return '$deviceName에 연결됨'; + } + + @override + String get scanner_searchingDevices => 'MeshCore 장치를 검색 중...'; + + @override + String get scanner_tapToScan => 'MeshCore 장치를 찾기 위해 스캔 버튼을 누르세요.'; + + @override + String scanner_connectionFailed(String error) { + return '연결 실패: $error'; + } + + @override + String get scanner_stop => '멈춰'; + + @override + String get scanner_scan => '스캔'; + + @override + String get scanner_bluetoothOff => '블루투스는 꺼져 있습니다.'; + + @override + String get scanner_bluetoothOffMessage => '블루투스를 켜서 장치를 검색해주세요.'; + + @override + String get scanner_chromeRequired => '크롬 브라우저 필요'; + + @override + String get scanner_chromeRequiredMessage => + '이 웹 애플리케이션은 블루투드 지원을 위해 Google Chrome 또는 Chromium 기반 브라우저가 필요합니다.'; + + @override + String get scanner_enableBluetooth => '블루투스 활성화'; + + @override + String get device_quickSwitch => '빠른 전환'; + + @override + String get device_meshcore => '메쉬코어'; + + @override + String get settings_title => '설정'; + + @override + String get settings_deviceInfo => '장치 정보'; + + @override + String get settings_appSettings => '앱 설정'; + + @override + String get settings_appSettingsSubtitle => '알림, 메시징, 지도 설정'; + + @override + String get settings_nodeSettings => '노드 설정'; + + @override + String get settings_nodeName => '노드 이름'; + + @override + String get settings_nodeNameNotSet => '설정되지 않음'; + + @override + String get settings_nodeNameHint => '노드 이름을 입력하세요'; + + @override + String get settings_nodeNameUpdated => '이름 변경'; + + @override + String get settings_radioSettings => '라디오 설정'; + + @override + String get settings_radioSettingsSubtitle => '주파수, 전력, 스펙트럼'; + + @override + String get settings_radioSettingsUpdated => '라디오 설정이 업데이트되었습니다.'; + + @override + String get settings_location => '위치'; + + @override + String get settings_locationSubtitle => 'GPS 좌표'; + + @override + String get settings_locationUpdated => '위치 및 GPS 설정이 업데이트되었습니다.'; + + @override + String get settings_locationBothRequired => '위도와 경도를 모두 입력하세요.'; + + @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 => '위도'; + + @override + String get settings_longitude => '경도'; + + @override + String get settings_contactSettings => '연락처 설정'; + + @override + String get settings_contactSettingsSubtitle => '연락처 추가 방식 설정'; + + @override + String get settings_privacyMode => '개인 정보 보호 모드'; + + @override + String get settings_privacyModeSubtitle => '광고에 이름/위치 정보 숨기기'; + + @override + String get settings_privacyModeToggle => + '광고에 자신의 이름과 위치를 숨기기 위해 개인 정보 보호 모드를 켜거나 끄십시오.'; + + @override + String get settings_privacyModeEnabled => '개인 정보 보호 모드 활성화'; + + @override + String get settings_privacyModeDisabled => '개인 정보 보호 모드 비활성화'; + + @override + String get settings_privacy => '개인 정보 설정'; + + @override + String get settings_privacySubtitle => '어떤 정보를 공유할지 통제하세요.'; + + @override + String get settings_privacySettingsDescription => + '어떤 정보를 기기가 다른 사람들과 공유할지 선택하세요.'; + + @override + String get settings_denyAll => '모든 것을 부정'; + + @override + String get settings_allowByContact => '연락처 표시 기능 활성화'; + + @override + String get settings_allowAll => '모든 것을 허용'; + + @override + String get settings_telemetryBaseMode => '원격 모니터링 기본 설정'; + + @override + String get settings_telemetryLocationMode => '텔레메트리 위치 모드'; + + @override + String get settings_telemetryEnvironmentMode => '텔레메트리 환경 모드'; + + @override + String get settings_advertLocation => '광고 위치'; + + @override + String get settings_advertLocationSubtitle => '광고에 위치 정보를 포함하세요.'; + + @override + String settings_multiAck(String value) { + return '다중 ACK: $value'; + } + + @override + String get settings_telemetryModeUpdated => '텔레메트리 모드 업데이트 완료'; + + @override + String get settings_actions => '행동'; + + @override + String get settings_sendAdvertisement => '광고 전송'; + + @override + String get settings_sendAdvertisementSubtitle => '방송 활동'; + + @override + String get settings_advertisementSent => '광고 전송'; + + @override + String get settings_syncTime => '동기화 시간'; + + @override + String get settings_syncTimeSubtitle => '장치 시계를 휴대폰 시간으로 설정'; + + @override + String get settings_timeSynchronized => '시간 동기화'; + + @override + String get settings_refreshContacts => '연락처 갱신'; + + @override + String get settings_refreshContactsSubtitle => '장치에서 연락처 목록을 다시 불러오기'; + + @override + String get settings_rebootDevice => '장치 재부팅'; + + @override + String get settings_rebootDeviceSubtitle => 'MeshCore 장치를 재부팅하세요.'; + + @override + String get settings_rebootDeviceConfirm => + '정말로 장치를 재부팅하시겠습니까? 이 경우 연결이 끊어집니다.'; + + @override + String get settings_debug => '디버깅'; + + @override + String get settings_bleDebugLog => 'BLE 디버그 로그'; + + @override + String get settings_bleDebugLogSubtitle => 'BLE 명령어, 응답 및 원시 데이터'; + + @override + String get settings_appDebugLog => '앱 디버깅 로그'; + + @override + String get settings_appDebugLogSubtitle => '애플리케이션 디버깅 메시지'; + + @override + String get settings_about => '소개'; + + @override + String settings_aboutVersion(String version) { + return 'MeshCore Open $version 버전'; + } + + @override + String get settings_aboutLegalese => '2026년 MeshCore 오픈 소스 프로젝트'; + + @override + String get settings_aboutDescription => + 'MeshCore LoRa 메시 네트워크 장치를 위한 오픈 소스 Flutter 클라이언트.'; + + @override + String get settings_aboutOpenMeteoAttribution => + 'LOS 고도 데이터: Open-Meteo (CC BY 4.0)'; + + @override + String get settings_infoName => '이름'; + + @override + String get settings_infoId => 'ID'; + + @override + String get settings_infoStatus => '상태'; + + @override + String get settings_infoBattery => '배터리'; + + @override + String get settings_infoPublicKey => '공개 키'; + + @override + String get settings_infoContactsCount => '연락처 수'; + + @override + String get settings_infoChannelCount => '채널 수'; + + @override + String get settings_presets => '기본 설정'; + + @override + String get settings_frequency => '주파수 (MHz)'; + + @override + String get settings_frequencyHelper => '300.0 - 2500.0'; + + @override + String get settings_frequencyInvalid => '유효하지 않은 주파수 (300-2500 MHz)'; + + @override + String get settings_bandwidth => '대역폭'; + + @override + String get settings_spreadingFactor => '분산 계수'; + + @override + String get settings_codingRate => '코딩 속도'; + + @override + String get settings_txPower => 'TX 전력 (dBm)'; + + @override + String get settings_txPowerHelper => '0 - 22'; + + @override + String get settings_txPowerInvalid => '유효하지 않은 TX 전력 (0-22 dBm)'; + + @override + String get settings_clientRepeat => '오프그리드 반복'; + + @override + String get settings_clientRepeatSubtitle => + '이 장치가 다른 사람들을 위해 메시 패킷을 반복하도록 허용합니다.'; + + @override + String get settings_clientRepeatFreqWarning => + '오프그리드(무전력) 시스템 재연결에는 433MHz, 869MHz, 또는 918MHz 주파수가 필요합니다.'; + + @override + String settings_error(String message) { + return '오류: $message'; + } + + @override + String get appSettings_title => '앱 설정'; + + @override + String get appSettings_appearance => '외관'; + + @override + String get appSettings_theme => '주제'; + + @override + String get appSettings_themeSystem => '기본 설정'; + + @override + String get appSettings_themeLight => '빛'; + + @override + String get appSettings_themeDark => '어둡다'; + + @override + String get appSettings_language => '언어'; + + @override + String get appSettings_languageSystem => '기본 설정'; + + @override + String get appSettings_languageEn => '영어'; + + @override + String get appSettings_languageFr => '프랑스어'; + + @override + String get appSettings_languageEs => '스페인어'; + + @override + String get appSettings_languageDe => '독일어'; + + @override + String get appSettings_languagePl => '폴란드'; + + @override + String get appSettings_languageSl => '슬로베니아어'; + + @override + String get appSettings_languagePt => '포르투갈어'; + + @override + String get appSettings_languageIt => '이탈리아어'; + + @override + String get appSettings_languageZh => '중국어'; + + @override + String get appSettings_languageSv => '스웨덴어'; + + @override + String get appSettings_languageNl => '네덜란드어'; + + @override + String get appSettings_languageSk => '슬로베니아어'; + + @override + String get appSettings_languageBg => '불가리'; + + @override + String get appSettings_languageRu => '러시아어'; + + @override + String get appSettings_languageUk => '우크라이나'; + + @override + String get appSettings_enableMessageTracing => '메시지 추적 기능 활성화'; + + @override + String get appSettings_enableMessageTracingSubtitle => + '메시지에 대한 상세한 경로 및 시간 정보를 표시'; + + @override + String get appSettings_notifications => '알림'; + + @override + String get appSettings_enableNotifications => '알림 활성화'; + + @override + String get appSettings_enableNotificationsSubtitle => '메시지와 광고에 대한 알림을 받으세요.'; + + @override + String get appSettings_notificationPermissionDenied => '알림 권한 거부'; + + @override + String get appSettings_notificationsEnabled => '알림 기능 활성화'; + + @override + String get appSettings_notificationsDisabled => '알림 기능 끄기'; + + @override + String get appSettings_messageNotifications => '메시지 알림'; + + @override + String get appSettings_messageNotificationsSubtitle => '새로운 메시지를 받을 때 알림 표시'; + + @override + String get appSettings_channelMessageNotifications => '채널 메시지 알림'; + + @override + String get appSettings_channelMessageNotificationsSubtitle => + '채널 메시지를 수신할 때 알림 표시'; + + @override + String get appSettings_advertisementNotifications => '광고 알림'; + + @override + String get appSettings_advertisementNotificationsSubtitle => + '새 노드가 발견되었을 때 알림 표시'; + + @override + String get appSettings_messaging => '메시징'; + + @override + String get appSettings_clearPathOnMaxRetry => 'Max 재시도 시 경로 명확하게 설정'; + + @override + String get appSettings_clearPathOnMaxRetrySubtitle => + '5번의 전송 시도가 실패하면 연락 경로를 재설정'; + + @override + String get appSettings_pathsWillBeCleared => '5번의 시도 실패 후, 해당 경로가 확보될 것입니다.'; + + @override + String get appSettings_pathsWillNotBeCleared => '경로는 자동으로 정리되지 않습니다.'; + + @override + String get appSettings_autoRouteRotation => '자동 경로 순환'; + + @override + String get appSettings_autoRouteRotationSubtitle => '최적 경로와 방수 모드 사이를 전환'; + + @override + String get appSettings_autoRouteRotationEnabled => '자동 경로 순환 기능 활성화'; + + @override + String get appSettings_autoRouteRotationDisabled => '자동 경로 순환 기능 비활성화'; + + @override + String get appSettings_maxRouteWeight => '최대 경로 무게'; + + @override + String get appSettings_maxRouteWeightSubtitle => + '한 경로가 성공적인 배송을 통해 누적할 수 있는 최대 무게'; + + @override + String get appSettings_initialRouteWeight => '초기 경로 가중치'; + + @override + String get appSettings_initialRouteWeightSubtitle => '새롭게 발견된 경로의 초기 무게'; + + @override + String get appSettings_routeWeightSuccessIncrement => '성공 횟수 증가'; + + @override + String get appSettings_routeWeightSuccessIncrementSubtitle => + '성공적으로 배송된 경로에 추가된 무게'; + + @override + String get appSettings_routeWeightFailureDecrement => '오류 가중치 감소'; + + @override + String get appSettings_routeWeightFailureDecrementSubtitle => + '배송 실패 후 경로에서 제거된 무게'; + + @override + String get appSettings_maxMessageRetries => '최대 메시지 재시도 횟수'; + + @override + String get appSettings_maxMessageRetriesSubtitle => '메시지를 실패로 처리하기 전 시도 횟수'; + + @override + String path_routeWeight(String weight, String max) { + return '$weight/$max'; + } + + @override + String get appSettings_battery => '배터리'; + + @override + String get appSettings_batteryChemistry => '배터리 화학'; + + @override + String appSettings_batteryChemistryPerDevice(String deviceName) { + return '$deviceName 당분간'; + } + + @override + String get appSettings_batteryChemistryConnectFirst => '장치를 선택하기 위해 연결'; + + @override + String get appSettings_batteryNmc => '18650 NMC (3.0-4.2V)'; + + @override + String get appSettings_batteryLifepo4 => 'LiFePO4 (2.6-3.65V)'; + + @override + String get appSettings_batteryLipo => '리튬 폴리머 (3.0-4.2V)'; + + @override + String get appSettings_mapDisplay => '지도 표시'; + + @override + String get appSettings_showRepeaters => '반복 기능 표시'; + + @override + String get appSettings_showRepeatersSubtitle => '지도에 반복자 노드를 표시'; + + @override + String get appSettings_showChatNodes => '채팅 노드 표시'; + + @override + String get appSettings_showChatNodesSubtitle => '지도에 채팅 노드를 표시'; + + @override + String get appSettings_showOtherNodes => '다른 노드 표시'; + + @override + String get appSettings_showOtherNodesSubtitle => '지도에서 다른 노드 유형을 표시'; + + @override + String get appSettings_timeFilter => '시간 필터'; + + @override + String get appSettings_timeFilterShowAll => '모든 노드 표시'; + + @override + String appSettings_timeFilterShowLast(int hours) { + return '지난 $hours 시간 동안의 노드 표시'; + } + + @override + String get appSettings_mapTimeFilter => '지도 필터'; + + @override + String get appSettings_showNodesDiscoveredWithin => '다음 내역에서 발견된 노드 표시:'; + + @override + String get appSettings_allTime => '모든 시간'; + + @override + String get appSettings_lastHour => '지난 시간'; + + @override + String get appSettings_last6Hours => '지난 6시간'; + + @override + String get appSettings_last24Hours => '지난 24시간'; + + @override + String get appSettings_lastWeek => '지난 주'; + + @override + String get appSettings_offlineMapCache => '오프라인 지도 캐시'; + + @override + String get appSettings_unitsTitle => '단위'; + + @override + String get appSettings_unitsMetric => '단위 (m / km)'; + + @override + String get appSettings_unitsImperial => '제국 (피트/마일)'; + + @override + String get appSettings_noAreaSelected => '선택된 영역 없음'; + + @override + String appSettings_areaSelectedZoom(int minZoom, int maxZoom) { + return '선택된 영역 (줌 레벨: $minZoom - $maxZoom)'; + } + + @override + String get appSettings_debugCard => '디버깅'; + + @override + String get appSettings_appDebugLogging => '앱 디버깅 로깅'; + + @override + String get appSettings_appDebugLoggingSubtitle => '로그 앱 디버깅 메시지 (문제 해결을 위한)'; + + @override + String get appSettings_appDebugLoggingEnabled => '앱 디버깅 로깅 활성화'; + + @override + String get appSettings_appDebugLoggingDisabled => '앱 디버깅 로깅 비활성화'; + + @override + String get contacts_title => '연락처'; + + @override + String get contacts_noContacts => '아직 연락처는 없습니다.'; + + @override + String get contacts_contactsWillAppear => '장치가 광고를 할 때, 연락처 정보가 표시됩니다.'; + + @override + String get contacts_unread => '읽지 않음'; + + @override + String get contacts_searchContactsNoNumber => '연락처 검색...'; + + @override + String contacts_searchContacts(int number, String str) { + return '$number $str 연락처 검색...'; + } + + @override + String contacts_searchFavorites(int number, String str) { + return '$number $str 검색 결과 보기...'; + } + + @override + String contacts_searchUsers(int number, String str) { + return '$number $str 사용자 검색...'; + } + + @override + String contacts_searchRepeaters(int number, String str) { + return '$number $str 검색 결과 반복기 검색'; + } + + @override + String contacts_searchRoomServers(int number, String str) { + return '$number $str 방 서버 검색'; + } + + @override + String get contacts_noUnreadContacts => '읽지 않은 연락처가 없습니다.'; + + @override + String get contacts_noContactsFound => '연락처 또는 그룹이 검색되지 않았습니다.'; + + @override + String get contacts_deleteContact => '연락처 삭제'; + + @override + String contacts_removeConfirm(String contactName) { + return '$contactName를 연락처 목록에서 제거하시겠습니까?'; + } + + @override + String get contacts_manageRepeater => '리피터 관리'; + + @override + String get contacts_manageRoom => '방 서버 관리'; + + @override + String get contacts_roomLogin => '방 서버 로그인'; + + @override + String get contacts_openChat => '자유로운 대화'; + + @override + String get contacts_editGroup => '편집 그룹'; + + @override + String get contacts_deleteGroup => '그룹 삭제'; + + @override + String contacts_deleteGroupConfirm(String groupName) { + return '$groupName 삭제?'; + } + + @override + String get contacts_newGroup => '새로운 그룹'; + + @override + String get contacts_groupName => '그룹 이름'; + + @override + String get contacts_groupNameRequired => '그룹 이름이 필요합니다'; + + @override + String get contacts_groupNameReserved => '이 그룹 이름은 이미 사용 중입니다.'; + + @override + String contacts_groupAlreadyExists(String name) { + return '그룹 \"$name\"은 이미 존재합니다.'; + } + + @override + String get contacts_filterContacts => '연락처 필터링...'; + + @override + String get contacts_noContactsMatchFilter => '입력하신 검색 조건과 일치하는 연락처가 없습니다.'; + + @override + String get contacts_noMembers => '회원 없음'; + + @override + String get contacts_lastSeenNow => '최근'; + + @override + String contacts_lastSeenMinsAgo(int minutes) { + return '~ $minutes min.'; + } + + @override + String get contacts_lastSeenHourAgo => '약 1시간'; + + @override + String contacts_lastSeenHoursAgo(int hours) { + return '~ $hours hours'; + } + + @override + String get contacts_lastSeenDayAgo => '~ 1일'; + + @override + String contacts_lastSeenDaysAgo(int days) { + return '~ $days일'; + } + + @override + String get contact_info => '연락처'; + + @override + String get contact_settings => '연락처 설정'; + + @override + String get contact_telemetry => '텔레메트리'; + + @override + String get contact_lastSeen => '마지막으로 목격'; + + @override + String get contact_clearChat => '명확한 대화'; + + @override + String get contact_teleBase => '텔레메트리 기반'; + + @override + String get contact_teleBaseSubtitle => '배터리 잔량 및 기본적인 통신 데이터를 공유할 수 있도록 허용'; + + @override + String get contact_teleLoc => '텔레메트리 위치'; + + @override + String get contact_teleLocSubtitle => '위치 정보 공유 허용'; + + @override + String get contact_teleEnv => '텔레메트리 환경'; + + @override + String get contact_teleEnvSubtitle => '환경 센서 데이터를 공유하도록 허용'; + + @override + String get channels_title => '채널'; + + @override + String get channels_noChannelsConfigured => '구성된 채널이 없습니다.'; + + @override + String get channels_addPublicChannel => '공개 채널 추가'; + + @override + String get channels_searchChannels => '검색 채널...'; + + @override + String get channels_noChannelsFound => '채널을 찾을 수 없습니다.'; + + @override + String channels_channelIndex(int index) { + return '채널 $index'; + } + + @override + String get channels_hashtagChannel => '해시태그 채널'; + + @override + String get channels_public => '대중의'; + + @override + String get channels_private => '사립'; + + @override + String get channels_publicChannel => '공개 채널'; + + @override + String get channels_privateChannel => '개인 채널'; + + @override + String get channels_editChannel => '채널 편집'; + + @override + String get channels_muteChannel => '음소거 채널'; + + @override + String get channels_unmuteChannel => '채널 음소거 해제'; + + @override + String get channels_deleteChannel => '채널 삭제'; + + @override + String channels_deleteChannelConfirm(String name) { + return '$name 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.'; + } + + @override + String channels_channelDeleteFailed(String name) { + return '채널 \"$name\" 삭제에 실패했습니다.'; + } + + @override + String channels_channelDeleted(String name) { + return '채널 \"$name\" 삭제'; + } + + @override + String get channels_addChannel => '채널 추가'; + + @override + String get channels_channelIndexLabel => '채널 인덱스'; + + @override + String get channels_channelName => '채널 이름'; + + @override + String get channels_usePublicChannel => '공개 채널 사용'; + + @override + String get channels_standardPublicPsk => '표준 공공 PSK'; + + @override + String get channels_pskHex => 'PSK (헥스)'; + + @override + String get channels_generateRandomPsk => '임의의 PSK 생성'; + + @override + String get channels_enterChannelName => '채널 이름을 입력해 주세요.'; + + @override + String get channels_pskMustBe32Hex => 'PSK(개인식별키)는 32자리 16진수 문자여야 합니다.'; + + @override + String channels_channelAdded(String name) { + return '채널 \"$name\" 추가'; + } + + @override + String channels_editChannelTitle(int index) { + return '채널 $index 편집'; + } + + @override + String get channels_smazCompression => 'SMAZ 압축'; + + @override + String channels_channelUpdated(String name) { + return '채널 \"$name\"이 업데이트되었습니다.'; + } + + @override + String get channels_publicChannelAdded => '공개 채널 추가'; + + @override + String get channels_sortBy => '정렬 기준 선택'; + + @override + String get channels_sortManual => '사용 설명서'; + + @override + String get channels_sortAZ => 'A부터 Z까지'; + + @override + String get channels_sortLatestMessages => '최신 메시지'; + + @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 => '아직 메시지가 없습니다.'; + + @override + String get chat_sendMessageToStart => '시작하려면 메시지를 보내세요.'; + + @override + String get chat_originalMessageNotFound => '원래 메시지를 찾을 수 없음'; + + @override + String chat_replyingTo(String name) { + return '$name에게 답변'; + } + + @override + String chat_replyTo(String name) { + return '$name님께 회신'; + } + + @override + String get chat_location => '위치'; + + @override + String chat_sendMessageTo(String contactName) { + return '$contactName에게 메시지를 보내'; + } + + @override + String get chat_typeMessage => '메시지를 입력하세요...'; + + @override + String chat_messageTooLong(int maxBytes) { + return '메시지가 너무 길어서 (최대 $maxBytes 바이트).'; + } + + @override + String get chat_messageCopied => '메시지가 복사되었습니다'; + + @override + String get chat_messageDeleted => '메시지가 삭제되었습니다.'; + + @override + String get chat_retryingMessage => '재시도 메시지'; + + @override + String chat_retryCount(int current, int max) { + return '$current/$max 시도'; + } + + @override + String get chat_sendGif => 'GIF 보내기'; + + @override + String get chat_reply => '답변'; + + @override + String get chat_addReaction => '댓글 추가'; + + @override + String get chat_me => '나'; + + @override + String get emojiCategorySmileys => '이모티콘'; + + @override + String get emojiCategoryGestures => '제스처'; + + @override + String get emojiCategoryHearts => '심장'; + + @override + String get emojiCategoryObjects => '대상'; + + @override + String get gifPicker_title => 'GIF 선택'; + + @override + String get gifPicker_searchHint => 'GIF 검색...'; + + @override + String get gifPicker_poweredBy => 'GIPHY에서 제공'; + + @override + String get gifPicker_noGifsFound => 'GIF 파일이 없습니다.'; + + @override + String get gifPicker_failedLoad => 'GIF 파일 로딩 실패'; + + @override + String get gifPicker_failedSearch => 'GIF 검색에 실패했습니다.'; + + @override + String get gifPicker_noInternet => '인터넷 연결 없음'; + + @override + String get debugLog_appTitle => '앱 디버깅 로그'; + + @override + String get debugLog_bleTitle => 'BLE 디버그 로그'; + + @override + String get debugLog_copyLog => '로그 기록'; + + @override + String get debugLog_clearLog => '명확한 로그'; + + @override + String get debugLog_copied => '디버깅 로그 복사'; + + @override + String get debugLog_bleCopied => 'BLE 로그 복사'; + + @override + String get debugLog_noEntries => '현재 디버깅 로그는 생성되지 않았습니다.'; + + @override + String get debugLog_enableInSettings => '설정에서 앱 디버깅 로깅을 활성화합니다.'; + + @override + String get debugLog_frames => '프레임'; + + @override + String get debugLog_rawLogRx => '원시 로그-RX'; + + @override + String get debugLog_noBleActivity => '현재 BLE 관련 활동은 없습니다.'; + + @override + String debugFrame_length(int count) { + return '프레임 길이: $count 바이트'; + } + + @override + String debugFrame_command(String value) { + return '명령: 0x$value'; + } + + @override + String get debugFrame_textMessageHeader => '텍스트 메시지 프레임:'; + + @override + String debugFrame_destinationPubKey(String pubKey) { + return '- 목적지 공개 키: $pubKey'; + } + + @override + String debugFrame_timestamp(int timestamp) { + return '- 시간: $timestamp'; + } + + @override + String debugFrame_flags(String value) { + return '- 플래그: 0x$value'; + } + + @override + String debugFrame_textType(int type, String label) { + return '- 텍스트 유형: $type ($label)'; + } + + @override + String get debugFrame_textTypeCli => '명령줄 인터페이스 (CLI)'; + + @override + String get debugFrame_textTypePlain => '단순한'; + + @override + String debugFrame_text(String text) { + return '- 텍스트: \"$text\"'; + } + + @override + String get debugFrame_hexDump => '헥스 덤프:'; + + @override + String get chat_pathManagement => '경로 관리'; + + @override + String get chat_ShowAllPaths => '모든 경로 표시'; + + @override + String get chat_routingMode => '라우팅 방식'; + + @override + String get chat_autoUseSavedPath => '자동 (저장된 경로 사용)'; + + @override + String get chat_forceFloodMode => '강수 모드 활성화'; + + @override + String get chat_recentAckPaths => '최근 사용한 ACK 경로 (사용하려면 탭):'; + + @override + String get chat_pathHistoryFull => + '이력 기록은 이미 가득 차 있습니다. 항목을 삭제하여 새로운 항목을 추가할 수 있습니다.'; + + @override + String get chat_hopSingular => '점프'; + + @override + String get chat_hopPlural => '홉'; + + @override + String chat_hopsCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '홉', + one: '홉', + ); + return '$count $_temp0'; + } + + @override + String get chat_successes => '성공 사례'; + + @override + String get chat_removePath => '경로 제거'; + + @override + String get chat_noPathHistoryYet => '아직 경로 기록이 없습니다.\n경로를 찾기 위해 메시지를 보내세요.'; + + @override + String get chat_pathActions => '경로 작업:'; + + @override + String get chat_setCustomPath => '사용자 지정 경로 설정'; + + @override + String get chat_setCustomPathSubtitle => '수동으로 경로를 지정'; + + @override + String get chat_clearPath => '명확한 길'; + + @override + String get chat_clearPathSubtitle => '다음 전송 시, 강제 재전송 설정'; + + @override + String get chat_pathCleared => '경로가 확보되었습니다. 다음 메시지는 경로를 다시 찾을 것입니다.'; + + @override + String get chat_floodModeSubtitle => '앱 바에서 라우팅 스위치를 사용'; + + @override + String get chat_floodModeEnabled => + '홍수 모드 활성화됨. 앱 바의 경로 아이콘을 사용하여 다시 전환할 수 있습니다.'; + + @override + String get chat_fullPath => '전체 경로'; + + @override + String get chat_pathDetailsNotAvailable => + '경로 정보는 아직 제공되지 않습니다. 메시지를 보내어 다시 시도해 보세요.'; + + @override + String chat_pathSetHops(int hopCount, String status) { + String _temp0 = intl.Intl.pluralLogic( + hopCount, + locale: localeName, + other: 'hops', + one: 'hop', + ); + return 'Path set: $hopCount $_temp0 - $status'; + } + + @override + String get chat_pathSavedLocally => '로컬에 저장. 동기화 연결'; + + @override + String get chat_pathDeviceConfirmed => '장치 확인 완료.'; + + @override + String get chat_pathDeviceNotConfirmed => '기기가 아직 확인되지 않았습니다.'; + + @override + String get chat_type => '종류'; + + @override + String get chat_path => '경로'; + + @override + String get chat_publicKey => '공개 키'; + + @override + String get chat_compressOutgoingMessages => '전송되는 메시지 압축'; + + @override + String get chat_floodForced => '홍수 (강제)'; + + @override + String get chat_directForced => '직접적인 (강제적인)'; + + @override + String chat_hopsForced(int count) { + return '$count번 띄우기 (강제)'; + } + + @override + String get chat_floodAuto => '홍수 (자동)'; + + @override + String get chat_direct => '직접'; + + @override + String get chat_poiShared => '공유된 POI'; + + @override + String chat_unread(int count) { + 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 => '노드 매핑'; + + @override + String get map_lineOfSight => '시야'; + + @override + String get map_losScreenTitle => '시야'; + + @override + String get map_noNodesWithLocation => '위치 정보가 있는 노드가 없습니다.'; + + @override + String get map_nodesNeedGps => '노드는 지도에 표시되려면 GPS 좌표를 공유해야 합니다.'; + + @override + String map_nodesCount(int count) { + return '노드: $count'; + } + + @override + String map_pinsCount(int count) { + return '핀: $count'; + } + + @override + String get map_chat => '채팅'; + + @override + String get map_repeater => '반복기'; + + @override + String get map_room => '방'; + + @override + String get map_sensor => '센서'; + + @override + String get map_pinDm => '핀 (DM)'; + + @override + String get map_pinPrivate => '개인 계정'; + + @override + String get map_pinPublic => '공개 (일반 공개)'; + + @override + String get map_lastSeen => '마지막으로 목격'; + + @override + String get map_disconnectConfirm => '이 장치와의 연결을 해제하시겠습니까?'; + + @override + String get map_from => '~부터'; + + @override + String get map_source => '출처'; + + @override + String get map_flags => '깃발'; + + @override + String get map_shareMarkerHere => '여기에서 마커 공유'; + + @override + String get map_setAsMyLocation => '내 위치로 설정'; + + @override + String get map_pinLabel => '핀 라벨'; + + @override + String get map_label => '레이블'; + + @override + String get map_pointOfInterest => '관심 지점'; + + @override + String get map_sendToContact => '연락처로 보내기'; + + @override + String get map_sendToChannel => '채널로 전송'; + + @override + String get map_noChannelsAvailable => '사용 가능한 채널이 없습니다.'; + + @override + String get map_publicLocationShare => '공개 장소 공유'; + + @override + String map_publicLocationShareConfirm(String channelLabel) { + return '현재 $channelLabel 채널에서 위치 정보를 공유하려고 합니다. 이 채널은 공개되어 있으며, PSK를 가진 모든 사용자가 이 위치 정보를 볼 수 있습니다.'; + } + + @override + String get map_connectToShareMarkers => '장치를 연결하여 마커를 공유'; + + @override + String get map_filterNodes => '필터 노드'; + + @override + String get map_nodeTypes => '노드 유형'; + + @override + String get map_chatNodes => '채팅 노드'; + + @override + String get map_repeaters => '다시 보내는 장치'; + + @override + String get map_otherNodes => '다른 노드'; + + @override + String get map_showOverlaps => '반복 키 중복'; + + @override + String get map_keyPrefix => '핵심 접두사'; + + @override + String get map_filterByKeyPrefix => '주요 접두사 기준으로 필터링'; + + @override + String get map_publicKeyPrefix => '공개 키 접두사'; + + @override + String get map_markers => '마커'; + + @override + String get map_showSharedMarkers => '공통 마커 표시'; + + @override + String get map_showGuessedLocations => '추정된 노드 위치 표시'; + + @override + String get map_showDiscoveryContacts => '디스커버리 담당자 연락처 보기'; + + @override + String get map_guessedLocation => '추측된 위치'; + + @override + String get map_lastSeenTime => '마지막으로 확인된 시간'; + + @override + String get map_sharedPin => '공유 비밀번호'; + + @override + String get map_joinRoom => '방에 참여'; + + @override + String get map_manageRepeater => '리피터 관리'; + + @override + String get map_tapToAdd => '노드에 클릭하여 경로에 추가합니다.'; + + @override + String get map_runTrace => '경로 추적'; + + @override + String get map_runTraceWithReturnPath => '원래 경로로 돌아가세요.'; + + @override + String get map_removeLast => '마지막 항목 삭제'; + + @override + String get map_pathTraceCancelled => '경로 추적 기능이 취소되었습니다.'; + + @override + String get mapCache_title => '오프라인 지도 캐시'; + + @override + String get mapCache_selectAreaFirst => '캐시할 영역을 먼저 선택하세요'; + + @override + String get mapCache_noTilesToDownload => '이 지역에 다운로드할 타일이 없습니다.'; + + @override + String get mapCache_downloadTilesTitle => '타일 다운로드'; + + @override + String mapCache_downloadTilesPrompt(int count) { + return '$count개의 타일을 오프라인 사용을 위해 다운로드하시겠습니까?'; + } + + @override + String get mapCache_downloadAction => '다운로드'; + + @override + String mapCache_cachedTiles(int count) { + return '$count 개의 타일 캐시'; + } + + @override + String mapCache_cachedTilesWithFailed(int downloaded, int failed) { + return 'Cached $downloaded tiles ($failed failed)'; + } + + @override + String get mapCache_clearOfflineCacheTitle => '오프라인 캐시 삭제'; + + @override + String get mapCache_clearOfflineCachePrompt => '모든 캐시된 지도 템플릿을 삭제하시겠습니까?'; + + @override + String get mapCache_offlineCacheCleared => '오프라인 캐시 삭제'; + + @override + String get mapCache_noAreaSelected => '선택된 영역 없음'; + + @override + String get mapCache_cacheArea => '캐시 영역'; + + @override + String get mapCache_useCurrentView => '현재 보기 유지'; + + @override + String get mapCache_zoomRange => '줌 기능 범위'; + + @override + String mapCache_estimatedTiles(int count) { + return '예상되는 타일 개수: $count'; + } + + @override + String mapCache_downloadedTiles(int completed, int total) { + return 'Downloaded $completed / $total'; + } + + @override + String get mapCache_downloadTilesButton => '타일 다운로드'; + + @override + String get mapCache_clearCacheButton => '캐시 삭제'; + + @override + String mapCache_failedDownloads(int count) { + return '실패한 다운로드: $count'; + } + + @override + String mapCache_boundsLabel( + String north, + String south, + String east, + String west, + ) { + return 'N $north, S $south, E $east, W $west'; + } + + @override + String get time_justNow => '방금'; + + @override + String time_minutesAgo(int minutes) { + return '$minutes분 전'; + } + + @override + String time_hoursAgo(int hours) { + return '${hours}h ago'; + } + + @override + String time_daysAgo(int days) { + return '$days일 전'; + } + + @override + String get time_hour => '시간'; + + @override + String get time_hours => '시간'; + + @override + String get time_day => '하루'; + + @override + String get time_days => '일'; + + @override + String get time_week => '주'; + + @override + String get time_weeks => '몇 주'; + + @override + String get time_month => '달'; + + @override + String get time_months => '개월'; + + @override + String get time_minutes => '분'; + + @override + String get time_allTime => '모든 시간'; + + @override + String get dialog_disconnect => '연결 해제'; + + @override + String get dialog_disconnectConfirm => '이 장치와의 연결을 해제하시겠습니까?'; + + @override + String get login_repeaterLogin => '다시 로그인'; + + @override + String get login_roomLogin => '방 서버 로그인'; + + @override + String get login_password => '비밀번호'; + + @override + String get login_enterPassword => '비밀번호를 입력하세요'; + + @override + String get login_savePassword => '비밀번호 저장'; + + @override + String get login_savePasswordSubtitle => '비밀번호는 이 장치에 안전하게 저장됩니다.'; + + @override + String get login_repeaterDescription => '반복기 비밀번호를 입력하여 설정 및 상태를 확인하십시오.'; + + @override + String get login_roomDescription => '설정 및 상태에 액세스하려면 방 비밀번호를 입력하세요.'; + + @override + String get login_routing => '라우팅'; + + @override + String get login_routingMode => '라우팅 모드'; + + @override + String get login_autoUseSavedPath => '자동 (저장된 경로 사용)'; + + @override + String get login_forceFloodMode => '강수 모드 활성화'; + + @override + String get login_managePaths => '경로 관리'; + + @override + String get login_login => '로그인'; + + @override + String login_attempt(int current, int max) { + return '시도 $current/$max'; + } + + @override + String login_failed(String error) { + return '로그인 실패: $error'; + } + + @override + String get login_failedMessage => + '로그인에 실패했습니다. 비밀번호가 잘못되었거나, 연결이 되지 않는 것 같습니다.'; + + @override + String get common_reload => '다시 로드'; + + @override + String get common_clear => '명확하게'; + + @override + String path_currentPath(String path) { + return '현재 경로: $path'; + } + + @override + String path_usingHopsPath(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'hops', + one: 'hop', + ); + return 'Using $count $_temp0 path'; + } + + @override + String get path_enterCustomPath => '사용자 지정 경로 입력'; + + @override + String get path_currentPathLabel => '현재 경로'; + + @override + String get path_hexPrefixInstructions => + '각 단계에 대한 2자리 헥사데진 접두사를 쉼표로 구분하여 입력하세요.'; + + @override + String get path_hexPrefixExample => + '예시: A1, F2, 3C (각 노드는 자신의 공개 키의 첫 번째 바이트를 사용)'; + + @override + String get path_labelHexPrefixes => '경로 (헥스 접두사)'; + + @override + String get path_helperMaxHops => + '최대 64개의 홉. 각 접두사는 2개의 16진수 문자(1바이트)로 구성됩니다.'; + + @override + String get path_selectFromContacts => '또 연락처 목록에서 선택:'; + + @override + String get path_noRepeatersFound => '반복 장치 또는 서버는 찾을 수 없습니다.'; + + @override + String get path_customPathsRequire => + '사용자 정의 경로에는 메시지를 전달할 수 있는 중간 경로가 필요합니다.'; + + @override + String path_invalidHexPrefixes(String prefixes) { + return '유효하지 않은 16진수 접두사: $prefixes'; + } + + @override + String get path_tooLong => '경로가 너무 길어. 최대 64개의 연결만 허용됩니다.'; + + @override + String get path_setPath => '경로 설정'; + + @override + String get repeater_management => '리피터 관리'; + + @override + String get room_management => '방 서버 관리'; + + @override + String get repeater_managementTools => '관리 도구'; + + @override + String get repeater_status => '상태'; + + @override + String get repeater_statusSubtitle => '반복 장비의 상태, 통계, 및 이웃 장비 목록 보기'; + + @override + String get repeater_telemetry => '텔레메트리'; + + @override + String get repeater_telemetrySubtitle => '센서 및 시스템 상태에 대한 통신 데이터를 확인'; + + @override + String get repeater_cli => '명령줄 인터페이스 (CLI)'; + + @override + String get repeater_cliSubtitle => '리피터에 명령을 전송'; + + @override + String get repeater_neighbors => '이웃'; + + @override + String get repeater_neighborsSubtitle => '0홉 이웃 노드를 확인합니다.'; + + @override + String get repeater_settings => '설정'; + + @override + String get repeater_settingsSubtitle => '리피터 파라미터 설정'; + + @override + String get repeater_statusTitle => '반복 장치 상태'; + + @override + String get repeater_routingMode => '라우팅 방식'; + + @override + String get repeater_autoUseSavedPath => '자동 (저장된 경로 사용)'; + + @override + String get repeater_forceFloodMode => '강수 모드 활성화'; + + @override + String get repeater_pathManagement => '경로 관리'; + + @override + String get repeater_refresh => '새롭게'; + + @override + String get repeater_statusRequestTimeout => '상태 확인 요청이 시간 초과되었습니다.'; + + @override + String repeater_errorLoadingStatus(String error) { + return '상태 로딩 오류: $error'; + } + + @override + String get repeater_systemInformation => '시스템 정보'; + + @override + String get repeater_battery => '배터리'; + + @override + String get repeater_clockAtLogin => '로그인 시 시간 표시'; + + @override + String get repeater_uptime => '가동 시간'; + + @override + String get repeater_queueLength => '대기 줄의 길이'; + + @override + String get repeater_debugFlags => '디버깅 플래그'; + + @override + String get repeater_radioStatistics => '라디오 통계'; + + @override + String get repeater_lastRssi => '마지막 RSSI 값'; + + @override + String get repeater_lastSnr => '마지막 SNR'; + + @override + String get repeater_noiseFloor => '잡음 수준'; + + @override + String get repeater_txAirtime => 'TX 에어타임'; + + @override + String get repeater_rxAirtime => 'RX 에어타임'; + + @override + String get repeater_packetStatistics => '패킷 통계'; + + @override + String get repeater_sent => '발송'; + + @override + String get repeater_received => '수신'; + + @override + String get repeater_duplicates => '중복'; + + @override + String repeater_daysHoursMinsSecs( + int days, + int hours, + int minutes, + int seconds, + ) { + return '$days일 $hours시간 $minutes분 $seconds초'; + } + + @override + String repeater_packetTxTotal(int total, String flood, String direct) { + return '총: $total, 홍수: $flood, 직접: $direct'; + } + + @override + String repeater_packetRxTotal(int total, String flood, String direct) { + return '총: $total, 홍수: $flood, 직접: $direct'; + } + + @override + String repeater_duplicatesFloodDirect(String flood, String direct) { + return '홍수: $flood, 직접: $direct'; + } + + @override + String repeater_duplicatesTotal(int total) { + return '총: $total'; + } + + @override + String get repeater_settingsTitle => '리피터 설정'; + + @override + String get repeater_basicSettings => '기본 설정'; + + @override + String get repeater_repeaterName => '반복 장비 이름'; + + @override + String get repeater_repeaterNameHelper => '이 반복기용 표시 이름'; + + @override + String get repeater_adminPassword => '관리자 비밀번호'; + + @override + String get repeater_adminPasswordHelper => '전체 접근 권한 비밀번호'; + + @override + String get repeater_guestPassword => '게스트 비밀번호'; + + @override + String get repeater_guestPasswordHelper => '읽기 전용 접근 비밀번호'; + + @override + String get repeater_radioSettings => '라디오 설정'; + + @override + String get repeater_frequencyMhz => '주파수 (MHz)'; + + @override + String get repeater_frequencyHelper => '300-2500 MHz'; + + @override + String get repeater_txPower => 'TX 파워'; + + @override + String get repeater_txPowerHelper => '1~30 dBm'; + + @override + String get repeater_bandwidth => '대역폭'; + + @override + String get repeater_spreadingFactor => '분산 계수'; + + @override + String get repeater_codingRate => '코딩 속도'; + + @override + String get repeater_locationSettings => '위치 설정'; + + @override + String get repeater_latitude => '위도'; + + @override + String get repeater_latitudeHelper => '십진법 위도 (예: 37.7749)'; + + @override + String get repeater_longitude => '경도'; + + @override + String get repeater_longitudeHelper => '십진법 위도 (예: -122.4194)'; + + @override + String get repeater_features => '특징'; + + @override + String get repeater_packetForwarding => '패킷 전송'; + + @override + String get repeater_packetForwardingSubtitle => '리피터가 패킷을 전달하도록 설정'; + + @override + String get repeater_guestAccess => '게스트 접근'; + + @override + String get repeater_guestAccessSubtitle => '게스트의 읽기 전용 접근 권한 허용'; + + @override + String get repeater_privacyMode => '개인 정보 보호 모드'; + + @override + String get repeater_privacyModeSubtitle => '광고에 이름/위치 정보 숨기기'; + + @override + String get repeater_advertisementSettings => '광고 설정'; + + @override + String get repeater_localAdvertInterval => '지역 광고 시간 간격'; + + @override + String repeater_localAdvertIntervalMinutes(int minutes) { + return '$minutes 분'; + } + + @override + String get repeater_floodAdvertInterval => '홍수 광고 간격'; + + @override + String repeater_floodAdvertIntervalHours(int hours) { + return '$hours 시간'; + } + + @override + String get repeater_encryptedAdvertInterval => '암호화된 광고 간격'; + + @override + String get repeater_dangerZone => '위험 구역'; + + @override + String get repeater_rebootRepeater => '리부트 반복'; + + @override + String get repeater_rebootRepeaterSubtitle => '리피터 장치를 재시작하세요.'; + + @override + String get repeater_rebootRepeaterConfirm => '반복기를 재부팅하시려는 것이 맞으신가요?'; + + @override + String get repeater_regenerateIdentityKey => '아이디 키 재 생성'; + + @override + String get repeater_regenerateIdentityKeySubtitle => '새로운 공개/개인 키 쌍 생성'; + + @override + String get repeater_regenerateIdentityKeyConfirm => + '이를 통해 리피터에 새로운 식별자를 할당합니다. 계속 진행하시겠습니까?'; + + @override + String get repeater_eraseFileSystem => '파일 시스템 삭제'; + + @override + String get repeater_eraseFileSystemSubtitle => '리피터 파일 시스템을 포맷합니다.'; + + @override + String get repeater_eraseFileSystemConfirm => + '경고: 이 작업은 리피터에 있는 모든 데이터를 삭제합니다. 이 작업을 되돌릴 수 없습니다!'; + + @override + String get repeater_eraseSerialOnly => + '\'Erase\' 기능은 시리얼 콘솔을 통해서만 사용할 수 있습니다.'; + + @override + String repeater_commandSent(String command) { + return '명령 전송: $command'; + } + + @override + String repeater_errorSendingCommand(String error) { + return '명령 전송 오류: $error'; + } + + @override + String get repeater_confirm => '확인'; + + @override + String get repeater_settingsSaved => '설정이 성공적으로 저장되었습니다.'; + + @override + String repeater_errorSavingSettings(String error) { + return '설정 저장 오류: $error'; + } + + @override + String get repeater_refreshBasicSettings => '기본 설정 초기화'; + + @override + String get repeater_refreshRadioSettings => '라디오 설정 초기화'; + + @override + String get repeater_refreshTxPower => 'TX 전원 재설정'; + + @override + String get repeater_refreshLocationSettings => '위치 설정 초기화'; + + @override + String get repeater_refreshPacketForwarding => '패킷 전송 재시작'; + + @override + String get repeater_refreshGuestAccess => '게스트 접근 권한 갱신'; + + @override + String get repeater_refreshPrivacyMode => '개인 정보 보호 모드 재설정'; + + @override + String get repeater_refreshAdvertisementSettings => '광고 설정 재설정'; + + @override + String repeater_refreshed(String label) { + return '$label가 갱신됨'; + } + + @override + String repeater_errorRefreshing(String label) { + return '$label를 새로 고침 중 오류 발생'; + } + + @override + String get repeater_cliTitle => '리피터 CLI'; + + @override + String get repeater_debugNextCommand => '다음 명령 디버깅'; + + @override + String get repeater_commandHelp => '명령 도움'; + + @override + String get repeater_clearHistory => '명확한 역사'; + + @override + String get repeater_noCommandsSent => '아직 명령이 전송되지 않았습니다.'; + + @override + String get repeater_typeCommandOrUseQuick => '아래에 명령어를 입력하거나, 빠른 명령어를 사용하세요.'; + + @override + String get repeater_enterCommandHint => '명령어를 입력하세요...'; + + @override + String get repeater_previousCommand => '이전 명령어'; + + @override + String get repeater_nextCommand => '다음 명령어'; + + @override + String get repeater_enterCommandFirst => '먼저 명령어를 입력하세요'; + + @override + String get repeater_cliCommandFrameTitle => 'CLI 명령어 프레임'; + + @override + String repeater_cliCommandError(String error) { + return '오류: $error'; + } + + @override + String get repeater_cliQuickGetName => '이름을 알려주세요'; + + @override + String get repeater_cliQuickGetRadio => '라디오 듣기'; + + @override + String get repeater_cliQuickGetTx => 'TX 획득'; + + @override + String get repeater_cliQuickNeighbors => '이웃'; + + @override + String get repeater_cliQuickVersion => '버전'; + + @override + String get repeater_cliQuickAdvertise => '광고'; + + @override + String get repeater_cliQuickClock => '시계'; + + @override + String get repeater_cliHelpAdvert => '광고 패킷을 발송'; + + @override + String get repeater_cliHelpReboot => + '장치를 재부팅합니다. (참고: \'시간 초과\' 오류가 발생할 수 있으며, 이는 정상적인 현상입니다)'; + + @override + String get repeater_cliHelpClock => '각 기기의 시계에 표시되는 현재 시간'; + + @override + String get repeater_cliHelpPassword => '장치에 새로운 관리자 비밀번호를 설정합니다.'; + + @override + String get repeater_cliHelpVersion => '장치 버전 및 펌웨어 빌드 날짜를 표시합니다.'; + + @override + String get repeater_cliHelpClearStats => '다양한 통계 지표를 0으로 초기화합니다.'; + + @override + String get repeater_cliHelpSetAf => '에어 타임 요소를 설정합니다.'; + + @override + String get repeater_cliHelpSetTx => + 'LoRa 전송 전력을 dBm 단위로 설정합니다. (설정을 적용하려면 재부팅 필요)'; + + @override + String get repeater_cliHelpSetRepeat => '이 노드에 대한 리피터 역할을 활성화하거나 비활성화합니다.'; + + @override + String get repeater_cliHelpSetAllowReadOnly => + '(방 서버) \'켜짐\' 상태인 경우, 빈 비밀번호로 로그인할 수 있지만, 방에 게시할 수는 없습니다 (단, 읽기만 가능).'; + + @override + String get repeater_cliHelpSetFloodMax => + '들어오는 플러드 패킷의 최대 홉 수를 설정합니다 (최대 홉 수보다 크거나 같으면 패킷은 전달되지 않습니다).'; + + @override + String get repeater_cliHelpSetIntThresh => + '간섭 임계값을 설정합니다 (dB 단위). 기본값은 14입니다. 0으로 설정하면 채널 간섭 감지 기능을 비활성화합니다.'; + + @override + String get repeater_cliHelpSetAgcResetInterval => + '자동 게인 제어기를 재설정하는 간격을 설정합니다. 0으로 설정하면 비활성화됩니다.'; + + @override + String get repeater_cliHelpSetMultiAcks => + '\'더블 ACK\' 기능을 활성화하거나 비활성화할 수 있습니다.'; + + @override + String get repeater_cliHelpSetAdvertInterval => + '로컬 (제로 홉) 광고 패킷을 전송하는 간격 (분 단위)을 설정합니다. 0으로 설정하면 비활성화됩니다.'; + + @override + String get repeater_cliHelpSetFloodAdvertInterval => + '시간 단위로 광고 패킷을 전송하는 간격을 설정합니다. 0으로 설정하면 비활성화됩니다.'; + + @override + String get repeater_cliHelpSetGuestPassword => + '게스트 비밀번호를 설정하거나 업데이트합니다. (반복 사용자, 게스트 로그인 시 \"통계 가져오기\" 요청을 보낼 수 있음)'; + + @override + String get repeater_cliHelpSetName => '광고 이름을 설정합니다.'; + + @override + String get repeater_cliHelpSetLat => '광고 지도의 위도를 설정합니다. (십진법 단위)'; + + @override + String get repeater_cliHelpSetLon => '광고 지도의 경도를 설정합니다. (십진도)'; + + @override + String get repeater_cliHelpSetRadio => + '완전히 새로운 라디오 파라미터를 설정하고, 선호 사항에 저장합니다. 적용하려면 \"재부팅\" 명령이 필요합니다.'; + + @override + String get repeater_cliHelpSetRxDelay => + '(실험용) 기본 설정 (최소 1이어야 함)으로, 수신된 패킷에 약간의 지연을 적용하며, 신호 강도/점수를 기준으로 설정합니다. 0으로 설정하면 비활성화됩니다.'; + + @override + String get repeater_cliHelpSetTxDelay => + '공통 패킷의 전송 지연 시간을 설정하며, 시간-공기 시간과 무작위 슬롯 시스템을 곱하여 충돌 가능성을 줄입니다.'; + + @override + String get repeater_cliHelpSetDirectTxDelay => + 'txdelay와 동일하게, 하지만 직접 모드 패킷 전송 시 무작위 지연을 적용하는 경우'; + + @override + String get repeater_cliHelpSetBridgeEnabled => '브리지 활성화/비활성화'; + + @override + String get repeater_cliHelpSetBridgeDelay => '패킷 재전송 전에 지연 시간을 설정합니다.'; + + @override + String get repeater_cliHelpSetBridgeSource => + '브리지가 수신된 패킷을 다시 전송할지, 아니면 전송된 패킷을 다시 전송할지 선택하십시오.'; + + @override + String get repeater_cliHelpSetBridgeBaud => + 'rs232 브리지에 대한 직렬 통신 속도(baud rate)를 설정합니다.'; + + @override + String get repeater_cliHelpSetBridgeSecret => 'ESPNow 브리지에 대한 비밀 설정'; + + @override + String get repeater_cliHelpSetAdcMultiplier => + '특정 보드에서만 지원되는 방식으로, 보고되는 배터리 전압을 조정하기 위한 사용자 정의 요소를 설정할 수 있습니다.'; + + @override + String get repeater_cliHelpTempRadio => + '주어진 시간(분) 동안 임시 라디오 파라미터를 설정하고, 이후 원래 라디오 파라미터로 되돌립니다. (설정을 저장하지 않습니다).'; + + @override + String get repeater_cliHelpSetPerm => + 'ACL을 수정합니다. \"permissions\" 값이 0인 경우, 일치하는 항목(pubkey 접두사)을 제거합니다. pubkey-hex 길이가 완전하고 현재 ACL에 없는 경우 새로운 항목을 추가합니다. pubkey 접두사를 기준으로 항목을 업데이트합니다. 권한 비트는 펌웨어 역할에 따라 다르지만, 하위 2비트는 다음과 같습니다: 0 (게스트), 1 (읽기 전용), 2 (읽기/쓰기), 3 (관리자)'; + + @override + String get repeater_cliHelpGetBridgeType => '브리지형, RS232, ESPNOW 지원'; + + @override + String get repeater_cliHelpLogStart => '패킷 로깅을 파일 시스템으로 시작합니다.'; + + @override + String get repeater_cliHelpLogStop => '패킷 로깅을 파일 시스템으로 저장하는 것을 중단합니다.'; + + @override + String get repeater_cliHelpLogErase => '파일 시스템에서 패킷 로그를 삭제합니다.'; + + @override + String get repeater_cliHelpNeighbors => + '제로 홉 광고를 통해 수신된 다른 리피터 노드 목록을 보여줍니다. 각 줄은 ID-프리픽스-16진수:타임스탬프:SNR-횟수-4 형식입니다.'; + + @override + String get repeater_cliHelpNeighborRemove => + '이 함수는 지정된 pubkey 접두사(16진수)와 일치하는 첫 번째 항목을 이웃 목록에서 제거합니다.'; + + @override + String get repeater_cliHelpRegion => + '(단일 시리즈) 정의된 모든 지역과 현재 홍수 허가 정보를 나열합니다.'; + + @override + String get repeater_cliHelpRegionLoad => + '참고: 이는 여러 명령을 한 번에 실행하는 특별한 방식입니다. 각 후속 명령은 영역 이름이며 (부모 계층 구조를 나타내기 위해 공백으로 들여쓰기하며, 최소 1개의 공백을 사용) 공백으로 끝나는 줄 또는 명령을 보내어 종료합니다.'; + + @override + String get repeater_cliHelpRegionGet => + '주어진 이름 접두사(또는 전역 검색을 위한 \"\\*\" 사용)를 사용하여 특정 지역을 검색합니다. 결과를 \"-> 지역 이름 (상위 지역 이름) \'F\'\" 형태로 반환합니다.'; + + @override + String get repeater_cliHelpRegionPut => '주어진 이름으로 지역 정의를 추가하거나 업데이트합니다.'; + + @override + String get repeater_cliHelpRegionRemove => + '지정된 이름으로 특정 영역 정의를 제거합니다. (정확히 일치해야 하며, 하위 영역은 존재하지 않아야 합니다)'; + + @override + String get repeater_cliHelpRegionAllowf => + '지정된 영역에 대한 \'물\' 접근 권한을 설정합니다. (\'*\'는 전역/기존 범위에 해당)'; + + @override + String get repeater_cliHelpRegionDenyf => + '지정된 영역에 대해 \'Flood\' 권한을 제거합니다. (참고: 현재 단계에서는 전역/기존 범위에서 이 기능을 사용하지 않는 것이 좋습니다!!)'; + + @override + String get repeater_cliHelpRegionHome => + '현재 \'홈\' 지역으로 응답합니다. (아직 적용되지 않았으며, 향후 사용을 위해 예약됨)'; + + @override + String get repeater_cliHelpRegionHomeSet => '\'홈\' 지역을 설정합니다.'; + + @override + String get repeater_cliHelpRegionSave => '지역 목록/지도를 저장에 유지합니다.'; + + @override + String get repeater_cliHelpGps => + 'GPS 상태를 표시합니다. GPS가 꺼져 있으면 \"꺼짐\"이라고 표시하고, 켜져 있으면 \"켜짐\", 상태, 위치 정보, 위성 수 등을 표시합니다.'; + + @override + String get repeater_cliHelpGpsOnOff => 'GPS 전원 상태를 켜고 끄는 기능.'; + + @override + String get repeater_cliHelpGpsSync => '노드 시간을 GPS 시계와 동기화합니다.'; + + @override + String get repeater_cliHelpGpsSetLoc => '노드의 위치를 GPS 좌표로 설정하고, 설정을 저장합니다.'; + + @override + String get repeater_cliHelpGpsAdvert => + '노드의 위치 광고 설정:\n- none: 광고에 위치 정보를 포함하지 않음\n- share: GPS 위치 정보를 공유 (SensorManager에서 가져옴)\n- prefs: 설정에 저장된 위치를 광고'; + + @override + String get repeater_cliHelpGpsAdvertSet => '위치 기반 광고 설정 구성'; + + @override + String get repeater_commandsListTitle => '명령 목록'; + + @override + String get repeater_commandsListNote => + '참고: 다양한 \"set...\" 명령과 함께 \"get...\" 명령도 존재합니다.'; + + @override + String get repeater_general => '일반'; + + @override + String get repeater_settingsCategory => '설정'; + + @override + String get repeater_bridge => '다리'; + + @override + String get repeater_logging => '로깅'; + + @override + String get repeater_neighborsRepeaterOnly => '이웃 (단방향 통신만 지원)'; + + @override + String get repeater_regionManagementRepeaterOnly => '지역 관리 (단, 중계 기능만 사용)'; + + @override + String get repeater_regionNote => + '지역별 관리 기능을 도입하여 지역 정의 및 권한 관리를 수행할 수 있습니다.'; + + @override + String get repeater_gpsManagement => 'GPS 관리'; + + @override + String get repeater_gpsNote => 'GPS 명령이 위치 관련 주제를 관리하기 위해 도입되었습니다.'; + + @override + String get telemetry_receivedData => '수신된 통신 데이터'; + + @override + String get telemetry_requestTimeout => '원격 모니터링 요청이 시간 초과되었습니다.'; + + @override + String telemetry_errorLoading(String error) { + return '$error 오류로 인해 통신 데이터를 로드하지 못했습니다.'; + } + + @override + String get telemetry_noData => '텔레메트리 데이터는 제공되지 않습니다.'; + + @override + String telemetry_channelTitle(int channel) { + return '채널 $channel'; + } + + @override + String get telemetry_batteryLabel => '배터리'; + + @override + String get telemetry_voltageLabel => '전압'; + + @override + String get telemetry_mcuTemperatureLabel => 'MCU의 온도'; + + @override + String get telemetry_temperatureLabel => '온도'; + + @override + String get telemetry_currentLabel => '현재'; + + @override + String telemetry_batteryValue(int percent, String volts) { + return '$percent% / ${volts}V'; + } + + @override + String telemetry_voltageValue(String volts) { + return '${volts}V'; + } + + @override + String telemetry_currentValue(String amps) { + return '${amps}A'; + } + + @override + String telemetry_temperatureValue(String celsius, String fahrenheit) { + 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_repeatersNeighbors => '반복기, 이웃'; + + @override + String get neighbors_noData => '이웃 정보는 없습니다.'; + + @override + String neighbors_unknownContact(String pubkey) { + return '알 수 없는 $pubkey'; + } + + @override + String neighbors_heardAgo(String time) { + return 'Heard: $time ago'; + } + + @override + String get channelPath_title => '패킷 경로'; + + @override + String get channelPath_viewMap => '지도 보기'; + + @override + String get channelPath_otherObservedPaths => '관찰된 다른 경로'; + + @override + String get channelPath_repeaterHops => '반복 홉'; + + @override + String get channelPath_noHopDetails => '이 패키지에 대한 자세한 정보는 제공되지 않습니다.'; + + @override + String get channelPath_messageDetails => '메시지 세부 정보'; + + @override + String get channelPath_senderLabel => '발신자'; + + @override + String get channelPath_timeLabel => '시간'; + + @override + String get channelPath_repeatsLabel => '반복'; + + @override + String channelPath_pathLabel(int index) { + return '경로 $index'; + } + + @override + String get channelPath_observedLabel => '관찰'; + + @override + String channelPath_observedPathTitle(int index, String hops) { + return '관찰된 경로 $index • $hops'; + } + + @override + String get channelPath_noLocationData => '위치 정보 없음'; + + @override + String channelPath_timeWithDate(int day, int month, String time) { + return '$day/$month $time'; + } + + @override + String channelPath_timeOnly(String time) { + return '$time'; + } + + @override + String get channelPath_unknownPath => '알 수 없음'; + + @override + String get channelPath_floodPath => '홍수'; + + @override + String get channelPath_directPath => '직접'; + + @override + String channelPath_observedZeroOf(int total) { + return '$total 중 0개'; + } + + @override + String channelPath_observedSomeOf(int observed, int total) { + return '$observed of $total hops'; + } + + @override + String get channelPath_mapTitle => '경로 지도'; + + @override + String get channelPath_noRepeaterLocations => '이 경로에 대한 중계기 설치 위치는 없습니다.'; + + @override + String channelPath_primaryPath(int index) { + return '경로 $index (주 경로)'; + } + + @override + String get channelPath_pathLabelTitle => '경로'; + + @override + String get channelPath_observedPathHeader => '관찰된 경로'; + + @override + String channelPath_selectedPathLabel(String label, String prefixes) { + return '$label • $prefixes'; + } + + @override + String get channelPath_noHopDetailsAvailable => '이 패킷에 대한 이동 정보는 제공되지 않습니다.'; + + @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 => '필터링 및 정렬'; + + @override + String get listFilter_sortBy => '정렬 기준 선택'; + + @override + String get listFilter_latestMessages => '최신 메시지'; + + @override + String get listFilter_heardRecently => '최근에 들었습니다'; + + @override + String get listFilter_az => 'A부터 Z까지'; + + @override + String get listFilter_filters => '필터'; + + @override + String get listFilter_all => '모든'; + + @override + String get listFilter_favorites => '관심 목록'; + + @override + String get listFilter_addToFavorites => '즐겨찾으로 추가'; + + @override + String get listFilter_removeFromFavorites => '즐겨찾에서 제거'; + + @override + String get listFilter_users => '사용자'; + + @override + String get listFilter_repeaters => '다시 보내는 장치'; + + @override + String get listFilter_roomServers => '방 내 서버'; + + @override + String get listFilter_unreadOnly => '읽지 않은 항목만'; + + @override + String get listFilter_newGroup => '새로운 그룹'; + + @override + String get pathTrace_you => '당신'; + + @override + String get pathTrace_failed => '경로 추적 실패.'; + + @override + String get pathTrace_notAvailable => '경로 추적 기능은 제공되지 않습니다.'; + + @override + String get pathTrace_refreshTooltip => '경로 추적 재시작'; + + @override + String get pathTrace_someHopsNoLocation => '홉 중 하나 또는 여러 개에 위치 정보가 누락되었습니다!'; + + @override + String get pathTrace_clearTooltip => '명확한 경로.'; + + @override + String get losSelectStartEnd => 'LOS(최소 거리 경로)의 시작 및 종료 노드를 선택합니다.'; + + @override + String losRunFailed(String error) { + return '시야 확인 실패: $error'; + } + + @override + String get losClearAllPoints => '모든 사항을 명확히 합니다.'; + + @override + String get losRunToViewElevationProfile => + 'LOS(Line of Sight)를 사용하여 고도 프로필을 확인합니다.'; + + @override + String get losMenuTitle => 'LOS 메뉴'; + + @override + String get losMenuSubtitle => '사용자 지정 지점을 추가하려면, 노드를 탭하거나 맵을 길게 눌러 주세요.'; + + @override + String get losShowDisplayNodes => '노드 표시'; + + @override + String get losCustomPoints => '사용자 지정 포인트'; + + @override + String losCustomPointLabel(int index) { + return '맞춤형 $index'; + } + + @override + String get losPointA => 'A 지점'; + + @override + String get losPointB => '점 B'; + + @override + String losAntennaA(String value, String unit) { + return '안테나 A: $value $unit'; + } + + @override + String losAntennaB(String value, String unit) { + return 'Antenna B: $value $unit'; + } + + @override + String get losRun => 'LOS (Loss of Signal) 상태로 전환'; + + @override + String get losNoElevationData => '고도 정보 없음'; + + @override + String losProfileClear( + String distance, + String distanceUnit, + String clearance, + String heightUnit, + ) { + return '$distance $distanceUnit, clear LOS, min clearance $clearance $heightUnit'; + } + + @override + String losProfileBlocked( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit, blocked by $obstruction $heightUnit'; + } + + @override + String get losStatusChecking => 'LOS: 확인 중...'; + + @override + String get losStatusNoData => 'LOS: 데이터 없음'; + + @override + String losStatusSummary(int clear, int total, int blocked, int unknown) { + return 'LOS: $clear/$total 개, $blocked 개, $unknown 개'; + } + + @override + String get losErrorElevationUnavailable => '샘플 중 하나 이상에 대한 고도 데이터가 없습니다.'; + + @override + String get losErrorInvalidInput => 'LOS 계산에 사용되는 부정확한 지점/고도 데이터.'; + + @override + String get losRenameCustomPoint => '사용자 지정된 지점의 이름을 변경'; + + @override + String get losPointName => '항목 이름'; + + @override + String get losShowPanelTooltip => 'LOS 패널 표시'; + + @override + String get losHidePanelTooltip => 'LOS 패널 숨기기'; + + @override + String get losElevationAttribution => '고도 데이터: Open-Meteo (CC BY 4.0)'; + + @override + String get losLegendRadioHorizon => '라디오 호라이즌'; + + @override + String get losLegendLosBeam => 'LOS 빔'; + + @override + String get losLegendTerrain => '지형'; + + @override + String get losFrequencyLabel => '빈도'; + + @override + String get losFrequencyInfoTooltip => '계산 내역 보기'; + + @override + String get losFrequencyDialogTitle => '라디오 수신 가능 범위 계산'; + + @override + String losFrequencyDialogDescription( + double baselineK, + double baselineFreq, + double frequencyMHz, + double kFactor, + ) { + return '$baselineK에서 시작하여 $baselineFreq MHz의 주파수에서 계산을 시작하면, 현재 $frequencyMHz MHz 대역에 대한 k-값을 조정하여, 이는 곡선형 라디오 지평선 상한선을 정의합니다.'; + } + + @override + String get contacts_pathTrace => '경로 추적'; + + @override + String get contacts_ping => '핑'; + + @override + String get contacts_repeaterPathTrace => '리피터로 가는 경로'; + + @override + String get contacts_repeaterPing => '핑 반복'; + + @override + String get contacts_roomPathTrace => '방 서버로의 경로 추적'; + + @override + String get contacts_roomPing => '피нг 룸 서버'; + + @override + String get contacts_chatTraceRoute => '경로 추적 경로'; + + @override + String contacts_pathTraceTo(String name) { + return '$name까지의 경로 추적'; + } + + @override + String get contacts_clipboardEmpty => '클립보드가 비어 있습니다.'; + + @override + String get contacts_invalidAdvertFormat => '유효하지 않은 연락 정보'; + + @override + String get contacts_contactImported => '연락이 수신되었습니다.'; + + @override + String get contacts_contactImportFailed => '연락처를 가져오지 못했습니다.'; + + @override + String get contacts_zeroHopAdvert => '제로 홉 광고'; + + @override + String get contacts_floodAdvert => '홍수 광고'; + + @override + String get contacts_copyAdvertToClipboard => '광고 텍스트를 클립보드에 복사'; + + @override + String get contacts_addContactFromClipboard => '복사본에서 연락처 추가'; + + @override + String get contacts_ShareContact => '연락처를 복사'; + + @override + String get contacts_ShareContactZeroHop => '광고를 통해 연락처 공유'; + + @override + String get contacts_zeroHopContactAdvertSent => '광고를 통해 연락처를 받았습니다.'; + + @override + String get contacts_zeroHopContactAdvertFailed => '연락처 전송에 실패했습니다.'; + + @override + String get contacts_contactAdvertCopied => '광고 내용이 복사되었습니다.'; + + @override + String get contacts_contactAdvertCopyFailed => '광고를 클립보드에 복사하는 데 실패했습니다.'; + + @override + String get notification_activityTitle => '메쉬코어 활동'; + + @override + String notification_messagesCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '메시지들', + one: '메시지', + ); + return '$count $_temp0'; + } + + @override + String notification_channelMessagesCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '채널 메시지', + one: '채널 메시지', + ); + return '$count $_temp0'; + } + + @override + String notification_newNodesCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '새 노드들', + one: '새 노드', + ); + return '$count $_temp0'; + } + + @override + String notification_newTypeDiscovered(String contactType) { + return '새로운 $contactType 발견'; + } + + @override + String get notification_receivedNewMessage => '새로운 메시지를 받았습니다'; + + @override + String get settings_gpxExportRepeaters => 'GPX로 전송/방 관리 서버'; + + @override + String get settings_gpxExportRepeatersSubtitle => + 'GPX 파일에 위치 정보를 포함하여 반복자/룸 서버를 내보냅니다.'; + + @override + String get settings_gpxExportContacts => 'GPX 형식으로 내보내기'; + + @override + String get settings_gpxExportContactsSubtitle => + 'GPX 파일에 위치 정보를 포함하여 동행하는 기능을 내보냅니다.'; + + @override + String get settings_gpxExportAll => '모든 연락처를 GPX 형식으로 내보내기'; + + @override + String get settings_gpxExportAllSubtitle => + '위치 정보가 있는 모든 연락처를 GPX 파일로 내보냅니다.'; + + @override + String get settings_gpxExportSuccess => 'GPX 파일이 성공적으로 내보내졌습니다.'; + + @override + String get settings_gpxExportNoContacts => '수출할 연락처가 없습니다.'; + + @override + String get settings_gpxExportNotAvailable => '귀하의 장치/운영체제에서는 지원되지 않습니다.'; + + @override + String get settings_gpxExportError => '데이터 내보내기 과정에서 오류가 발생했습니다.'; + + @override + String get settings_gpxExportRepeatersRoom => '중계 장치 및 서버 위치'; + + @override + String get settings_gpxExportChat => '함께 방문할 장소'; + + @override + String get settings_gpxExportAllContacts => '모든 연락처 위치'; + + @override + String get settings_gpxExportShareText => 'meshcore-open에서 추출한 지도 데이터'; + + @override + String get settings_gpxExportShareSubject => 'meshcore-open GPX 지도 데이터 내보내기'; + + @override + String get snrIndicator_nearByRepeaters => '주변의 중계기'; + + @override + String get snrIndicator_lastSeen => '마지막으로 목격'; + + @override + String get contactsSettings_title => '연락처 설정'; + + @override + String get contactsSettings_autoAddTitle => '자동 검색'; + + @override + String get contactsSettings_otherTitle => '다른 연락 관련 설정'; + + @override + String get contactsSettings_autoAddUsersTitle => '자동으로 사용자 추가'; + + @override + String get contactsSettings_autoAddUsersSubtitle => + '동반자가 자동으로 발견한 사용자를 추가할 수 있도록 합니다.'; + + @override + String get contactsSettings_autoAddRepeatersTitle => '자동으로 중계기 추가'; + + @override + String get contactsSettings_autoAddRepeatersSubtitle => + '애완동물이 발견한 무선 라디오를 자동으로 추가할 수 있도록 설정합니다.'; + + @override + String get contactsSettings_autoAddRoomServersTitle => '자동으로 방 서버 추가'; + + @override + String get contactsSettings_autoAddRoomServersSubtitle => + '애완동물이 발견한 방 서버를 자동으로 추가할 수 있도록 설정합니다.'; + + @override + String get contactsSettings_autoAddSensorsTitle => '자동으로 센서 추가'; + + @override + String get contactsSettings_autoAddSensorsSubtitle => + '애완동물이 발견한 센서를 자동으로 추가할 수 있도록 설정합니다.'; + + @override + String get contactsSettings_overwriteOldestTitle => '가장 오래된 것을 덮어쓰기'; + + @override + String get contactsSettings_overwriteOldestSubtitle => + '연락처 목록이 가득 차면, 가장 오래된 (선호하지 않은) 연락처가 대체됩니다.'; + + @override + String get discoveredContacts_Title => '연락처 찾기'; + + @override + String get discoveredContacts_noMatching => '일치하는 연락처가 없습니다.'; + + @override + String get discoveredContacts_searchHint => '발견된 연락처 검색'; + + @override + String get discoveredContacts_contactAdded => '연락처 추가'; + + @override + String get discoveredContacts_addContact => '연락처 추가'; + + @override + String get discoveredContacts_copyContact => '복사'; + + @override + String get discoveredContacts_deleteContact => '발견된 연락처 삭제'; + + @override + String get discoveredContacts_deleteContactAll => '발견된 모든 연락처 삭제'; + + @override + String get discoveredContacts_deleteContactAllContent => + '정말로 모든 검색된 연락처를 삭제하시겠습니까?'; + + @override + String get chat_sendCooldown => '다시 보내기 전에 잠시 기다려 주시기 바랍니다.'; + + @override + String get appSettings_jumpToOldestUnread => '가장 오래된, 아직 읽지 않은 항목으로 이동'; + + @override + String get appSettings_jumpToOldestUnreadSubtitle => + '새로운 메시지가 없는 채팅을 열 때, 최신 메시지가 아닌 첫 번째 읽지 않은 메시지로 스크롤하세요.'; + + @override + String get appSettings_languageHu => '헝가리'; + + @override + String get appSettings_languageJa => '일본어'; + + @override + String get appSettings_languageKo => '한국어'; + + @override + String get radioStats_tooltip => '라디오 및 메시 통계'; + + @override + String get radioStats_screenTitle => '라디오 통계'; + + @override + String get radioStats_notConnected => '라디오 통계를 확인하기 위해 장치에 연결합니다.'; + + @override + String get radioStats_firmwareTooOld => + '무선 통계 기능을 사용하려면 v8 또는 그 이상의 호환 펌웨어가 필요합니다.'; + + @override + String get radioStats_waiting => '데이터를 기다리는 중…'; + + @override + String radioStats_noiseFloor(int noiseDbm) { + return '잡음 수준: $noiseDbm dBm'; + } + + @override + String radioStats_lastRssi(int rssiDbm) { + return '마지막 RSSI: $rssiDbm dBm'; + } + + @override + String radioStats_lastSnr(String snr) { + return '마지막 SNR: $snr dB'; + } + + @override + String radioStats_txAir(int seconds) { + return 'TX 방송 시간 (총): $seconds 초'; + } + + @override + String radioStats_rxAir(int seconds) { + return 'RX 사용 시간 (총): $seconds 초'; + } + + @override + String get radioStats_chartCaption => '최근 샘플의 잡음 수준 (dBm)'; + + @override + String radioStats_stripNoise(int noiseDbm) { + return '잡음 수준: $noiseDbm dBm'; + } + + @override + String get radioStats_stripWaiting => '라디오 통계 가져오기…'; + + @override + String get radioStats_settingsTile => '라디오 통계'; + + @override + String get radioStats_settingsSubtitle => '잡음 수준, RSSI, 신호 대 잡음비, 통신 시간'; +} diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index b1a58c1..946c26f 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -744,42 +744,42 @@ class AppLocalizationsPl extends AppLocalizations { 'Automatyczne obracanie tras wyłączone'; @override - String get appSettings_maxRouteWeight => 'Maksymalna waga ścieżki'; + String get appSettings_maxRouteWeight => + 'Maksymalny dopuszczalny ciężar pojazdu'; @override String get appSettings_maxRouteWeightSubtitle => - 'Maksymalna waga, jaką ścieżka może osiągnąć dzięki udanym dostarczeniom'; + 'Maksymalna waga, jaką ścieżka może zgromadzić dzięki udanym dostawom.'; @override - String get appSettings_initialRouteWeight => 'Początkowa waga ścieżki'; + String get appSettings_initialRouteWeight => 'Początkowa waga trasy'; @override String get appSettings_initialRouteWeightSubtitle => - 'Waga początkowa dla nowo odkrytych ścieżek'; + 'Początkowa waga dla nowych, odkrytych ścieżek'; @override - String get appSettings_routeWeightSuccessIncrement => - 'Przyrost wagi po sukcesie'; + String get appSettings_routeWeightSuccessIncrement => 'Wzrost wagi sukcesu'; @override String get appSettings_routeWeightSuccessIncrementSubtitle => - 'Waga dodawana do ścieżki po udanym dostarczeniu'; + 'Waga dodana do ścieżki po pomyślnym dostarczeniu'; @override String get appSettings_routeWeightFailureDecrement => - 'Spadek wagi po niepowodzeniu'; + 'Zmniejszenie wagi kary'; @override String get appSettings_routeWeightFailureDecrementSubtitle => - 'Waga odejmowana od ścieżki po nieudanym dostarczeniu'; + 'Waga usunięta z trasy po nieudanej dostawie'; @override String get appSettings_maxMessageRetries => - 'Maksymalna liczba ponowień wiadomości'; + 'Maksymalna liczba prób wysłania wiadomości'; @override String get appSettings_maxMessageRetriesSubtitle => - 'Liczba prób ponowienia przed oznaczeniem wiadomości jako nieudanej'; + 'Liczba prób ponownego wysłania wiadomości przed oznaczaniem jej jako nieudanej'; @override String path_routeWeight(String weight, String max) { diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 953a89b..72cbc7d 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -3501,7 +3501,7 @@ class AppLocalizationsSl extends AppLocalizations { @override String get radioStats_firmwareTooOld => - 'Statistika za radio zahteva združljivo programsko opremo v8 ali kasnejše različice.'; + 'Statistika za radio zahteva združljivo programsko opremo v8 ali kasnejše.'; @override String get radioStats_waiting => 'Čakam na podatke…'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index de334f6..66981e1 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -3227,7 +3227,7 @@ class AppLocalizationsZh extends AppLocalizations { String get chat_sendCooldown => '请稍等片刻后再尝试发送。'; @override - String get appSettings_jumpToOldestUnread => '跳转到最旧未读的文章'; + String get appSettings_jumpToOldestUnread => '跳转到最旧、未读的文章'; @override String get appSettings_jumpToOldestUnreadSubtitle => diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 43c08b1..0288e70 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -1943,5 +1943,68 @@ "settings_telemetryModeUpdated": "Telemetrie-modus bijgewerkt", "settings_multiAck": "Multi-ACKs: {value}", "map_showOverlaps": "Herhalingssleutel overlapt", - "map_runTraceWithReturnPath": "Terugkeren op hetzelfde pad." -} \ No newline at end of file + "map_runTraceWithReturnPath": "Terugkeren op hetzelfde pad.", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "appSettings_jumpToOldestUnread": "Ga naar het oudste ongelezen bericht", + "appSettings_jumpToOldestUnreadSubtitle": "Bij het openen van een chat met ongelezen berichten, scroll dan naar het eerste ongelezen bericht, in plaats van naar het meest recente.", + "chat_sendCooldown": "Gelieve even te wachten voordat u opnieuw verzendt.", + "appSettings_languageHu": "Hongaars", + "appSettings_languageJa": "Japanisch", + "appSettings_languageKo": "Koreaans", + "radioStats_tooltip": "Statistieken voor radio en mesh-netwerken", + "radioStats_screenTitle": "Statistieken over radio", + "radioStats_notConnected": "Verbind met een apparaat om radio-statistieken te bekijken.", + "radioStats_firmwareTooOld": "Om de statistieken via radio te kunnen gebruiken, is firmware versie 8 of een nieuwere vereist.", + "radioStats_waiting": "Wacht op gegevens…", + "radioStats_noiseFloor": "Ruisfrequentie: {noiseDbm} dBm", + "radioStats_lastRssi": "Laatste RSSI-waarde: {rssiDbm} dBm", + "radioStats_lastSnr": "Laatste SNR: {snr} dB", + "radioStats_txAir": "TX-tijd (totaal): {seconds} s", + "radioStats_rxAir": "Tijd besteed met RX (totaal): {seconds} s", + "radioStats_chartCaption": "Ruisfrequentie (dBm) over recente metingen.", + "radioStats_stripNoise": "Ruisfrequentie: {noiseDbm} dBm", + "radioStats_stripWaiting": "Radio-statistieken ophalen…", + "radioStats_settingsTile": "Statistieken over radio", + "radioStats_settingsSubtitle": "Ruimtelijke ruis, RSSI, SNR en beschikbare tijd" +} diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 27c4fc1..234fa3e 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1981,5 +1981,68 @@ "settings_telemetryModeUpdated": "Tryb telemetryczny zaktualizowany", "settings_multiAck": "Wiele potwierdzeń: {value}", "map_showOverlaps": "Nakładające się klucze powtarzalne", - "map_runTraceWithReturnPath": "Wróć z powrotem tą samą ścieżką" + "map_runTraceWithReturnPath": "Wróć z powrotem tą samą ścieżką", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "appSettings_languageHu": "Węgierski", + "appSettings_jumpToOldestUnreadSubtitle": "Przy otwieraniu czatu z nieodczytanymi wiadomościami, przewijaj, aby przejść do pierwszej nieodczytanej wiadomości, zamiast do najnowszej.", + "appSettings_jumpToOldestUnread": "Przejdź do najstarszego nieodczytanej wiadomości", + "chat_sendCooldown": "Prosimy o chwilowe oczekiwanie przed ponownym wysłaniem.", + "appSettings_languageJa": "Japoński", + "appSettings_languageKo": "Koreański", + "radioStats_tooltip": "Statystyki dotyczące radia i siatki", + "radioStats_screenTitle": "Statystyki radiowe", + "radioStats_notConnected": "Połącz się z urządzeniem, aby wyświetlić statystyki radiowe.", + "radioStats_firmwareTooOld": "Statystyki radiowe wymagają towarzyszącej oprogramowania w wersji 8 lub nowszej.", + "radioStats_waiting": "Czekam na dane…", + "radioStats_noiseFloor": "Poziom szumów: {noiseDbm} dBm", + "radioStats_lastRssi": "Ostatni poziom RSSI: {rssiDbm} dBm", + "radioStats_lastSnr": "Ostatni poziom SNR: {snr} dB", + "radioStats_txAir": "Czas emisji w stacji TX (całkowity): {seconds} s", + "radioStats_rxAir": "Czas wykorzystania kanału RX (całkowity): {seconds} s", + "radioStats_chartCaption": "Poziom szumów (dBm) w ostatnich próbkach.", + "radioStats_stripNoise": "Poziom szumów: {noiseDbm} dBm", + "radioStats_stripWaiting": "Pobieranie danych dotyczących radia…", + "radioStats_settingsTile": "Statystyki radiowe", + "radioStats_settingsSubtitle": "Szum tła, RSSI, SNR oraz czas dostępny" } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 1ee4130..1ed3dd3 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -1943,5 +1943,68 @@ "settings_telemetryModeUpdated": "Modo de telemetria atualizado", "settings_multiAck": "Multi-ACKs: {value}", "map_showOverlaps": "Sobreposições da Chave Repeater", - "map_runTraceWithReturnPath": "Retornar ao mesmo caminho." -} \ No newline at end of file + "map_runTraceWithReturnPath": "Retornar ao mesmo caminho.", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "appSettings_jumpToOldestUnread": "Vá para a mensagem mais antiga não lida", + "chat_sendCooldown": "Por favor, aguarde um momento antes de reenviar.", + "appSettings_languageHu": "Húngaro", + "appSettings_jumpToOldestUnreadSubtitle": "Ao abrir uma conversa com mensagens não lidas, role para a primeira mensagem não lida, em vez da mais recente.", + "appSettings_languageJa": "Japonês", + "appSettings_languageKo": "Coreano", + "radioStats_tooltip": "Estatísticas de rádio e malha", + "radioStats_screenTitle": "Estatísticas de rádio", + "radioStats_notConnected": "Conecte-se a um dispositivo para visualizar estatísticas de rádio.", + "radioStats_firmwareTooOld": "As estatísticas de rádio exigem o firmware v8 ou uma versão mais recente.", + "radioStats_waiting": "Aguardando dados…", + "radioStats_noiseFloor": "Nível de ruído: {noiseDbm} dBm", + "radioStats_lastRssi": "Último RSSI: {rssiDbm} dBm", + "radioStats_lastSnr": "Último SNR: {snr} dB", + "radioStats_txAir": "Tempo de transmissão da TX (total): {seconds} s", + "radioStats_rxAir": "Tempo de uso do RX (total): {seconds} s", + "radioStats_chartCaption": "Nível de ruído (dBm) em amostras recentes.", + "radioStats_stripNoise": "Nível de ruído: {noiseDbm} dBm", + "radioStats_stripWaiting": "Obtendo estatísticas de rádio…", + "radioStats_settingsTile": "Estatísticas de rádio", + "radioStats_settingsSubtitle": "Nível de ruído, RSSI, SNR e tempo de transmissão" +} diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index ab32362..7b40819 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -1183,5 +1183,68 @@ "settings_telemetryModeUpdated": "Режим телеметрии обновлен", "settings_multiAck": "Мульти-ACK: {value}", "map_showOverlaps": "Перекрытия ключа повтора", - "map_runTraceWithReturnPath": "Вернуться обратно по тому же пути" -} \ No newline at end of file + "map_runTraceWithReturnPath": "Вернуться обратно по тому же пути", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "chat_sendCooldown": "Пожалуйста, подождите немного, прежде чем отправлять сообщение снова.", + "appSettings_jumpToOldestUnread": "Перейти к самому старому непрочитанному сообщению", + "appSettings_languageHu": "Венгерский", + "appSettings_jumpToOldestUnreadSubtitle": "При открытии чата с непрочитанными сообщениями, прокрутите страницу, чтобы увидеть первое непрочитанное сообщение, а не последнее.", + "appSettings_languageJa": "Японский", + "appSettings_languageKo": "Корейский", + "radioStats_tooltip": "Статистика радио и беспроводной сети", + "radioStats_screenTitle": "Статистика радиовещания", + "radioStats_notConnected": "Подключитесь к устройству, чтобы просмотреть статистику радио.", + "radioStats_firmwareTooOld": "Для работы радиостатистики требуется установленная версия прошивки v8 или более новая.", + "radioStats_waiting": "Ожидаем данных…", + "radioStats_noiseFloor": "Уровень шума: {noiseDbm} дБм", + "radioStats_lastRssi": "Последнее значение RSSI: {rssiDbm} дБм", + "radioStats_lastSnr": "Последнее значение SNR: {snr} дБ", + "radioStats_txAir": "Время эфира на телеканале TX (общее): {seconds} секунд", + "radioStats_rxAir": "Общее время использования RX (в секундах): {seconds} с", + "radioStats_chartCaption": "Уровень шума (дБм) на основе последних измерений.", + "radioStats_stripNoise": "Уровень шума: {noiseDbm} дБм", + "radioStats_stripWaiting": "Получение данных о радио…", + "radioStats_settingsTile": "Статистика радиовещания", + "radioStats_settingsSubtitle": "Уровень шума, RSSI, SNR и время передачи" +} diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 12c2f9a..59303ba 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -1943,5 +1943,68 @@ "settings_telemetryModeUpdated": "Režim telemetrie bol aktualizovaný", "settings_multiAck": "Viaceré ACK: {value}", "map_showOverlaps": "Prekrývanie opakovača kľúča", - "map_runTraceWithReturnPath": "Vráťte sa späť po tej istej ceste." -} \ No newline at end of file + "map_runTraceWithReturnPath": "Vráťte sa späť po tej istej ceste.", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "chat_sendCooldown": "Prosím, počkajte chvíľu, než zašlete znova.", + "appSettings_jumpToOldestUnread": "Presk oceň", + "appSettings_jumpToOldestUnreadSubtitle": "Pri otvorení chatu s neprečítanými správami, prejdite do prvého neprečítaného, namiesto poslednej.", + "appSettings_languageHu": "Maďarský", + "appSettings_languageJa": "Japonský", + "appSettings_languageKo": "Kórejský", + "radioStats_tooltip": "Statistiky rádiových a sieťových kanálov", + "radioStats_screenTitle": "Štatistiky rádiových vysielaní", + "radioStats_notConnected": "Pripojte sa k zariadeniu, aby ste mohli sledovať štatistiky rádiového vysielania.", + "radioStats_firmwareTooOld": "Statistické údaje z rádia vyžadujú sprievodný softvér verzie v8 alebo novšej.", + "radioStats_waiting": "Čakám na údaje…", + "radioStats_noiseFloor": "Úroveň hluku: {noiseDbm} dBm", + "radioStats_lastRssi": "Posledný údaj RSSI: {rssiDbm} dBm", + "radioStats_lastSnr": "Posledná hodnota SNR: {snr} dB", + "radioStats_txAir": "Čas vysielania na TX (celkový): {seconds} s", + "radioStats_rxAir": "Čas RX (celkový): {seconds} s", + "radioStats_chartCaption": "Úroveň šumu (dBm) pre posledné vzorky.", + "radioStats_stripNoise": "Úroveň hluku: {noiseDbm} dBm", + "radioStats_stripWaiting": "Získavanie údajov o rádiu…", + "radioStats_settingsTile": "Štatistiky rádiových vysielaní", + "radioStats_settingsSubtitle": "Úroveň hluku, RSSI, SNR a časové rozloženie" +} diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index 54ea1f5..005054c 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -1943,5 +1943,68 @@ "settings_multiAck": "Večkratni potrditvi: {value}", "settings_telemetryModeUpdated": "Način telemetrije posodobljen", "map_showOverlaps": "Prekrivanje ključa ponovnega predvajanja", - "map_runTraceWithReturnPath": "Vrni se nazaj po isti poti." -} \ No newline at end of file + "map_runTraceWithReturnPath": "Vrni se nazaj po isti poti.", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "appSettings_languageHu": "Madžarski", + "appSettings_jumpToOldestUnreadSubtitle": "Ko odpirate klepet z neprebranimi sporočili, se premaknite na prvo neprebrano sporočilo, namesto najnovejšega.", + "chat_sendCooldown": "Prosimo, počakajte trenutek, preden pošljete ponovno.", + "appSettings_jumpToOldestUnread": "Pritisnite za najstarejše nepročitano sporočilo", + "appSettings_languageJa": "Japonski", + "appSettings_languageKo": "Korejski", + "radioStats_tooltip": "Statistike za radio in mrežo", + "radioStats_notConnected": "Povežite se z napravo, da si ogledate statistiko o radiju.", + "radioStats_screenTitle": "Radijske statistike", + "radioStats_firmwareTooOld": "Statistika za radio zahteva združljivo programsko opremo v8 ali kasnejše.", + "radioStats_waiting": "Čakam na podatke…", + "radioStats_noiseFloor": "Število šuma: {noiseDbm} dBm", + "radioStats_lastRssi": "Najkasnejše vrednost RSSI: {rssiDbm} dBm", + "radioStats_lastSnr": "Najkasnejše vrednost SNR: {snr} dB", + "radioStats_txAir": "Čas na TX (skupno): {seconds} s", + "radioStats_rxAir": "Čas, namenjen RX-ju (skupno): {seconds} s", + "radioStats_chartCaption": "Ravnovredna raven šuma (dBm) za nedavne vzorce.", + "radioStats_stripNoise": "Število šuma: {noiseDbm} dBm", + "radioStats_stripWaiting": "Prejemanje statistike o radiju…", + "radioStats_settingsTile": "Radijske statistike", + "radioStats_settingsSubtitle": "Število šumov, RSSI, SNR in čas, ki ga je napolnila oprema" +} diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index 1bb0c8a..0784229 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -1943,5 +1943,68 @@ "settings_telemetryModeUpdated": "Telemetri-läge uppdaterat", "settings_multiAck": "Multi-ACKs: {value}", "map_showOverlaps": "Repeater-nyckelöverlappningar", - "map_runTraceWithReturnPath": "Gå tillbaka på samma väg" -} \ No newline at end of file + "map_runTraceWithReturnPath": "Gå tillbaka på samma väg", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "appSettings_jumpToOldestUnreadSubtitle": "När du öppnar en chatt med oinlästa meddelanden, scrolla till det första oinlästa meddelandet istället för det senaste.", + "chat_sendCooldown": "Vänligen vänta en stund innan du skickar igen.", + "appSettings_jumpToOldestUnread": "Gå direkt till det äldsta, obesvarade meddelandet", + "appSettings_languageHu": "Ungerskt", + "appSettings_languageJa": "Japanska", + "appSettings_languageKo": "Koreanska", + "radioStats_tooltip": "Radio- och mesh-statistik", + "radioStats_screenTitle": "Radiostation", + "radioStats_notConnected": "Anslut till en enhet för att visa radiostatistik.", + "radioStats_firmwareTooOld": "Radio statistik kräver kompatibel firmware version 8 eller senare.", + "radioStats_waiting": "Väntar på data…", + "radioStats_noiseFloor": "Bakgrundsnivå: {noiseDbm} dBm", + "radioStats_lastRssi": "Senaste RSSI-värde: {rssiDbm} dBm", + "radioStats_lastSnr": "Senaste SNR: {snr} dB", + "radioStats_txAir": "TX-tid (total): {seconds} sekunder", + "radioStats_rxAir": "RX-tid (total): {seconds} s", + "radioStats_chartCaption": "Ljudnivå (dBm) baserat på de senaste mätningarna.", + "radioStats_stripNoise": "Bakgrundsnivå: {noiseDbm} dBm", + "radioStats_stripWaiting": "Hämtar radiostatistik…", + "radioStats_settingsTile": "Radiostation", + "radioStats_settingsSubtitle": "Bakgrundsnivå, RSSI, SNR och tillgänglig tid" +} diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index e55a582..cfed24b 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1943,5 +1943,68 @@ "settings_telemetryModeUpdated": "Режим телеметрії оновлено", "settings_multiAck": "Багатократне підтвердження: {value}", "map_showOverlaps": "Перекриття ключа повторювача", - "map_runTraceWithReturnPath": "Повернутися назад тим же шляхом" -} \ No newline at end of file + "map_runTraceWithReturnPath": "Повернутися назад тим же шляхом", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "chat_sendCooldown": "Будь ласка, зачекайте трохи, перш ніж відправляти знову.", + "appSettings_languageHu": "Угорський", + "appSettings_jumpToOldestUnreadSubtitle": "При відкритті чату з не прочитаними повідомленнями, прокрутіть до першого не прочитаного повідомлення, а не до останнього.", + "appSettings_jumpToOldestUnread": "Перейти до найстарішого непрочитаного повідомлення", + "appSettings_languageJa": "Японська", + "appSettings_languageKo": "Кореєська", + "radioStats_tooltip": "Статистика радіо та мережі", + "radioStats_screenTitle": "Дані про радіостанції", + "radioStats_notConnected": "Підключіться до пристрою, щоб переглядати статистику радіопередач.", + "radioStats_firmwareTooOld": "Статистика радіо приймача вимагає супутнього програмного забезпечення версії 8 або новішої.", + "radioStats_waiting": "Очікую на отримання даних…", + "radioStats_noiseFloor": "Рівень шуму: {noiseDbm} дБм", + "radioStats_lastRssi": "Останній показник RSSI: {rssiDbm} дБм", + "radioStats_lastSnr": "Останній показник SNR: {snr} дБ", + "radioStats_txAir": "Час трансляції на телеканалі TX (загальний): {seconds} секунд", + "radioStats_rxAir": "Загальний час використання RX: {seconds} секунд", + "radioStats_chartCaption": "Рівень шуму (дБм) на основі останніх вимірювань.", + "radioStats_stripNoise": "Рівень шуму: {noiseDbm} дБм", + "radioStats_stripWaiting": "Отримано статистику радіо…", + "radioStats_settingsTile": "Дані про радіостанції", + "radioStats_settingsSubtitle": "Рівень шуму, RSSI, SNR та час, протягом якого пристрій використовує радіоканал." +} diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index b415904..fb758b5 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1948,5 +1948,68 @@ "settings_multiAck": "多重ACK:{value}", "settings_telemetryModeUpdated": "遥测模式已更新", "map_showOverlaps": "重复键重叠", - "map_runTraceWithReturnPath": "沿着相同的路径返回" -} \ No newline at end of file + "map_runTraceWithReturnPath": "沿着相同的路径返回", + "@radioStats_noiseFloor": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "@radioStats_lastRssi": { + "placeholders": { + "rssiDbm": { + "type": "int" + } + } + }, + "@radioStats_lastSnr": { + "placeholders": { + "snr": { + "type": "String" + } + } + }, + "@radioStats_txAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_rxAir": { + "placeholders": { + "seconds": { + "type": "int" + } + } + }, + "@radioStats_stripNoise": { + "placeholders": { + "noiseDbm": { + "type": "int" + } + } + }, + "chat_sendCooldown": "请稍等片刻后再尝试发送。", + "appSettings_jumpToOldestUnreadSubtitle": "在打开包含未读消息的聊天时,请滚动到第一个未读消息,而不是最新的消息。", + "appSettings_jumpToOldestUnread": "跳转到最旧、未读的文章", + "appSettings_languageHu": "匈牙利", + "appSettings_languageJa": "日语", + "appSettings_languageKo": "韩语", + "radioStats_tooltip": "无线电和网状结构统计数据", + "radioStats_screenTitle": "广播统计数据", + "radioStats_notConnected": "连接到设备以查看收音机统计信息。", + "radioStats_firmwareTooOld": "使用无线电统计功能需要配合使用 v8 或更高版本的固件。", + "radioStats_waiting": "正在等待数据…", + "radioStats_noiseFloor": "噪声水平:{noiseDbm} dBm", + "radioStats_lastRssi": "上次 RSSI 值:{rssiDbm} dBm", + "radioStats_lastSnr": "上次 SNR:{snr} dB", + "radioStats_txAir": "TX 频道播出时间(总时长):{seconds} 秒", + "radioStats_rxAir": "RX 使用时长(总时长):{seconds} 秒", + "radioStats_chartCaption": "近期的噪声水平(dBm)。", + "radioStats_stripNoise": "噪声水平:{noiseDbm} dBm", + "radioStats_stripWaiting": "正在获取收音机数据…", + "radioStats_settingsTile": "广播统计数据", + "radioStats_settingsSubtitle": "噪声水平、RSSI、信噪比和空中时间" +} diff --git a/lib/models/companion_radio_stats.dart b/lib/models/companion_radio_stats.dart new file mode 100644 index 0000000..1e3d3c1 --- /dev/null +++ b/lib/models/companion_radio_stats.dart @@ -0,0 +1,48 @@ +import 'dart:typed_data'; + +import '../connector/meshcore_protocol.dart'; +import '../utils/app_logger.dart'; + +/// Parsed `RESP_CODE_STATS` + `STATS_TYPE_RADIO` (14 bytes total). +class CompanionRadioStats { + final int noiseFloorDbm; + final int lastRssiDbm; + final double lastSnrDb; + final int txAirSecs; + final int rxAirSecs; + final DateTime receivedAt; + + const CompanionRadioStats({ + required this.noiseFloorDbm, + required this.lastRssiDbm, + required this.lastSnrDb, + required this.txAirSecs, + required this.rxAirSecs, + required this.receivedAt, + }); + + static CompanionRadioStats? tryParse(Uint8List frame) { + if (frame.length < 14) return null; + if (frame[0] != respCodeStats || frame[1] != statsTypeRadio) return null; + try { + final reader = BufferReader(frame); + reader.skipBytes(2); + final noise = reader.readInt16LE(); + final rssi = reader.readInt8(); + final snrRaw = reader.readInt8(); + final txAir = reader.readUInt32LE(); + final rxAir = reader.readUInt32LE(); + return CompanionRadioStats( + noiseFloorDbm: noise, + lastRssiDbm: rssi, + lastSnrDb: snrRaw / 4.0, + txAirSecs: txAir, + rxAirSecs: rxAir, + receivedAt: DateTime.now(), + ); + } catch (e) { + appLogger.warn('CompanionRadioStats parse error: $e'); + return null; + } + } +} diff --git a/lib/screens/app_settings_screen.dart b/lib/screens/app_settings_screen.dart index f417715..82b0f1f 100644 --- a/lib/screens/app_settings_screen.dart +++ b/lib/screens/app_settings_screen.dart @@ -294,9 +294,7 @@ class AppSettingsScreen extends StatelessWidget { SwitchListTile( secondary: const Icon(Icons.vertical_align_top), title: Text(context.l10n.appSettings_jumpToOldestUnread), - subtitle: Text( - context.l10n.appSettings_jumpToOldestUnreadSubtitle, - ), + subtitle: Text(context.l10n.appSettings_jumpToOldestUnreadSubtitle), value: settingsService.settings.jumpToOldestUnread, onChanged: settingsService.setJumpToOldestUnread, ), diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index 7cfba56..12e98d6 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -1119,9 +1119,9 @@ class _ChannelChatScreenState extends State { final now = DateTime.now(); if (_lastChannelSendAt != null && now.difference(_lastChannelSendAt!) < const Duration(seconds: 1)) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.chat_sendCooldown)), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(context.l10n.chat_sendCooldown))); return; } _lastChannelSendAt = now; diff --git a/lib/screens/channel_message_path_screen.dart b/lib/screens/channel_message_path_screen.dart index dd36c21..94b8eee 100644 --- a/lib/screens/channel_message_path_screen.dart +++ b/lib/screens/channel_message_path_screen.dart @@ -64,8 +64,9 @@ class ChannelMessagePathScreen extends StatelessWidget { flipPathAround: true, reversePathAround: !(!channelMessage && !message.isOutgoing), - pathHashByteWidth: - context.read().pathHashByteWidth, + pathHashByteWidth: context + .read() + .pathHashByteWidth, ), ), ), diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index 3e34568..d67d03d 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -127,10 +127,7 @@ class _ChannelsScreenState extends State canPop: allowBack, child: Scaffold( appBar: AppBar( - title: AppBarTitle( - context.l10n.channels_title, - indicators: false, - ), + title: AppBarTitle(context.l10n.channels_title, indicators: false), centerTitle: true, automaticallyImplyLeading: false, actions: [ diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index 1a5a79b..2caddcd 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -613,9 +613,9 @@ class _ChatScreenState extends State { final now = DateTime.now(); if (_lastTextSendAt != null && now.difference(_lastTextSendAt!) < const Duration(seconds: 1)) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.chat_sendCooldown)), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(context.l10n.chat_sendCooldown))); return; } _lastTextSendAt = now; diff --git a/lib/screens/companion_radio_stats_screen.dart b/lib/screens/companion_radio_stats_screen.dart new file mode 100644 index 0000000..01fb64d --- /dev/null +++ b/lib/screens/companion_radio_stats_screen.dart @@ -0,0 +1,250 @@ +import 'package:flutter/material.dart'; +import 'package:meshcore_open/connector/meshcore_connector.dart'; +import 'package:meshcore_open/models/companion_radio_stats.dart'; +import 'package:meshcore_open/l10n/l10n.dart'; +import 'package:provider/provider.dart'; + +class CompanionRadioStatsScreen extends StatefulWidget { + const CompanionRadioStatsScreen({super.key}); + + @override + State createState() => + _CompanionRadioStatsScreenState(); +} + +class _CompanionRadioStatsScreenState extends State { + final List _noiseHistory = []; + static const int _maxSamples = 120; + MeshCoreConnector? _connector; + DateTime? _lastChartSampleAt; + + @override + void initState() { + super.initState(); + final c = context.read(); + _connector = c; + c.acquireRadioStatsPolling(); + c.radioStatsNotifier.addListener(_onStatsUpdate); + } + + void _onStatsUpdate() { + final s = _connector?.radioStatsNotifier.value; + if (s == null || !mounted) return; + if (_lastChartSampleAt == s.receivedAt) return; + _lastChartSampleAt = s.receivedAt; + setState(() { + _noiseHistory.add(s.noiseFloorDbm.toDouble()); + while (_noiseHistory.length > _maxSamples) { + _noiseHistory.removeAt(0); + } + }); + } + + @override + void dispose() { + _connector?.radioStatsNotifier.removeListener(_onStatsUpdate); + _connector?.releaseRadioStatsPolling(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return Scaffold( + appBar: AppBar( + title: Text(l10n.radioStats_screenTitle), + centerTitle: true, + ), + body: Selector( + selector: (_, c) => ( + connected: c.isConnected, + supported: c.supportsCompanionRadioStats, + ), + builder: (context, state, _) { + if (!state.connected) { + return Center(child: Text(l10n.radioStats_notConnected)); + } + if (!state.supported) { + return Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Text( + l10n.radioStats_firmwareTooOld, + textAlign: TextAlign.center, + ), + ), + ); + } + final connector = context.read(); + final scheme = Theme.of(context).colorScheme; + final tt = Theme.of(context).textTheme; + + return ValueListenableBuilder( + valueListenable: connector.radioStatsNotifier, + builder: (context, stats, _) { + return ListView( + padding: const EdgeInsets.all(16), + children: [ + if (stats != null) ...[ + Text( + l10n.radioStats_noiseFloor(stats.noiseFloorDbm), + style: tt.titleMedium, + ), + const SizedBox(height: 4), + Text(l10n.radioStats_lastRssi(stats.lastRssiDbm)), + Text( + l10n.radioStats_lastSnr( + stats.lastSnrDb.toStringAsFixed(1), + ), + ), + Text(l10n.radioStats_txAir(stats.txAirSecs)), + Text(l10n.radioStats_rxAir(stats.rxAirSecs)), + const SizedBox(height: 16), + ] else + Text(l10n.radioStats_waiting), + const SizedBox(height: 16), + SizedBox( + height: 200, + child: CustomPaint( + painter: _NoiseChartPainter( + samples: List.from(_noiseHistory), + colorScheme: scheme, + textTheme: tt, + ), + child: const SizedBox.expand(), + ), + ), + const SizedBox(height: 8), + Text( + l10n.radioStats_chartCaption, + style: tt.bodySmall?.copyWith( + color: scheme.onSurfaceVariant, + ), + ), + ], + ); + }, + ); + }, + ), + ); + } +} + +class _NoiseChartPainter extends CustomPainter { + final List samples; + final ColorScheme colorScheme; + final TextTheme textTheme; + + _NoiseChartPainter({ + required this.samples, + required this.colorScheme, + required this.textTheme, + }); + + @override + void paint(Canvas canvas, Size size) { + final bg = Paint()..color = colorScheme.surfaceContainerHighest; + final border = Paint() + ..color = colorScheme.outlineVariant + ..style = PaintingStyle.stroke + ..strokeWidth = 1; + final grid = Paint() + ..color = colorScheme.outlineVariant.withValues(alpha: 0.5) + ..strokeWidth = 1; + final line = Paint() + ..color = colorScheme.primary + ..strokeWidth = 2 + ..style = PaintingStyle.stroke; + + final rect = Rect.fromLTWH(0, 0, size.width, size.height); + canvas.drawRRect( + RRect.fromRectAndRadius(rect, const Radius.circular(8)), + bg, + ); + canvas.drawRRect( + RRect.fromRectAndRadius(rect, const Radius.circular(8)), + border, + ); + + const padL = 40.0; + const padR = 8.0; + const padT = 8.0; + const padB = 24.0; + final chart = Rect.fromLTRB( + padL, + padT, + size.width - padR, + size.height - padB, + ); + + for (var i = 0; i <= 4; i++) { + final y = chart.top + (chart.height * i / 4); + canvas.drawLine(Offset(chart.left, y), Offset(chart.right, y), grid); + } + + if (samples.length < 2) { + final tp = TextPainter( + text: TextSpan( + text: '—', + style: textTheme.bodySmall?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + textDirection: TextDirection.ltr, + )..layout(); + tp.paint( + canvas, + Offset(chart.left + 4, chart.top + chart.height / 2 - tp.height / 2), + ); + return; + } + + double minV = samples.reduce((a, b) => a < b ? a : b); + double maxV = samples.reduce((a, b) => a > b ? a : b); + if ((maxV - minV).abs() < 1) { + minV -= 2; + maxV += 2; + } + final span = maxV - minV; + + for (var i = 0; i <= 2; i++) { + final v = maxV - span * i / 2; + final tp = _yAxisLabel(v); + final y = chart.top + (chart.height * i / 2) - tp.height / 2; + tp.paint(canvas, Offset(4, y)); + } + + final path = Path(); + for (var i = 0; i < samples.length; i++) { + final x = chart.left + (chart.width * i / (samples.length - 1)); + final t = (samples[i] - minV) / span; + final y = chart.bottom - t * chart.height; + if (i == 0) { + path.moveTo(x, y); + } else { + path.lineTo(x, y); + } + } + canvas.drawPath(path, line); + } + + @override + bool shouldRepaint(covariant _NoiseChartPainter oldDelegate) { + return oldDelegate.samples.length != samples.length || + oldDelegate.colorScheme != colorScheme; + } + + TextPainter _yAxisLabel(double v) { + final tp = TextPainter( + text: TextSpan( + text: v.round().toString(), + style: textTheme.labelSmall?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + textDirection: TextDirection.ltr, + )..layout(); + return tp; + } +} diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index cce6a39..d5b01f2 100644 --- a/lib/screens/contacts_screen.dart +++ b/lib/screens/contacts_screen.dart @@ -1244,8 +1244,9 @@ class _ContactsScreenState extends State ? Text(context.l10n.contacts_pathTrace) : Text(context.l10n.contacts_ping), onTap: () { - final hw = - context.read().pathHashByteWidth; + final hw = context + .read() + .pathHashByteWidth; Navigator.push( context, MaterialPageRoute( @@ -1277,8 +1278,9 @@ class _ContactsScreenState extends State ? Text(context.l10n.contacts_pathTrace) : Text(context.l10n.contacts_ping), onTap: () { - final hw = - context.read().pathHashByteWidth; + final hw = context + .read() + .pathHashByteWidth; Navigator.push( context, MaterialPageRoute( @@ -1324,8 +1326,9 @@ class _ContactsScreenState extends State leading: const Icon(Icons.radar, color: Colors.green), title: Text(context.l10n.contacts_chatTraceRoute), onTap: () { - final hw = - context.read().pathHashByteWidth; + final hw = context + .read() + .pathHashByteWidth; Navigator.push( context, MaterialPageRoute( diff --git a/lib/screens/device_screen.dart b/lib/screens/device_screen.dart deleted file mode 100644 index 2343400..0000000 --- a/lib/screens/device_screen.dart +++ /dev/null @@ -1,267 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import '../connector/meshcore_connector.dart'; -import '../l10n/l10n.dart'; -import '../utils/dialog_utils.dart'; -import '../utils/disconnect_navigation_mixin.dart'; -import '../utils/route_transitions.dart'; -import '../widgets/quick_switch_bar.dart'; -import '../widgets/battery_indicator_chip.dart'; -import '../widgets/radio_stats_entry.dart'; -import 'channels_screen.dart'; -import 'contacts_screen.dart'; -import 'map_screen.dart'; -import 'settings_screen.dart'; - -/// Main hub screen after connecting to a MeshCore device -class DeviceScreen extends StatefulWidget { - const DeviceScreen({super.key}); - - @override - State createState() => _DeviceScreenState(); -} - -class _DeviceScreenState extends State - with DisconnectNavigationMixin { - bool _showBatteryVoltage = false; - int _quickIndex = 0; - - @override - Widget build(BuildContext context) { - return Consumer( - builder: (context, connector, child) { - // Auto-navigate back to scanner if disconnected - if (!checkConnectionAndNavigate(connector)) { - return const SizedBox.shrink(); - } - - final theme = Theme.of(context); - - return PopScope( - canPop: false, - child: Scaffold( - appBar: AppBar( - leadingWidth: 128, - leading: Row( - mainAxisSize: MainAxisSize.min, - children: [ - BatteryIndicatorChip( - connector: connector, - showVoltage: _showBatteryVoltage, - onPressed: () { - setState(() { - _showBatteryVoltage = !_showBatteryVoltage; - }); - }, - ), - const RadioStatsIconButton(), - ], - ), - titleSpacing: 16, - centerTitle: false, - title: _buildAppBarTitle(connector, theme), - automaticallyImplyLeading: false, - actions: [ - IconButton( - icon: const Icon(Icons.bluetooth_disabled), - tooltip: context.l10n.common_disconnect, - onPressed: () => _disconnect(context, connector), - ), - IconButton( - icon: const Icon(Icons.tune), - tooltip: context.l10n.common_settings, - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const SettingsScreen(), - ), - ), - ), - ], - ), - body: SafeArea( - child: ListView( - padding: const EdgeInsets.fromLTRB(16, 12, 16, 24), - children: [ - _buildConnectionCard(connector, context), - const SizedBox(height: 16), - _buildSectionLabel(theme, context.l10n.device_quickSwitch), - const SizedBox(height: 12), - _buildQuickSwitchBar(context), - ], - ), - ), - ), - ); - }, - ); - } - - Widget _buildAppBarTitle(MeshCoreConnector connector, ThemeData theme) { - final colorScheme = theme.colorScheme; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - context.l10n.device_meshcore, - style: theme.textTheme.labelSmall?.copyWith( - fontWeight: FontWeight.w600, - letterSpacing: 0.8, - color: colorScheme.onSurfaceVariant, - ), - ), - Text( - connector.deviceDisplayName, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w700, - ), - ), - ], - ); - } - - Widget _buildSectionLabel(ThemeData theme, String text) { - return Text( - text, - style: theme.textTheme.titleSmall?.copyWith( - fontWeight: FontWeight.w600, - letterSpacing: 0.6, - color: theme.colorScheme.onSurfaceVariant, - ), - ); - } - - Widget _buildConnectionCard( - MeshCoreConnector connector, - BuildContext context, - ) { - final theme = Theme.of(context); - final colorScheme = theme.colorScheme; - - return Card( - elevation: 0, - color: colorScheme.surfaceContainerHighest, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CircleAvatar( - radius: 24, - backgroundColor: colorScheme.primaryContainer, - child: Icon( - Icons.wifi_tethering_rounded, - color: colorScheme.onPrimaryContainer, - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - connector.deviceDisplayName, - style: theme.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w700, - ), - ), - const SizedBox(height: 4), - Text( - connector.deviceIdLabel, - style: theme.textTheme.bodySmall?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - ], - ), - ), - ], - ), - const SizedBox(height: 12), - Wrap( - spacing: 8, - runSpacing: 8, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - Chip( - avatar: Icon( - Icons.check_circle, - size: 18, - color: colorScheme.onSecondaryContainer, - ), - label: Text(context.l10n.common_connected), - backgroundColor: colorScheme.secondaryContainer, - labelStyle: theme.textTheme.labelMedium?.copyWith( - color: colorScheme.onSecondaryContainer, - fontWeight: FontWeight.w600, - ), - visualDensity: VisualDensity.compact, - ), - BatteryIndicatorChip( - connector: connector, - showVoltage: _showBatteryVoltage, - onPressed: () { - setState(() { - _showBatteryVoltage = !_showBatteryVoltage; - }); - }, - ), - ], - ), - ], - ), - ), - ); - } - - Widget _buildQuickSwitchBar(BuildContext context) { - return QuickSwitchBar( - selectedIndex: _quickIndex, - onDestinationSelected: (index) { - _openQuickDestination(index, context); - }, - ); - } - - void _openQuickDestination(int index, BuildContext context) { - if (_quickIndex != index) { - setState(() { - _quickIndex = index; - }); - } - switch (index) { - case 0: - Navigator.pushReplacement( - context, - buildQuickSwitchRoute(const ContactsScreen(hideBackButton: true)), - ); - break; - case 1: - Navigator.pushReplacement( - context, - buildQuickSwitchRoute(const ChannelsScreen(hideBackButton: true)), - ); - break; - case 2: - Navigator.pushReplacement( - context, - buildQuickSwitchRoute(const MapScreen(hideBackButton: true)), - ); - break; - } - } - - Future _disconnect( - BuildContext context, - MeshCoreConnector connector, - ) async { - await showDisconnectDialog(context, connector); - } -} diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index f42790c..9616d47 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -2191,8 +2191,9 @@ class _MapScreenState extends State { if (_pathTrace.isNotEmpty) IconButton( onPressed: () { - final hashW = - context.read().pathHashByteWidth; + final hashW = context + .read() + .pathHashByteWidth; Navigator.push( context, MaterialPageRoute( diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index a926e2b..d9e0d20 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -275,8 +275,8 @@ class _SettingsScreenState extends State { title: Text(l10n.radioStats_settingsTile), subtitle: Text(l10n.radioStats_settingsSubtitle), trailing: const Icon(Icons.chevron_right), - enabled: connector.isConnected && - connector.supportsCompanionRadioStats, + enabled: + connector.isConnected && connector.supportsCompanionRadioStats, onTap: () => pushCompanionRadioStatsScreen(context), ), const Divider(height: 1), diff --git a/lib/widgets/app_bar.dart b/lib/widgets/app_bar.dart index 4a49daf..3062b59 100644 --- a/lib/widgets/app_bar.dart +++ b/lib/widgets/app_bar.dart @@ -36,8 +36,7 @@ class AppBarTitle extends StatelessWidget { final compact = availableWidth < 170; final showSubtitle = !compact && connector.isConnected && selfName != null && subtitle; - final showBattery = - showBatteryIndicator && availableWidth >= 60; + final showBattery = showBatteryIndicator && availableWidth >= 60; final showSnr = availableWidth >= 110; final showIndicators = (showBattery || showSnr) && indicators; @@ -64,21 +63,13 @@ class AppBarTitle extends StatelessWidget { if (showIndicators) const SizedBox(width: 6), if (showIndicators) Row( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ if (showBattery) BatteryIndicator(connector: connector), if (showSnr) SNRIndicator(connector: connector), if (connector.supportsCompanionRadioStats) - ValueListenableBuilder( - valueListenable: connector.radioStatsNotifier, - builder: (context, _, child) => Padding( - padding: const EdgeInsets.only(left: 4), - child: AirActivityDot( - active: connector.radioStatsAirActivityPulse, - ), - ), - ), + const RadioStatsIconButton(compact: true), ], ), trailing ?? const SizedBox.shrink(), diff --git a/lib/widgets/radio_stats_entry.dart b/lib/widgets/radio_stats_entry.dart new file mode 100644 index 0000000..eda0848 --- /dev/null +++ b/lib/widgets/radio_stats_entry.dart @@ -0,0 +1,147 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:meshcore_open/connector/meshcore_connector.dart'; +import 'package:meshcore_open/models/companion_radio_stats.dart'; +import 'package:meshcore_open/l10n/l10n.dart'; +import 'package:meshcore_open/screens/companion_radio_stats_screen.dart'; +import 'package:provider/provider.dart'; + +void pushCompanionRadioStatsScreen(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CompanionRadioStatsScreen(), + ), + ); +} + +class RadioStatsIconButton extends StatefulWidget { + final bool compact; + + const RadioStatsIconButton({super.key, this.compact = false}); + + @override + State createState() => _RadioStatsIconButtonState(); +} + +class _RadioStatsIconButtonState extends State { + MeshCoreConnector? _connector; + + @override + void initState() { + super.initState(); + final c = context.read(); + _connector = c; + c.acquireRadioStatsPolling(); + } + + @override + void dispose() { + _connector?.releaseRadioStatsPolling(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Selector( + selector: (_, c) => + (connected: c.isConnected, supported: c.supportsCompanionRadioStats), + builder: (context, state, _) { + if (!state.connected || !state.supported) { + return const SizedBox.shrink(); + } + final connector = context.read(); + return ValueListenableBuilder( + valueListenable: connector.radioStatsNotifier, + builder: (context, _, child) { + final dot = AirActivityDot( + active: connector.radioStatsAirActivityPulse, + ); + if (widget.compact) { + return GestureDetector( + onTap: () => pushCompanionRadioStatsScreen(context), + child: Padding( + padding: const EdgeInsets.only(left: 4), + child: dot, + ), + ); + } + return Tooltip( + message: context.l10n.radioStats_tooltip, + child: InkWell( + customBorder: const CircleBorder(), + onTap: () => pushCompanionRadioStatsScreen(context), + child: SizedBox( + width: 48, + height: 48, + child: Center(child: dot), + ), + ), + ); + }, + ); + }, + ); + } +} + +class AirActivityDot extends StatefulWidget { + final bool active; + + const AirActivityDot({super.key, required this.active}); + + @override + State createState() => AirActivityDotState(); +} + +class AirActivityDotState extends State { + Timer? _timer; + bool _blink = true; + + @override + void initState() { + super.initState(); + if (widget.active) _startTimer(); + } + + @override + void didUpdateWidget(covariant AirActivityDot oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.active && !oldWidget.active) { + _startTimer(); + } else if (!widget.active && oldWidget.active) { + _stopTimer(); + _blink = true; + } + } + + void _startTimer() { + _timer ??= Timer.periodic(const Duration(milliseconds: 400), (_) { + if (!mounted) return; + setState(() => _blink = !_blink); + }); + } + + void _stopTimer() { + _timer?.cancel(); + _timer = null; + } + + @override + void dispose() { + _stopTimer(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final scheme = Theme.of(context).colorScheme; + final on = widget.active && _blink; + return Icon( + Icons.circle, + size: 12, + color: on ? scheme.primary : scheme.outline, + ); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 2428a77..ffc8c59 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,7 +9,6 @@ import flutter_blue_plus_darwin import flutter_local_notifications import mobile_scanner import package_info_plus -import path_provider_foundation import share_plus import shared_preferences_foundation import sqflite_darwin @@ -20,7 +19,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 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")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) diff --git a/test/companion_radio_stats_test.dart b/test/companion_radio_stats_test.dart new file mode 100644 index 0000000..0650ff6 --- /dev/null +++ b/test/companion_radio_stats_test.dart @@ -0,0 +1,39 @@ +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:meshcore_open/connector/meshcore_protocol.dart'; +import 'package:meshcore_open/models/companion_radio_stats.dart'; + +void main() { + test('CompanionRadioStats.tryParse golden 14-byte radio frame', () { + // noise -90 (0xA6FF LE), rssi -70 (0xBA), snr raw 8 -> 2.0 dB, + // tx_air 1000 LE, rx_air 2000 LE + final frame = Uint8List.fromList([ + respCodeStats, + statsTypeRadio, + 0xA6, + 0xFF, + 0xBA, + 0x08, + 0xE8, + 0x03, + 0x00, + 0x00, + 0xD0, + 0x07, + 0x00, + 0x00, + ]); + final s = CompanionRadioStats.tryParse(frame); + expect(s, isNotNull); + expect(s!.noiseFloorDbm, -90); + expect(s.lastRssiDbm, -70); + expect(s.lastSnrDb, 2.0); + expect(s.txAirSecs, 1000); + expect(s.rxAirSecs, 2000); + }); + + test('CompanionRadioStats.tryParse rejects short frame', () { + expect(CompanionRadioStats.tryParse(Uint8List(10)), isNull); + }); +} From 3754cf14eae405f0901dd13a6ff96d5871dab347 Mon Sep 17 00:00:00 2001 From: zjs81 Date: Mon, 23 Mar 2026 19:50:52 -0700 Subject: [PATCH 34/36] Bump version to 7.0.0+9 in pubspec.yaml --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index f347ed3..59825c4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: 7.0.0+8 +version: 7.0.0+9 environment: sdk: ^3.9.2 From eca78453d6322287e130662db5c84e915033d503 Mon Sep 17 00:00:00 2001 From: zjs81 Date: Mon, 23 Mar 2026 22:26:51 -0700 Subject: [PATCH 35/36] Remove debug print statements from MeshCoreConnector, MessageRetryService, and UsbSerialService and fix wrong retry being credited --- lib/connector/meshcore_connector.dart | 2 +- lib/screens/chat_screen.dart | 2 +- lib/services/message_retry_service.dart | 25 ++++++++++++++------- lib/services/usb_serial_service_native.dart | 22 +++++++++--------- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 7208454..d489238 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -2839,7 +2839,7 @@ class MeshCoreConnector extends ChangeNotifier { _bleDebugLogService?.logFrame(frame, outgoing: false); final code = frame[0]; - debugPrint('RX frame: code=$code len=${frame.length}'); + // debugPrint('RX frame: code=$code len=${frame.length}'); switch (code) { case respCodeOk: diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index 2caddcd..f990412 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -1697,7 +1697,7 @@ class _MessageBubble extends StatelessWidget { child: Text( context.l10n.chat_retryCount( message.retryCount, - 4, + context.read().settings.maxMessageRetries, ), style: TextStyle( fontSize: 10, diff --git a/lib/services/message_retry_service.dart b/lib/services/message_retry_service.dart index c8e89aa..5bc8812 100644 --- a/lib/services/message_retry_service.dart +++ b/lib/services/message_retry_service.dart @@ -21,11 +21,16 @@ class _AckHistoryEntry { }); } -/// (messageId, timestamp, attemptIndex) — stored per ACK hash for O(1) lookup. +/// (messageId, timestamp, attemptIndex, pathSelection) — stored per ACK hash +/// for O(1) lookup. [pathSelection] snapshots the route used for this +/// specific attempt so that a late PUSH_CODE_SEND_CONFIRMED credits the +/// correct path even when the message has since been retried on a different +/// route. typedef AckHashMapping = ({ String messageId, DateTime timestamp, int attemptIndex, + PathSelection? pathSelection, }); class RetryServiceConfig { @@ -382,6 +387,7 @@ class MessageRetryService extends ChangeNotifier { messageId: messageId, timestamp: DateTime.now(), attemptIndex: message.retryCount, + pathSelection: _selectionFromMessage(message), ); // Add this ACK hash to the list of expected ACKs for this message (for history) @@ -395,14 +401,11 @@ class MessageRetryService extends ChangeNotifier { int actualTimeout = timeoutMs; if (config.calculateTimeout != null) { - final calculated = config.calculateTimeout!( + actualTimeout = config.calculateTimeout!( pathLengthValue, message.text.length, contactKey: contact.publicKeyHex, ); - if (timeoutMs <= 0 || calculated < timeoutMs) { - actualTimeout = calculated; - } } final updatedMessage = message.copyWith( @@ -569,6 +572,7 @@ class MessageRetryService extends ChangeNotifier { final config = _config; String? matchedMessageId; int? matchedAttemptIndex; + PathSelection? matchedPathSelection; final ackHashHex = ackHash.toRadixString(16).padLeft(8, '0'); // Clean up old ACK hash mappings (older than 15 minutes) @@ -588,6 +592,7 @@ class MessageRetryService extends ChangeNotifier { if (mapping != null) { matchedMessageId = mapping.messageId; matchedAttemptIndex = mapping.attemptIndex; + matchedPathSelection = mapping.pathSelection; } else { config?.debugLogService?.warn( 'PUSH_CODE_SEND_CONFIRMED: ACK hash $ackHashHex not found in direct mapping, trying fallback', @@ -618,13 +623,13 @@ class MessageRetryService extends ChangeNotifier { } final contact = _pendingContacts[matchedMessageId]; final ackedAttempt = matchedAttemptIndex ?? message.retryCount; - final selection = _selectionFromMessage(message); + final selection = matchedPathSelection ?? _selectionFromMessage(message); final shortText = message.text.length > 20 ? '${message.text.substring(0, 20)}...' : message.text; config?.debugLogService?.info( - 'PUSH_CODE_SEND_CONFIRMED: ACK hash $ackHashHex ✓ "$shortText" delivered to ${contact?.name ?? "unknown"} on retry ${ackedAttempt + 1} in ${tripTimeMs}ms', + 'PUSH_CODE_SEND_CONFIRMED: ACK hash $ackHashHex ✓ "$shortText" delivered to ${contact?.name ?? "unknown"} on attempt $ackedAttempt in ${tripTimeMs}ms', tag: 'AckHash', ); @@ -636,6 +641,8 @@ class MessageRetryService extends ChangeNotifier { tripTimeMs: tripTimeMs, ); + final wasAlreadyResolved = _resolvedMessages.contains(matchedMessageId); + _cleanupMessage(matchedMessageId); config?.updateMessage(deliveredMessage); @@ -658,7 +665,9 @@ class MessageRetryService extends ChangeNotifier { tripTimeMs, ); } - _onMessageResolved(matchedMessageId, contact.publicKeyHex); + if (!wasAlreadyResolved) { + _onMessageResolved(matchedMessageId, contact.publicKeyHex); + } } notifyListeners(); diff --git a/lib/services/usb_serial_service_native.dart b/lib/services/usb_serial_service_native.dart index 40861db..9c8af85 100644 --- a/lib/services/usb_serial_service_native.dart +++ b/lib/services/usb_serial_service_native.dart @@ -273,7 +273,7 @@ class UsbSerialService { throw StateError('USB serial port is not open'); } final packet = wrapUsbSerialTxFrame(data); - _logFrameSummary('USB TX frame', data); + // _logFrameSummary('USB TX frame', data); if (_useAndroidUsbHost) { try { await _androidMethodChannel.invokeMethod('write', { @@ -447,16 +447,16 @@ class UsbSerialService { await _frameController.close(); } - void _logFrameSummary(String prefix, Uint8List bytes) { - if (bytes.isEmpty) { - _debugLogService?.info('$prefix len=0', tag: 'USB Serial'); - return; - } - _debugLogService?.info( - '$prefix code=${bytes[0]} len=${bytes.length}', - tag: 'USB Serial', - ); - } + // void _logFrameSummary(String prefix, Uint8List bytes) { + // if (bytes.isEmpty) { + // _debugLogService?.info('$prefix len=0', tag: 'USB Serial'); + // return; + // } + // _debugLogService?.info( + // '$prefix code=${bytes[0]} len=${bytes.length}', + // tag: 'USB Serial', + // ); + // } /// Returns an ordered list of port paths to try for [portName]. /// From c272c60f9abe73e48991729e67baef08c779bf6d Mon Sep 17 00:00:00 2001 From: zjs81 Date: Mon, 23 Mar 2026 22:37:05 -0700 Subject: [PATCH 36/36] Formatted file --- lib/screens/chat_screen.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index f990412..aecdc81 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -1697,7 +1697,10 @@ class _MessageBubble extends StatelessWidget { child: Text( context.l10n.chat_retryCount( message.retryCount, - context.read().settings.maxMessageRetries, + context + .read() + .settings + .maxMessageRetries, ), style: TextStyle( fontSize: 10,