From e97fb9bd24b9a7ae206c1885ccbab380361bead8 Mon Sep 17 00:00:00 2001 From: "Enot (ded) Skelly" Date: Wed, 15 Apr 2026 08:35:09 -0700 Subject: [PATCH 01/32] add byte counted text input adds a new widget that counts bytes during entry configurable limit and shows user both count and limit provides color feedback use new widget in chat and channel text entry --- lib/screens/channel_chat_screen.dart | 23 +++-- lib/screens/chat_screen.dart | 26 +++--- lib/widgets/byte_count_input.dart | 125 +++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 24 deletions(-) create mode 100644 lib/widgets/byte_count_input.dart diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index e5b5f67e..ebf8264a 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -13,7 +13,6 @@ import '../helpers/chat_scroll_controller.dart'; import '../connector/meshcore_protocol.dart'; import '../helpers/gif_helper.dart'; import '../helpers/reaction_helper.dart'; -import '../helpers/utf8_length_limiter.dart'; import '../helpers/snack_bar_builder.dart'; import '../l10n/l10n.dart'; import '../models/channel.dart'; @@ -23,6 +22,7 @@ import '../services/app_settings_service.dart'; import '../services/chat_text_scale_service.dart'; import '../services/translation_service.dart'; import '../utils/emoji_utils.dart'; +import '../widgets/byte_count_input.dart'; import '../widgets/chat_zoom_wrapper.dart'; import '../widgets/emoji_picker.dart'; import '../widgets/gif_message.dart'; @@ -1093,27 +1093,26 @@ class _ChannelChatScreenState extends State { ), ); } - - return TextField( + return ByteCountedTextField( + maxBytes: maxBytes, controller: _textController, focusNode: _textFieldFocusNode, - inputFormatters: [ - Utf8LengthLimitingTextInputFormatter(maxBytes), - ], - textCapitalization: TextCapitalization.sentences, + hintText: context.l10n.chat_typeMessage, + onSubmitted: (_) => _sendMessage(), decoration: InputDecoration( hintText: context.l10n.chat_typeMessage, border: OutlineInputBorder( borderRadius: BorderRadius.circular(24), ), + filled: true, + fillColor: Theme.of( + context, + ).colorScheme.surfaceContainerLow, contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, + horizontal: 20, + vertical: 14, ), ), - maxLines: null, - textInputAction: TextInputAction.send, - onSubmitted: (_) => _sendMessage(), ); }, ), diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index 2aee61c3..9fc33eb4 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -18,7 +18,6 @@ import '../widgets/message_status_icon.dart'; import '../helpers/chat_scroll_controller.dart'; import '../helpers/gif_helper.dart'; import '../helpers/path_helper.dart'; -import '../helpers/utf8_length_limiter.dart'; import '../models/channel_message.dart'; import '../models/contact.dart'; import '../models/message.dart'; @@ -30,6 +29,7 @@ import '../services/path_history_service.dart'; import '../services/translation_service.dart'; import '../widgets/chat_zoom_wrapper.dart'; import '../widgets/elements_ui.dart'; +import '../widgets/byte_count_input.dart'; import 'channel_message_path_screen.dart'; import 'map_screen.dart'; import '../utils/emoji_utils.dart'; @@ -567,24 +567,26 @@ class _ChatScreenState extends State { ), ); } - - return TextField( + return ByteCountedTextField( + maxBytes: maxBytes, controller: _textController, focusNode: _textFieldFocusNode, - inputFormatters: [ - Utf8LengthLimitingTextInputFormatter(maxBytes), - ], - textCapitalization: TextCapitalization.sentences, + hintText: context.l10n.chat_typeMessage, + onSubmitted: (_) => _sendMessage(connector), decoration: InputDecoration( hintText: context.l10n.chat_typeMessage, - border: const OutlineInputBorder(), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(24), + ), + filled: true, + fillColor: Theme.of( + context, + ).colorScheme.surfaceContainerLow, contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, + horizontal: 20, + vertical: 14, ), ), - textInputAction: TextInputAction.send, - onSubmitted: (_) => _sendMessage(connector), ); }, ), diff --git a/lib/widgets/byte_count_input.dart b/lib/widgets/byte_count_input.dart new file mode 100644 index 00000000..5832ae89 --- /dev/null +++ b/lib/widgets/byte_count_input.dart @@ -0,0 +1,125 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../helpers/utf8_length_limiter.dart'; + +/// A [TextField] that displays a live UTF-8 byte counter. +/// +/// The counter appears below the field once the user starts typing and changes +/// colour as the limit is approached (orange at 70 %, error-red at 90 %). +/// +/// All standard [TextField] behaviour (focus nodes, input actions, decoration +/// overrides, etc.) is forwarded so the widget can be dropped into any screen. +class ByteCountedTextField extends StatelessWidget { + /// Maximum number of UTF-8 bytes allowed. + final int maxBytes; + + /// Controller for the text field. + final TextEditingController controller; + + /// Optional focus node forwarded to the inner [TextField]. + final FocusNode? focusNode; + + /// Hint text shown when the field is empty. + final String? hintText; + + /// Keyboard action button (defaults to [TextInputAction.send]). + final TextInputAction textInputAction; + + /// Called when the user submits via the keyboard action button. + final ValueChanged? onSubmitted; + + /// Additional [TextInputFormatter]s applied *before* the byte limiter. + final List extraFormatters; + + /// Text capitalisation forwarded to the inner [TextField]. + final TextCapitalization textCapitalization; + + /// Optional full [InputDecoration] override. When provided, [hintText] is + /// ignored – set it inside the decoration instead. + final InputDecoration? decoration; + + /// Ratio (0–1) at which the counter turns the warning colour (default 0.7). + final double warningThreshold; + + /// Ratio (0–1) at which the counter turns the error colour (default 0.9). + final double errorThreshold; + + /// Whether to hide the counter when the field is empty (default `true`). + final bool hideCounterWhenEmpty; + + const ByteCountedTextField({ + super.key, + required this.maxBytes, + required this.controller, + this.focusNode, + this.hintText, + this.textInputAction = TextInputAction.send, + this.onSubmitted, + this.extraFormatters = const [], + this.textCapitalization = TextCapitalization.sentences, + this.decoration, + this.warningThreshold = 0.7, + this.errorThreshold = 0.9, + this.hideCounterWhenEmpty = true, + }); + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: controller, + builder: (context, value, _) { + final usedBytes = utf8.encode(value.text).length; + final ratio = maxBytes > 0 ? usedBytes / maxBytes : 0.0; + final showCounter = !(hideCounterWhenEmpty && value.text.isEmpty); + + final counterColor = ratio > errorThreshold + ? Theme.of(context).colorScheme.error + : ratio > warningThreshold + ? Colors.orange + : Theme.of(context).colorScheme.onSurfaceVariant; + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextField( + controller: controller, + focusNode: focusNode, + inputFormatters: [ + ...extraFormatters, + Utf8LengthLimitingTextInputFormatter(maxBytes), + ], + textCapitalization: textCapitalization, + decoration: + decoration ?? + InputDecoration( + hintText: hintText, + border: const OutlineInputBorder(), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + textInputAction: textInputAction, + onSubmitted: onSubmitted, + ), + if (showCounter) + Padding( + padding: const EdgeInsets.only(top: 4, right: 4), + child: Align( + alignment: Alignment.centerRight, + child: Text( + '$usedBytes / $maxBytes', + style: TextStyle(fontSize: 11, color: counterColor), + ), + ), + ), + ], + ); + }, + ); + } +} From b572314ae9e7c9c924e63f95b6ea5251d280098d Mon Sep 17 00:00:00 2001 From: ericz Date: Sat, 11 Apr 2026 18:48:43 +0200 Subject: [PATCH 02/32] respect smaz encoding in message byte length calculation. --- lib/connector/meshcore_connector.dart | 18 +++++++++++------- lib/helpers/utf8_length_limiter.dart | 19 ++++++++++++++++--- lib/screens/channel_chat_screen.dart | 11 ++++++++++- lib/screens/chat_screen.dart | 13 ++++++++++++- lib/widgets/byte_count_input.dart | 15 +++++++++++++-- 5 files changed, 62 insertions(+), 14 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index b4322773..fceee150 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -2994,13 +2994,7 @@ class MeshCoreConnector extends ChangeNotifier { _pendingChannelSentQueue.add(message.messageId); notifyListeners(); - final trimmed = text.trim(); - final isStructuredPayload = - trimmed.startsWith('g:') || trimmed.startsWith('m:'); - final outboundText = - (isChannelSmazEnabled(channel.index) && !isStructuredPayload) - ? Smaz.encodeIfSmaller(text) - : text; + final outboundText = prepareChannelOutboundText(channel.index, text); await _waitForRadioQuiet(lastInboundRxTime: _lastChannelMsgRxTime); await sendFrame( buildSendChannelTextMsgFrame(channel.index, outboundText), @@ -4452,6 +4446,16 @@ class MeshCoreConnector extends ChangeNotifier { return text; } + String prepareChannelOutboundText(int channelIndex, String text) { + final trimmed = text.trim(); + final isStructuredPayload = + trimmed.startsWith('g:') || trimmed.startsWith('m:'); + if (!isStructuredPayload && isChannelSmazEnabled(channelIndex)) { + return Smaz.encodeIfSmaller(text); + } + return text; + } + String _channelDisplayName(int channelIndex) { for (final channel in _channels) { if (channel.index != channelIndex) continue; diff --git a/lib/helpers/utf8_length_limiter.dart b/lib/helpers/utf8_length_limiter.dart index c6acdd29..4188ec6a 100644 --- a/lib/helpers/utf8_length_limiter.dart +++ b/lib/helpers/utf8_length_limiter.dart @@ -4,8 +4,14 @@ import 'package:flutter/services.dart'; class Utf8LengthLimitingTextInputFormatter extends TextInputFormatter { final int maxBytes; + final String Function(String)? encoder; - const Utf8LengthLimitingTextInputFormatter(this.maxBytes); + const Utf8LengthLimitingTextInputFormatter(this.maxBytes, {this.encoder}); + + int _effectiveByteLength(String text) { + final effective = encoder != null ? encoder!(text) : text; + return utf8.encode(effective).length; + } @override TextEditingValue formatEditUpdate( @@ -13,8 +19,7 @@ class Utf8LengthLimitingTextInputFormatter extends TextInputFormatter { TextEditingValue newValue, ) { if (maxBytes <= 0) return oldValue; - final bytes = utf8.encode(newValue.text); - if (bytes.length <= maxBytes) return newValue; + if (_effectiveByteLength(newValue.text) <= maxBytes) return newValue; final truncated = _truncateToMaxBytes(newValue.text, maxBytes); return TextEditingValue( @@ -25,6 +30,14 @@ class Utf8LengthLimitingTextInputFormatter extends TextInputFormatter { } String _truncateToMaxBytes(String text, int limit) { + if (encoder != null) { + final runes = text.runes.toList(); + while (runes.isNotEmpty && + _effectiveByteLength(String.fromCharCodes(runes)) > maxBytes) { + runes.removeLast(); + } + return String.fromCharCodes(runes); + } final buffer = StringBuffer(); var used = 0; for (final rune in text.runes) { diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index ebf8264a..dfcdcdbc 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -10,6 +10,7 @@ import 'package:provider/provider.dart'; import '../connector/meshcore_connector.dart'; import '../utils/platform_info.dart'; import '../helpers/chat_scroll_controller.dart'; +import '../helpers/smaz.dart'; import '../connector/meshcore_protocol.dart'; import '../helpers/gif_helper.dart'; import '../helpers/reaction_helper.dart'; @@ -1099,6 +1100,10 @@ class _ChannelChatScreenState extends State { focusNode: _textFieldFocusNode, hintText: context.l10n.chat_typeMessage, onSubmitted: (_) => _sendMessage(), + encoder: + connector.isChannelSmazEnabled(widget.channel.index) + ? Smaz.encodeIfSmaller + : null, decoration: InputDecoration( hintText: context.l10n.chat_typeMessage, border: OutlineInputBorder( @@ -1194,7 +1199,11 @@ class _ChannelChatScreenState extends State { } final maxBytes = maxChannelMessageBytes(connector.selfName); - if (utf8.encode(messageText).length > maxBytes) { + final outboundText = connector.prepareChannelOutboundText( + widget.channel.index, + messageText, + ); + if (utf8.encode(outboundText).length > maxBytes) { showDismissibleSnackBar( context, content: Text(context.l10n.chat_messageTooLong(maxBytes)), diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index 9fc33eb4..435e6c5f 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -14,6 +14,7 @@ import 'package:latlong2/latlong.dart'; import '../connector/meshcore_connector.dart'; import '../connector/meshcore_protocol.dart'; import '../helpers/reaction_helper.dart'; +import '../helpers/smaz.dart'; import '../widgets/message_status_icon.dart'; import '../helpers/chat_scroll_controller.dart'; import '../helpers/gif_helper.dart'; @@ -573,6 +574,12 @@ class _ChatScreenState extends State { focusNode: _textFieldFocusNode, hintText: context.l10n.chat_typeMessage, onSubmitted: (_) => _sendMessage(connector), + encoder: + connector.isContactSmazEnabled( + widget.contact.publicKeyHex, + ) + ? Smaz.encodeIfSmaller + : null, decoration: InputDecoration( hintText: context.l10n.chat_typeMessage, border: OutlineInputBorder( @@ -674,7 +681,11 @@ class _ChatScreenState extends State { } } final maxBytes = maxContactMessageBytes(); - if (utf8.encode(outgoingText).length > maxBytes) { + final outboundText = connector.prepareContactOutboundText( + _resolveContact(connector), + outgoingText, + ); + if (utf8.encode(outboundText).length > maxBytes) { showDismissibleSnackBar( context, content: Text(context.l10n.chat_messageTooLong(maxBytes)), diff --git a/lib/widgets/byte_count_input.dart b/lib/widgets/byte_count_input.dart index 5832ae89..bfb5fcc5 100644 --- a/lib/widgets/byte_count_input.dart +++ b/lib/widgets/byte_count_input.dart @@ -50,6 +50,10 @@ class ByteCountedTextField extends StatelessWidget { /// Whether to hide the counter when the field is empty (default `true`). final bool hideCounterWhenEmpty; + /// Optional encoder function to transform text before byte counting/limiting. + /// If provided, byte limits and counters will use the encoded text length. + final String Function(String)? encoder; + const ByteCountedTextField({ super.key, required this.maxBytes, @@ -64,6 +68,7 @@ class ByteCountedTextField extends StatelessWidget { this.warningThreshold = 0.7, this.errorThreshold = 0.9, this.hideCounterWhenEmpty = true, + this.encoder, }); @override @@ -71,7 +76,10 @@ class ByteCountedTextField extends StatelessWidget { return ValueListenableBuilder( valueListenable: controller, builder: (context, value, _) { - final usedBytes = utf8.encode(value.text).length; + final effectiveText = encoder != null + ? encoder!(value.text) + : value.text; + final usedBytes = utf8.encode(effectiveText).length; final ratio = maxBytes > 0 ? usedBytes / maxBytes : 0.0; final showCounter = !(hideCounterWhenEmpty && value.text.isEmpty); @@ -90,7 +98,10 @@ class ByteCountedTextField extends StatelessWidget { focusNode: focusNode, inputFormatters: [ ...extraFormatters, - Utf8LengthLimitingTextInputFormatter(maxBytes), + Utf8LengthLimitingTextInputFormatter( + maxBytes, + encoder: encoder, + ), ], textCapitalization: textCapitalization, decoration: From ddcda4ba5ad161799334355fdfa65ad0f12a107e Mon Sep 17 00:00:00 2001 From: "Enot (ded) Skelly" Date: Fri, 17 Apr 2026 14:07:00 -0700 Subject: [PATCH 03/32] keep multiline editing --- lib/widgets/byte_count_input.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/widgets/byte_count_input.dart b/lib/widgets/byte_count_input.dart index bfb5fcc5..ca432522 100644 --- a/lib/widgets/byte_count_input.dart +++ b/lib/widgets/byte_count_input.dart @@ -94,6 +94,7 @@ class ByteCountedTextField extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextField( + maxLines: null, controller: controller, focusNode: focusNode, inputFormatters: [ From 8ef8a3849553ede3c8d3471d93ce84fee47e6e61 Mon Sep 17 00:00:00 2001 From: ericz Date: Sat, 18 Apr 2026 00:06:03 +0200 Subject: [PATCH 04/32] change to prepare Outbound Text Functions. --- lib/screens/channel_chat_screen.dart | 6 ++++-- lib/screens/chat_screen.dart | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index dfcdcdbc..b203cbbe 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -10,7 +10,6 @@ import 'package:provider/provider.dart'; import '../connector/meshcore_connector.dart'; import '../utils/platform_info.dart'; import '../helpers/chat_scroll_controller.dart'; -import '../helpers/smaz.dart'; import '../connector/meshcore_protocol.dart'; import '../helpers/gif_helper.dart'; import '../helpers/reaction_helper.dart'; @@ -1102,7 +1101,10 @@ class _ChannelChatScreenState extends State { onSubmitted: (_) => _sendMessage(), encoder: connector.isChannelSmazEnabled(widget.channel.index) - ? Smaz.encodeIfSmaller + ? (text) => connector.prepareChannelOutboundText( + widget.channel.index, + text, + ) : null, decoration: InputDecoration( hintText: context.l10n.chat_typeMessage, diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index 435e6c5f..ffa8344b 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -14,7 +14,6 @@ import 'package:latlong2/latlong.dart'; import '../connector/meshcore_connector.dart'; import '../connector/meshcore_protocol.dart'; import '../helpers/reaction_helper.dart'; -import '../helpers/smaz.dart'; import '../widgets/message_status_icon.dart'; import '../helpers/chat_scroll_controller.dart'; import '../helpers/gif_helper.dart'; @@ -578,7 +577,10 @@ class _ChatScreenState extends State { connector.isContactSmazEnabled( widget.contact.publicKeyHex, ) - ? Smaz.encodeIfSmaller + ? (text) => connector.prepareContactOutboundText( + widget.contact, + text, + ) : null, decoration: InputDecoration( hintText: context.l10n.chat_typeMessage, From 820bac0db049ccedd91aae909527be161b5a5f9e Mon Sep 17 00:00:00 2001 From: "Enot (ded) Skelly" Date: Tue, 21 Apr 2026 16:44:04 -0700 Subject: [PATCH 05/32] fix issues with contact sync this adds the actual last modified timestamp when present, before we used last advert time as last modified in error also sets _pendingInitialContactsSync to true on first connect over BLE --- lib/connector/meshcore_connector.dart | 5 +++- lib/connector/meshcore_protocol.dart | 13 +++++++--- lib/models/contact.dart | 31 +++++++++++++++++++++--- lib/storage/contact_discovery_store.dart | 5 ++++ lib/storage/contact_store.dart | 5 ++++ 5 files changed, 51 insertions(+), 8 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index fceee150..be0d8935 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -2140,6 +2140,7 @@ class MeshCoreConnector extends ChangeNotifier { return; } _bleInitialSyncStarted = true; + _pendingInitialContactsSync = true; await _requestDeviceInfo(); _startBatteryPolling(); @@ -4117,7 +4118,9 @@ class MeshCoreConnector extends ChangeNotifier { if (_contacts.isEmpty) return 0; var latest = 0; for (final contact in _contacts) { - final seconds = contact.lastSeen.millisecondsSinceEpoch ~/ 1000; + // prefer lastmod per spec, fallback to lastseen + final source = contact.lastModified ?? contact.lastSeen; + final seconds = source.millisecondsSinceEpoch ~/ 1000; if (seconds > latest) { latest = seconds; } diff --git a/lib/connector/meshcore_protocol.dart b/lib/connector/meshcore_protocol.dart index 396d78b3..dcb6948e 100644 --- a/lib/connector/meshcore_protocol.dart +++ b/lib/connector/meshcore_protocol.dart @@ -735,10 +735,15 @@ Uint8List buildUpdateContactPathFrame( writer.writeInt32LE((longitude * 1e6).round()); } - if (lastModified != null) { - // Last modified - final lastModifiedTimestamp = lastModified.millisecondsSinceEpoch ~/ 1000; - writer.writeUInt32LE(lastModifiedTimestamp); + final hasLocation = lat != null && lon != null; + if (hasLocation || lastModified != null) { + writer.writeInt32LE(hasLocation ? (lat * 1e6).round() : 0); + writer.writeInt32LE(hasLocation ? (lon * 1e6).round() : 0); + if (lastModified != null) { + // Last modified + final lastModifiedTimestamp = lastModified.millisecondsSinceEpoch ~/ 1000; + writer.writeUInt32LE(lastModifiedTimestamp); + } } return writer.toBytes(); diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 2699f939..a126ade8 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -17,6 +17,7 @@ class Contact { final double? longitude; final DateTime lastSeen; final DateTime lastMessageAt; + final DateTime? lastModified; final bool isActive; final bool wasPulled; final Uint8List? rawPacket; @@ -33,6 +34,7 @@ class Contact { this.latitude, this.longitude, required this.lastSeen, + this.lastModified, DateTime? lastMessageAt, this.isActive = true, this.wasPulled = false, @@ -94,6 +96,7 @@ class Contact { double? longitude, DateTime? lastSeen, DateTime? lastMessageAt, + DateTime? lastModified, bool? isActive, Uint8List? rawPacket, }) { @@ -114,6 +117,7 @@ class Contact { longitude: longitude ?? this.longitude, lastSeen: lastSeen ?? this.lastSeen, lastMessageAt: lastMessageAt ?? this.lastMessageAt, + lastModified: lastModified ?? this.lastModified, isActive: isActive ?? this.isActive, rawPacket: rawPacket ?? this.rawPacket, ); @@ -182,16 +186,34 @@ class Contact { return null; } - final lastMod = reader.readUInt32LE(); + // mandatory last_advert_timestamp + final lastAdvertTimestamp = reader.readUInt32LE(); double? lat, lon; - if (reader.remaining >= 8) { + DateTime? lastModified; + if (reader.remaining >= 12) { + final latRaw = reader.readInt32LE(); + final lonRaw = reader.readInt32LE(); + final lastModRaw = reader.readUInt32LE(); + // TODO: should this be &&? + if (latRaw != 0 || lonRaw != 0) { + lat = latRaw / 1e6; + lon = lonRaw / 1e6; + } + if (lastModRaw != 0) { + lastModified = DateTime.fromMillisecondsSinceEpoch(lastModRaw * 1000); + } + } else if (reader.remaining >= 8) { + // Old layout: gps without lastmod final latRaw = reader.readInt32LE(); final lonRaw = reader.readInt32LE(); if (latRaw != 0 || lonRaw != 0) { lat = latRaw / 1e6; lon = lonRaw / 1e6; } + appLogger.info( + 'Contact ${pubKeyToHex(pubKey).substring(0, 8)} has gps but no lastmod (legacy firmware layout)', + ); } return Contact( @@ -203,7 +225,10 @@ class Contact { path: pathBytes, latitude: lat, longitude: lon, - lastSeen: DateTime.fromMillisecondsSinceEpoch(lastMod * 1000), + lastSeen: DateTime.fromMillisecondsSinceEpoch( + lastAdvertTimestamp * 1000, + ), + lastModified: lastModified, isActive: true, rawPacket: null, ); diff --git a/lib/storage/contact_discovery_store.dart b/lib/storage/contact_discovery_store.dart index 89ca0273..3f6f1718 100644 --- a/lib/storage/contact_discovery_store.dart +++ b/lib/storage/contact_discovery_store.dart @@ -43,6 +43,7 @@ class ContactDiscoveryStore { 'latitude': contact.latitude, 'longitude': contact.longitude, 'lastSeen': contact.lastSeen.millisecondsSinceEpoch, + 'lastModified': contact.lastModified?.millisecondsSinceEpoch, 'lastMessageAt': contact.lastMessageAt.millisecondsSinceEpoch, 'rawPacket': contact.rawPacket != null ? base64Encode(contact.rawPacket!) @@ -53,6 +54,7 @@ class ContactDiscoveryStore { Contact _fromJson(Map json) { final lastSeenMs = json['lastSeen'] as int? ?? 0; final lastMessageMs = json['lastMessageAt'] as int?; + final lastModifiedMs = json['lastModified'] as int?; return Contact( publicKey: Uint8List.fromList(base64Decode(json['publicKey'] as String)), name: json['name'] as String? ?? 'Unknown', @@ -71,6 +73,9 @@ class ContactDiscoveryStore { latitude: (json['latitude'] as num?)?.toDouble(), longitude: (json['longitude'] as num?)?.toDouble(), lastSeen: DateTime.fromMillisecondsSinceEpoch(lastSeenMs), + lastModified: lastModifiedMs == null + ? null + : DateTime.fromMillisecondsSinceEpoch(lastModifiedMs), lastMessageAt: DateTime.fromMillisecondsSinceEpoch( lastMessageMs ?? lastSeenMs, ), diff --git a/lib/storage/contact_store.dart b/lib/storage/contact_store.dart index 0e2e3ad1..7883d885 100644 --- a/lib/storage/contact_store.dart +++ b/lib/storage/contact_store.dart @@ -75,6 +75,7 @@ class ContactStore { 'latitude': contact.latitude, 'longitude': contact.longitude, 'lastSeen': contact.lastSeen.millisecondsSinceEpoch, + 'lastModified': contact.lastModified?.millisecondsSinceEpoch, 'lastMessageAt': contact.lastMessageAt.millisecondsSinceEpoch, 'isActive': contact.isActive, 'rawPacket': contact.rawPacket != null @@ -86,6 +87,7 @@ class ContactStore { Contact _fromJson(Map json) { final lastSeenMs = json['lastSeen'] as int? ?? 0; final lastMessageMs = json['lastMessageAt'] as int?; + final lastModifiedMs = json['lastModified'] as int?; return Contact( publicKey: Uint8List.fromList(base64Decode(json['publicKey'] as String)), name: json['name'] as String? ?? 'Unknown', @@ -104,6 +106,9 @@ class ContactStore { latitude: (json['latitude'] as num?)?.toDouble(), longitude: (json['longitude'] as num?)?.toDouble(), lastSeen: DateTime.fromMillisecondsSinceEpoch(lastSeenMs), + lastModified: lastModifiedMs == null + ? null + : DateTime.fromMillisecondsSinceEpoch(lastModifiedMs), lastMessageAt: DateTime.fromMillisecondsSinceEpoch( lastMessageMs ?? lastSeenMs, ), From 5e446207c655b202ca9c7ad3415420c011d2c915 Mon Sep 17 00:00:00 2001 From: Serge Tarkovski Date: Thu, 23 Apr 2026 17:27:39 +0300 Subject: [PATCH 06/32] Ukrainian translation polished; localized remaining hardcoded UI strings --- lib/l10n/app_bg.arb | 43 ++- lib/l10n/app_de.arb | 43 ++- lib/l10n/app_en.arb | 50 ++- lib/l10n/app_es.arb | 43 ++- lib/l10n/app_fr.arb | 43 ++- lib/l10n/app_hu.arb | 43 ++- lib/l10n/app_it.arb | 43 ++- lib/l10n/app_ja.arb | 43 ++- lib/l10n/app_ko.arb | 43 ++- lib/l10n/app_localizations.dart | 120 +++++++ lib/l10n/app_localizations_bg.dart | 69 ++++ lib/l10n/app_localizations_de.dart | 69 ++++ lib/l10n/app_localizations_en.dart | 69 ++++ lib/l10n/app_localizations_es.dart | 69 ++++ lib/l10n/app_localizations_fr.dart | 69 ++++ lib/l10n/app_localizations_hu.dart | 69 ++++ lib/l10n/app_localizations_it.dart | 69 ++++ lib/l10n/app_localizations_ja.dart | 69 ++++ lib/l10n/app_localizations_ko.dart | 69 ++++ lib/l10n/app_localizations_nl.dart | 69 ++++ lib/l10n/app_localizations_pl.dart | 69 ++++ lib/l10n/app_localizations_pt.dart | 69 ++++ lib/l10n/app_localizations_ru.dart | 69 ++++ lib/l10n/app_localizations_sk.dart | 69 ++++ lib/l10n/app_localizations_sl.dart | 69 ++++ lib/l10n/app_localizations_sv.dart | 69 ++++ lib/l10n/app_localizations_uk.dart | 441 ++++++++++++++---------- lib/l10n/app_localizations_zh.dart | 69 ++++ lib/l10n/app_nl.arb | 43 ++- lib/l10n/app_pl.arb | 43 ++- lib/l10n/app_pt.arb | 43 ++- lib/l10n/app_ru.arb | 43 ++- lib/l10n/app_sk.arb | 43 ++- lib/l10n/app_sl.arb | 43 ++- lib/l10n/app_sv.arb | 43 ++- lib/l10n/app_uk.arb | 372 ++++++++++---------- lib/l10n/app_zh.arb | 43 ++- lib/models/contact.dart | 32 +- lib/screens/app_settings_screen.dart | 13 +- lib/screens/channel_chat_screen.dart | 4 +- lib/screens/channels_screen.dart | 2 +- lib/screens/chat_screen.dart | 4 +- lib/screens/contacts_screen.dart | 6 +- lib/screens/map_screen.dart | 14 +- lib/screens/repeater_hub_screen.dart | 2 +- lib/screens/settings_screen.dart | 4 +- lib/services/background_service.dart | 21 +- lib/widgets/path_management_dialog.dart | 4 +- lib/widgets/path_selection_dialog.dart | 2 +- lib/widgets/qr_code_display.dart | 4 +- lib/widgets/repeater_login_dialog.dart | 2 +- lib/widgets/room_login_dialog.dart | 2 +- 52 files changed, 2543 insertions(+), 417 deletions(-) diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 7ac54177..e1a955bd 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -104,6 +104,8 @@ "settings_privacyModeEnabled": "Режим на поверителност е активиран", "settings_privacyModeDisabled": "Режим на поверителност е деактивиран", "settings_actions": "Действия", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "Изпрати Реклама", "settings_sendAdvertisementSubtitle": "Сега присъствие в ефир", "settings_advertisementSent": "Реклама изпратена", @@ -2073,5 +2075,44 @@ "chat_sendMessage": "Изпратете съобщение", "room_guest": "Информация за сървъра на стаята", "repeater_guest": "Информация за ретранслаторите", - "repeater_guestTools": "Инструменти за гости" + "repeater_guestTools": "Инструменти за гости", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 46955052..885d2de8 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -104,6 +104,8 @@ "settings_privacyModeEnabled": "Datenschutzmodus aktiviert", "settings_privacyModeDisabled": "Datenschutzmodus deaktiviert", "settings_actions": "Aktionen", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "Sende Ankündigung", "settings_sendAdvertisementSubtitle": "Sende eine Ankündigung", "settings_advertisementSent": "Ankündigung gesendet", @@ -2101,5 +2103,44 @@ "repeater_guest": "Informationen zu Repeatern", "repeater_guestTools": "Gastwerkzeuge", "chat_sendMessage": "Nachricht senden", - "room_guest": "Informationen zum Room Server" + "room_guest": "Informationen zum Room Server", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8ad6bf37..5b8b2c47 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -12,6 +12,7 @@ "common_delete": "Delete", "common_deleteAll": "Delete All", "common_close": "Close", + "common_done": "Done", "common_edit": "Edit", "common_add": "Add", "common_settings": "Settings", @@ -188,6 +189,8 @@ }, "settings_telemetryModeUpdated": "Telemetry mode updated", "settings_actions": "Actions", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "Send Advertisement", "settings_sendAdvertisementSubtitle": "Broadcast presence now", "settings_advertisementSent": "Advertisement sent", @@ -523,6 +526,14 @@ }, "channels_hashtagChannel": "Hashtag channel", "channels_public": "Public", + "channels_via": "via {path}", + "@channels_via": { + "placeholders": { + "path": { + "type": "String" + } + } + }, "channels_private": "Private", "channels_publicChannel": "Public channel", "channels_privateChannel": "Private channel", @@ -869,6 +880,12 @@ "map_from": "From", "map_source": "Source", "map_flags": "Flags", + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", "map_shareMarkerHere": "Share marker here", "map_setAsMyLocation": "Set as my location", "map_pinLabel": "Pin label", @@ -2121,5 +2138,36 @@ } }, "translation_translationOptions": "Translation options", - "translation_systemLanguage": "System language" + "translation_systemLanguage": "System language", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown" } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index ac9527bd..e830630a 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -104,6 +104,8 @@ "settings_privacyModeEnabled": "Modo de privacidad activado", "settings_privacyModeDisabled": "Modo de privacidad desactivado", "settings_actions": "Acciones", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "Enviar Anuncio", "settings_sendAdvertisementSubtitle": "Presencia de transmisión ahora", "settings_advertisementSent": "Anuncio enviado", @@ -2101,5 +2103,44 @@ "repeater_guest": "Información sobre repetidores", "chat_sendMessage": "Enviar mensaje", "repeater_guestTools": "Herramientas para invitados", - "room_guest": "Información del servidor" + "room_guest": "Información del servidor", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index a942aa2b..ad74eb25 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -104,6 +104,8 @@ "settings_privacyModeEnabled": "Mode de confidentialité activé", "settings_privacyModeDisabled": "Mode de confidentialité désactivé", "settings_actions": "Actions", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "S'annoncer", "settings_sendAdvertisementSubtitle": "Présence diffusée maintenant", "settings_advertisementSent": "Annonce envoyée", @@ -2073,5 +2075,44 @@ "repeater_guestTools": "Outils pour les invités", "chat_sendMessage": "Envoyer un message", "room_guest": "Informations sur le serveur", - "repeater_guest": "Informations sur les répéteurs" + "repeater_guest": "Informations sur les répéteurs", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index 6f43463c..89d7389c 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -167,6 +167,8 @@ "settings_privacyModeEnabled": "Adatvédelem mód beállítva", "settings_privacyModeDisabled": "Adatvédelem mód kikapcsolva", "settings_actions": "Tevékenységek", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "Hirdetés küldése", "settings_sendAdvertisementSubtitle": "A nyilvános megjelenés", "settings_advertisementSent": "Hirdetés elküldve", @@ -2111,5 +2113,44 @@ "repeater_guestTools": "Vendégek számára elérhető eszközök", "room_guest": "Szoba szerver információk", "chat_sendMessage": "Üzenet küldése", - "repeater_guest": "Adatok a repeaterről" + "repeater_guest": "Adatok a repeaterről", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 387c8cff..13069a5b 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -104,6 +104,8 @@ "settings_privacyModeEnabled": "Modalità privacy abilitata", "settings_privacyModeDisabled": "Modalità privacy disabilitata", "settings_actions": "Azioni", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "Invia Annuncio", "settings_sendAdvertisementSubtitle": "Presenza trasmessa ora", "settings_advertisementSent": "Annuncio inviato", @@ -2073,5 +2075,44 @@ "repeater_guest": "Informazioni sul ripetitore", "repeater_guestTools": "Strumenti per gli ospiti", "chat_sendMessage": "Invia messaggio", - "room_guest": "Informazioni sul server" + "room_guest": "Informazioni sul server", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index b63f146d..b6736b18 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -167,6 +167,8 @@ "settings_privacyModeEnabled": "プライバシーモードが有効になっています", "settings_privacyModeDisabled": "プライバシーモードは無効化されています", "settings_actions": "行動", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "広告を送信する", "settings_sendAdvertisementSubtitle": "現在、放送での活動", "settings_advertisementSent": "広告が送信されました", @@ -2111,5 +2113,44 @@ "room_guest": "ルームサーバーに関する情報", "chat_sendMessage": "メッセージを送信する", "repeater_guest": "繰り返し送信に関する情報", - "repeater_guestTools": "ゲスト向けツール" + "repeater_guestTools": "ゲスト向けツール", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 40721fe1..d2e46a40 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -167,6 +167,8 @@ "settings_privacyModeEnabled": "개인 정보 보호 모드 활성화", "settings_privacyModeDisabled": "개인 정보 보호 모드 비활성화", "settings_actions": "행동", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "광고 전송", "settings_sendAdvertisementSubtitle": "방송 활동", "settings_advertisementSent": "광고 전송", @@ -2111,5 +2113,44 @@ "repeater_guestTools": "손님용 도구", "chat_sendMessage": "메시지를 보내기", "repeater_guest": "반복 장비 정보", - "room_guest": "서버 정보" + "room_guest": "서버 정보", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 2c1342d0..d12fe26d 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -202,6 +202,12 @@ abstract class AppLocalizations { /// **'Close'** String get common_close; + /// No description provided for @common_done. + /// + /// In en, this message translates to: + /// **'Done'** + String get common_done; + /// No description provided for @common_edit. /// /// In en, this message translates to: @@ -916,6 +922,18 @@ abstract class AppLocalizations { /// **'Actions'** String get settings_actions; + /// No description provided for @settings_deleteAllPaths. + /// + /// In en, this message translates to: + /// **'Delete All Paths'** + String get settings_deleteAllPaths; + + /// No description provided for @settings_deleteAllPathsSubtitle. + /// + /// In en, this message translates to: + /// **'Clear all path data from contacts.'** + String get settings_deleteAllPathsSubtitle; + /// No description provided for @settings_sendAdvertisement. /// /// In en, this message translates to: @@ -2044,6 +2062,12 @@ abstract class AppLocalizations { /// **'Public'** String get channels_public; + /// No description provided for @channels_via. + /// + /// In en, this message translates to: + /// **'via {path}'** + String channels_via(String path); + /// No description provided for @channels_private. /// /// In en, this message translates to: @@ -2968,6 +2992,42 @@ abstract class AppLocalizations { /// **'Flags'** String get map_flags; + /// No description provided for @map_type. + /// + /// In en, this message translates to: + /// **'Type'** + String get map_type; + + /// No description provided for @map_path. + /// + /// In en, this message translates to: + /// **'Path'** + String get map_path; + + /// No description provided for @map_location. + /// + /// In en, this message translates to: + /// **'Location'** + String get map_location; + + /// No description provided for @map_estLocation. + /// + /// In en, this message translates to: + /// **'Est. Location'** + String get map_estLocation; + + /// No description provided for @map_publicKey. + /// + /// In en, this message translates to: + /// **'Public Key'** + String get map_publicKey; + + /// No description provided for @map_publicKeyPrefixHint. + /// + /// In en, this message translates to: + /// **'e.g. ab12'** + String get map_publicKeyPrefixHint; + /// No description provided for @map_shareMarkerHere. /// /// In en, this message translates to: @@ -6388,6 +6448,66 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'System language'** String get translation_systemLanguage; + + /// No description provided for @background_serviceTitle. + /// + /// In en, this message translates to: + /// **'MeshCore running'** + String get background_serviceTitle; + + /// No description provided for @background_serviceText. + /// + /// In en, this message translates to: + /// **'Keeping BLE connected'** + String get background_serviceText; + + /// No description provided for @appSettings_translationModelDeleted. + /// + /// In en, this message translates to: + /// **'Deleted {name}'** + String appSettings_translationModelDeleted(String name); + + /// No description provided for @appSettings_translationModelDeleteFailed. + /// + /// In en, this message translates to: + /// **'Failed to delete: {error}'** + String appSettings_translationModelDeleteFailed(String error); + + /// No description provided for @channels_channelUpdateFailed. + /// + /// In en, this message translates to: + /// **'Failed to update channel: {error}'** + String channels_channelUpdateFailed(String error); + + /// No description provided for @contact_typeChat. + /// + /// In en, this message translates to: + /// **'Chat'** + String get contact_typeChat; + + /// No description provided for @contact_typeRepeater. + /// + /// In en, this message translates to: + /// **'Repeater'** + String get contact_typeRepeater; + + /// No description provided for @contact_typeRoom. + /// + /// In en, this message translates to: + /// **'Room'** + String get contact_typeRoom; + + /// No description provided for @contact_typeSensor. + /// + /// In en, this message translates to: + /// **'Sensor'** + String get contact_typeSensor; + + /// No description provided for @contact_typeUnknown. + /// + /// In en, this message translates to: + /// **'Unknown'** + String get contact_typeUnknown; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index b3e12799..81df50a3 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -44,6 +44,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get common_close => 'Затвори'; + @override + String get common_done => 'Done'; + @override String get common_edit => 'Редактирай'; @@ -447,6 +450,13 @@ class AppLocalizationsBg extends AppLocalizations { @override String get settings_actions => 'Действия'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => 'Изпрати Реклама'; @@ -1097,6 +1107,11 @@ class AppLocalizationsBg extends AppLocalizations { @override String get channels_public => 'Публично'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => 'Личен'; @@ -1641,6 +1656,24 @@ class AppLocalizationsBg extends AppLocalizations { @override String get map_flags => 'Флаг'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => 'Споделете маркер тук'; @@ -3702,4 +3735,40 @@ class AppLocalizationsBg extends AppLocalizations { @override String get translation_systemLanguage => 'Език на системата'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index d7c16914..aec7223e 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -44,6 +44,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get common_close => 'Schließen'; + @override + String get common_done => 'Done'; + @override String get common_edit => 'Bearbeiten'; @@ -445,6 +448,13 @@ class AppLocalizationsDe extends AppLocalizations { @override String get settings_actions => 'Aktionen'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => 'Sende Ankündigung'; @@ -1092,6 +1102,11 @@ class AppLocalizationsDe extends AppLocalizations { @override String get channels_public => 'Öffentlich'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => 'Privat'; @@ -1638,6 +1653,24 @@ class AppLocalizationsDe extends AppLocalizations { @override String get map_flags => 'Flags'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => 'Teilen Sie den Marker hier.'; @@ -3713,4 +3746,40 @@ class AppLocalizationsDe extends AppLocalizations { @override String get translation_systemLanguage => 'Sprache des Systems'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index a2a88b0d..00926c02 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -44,6 +44,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get common_close => 'Close'; + @override + String get common_done => 'Done'; + @override String get common_edit => 'Edit'; @@ -437,6 +440,13 @@ class AppLocalizationsEn extends AppLocalizations { @override String get settings_actions => 'Actions'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => 'Send Advertisement'; @@ -1074,6 +1084,11 @@ class AppLocalizationsEn extends AppLocalizations { @override String get channels_public => 'Public'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => 'Private'; @@ -1608,6 +1623,24 @@ class AppLocalizationsEn extends AppLocalizations { @override String get map_flags => 'Flags'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => 'Share marker here'; @@ -3633,4 +3666,40 @@ class AppLocalizationsEn extends AppLocalizations { @override String get translation_systemLanguage => 'System language'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index a1270124..c9bf19aa 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -44,6 +44,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get common_close => 'Cerrar'; + @override + String get common_done => 'Done'; + @override String get common_edit => 'Editar'; @@ -444,6 +447,13 @@ class AppLocalizationsEs extends AppLocalizations { @override String get settings_actions => 'Acciones'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => 'Enviar Anuncio'; @@ -1094,6 +1104,11 @@ class AppLocalizationsEs extends AppLocalizations { @override String get channels_public => 'Público'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => 'Privado'; @@ -1637,6 +1652,24 @@ class AppLocalizationsEs extends AppLocalizations { @override String get map_flags => 'Banderas'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => 'Compartir marcador aquí'; @@ -3706,4 +3739,40 @@ class AppLocalizationsEs extends AppLocalizations { @override String get translation_systemLanguage => 'Idioma del sistema'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index a0063914..f88101fa 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -44,6 +44,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get common_close => 'Fermer'; + @override + String get common_done => 'Done'; + @override String get common_edit => 'Modifier'; @@ -449,6 +452,13 @@ class AppLocalizationsFr extends AppLocalizations { @override String get settings_actions => 'Actions'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => 'S\'annoncer'; @@ -1099,6 +1109,11 @@ class AppLocalizationsFr extends AppLocalizations { @override String get channels_public => 'Public'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => 'Privé'; @@ -1647,6 +1662,24 @@ class AppLocalizationsFr extends AppLocalizations { @override String get map_flags => 'Drapeaux'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => 'Partager le marqueur ici'; @@ -3730,4 +3763,40 @@ class AppLocalizationsFr extends AppLocalizations { @override String get translation_systemLanguage => 'Langue du système'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 1ad8558c..f6642caf 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -44,6 +44,9 @@ class AppLocalizationsHu extends AppLocalizations { @override String get common_close => 'Bezárás'; + @override + String get common_done => 'Done'; + @override String get common_edit => 'Szerkesztés'; @@ -447,6 +450,13 @@ class AppLocalizationsHu extends AppLocalizations { @override String get settings_actions => 'Tevékenységek'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => 'Hirdetés küldése'; @@ -1099,6 +1109,11 @@ class AppLocalizationsHu extends AppLocalizations { @override String get channels_public => 'A nyilvánosság számára'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => 'Személyes'; @@ -1649,6 +1664,24 @@ class AppLocalizationsHu extends AppLocalizations { @override String get map_flags => 'Zászló'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => 'Osztja ezt a tartalmat itt'; @@ -3722,4 +3755,40 @@ class AppLocalizationsHu extends AppLocalizations { @override String get translation_systemLanguage => 'Rendszer nyelvé'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 3a55559a..a4d04705 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -44,6 +44,9 @@ class AppLocalizationsIt extends AppLocalizations { @override String get common_close => 'Chiudi'; + @override + String get common_done => 'Done'; + @override String get common_edit => 'Modifica'; @@ -447,6 +450,13 @@ class AppLocalizationsIt extends AppLocalizations { @override String get settings_actions => 'Azioni'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => 'Invia Annuncio'; @@ -1095,6 +1105,11 @@ class AppLocalizationsIt extends AppLocalizations { @override String get channels_public => 'Pubblico'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => 'Privato'; @@ -1639,6 +1654,24 @@ class AppLocalizationsIt extends AppLocalizations { @override String get map_flags => 'Bandiere'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => 'Condividi marcatore qui'; @@ -3709,4 +3742,40 @@ class AppLocalizationsIt extends AppLocalizations { @override String get translation_systemLanguage => 'Lingua del sistema'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index afb8c29b..d93481be 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -44,6 +44,9 @@ class AppLocalizationsJa extends AppLocalizations { @override String get common_close => '閉じる'; + @override + String get common_done => 'Done'; + @override String get common_edit => '編集'; @@ -424,6 +427,13 @@ class AppLocalizationsJa extends AppLocalizations { @override String get settings_actions => '行動'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => '広告を送信する'; @@ -1041,6 +1051,11 @@ class AppLocalizationsJa extends AppLocalizations { @override String get channels_public => '一般の人々'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => '個人の'; @@ -1567,6 +1582,24 @@ class AppLocalizationsJa extends AppLocalizations { @override String get map_flags => '旗'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => 'この場所でシェア'; @@ -3528,4 +3561,40 @@ class AppLocalizationsJa extends AppLocalizations { @override String get translation_systemLanguage => 'システム言語'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index ff4bd261..e8d5d64a 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -44,6 +44,9 @@ class AppLocalizationsKo extends AppLocalizations { @override String get common_close => '닫기'; + @override + String get common_done => 'Done'; + @override String get common_edit => '수정'; @@ -424,6 +427,13 @@ class AppLocalizationsKo extends AppLocalizations { @override String get settings_actions => '행동'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => '광고 전송'; @@ -1036,6 +1046,11 @@ class AppLocalizationsKo extends AppLocalizations { @override String get channels_public => '대중의'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => '사립'; @@ -1563,6 +1578,24 @@ class AppLocalizationsKo extends AppLocalizations { @override String get map_flags => '깃발'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => '여기에서 마커 공유'; @@ -3527,4 +3560,40 @@ class AppLocalizationsKo extends AppLocalizations { @override String get translation_systemLanguage => '시스템 언어'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index dd770e15..db1701ed 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -44,6 +44,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get common_close => 'Sluiten'; + @override + String get common_done => 'Done'; + @override String get common_edit => 'Bewerken'; @@ -442,6 +445,13 @@ class AppLocalizationsNl extends AppLocalizations { @override String get settings_actions => 'Acties'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => 'Verzend Advertentie'; @@ -1084,6 +1094,11 @@ class AppLocalizationsNl extends AppLocalizations { @override String get channels_public => 'Openbaar'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => 'Privé'; @@ -1626,6 +1641,24 @@ class AppLocalizationsNl extends AppLocalizations { @override String get map_flags => 'Vlaggen'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => 'Deel marker hier'; @@ -3686,4 +3719,40 @@ class AppLocalizationsNl extends AppLocalizations { @override String get translation_systemLanguage => 'Taal van het systeem'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 357dd7e3..aa8db9cf 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -44,6 +44,9 @@ class AppLocalizationsPl extends AppLocalizations { @override String get common_close => 'Zamknij'; + @override + String get common_done => 'Done'; + @override String get common_edit => 'Edytuj'; @@ -450,6 +453,13 @@ class AppLocalizationsPl extends AppLocalizations { @override String get settings_actions => 'Działania'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => 'Wyślij rozgłoszenie'; @@ -1104,6 +1114,11 @@ class AppLocalizationsPl extends AppLocalizations { @override String get channels_public => 'Publiczny'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => 'Prywatny'; @@ -1650,6 +1665,24 @@ class AppLocalizationsPl extends AppLocalizations { @override String get map_flags => 'Flagi'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => 'Udostępnij znacznik tutaj'; @@ -3717,4 +3750,40 @@ class AppLocalizationsPl extends AppLocalizations { @override String get translation_systemLanguage => 'Język systemu'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 2dfcd8bd..483447dd 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -44,6 +44,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get common_close => 'Fechar'; + @override + String get common_done => 'Done'; + @override String get common_edit => 'Editar'; @@ -446,6 +449,13 @@ class AppLocalizationsPt extends AppLocalizations { @override String get settings_actions => 'Ações'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => 'Enviar Publicidade'; @@ -1095,6 +1105,11 @@ class AppLocalizationsPt extends AppLocalizations { @override String get channels_public => 'Público'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => 'Privado'; @@ -1638,6 +1653,24 @@ class AppLocalizationsPt extends AppLocalizations { @override String get map_flags => 'Bandeiras'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => 'Compartilhar marcador aqui'; @@ -3700,4 +3733,40 @@ class AppLocalizationsPt extends AppLocalizations { @override String get translation_systemLanguage => 'Idioma do sistema'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 4fac42ce..743ffbe2 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -44,6 +44,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get common_close => 'Закрыть'; + @override + String get common_done => 'Done'; + @override String get common_edit => 'Изменить'; @@ -446,6 +449,13 @@ class AppLocalizationsRu extends AppLocalizations { @override String get settings_actions => 'Действия'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => 'Отправить анонсирование'; @@ -1095,6 +1105,11 @@ class AppLocalizationsRu extends AppLocalizations { @override String get channels_public => 'Публичный'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => 'Приватный'; @@ -1641,6 +1656,24 @@ class AppLocalizationsRu extends AppLocalizations { @override String get map_flags => 'Флаги'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => 'Поделиться меткой здесь'; @@ -3714,4 +3747,40 @@ class AppLocalizationsRu extends AppLocalizations { @override String get translation_systemLanguage => 'Язык системы'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index c42e0249..6deb5e3e 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -44,6 +44,9 @@ class AppLocalizationsSk extends AppLocalizations { @override String get common_close => 'Zavrieť'; + @override + String get common_done => 'Done'; + @override String get common_edit => 'Upraviť'; @@ -441,6 +444,13 @@ class AppLocalizationsSk extends AppLocalizations { @override String get settings_actions => 'Možné akcie'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => 'Odoslať reklamu'; @@ -1084,6 +1094,11 @@ class AppLocalizationsSk extends AppLocalizations { @override String get channels_public => 'Veľké verejné'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => 'Osobné'; @@ -1627,6 +1642,24 @@ class AppLocalizationsSk extends AppLocalizations { @override String get map_flags => 'Zástavy'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => 'Zdieľte značku tu'; @@ -3680,4 +3713,40 @@ class AppLocalizationsSk extends AppLocalizations { @override String get translation_systemLanguage => 'Jazyk systému'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 2d89aa41..eee54439 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -44,6 +44,9 @@ class AppLocalizationsSl extends AppLocalizations { @override String get common_close => 'Zapri'; + @override + String get common_done => 'Done'; + @override String get common_edit => 'Uredi'; @@ -440,6 +443,13 @@ class AppLocalizationsSl extends AppLocalizations { @override String get settings_actions => 'Akcije'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => 'Pošlji Oglas'; @@ -1082,6 +1092,11 @@ class AppLocalizationsSl extends AppLocalizations { @override String get channels_public => 'Javni'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => 'Zasebni'; @@ -1623,6 +1638,24 @@ class AppLocalizationsSl extends AppLocalizations { @override String get map_flags => 'Zapestnice'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => 'Delite točke tukaj.'; @@ -3684,4 +3717,40 @@ class AppLocalizationsSl extends AppLocalizations { @override String get translation_systemLanguage => 'Jezik sistema'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 38e08939..cc4b77c5 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -44,6 +44,9 @@ class AppLocalizationsSv extends AppLocalizations { @override String get common_close => 'Stänga'; + @override + String get common_done => 'Done'; + @override String get common_edit => 'Redigera'; @@ -438,6 +441,13 @@ class AppLocalizationsSv extends AppLocalizations { @override String get settings_actions => 'Åtgärder'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => 'Skicka Annons'; @@ -1075,6 +1085,11 @@ class AppLocalizationsSv extends AppLocalizations { @override String get channels_public => 'Offentligt'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => 'Privat'; @@ -1616,6 +1631,24 @@ class AppLocalizationsSv extends AppLocalizations { @override String get map_flags => 'Flaggor'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => 'Dela markeringen här'; @@ -3662,4 +3695,40 @@ class AppLocalizationsSv extends AppLocalizations { @override String get translation_systemLanguage => 'Språk för systemet'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 8ed4b9ff..834db157 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -44,6 +44,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get common_close => 'Закрити'; + @override + String get common_done => 'Готово'; + @override String get common_edit => 'Редагувати'; @@ -69,7 +72,7 @@ class AppLocalizationsUk extends AppLocalizations { String get common_continue => 'Продовжити'; @override - String get common_share => 'Поділитися'; + String get common_share => 'Поділитись'; @override String get common_copy => 'Копіювати'; @@ -121,7 +124,7 @@ class AppLocalizationsUk extends AppLocalizations { String get connectionChoiceTcpLabel => 'TCP'; @override - String get tcpScreenTitle => 'З\'єднатися через протокол TCP'; + String get tcpScreenTitle => 'Підключитись через TCP'; @override String get tcpHostLabel => 'IP-адреса'; @@ -147,7 +150,7 @@ class AppLocalizationsUk extends AppLocalizations { String get tcpErrorHostRequired => 'Необхідно вказати IP-адресу.'; @override - String get tcpErrorPortInvalid => 'Порт повинен бути в межах від 1 до 65535.'; + String get tcpErrorPortInvalid => 'Порт має бути в межах від 1 до 65535.'; @override String get tcpErrorUnsupported => @@ -155,11 +158,11 @@ class AppLocalizationsUk extends AppLocalizations { @override String get tcpErrorTimedOut => - 'З\'єднання TCP завершилося через закінчення часу очікування.'; + 'З\'єднання TCP завершилось через закінчення часу очікування.'; @override String tcpConnectionFailed(String error) { - return 'Не вдалося встановити з\'єднання TCP: $error'; + return 'Не вдалось встановити з\'єднання TCP: $error'; } @override @@ -167,7 +170,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get usbScreenSubtitle => - 'Виберіть виявлене серійне пристрій і підключіть його безпосередньо до вашого вузла MeshCore.'; + 'Виберіть виявлений USB-пристрій і підключіть його безпосередньо до вашого вузла MeshCore.'; @override String get usbScreenStatus => 'Виберіть пристрій USB'; @@ -198,11 +201,11 @@ class AppLocalizationsUk extends AppLocalizations { String get usbErrorNotConnected => 'Немає підключених пристроїв USB.'; @override - String get usbErrorOpenFailed => 'Не вдалося відкрити вибране USB-пристрій.'; + String get usbErrorOpenFailed => 'Не вдалось відкрити вибране USB-пристрій.'; @override String get usbErrorConnectFailed => - 'Не вдалося підключитися до вибраного USB-пристрою.'; + 'Не вдалось підключитись до вибраного USB-пристрою.'; @override String get usbErrorUnsupported => @@ -220,7 +223,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get usbErrorConnectTimedOut => - 'З\'єднання не вдалося встановити. Переконайтеся, що пристрій має встановлене програмне забезпечення USB Companion.'; + 'З\'єднання не вдалось встановити. Переконайтесь, що пристрій має встановлене програмне забезпечення USB Companion.'; @override String get usbFallbackDeviceName => @@ -237,7 +240,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String usbConnectionFailed(String error) { - return 'Не вдалося встановити з\'єднання через USB: $error'; + return 'Не вдалось встановити з\'єднання через USB: $error'; } @override @@ -287,7 +290,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get scanner_chromeRequiredMessage => - 'Для підтримки Bluetooth у цьому веб-додатку потрібен Google Chrome або браузер на базі Chromium.'; + 'Для підтримки Bluetooth у цьому вебзастосунку потрібен Google Chrome або браузер на базі Chromium.'; @override String get scanner_enableBluetooth => 'Увімкніть Bluetooth'; @@ -305,7 +308,7 @@ class AppLocalizationsUk extends AppLocalizations { String get settings_deviceInfo => 'Інформація про пристрій'; @override - String get settings_appSettings => 'Налаштування програми'; + String get settings_appSettings => 'Налаштування застосунку'; @override String get settings_appSettingsSubtitle => @@ -337,13 +340,13 @@ class AppLocalizationsUk extends AppLocalizations { String get settings_radioSettingsUpdated => 'Налаштування радіо оновлено'; @override - String get settings_location => 'Розташування'; + String get settings_location => 'Геопозиція'; @override - String get settings_locationSubtitle => 'GPS координати'; + String get settings_locationSubtitle => 'GPS-координати'; @override - String get settings_locationUpdated => 'Розташування оновлено'; + String get settings_locationUpdated => 'Геопозицію оновлено'; @override String get settings_locationBothRequired => 'Введіть широту та довготу.'; @@ -356,7 +359,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get settings_locationGPSEnableSubtitle => - 'Вмикає автоматичне оновлення місцезнаходження через GPS.'; + 'Вмикає автоматичне оновлення геопозиції через GPS.'; @override String get settings_locationIntervalSec => 'Інтервал для GPS (Секунди)'; @@ -383,11 +386,11 @@ class AppLocalizationsUk extends AppLocalizations { @override String get settings_privacyModeSubtitle => - 'Приховати ім\'я/розташування в оголошеннях'; + 'Приховати ім\'я/геопозицію в оголошеннях'; @override String get settings_privacyModeToggle => - 'Увімкніть режим приватності, щоб приховати своє ім\'я та місцезнаходження в оголошеннях.'; + 'Увімкніть режим приватності, щоб приховати своє ім\'я та геопозицію в оголошеннях.'; @override String get settings_privacyModeEnabled => 'Режим приватності увімкнено'; @@ -400,7 +403,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get settings_privacySubtitle => - 'Керуйте інформацією, яку буде спільно використовуватися'; + 'Керуйте інформацією, яка буде спільно використовуватись'; @override String get settings_privacySettingsDescription => @@ -425,11 +428,11 @@ class AppLocalizationsUk extends AppLocalizations { String get settings_telemetryEnvironmentMode => 'Режим середовища телеметрії'; @override - String get settings_advertLocation => 'Розміщення реклами'; + String get settings_advertLocation => 'Геопозиція в оголошенні'; @override String get settings_advertLocationSubtitle => - 'Включити місце розташування в оголошення'; + 'Включити геопозицію в оголошення'; @override String settings_multiAck(String value) { @@ -442,6 +445,13 @@ class AppLocalizationsUk extends AppLocalizations { @override String get settings_actions => 'Дії'; + @override + String get settings_deleteAllPaths => 'Видалити всі шляхи'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Очистити всі дані шляхів у контактах.'; + @override String get settings_sendAdvertisement => 'Оголосити себе'; @@ -491,14 +501,14 @@ class AppLocalizationsUk extends AppLocalizations { 'Команди BLE, відповіді та необроблені дані'; @override - String get settings_appDebugLog => 'Журнал налагодження програми'; + String get settings_appDebugLog => 'Журнал налагодження застосунку'; @override String get settings_appDebugLogSubtitle => - 'Повідомлення налагодження програми'; + 'Повідомлення налагодження застосунку'; @override - String get settings_about => 'Про програму'; + String get settings_about => 'Про застосунок'; @override String settings_aboutVersion(String version) { @@ -584,7 +594,7 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get appSettings_title => 'Налаштування програми'; + String get appSettings_title => 'Налаштування застосунку'; @override String get appSettings_appearance => 'Вигляд'; @@ -719,14 +729,14 @@ class AppLocalizationsUk extends AppLocalizations { @override String get appSettings_pathsWillNotBeCleared => - 'Шляхи не будуть очищатися автоматично.'; + 'Шляхи не будуть очищатись автоматично.'; @override String get appSettings_autoRouteRotation => 'Авторотація маршруту'; @override String get appSettings_autoRouteRotationSubtitle => - 'Чергувати найкращі шляхи та режим «на всю мережу» (flood)'; + 'Чергувати найкращі шляхи та режим «через всю мережу» (flood)'; @override String get appSettings_autoRouteRotationEnabled => @@ -864,13 +874,13 @@ class AppLocalizationsUk extends AppLocalizations { String get appSettings_offlineMapCache => 'Офлайн-кеш карти'; @override - String get appSettings_unitsTitle => 'одиниці'; + String get appSettings_unitsTitle => 'Одиниці'; @override - String get appSettings_unitsMetric => 'Метричний (м / км)'; + String get appSettings_unitsMetric => 'Метричні (м / км)'; @override - String get appSettings_unitsImperial => 'Імперська (ft / mi)'; + String get appSettings_unitsImperial => 'Імперські (ft / mi)'; @override String get appSettings_noAreaSelected => 'Область не вибрано'; @@ -884,19 +894,20 @@ class AppLocalizationsUk extends AppLocalizations { String get appSettings_debugCard => 'Налагодження'; @override - String get appSettings_appDebugLogging => 'Логування налагодження програми'; + String get appSettings_appDebugLogging => + 'Журналювання налагодження застосунку'; @override String get appSettings_appDebugLoggingSubtitle => - 'Записувати повідомлення налагодження програми в лог для усунення несправностей.'; + 'Записувати повідомлення налагодження застосунку в журнал для усунення несправностей.'; @override String get appSettings_appDebugLoggingEnabled => - 'Логування налагодження програми увімкнено'; + 'Журналювання налагодження застосунку увімкнено'; @override String get appSettings_appDebugLoggingDisabled => - 'Налагодження програми вимкнено.'; + 'Журналювання налагодження застосунку вимкнено.'; @override String get contacts_title => 'Контакти'; @@ -1043,14 +1054,14 @@ class AppLocalizationsUk extends AppLocalizations { String get contact_clearChat => 'Очистити чат'; @override - String get contact_teleBase => 'Базовий телебачення'; + String get contact_teleBase => 'Базова телеметрія'; @override String get contact_teleBaseSubtitle => 'Дозволити спільний доступ до рівня заряду батареї та базової телеметрії'; @override - String get contact_teleLoc => 'Розташування телеметрії'; + String get contact_teleLoc => 'Геопозиція телеметрії'; @override String get contact_teleLocSubtitle => @@ -1084,11 +1095,16 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get channels_hashtagChannel => 'Канал з хештегом'; + String get channels_hashtagChannel => 'Хештег-канал'; @override String get channels_public => 'Публічний'; + @override + String channels_via(String path) { + return 'через $path'; + } + @override String get channels_private => 'Приватний'; @@ -1117,7 +1133,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String channels_channelDeleteFailed(String name) { - return 'Не вдалося видалити канал \"$name\"'; + return 'Не вдалось видалити канал \"$name\"'; } @override @@ -1151,7 +1167,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get channels_pskMustBe32Hex => - 'PSK має складатися з 32 шістнадцяткових символів.'; + 'PSK має складатись з 32 шістнадцяткових символів.'; @override String channels_channelAdded(String name) { @@ -1196,24 +1212,24 @@ class AppLocalizationsUk extends AppLocalizations { String get channels_createPrivateChannelDesc => 'Захищено секретним ключем.'; @override - String get channels_joinPrivateChannel => 'Приєднатися до приватного каналу'; + String get channels_joinPrivateChannel => 'Приєднатись до приватного каналу'; @override String get channels_joinPrivateChannelDesc => 'Ввести секретний ключ вручну.'; @override - String get channels_joinPublicChannel => 'Приєднатися до публічного каналу'; + String get channels_joinPublicChannel => 'Приєднатись до публічного каналу'; @override String get channels_joinPublicChannelDesc => - 'Будь-хто може приєднатися до цього каналу.'; + 'Будь-хто може приєднатись до цього каналу.'; @override - String get channels_joinHashtagChannel => 'Приєднатися до каналу з хештегом'; + String get channels_joinHashtagChannel => 'Приєднатись до хештег-каналу'; @override String get channels_joinHashtagChannelDesc => - 'Будь-хто може приєднатися до каналів #hashtag.'; + 'Будь-хто може приєднатись до хештег-каналів.'; @override String get channels_scanQrCode => 'Сканувати QR-код'; @@ -1256,7 +1272,7 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get chat_location => 'Розташування'; + String get chat_location => 'Геопозиція'; @override String get chat_typeMessage => 'Введіть повідомлення...'; @@ -1317,7 +1333,7 @@ class AppLocalizationsUk extends AppLocalizations { String get gifPicker_noGifsFound => 'GIF не знайдено'; @override - String get gifPicker_failedLoad => 'Не вдалося завантажити GIF-файли'; + String get gifPicker_failedLoad => 'Не вдалось завантажити GIF-файли'; @override String get gifPicker_failedSearch => 'Пошук GIF не вдався'; @@ -1326,7 +1342,7 @@ class AppLocalizationsUk extends AppLocalizations { String get gifPicker_noInternet => 'Немає інтернет-з\'єднання'; @override - String get debugLog_appTitle => 'Журнал налагодження програми'; + String get debugLog_appTitle => 'Журнал налагодження застосунку'; @override String get debugLog_bleTitle => 'Журнал налагодження BLE'; @@ -1349,7 +1365,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get debugLog_enableInSettings => - 'Увімкніть налагодження програми в налаштуваннях'; + 'Увімкніть налагодження застосунку в налаштуваннях'; @override String get debugLog_frames => 'Кадри'; @@ -1420,31 +1436,31 @@ class AppLocalizationsUk extends AppLocalizations { String get chat_autoUseSavedPath => 'Авто (використовувати збережений шлях)'; @override - String get chat_forceFloodMode => 'Примусово на всю мережу'; + String get chat_forceFloodMode => 'Примусово через всю мережу'; @override String get chat_recentAckPaths => - 'Недавні шляхи ACK (натисніть, щоб використати):'; + 'Підтверджені шляхи (натисніть, щоб використати):'; @override String get chat_pathHistoryFull => 'Історія шляхів заповнена. Видаліть записи, щоб додати нові.'; @override - String get chat_hopSingular => 'Стрибок'; + String get chat_hopSingular => 'Перехід'; @override - String get chat_hopPlural => 'стрибків'; + String get chat_hopPlural => 'переходів'; @override String chat_hopsCount(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'стрибків', - many: 'стрибків', - few: 'стрибки', - one: 'стрибок', + other: 'переходів', + many: 'переходів', + few: 'переходи', + one: 'перехід', ); return '$count $_temp0'; } @@ -1481,11 +1497,11 @@ class AppLocalizationsUk extends AppLocalizations { @override String get chat_floodModeSubtitle => - 'Використовувати перемикач маршрутизації в панелі програми'; + 'Використовувати перемикач маршрутизації в панелі застосунку'; @override String get chat_floodModeEnabled => - 'Увімкнено режим «на всю мережу». Перемикайте через іконку маршрутизації на панелі інструментів.'; + 'Увімкнено режим «через всю мережу». Перемикайте через іконку маршрутизації на панелі інструментів.'; @override String get chat_fullPath => 'Повний шлях'; @@ -1499,10 +1515,10 @@ class AppLocalizationsUk extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( hopCount, locale: localeName, - other: 'стрибків', - many: 'стрибків', - few: 'стрибки', - one: 'стрибок', + other: 'переходів', + many: 'переходів', + few: 'переходи', + one: 'перехід', ); return 'Шлях встановлено: $hopCount $_temp0 - $status'; } @@ -1530,24 +1546,24 @@ class AppLocalizationsUk extends AppLocalizations { String get chat_compressOutgoingMessages => 'Стискати вихідні повідомлення'; @override - String get chat_floodForced => 'На всю мережу (примусово)'; + String get chat_floodForced => 'Через всю мережу (примусово)'; @override - String get chat_directForced => 'Прямий (примусово)'; + String get chat_directForced => 'Напряму (примусово)'; @override String chat_hopsForced(int count) { - return '$count стрибків (примусово)'; + return '$count переходів (примусово)'; } @override - String get chat_floodAuto => 'На всю мережу (авто)'; + String get chat_floodAuto => 'Через всю мережу (авто)'; @override - String get chat_direct => 'Прямий'; + String get chat_direct => 'Напряму'; @override - String get chat_poiShared => 'Точкою інтересу поділилися'; + String get chat_poiShared => 'Поділилися точкою інтересу'; @override String chat_unread(int count) { @@ -1566,7 +1582,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String chat_couldNotOpenLink(String url) { - return 'Не вдалося відкрити посилання: $url'; + return 'Не вдалось відкрити посилання: $url'; } @override @@ -1582,12 +1598,11 @@ class AppLocalizationsUk extends AppLocalizations { String get map_losScreenTitle => 'Пряма видимість'; @override - String get map_noNodesWithLocation => - 'Немає вузлів з даними про розташування'; + String get map_noNodesWithLocation => 'Немає вузлів з даними про геопозицію'; @override String get map_nodesNeedGps => - 'Вузли повинні надавати свої GPS координати,\nщоб з\'явитися на карті.'; + 'Вузли мають надавати свої GPS координати,\nщоб з\'явитись на карті.'; @override String map_nodesCount(int count) { @@ -1625,7 +1640,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get map_disconnectConfirm => - 'Ви впевнені, що хочете відключитися від цього пристрою?'; + 'Ви впевнені, що хочете відключитись від цього пристрою?'; @override String get map_from => 'Від'; @@ -1637,10 +1652,28 @@ class AppLocalizationsUk extends AppLocalizations { String get map_flags => 'Прапорці'; @override - String get map_shareMarkerHere => 'Поділитися маркером тут'; + String get map_type => 'Тип'; @override - String get map_setAsMyLocation => 'Встановити моє місцезнаходження'; + String get map_path => 'Шлях'; + + @override + String get map_location => 'Геопозиція'; + + @override + String get map_estLocation => 'Орієнтовна геопозиція'; + + @override + String get map_publicKey => 'Публічний ключ'; + + @override + String get map_publicKeyPrefixHint => 'напр. ab12'; + + @override + String get map_shareMarkerHere => 'Поділитись маркером тут'; + + @override + String get map_setAsMyLocation => 'Встановити мою геопозицію'; @override String get map_pinLabel => 'Мітка піна'; @@ -1661,16 +1694,16 @@ class AppLocalizationsUk extends AppLocalizations { String get map_noChannelsAvailable => 'Немає доступних каналів'; @override - String get map_publicLocationShare => 'Поділитися в публічному місці'; + String get map_publicLocationShare => 'Поділитись в публічному місці'; @override String map_publicLocationShareConfirm(String channelLabel) { - return 'Ви збираєтеся поділитися розташуванням у $channelLabel. Цей канал публічний, і кожен, хто має ключ PSK, може це побачити.'; + return 'Ви збираєтесь поділитись геопозицією у $channelLabel. Цей канал публічний, і кожен, хто має ключ PSK, може це побачити.'; } @override String get map_connectToShareMarkers => - 'Підключіться до пристрою, щоб поділитися маркерами'; + 'Підключіться до пристрою, щоб поділитись маркерами'; @override String get map_filterNodes => 'Фільтрувати вузли'; @@ -1688,7 +1721,7 @@ class AppLocalizationsUk extends AppLocalizations { String get map_otherNodes => 'Інші вузли'; @override - String get map_showOverlaps => 'Перекриття ключа повторювача'; + String get map_showOverlaps => 'Перекриття ключів ретрансляторів'; @override String get map_keyPrefix => 'Префікс ключа'; @@ -1707,13 +1740,13 @@ class AppLocalizationsUk extends AppLocalizations { @override String get map_showGuessedLocations => - 'Показати місцезнаходження передбачених вузлів'; + 'Показати геопозиції передбачених вузлів'; @override - String get map_showDiscoveryContacts => 'Показати контакти Відкриття'; + String get map_showDiscoveryContacts => 'Показати виявлені контакти'; @override - String get map_guessedLocation => 'Визначено місцезнаходження'; + String get map_guessedLocation => 'Передбачена геопозиція'; @override String get map_lastSeenTime => 'Час останньої активності'; @@ -1722,7 +1755,7 @@ class AppLocalizationsUk extends AppLocalizations { String get map_sharedPin => 'Спільний пін'; @override - String get map_joinRoom => 'Приєднатися до кімнати'; + String get map_joinRoom => 'Приєднатись до кімнати'; @override String get map_manageRepeater => 'Керувати ретранслятором'; @@ -1734,13 +1767,13 @@ class AppLocalizationsUk extends AppLocalizations { String get map_runTrace => 'Виконати трасування шляху'; @override - String get map_runTraceWithReturnPath => 'Повернутися назад тим же шляхом'; + String get map_runTraceWithReturnPath => 'Повернутись назад тим же шляхом'; @override String get map_removeLast => 'Видалити останній'; @override - String get map_pathTraceCancelled => 'Відмінується трасування шляху'; + String get map_pathTraceCancelled => 'Трасування шляху скасовано.'; @override String get mapCache_title => 'Офлайн-кеш карти'; @@ -1880,7 +1913,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get dialog_disconnectConfirm => - 'Ви впевнені, що хочете відключитися від цього пристрою?'; + 'Ви впевнені, що хочете відключитись від цього пристрою?'; @override String get login_repeaterLogin => 'Вхід у ретранслятор'; @@ -1919,7 +1952,7 @@ class AppLocalizationsUk extends AppLocalizations { String get login_autoUseSavedPath => 'Авто (використовувати збережений шлях)'; @override - String get login_forceFloodMode => 'Примусово на всю мережу'; + String get login_forceFloodMode => 'Примусово через всю мережу'; @override String get login_managePaths => 'Керувати шляхами'; @@ -1957,10 +1990,10 @@ class AppLocalizationsUk extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'стрибками', - many: 'стрибками', - few: 'стрибками', - one: 'стрибком', + other: 'переходами', + many: 'переходами', + few: 'переходами', + one: 'переходом', ); return 'Використання шляху з $count $_temp0'; } @@ -1973,7 +2006,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get path_hexPrefixInstructions => - 'Введіть 2-символьні hex-префікси для кожного стрибка, розділені комами.'; + 'Введіть 2-символьні hex-префікси для кожного переходу, розділені комами.'; @override String get path_hexPrefixExample => @@ -1984,7 +2017,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get path_helperMaxHops => - 'Макс. 64 стрибки. Кожен префікс - 2 шістнадцяткові символи (1 байт)'; + 'Макс. 64 переходи. Кожен префікс — 2 шістнадцяткові символи (1 байт)'; @override String get path_selectFromContacts => 'Вибрати з контактів:'; @@ -2003,7 +2036,7 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get path_tooLong => 'Шлях занадто довгий. Максимум 64 стрибки.'; + String get path_tooLong => 'Шлях занадто довгий. Максимум 64 переходи.'; @override String get path_setPath => 'Встановити шлях'; @@ -2024,7 +2057,7 @@ class AppLocalizationsUk extends AppLocalizations { String get repeater_managementTools => 'Інструменти керування'; @override - String get repeater_guestTools => 'Інструменти для гостей'; + String get repeater_guestTools => 'Гостьові інструменти'; @override String get repeater_status => 'Статус'; @@ -2051,7 +2084,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_neighborsSubtitle => - 'Показати сусідів нульового стрибка.'; + 'Показати сусідів, доступних без ретрансляції.'; @override String get repeater_settings => 'Налаштування'; @@ -2077,7 +2110,7 @@ class AppLocalizationsUk extends AppLocalizations { 'Авто (використовувати збережений шлях)'; @override - String get repeater_forceFloodMode => 'Примусово на всю мережу'; + String get repeater_forceFloodMode => 'Примусово через всю мережу'; @override String get repeater_pathManagement => 'Керування шляхами'; @@ -2154,17 +2187,17 @@ class AppLocalizationsUk extends AppLocalizations { @override String repeater_packetTxTotal(int total, String flood, String direct) { - return 'Всього: $total, На всю мережу: $flood, Прямі: $direct'; + return 'Всього: $total, Через всю мережу: $flood, Прямі: $direct'; } @override String repeater_packetRxTotal(int total, String flood, String direct) { - return 'Всього: $total, На всю мережу: $flood, Прямі: $direct'; + return 'Всього: $total, Через всю мережу: $flood, Прямі: $direct'; } @override String repeater_duplicatesFloodDirect(String flood, String direct) { - return 'На всю мережу: $flood, Прямі: $direct'; + return 'Через всю мережу: $flood, Прямі: $direct'; } @override @@ -2223,7 +2256,7 @@ class AppLocalizationsUk extends AppLocalizations { String get repeater_codingRate => 'Швидкість кодування'; @override - String get repeater_locationSettings => 'Налаштування розташування'; + String get repeater_locationSettings => 'Налаштування геопозиції'; @override String get repeater_latitude => 'Широта'; @@ -2261,14 +2294,14 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_privacyModeSubtitle => - 'Приховати ім\'я/розташування в оголошеннях'; + 'Приховати ім\'я/геопозицію в оголошеннях'; @override String get repeater_advertisementSettings => 'Налаштування оголошень'; @override String get repeater_localAdvertInterval => - 'Інтервал локальних оголошень (0 стрибків)'; + 'Інтервал локальних оголошень (без ретрансляції)'; @override String repeater_localAdvertIntervalMinutes(int minutes) { @@ -2277,7 +2310,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_floodAdvertInterval => - 'Інтервал оголошень на всю мережу (flood)'; + 'Інтервал оголошень через всю мережу (flood)'; @override String repeater_floodAdvertIntervalHours(int hours) { @@ -2304,7 +2337,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_regenerateIdentityKey => - 'Перегенерувати ключ ідентичності'; + 'Перегенерувати ключ ідентифікації'; @override String get repeater_regenerateIdentityKeySubtitle => @@ -2312,7 +2345,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_regenerateIdentityKeyConfirm => - 'Це створить нову ідентичність для ретранслятора. Продовжити?'; + 'Це створить нову ідентифікацію для ретранслятора. Продовжити?'; @override String get repeater_eraseFileSystem => 'Очистити файлову систему'; @@ -2361,7 +2394,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_refreshLocationSettings => - 'Оновити налаштування розташування'; + 'Оновити налаштування геопозиції'; @override String get repeater_refreshPacketForwarding => 'Оновити пересилання пакетів'; @@ -2450,7 +2483,7 @@ class AppLocalizationsUk extends AppLocalizations { String get repeater_cliQuickClockSync => 'Синхронізація годинника'; @override - String get repeater_cliQuickDiscovery => 'Відкрити сусідів'; + String get repeater_cliQuickDiscovery => 'Виявити сусідів'; @override String get repeater_cliHelpAdvert => 'Надсилає пакет оголошення'; @@ -2492,7 +2525,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_cliHelpSetFloodMax => - 'Встановлює максимальну кількість стрибків для вхідних пакетів flood (якщо >= max, пакет не пересилається).'; + 'Встановлює максимальну кількість переходів для вхідних пакетів flood (якщо >= max, пакет не пересилається).'; @override String get repeater_cliHelpSetIntThresh => @@ -2512,7 +2545,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_cliHelpSetFloodAdvertInterval => - 'Встановлює інтервал таймера в годинах для надсилання пакету оголошення на всю мережу. Встановіть 0 для вимкнення.'; + 'Встановлює інтервал таймера в годинах для надсилання пакету оголошення через всю мережу. Встановіть 0 для вимкнення.'; @override String get repeater_cliHelpSetGuestPassword => @@ -2539,7 +2572,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_cliHelpSetTxDelay => - 'Встановлює множник для часу роботи в режимі «на всю мережу» (flood) для пакету та системи випадкових слотів, щоб затримати його відправку (для зменшення ймовірності колізій).'; + 'Встановлює множник для часу роботи в режимі «через всю мережу» (flood) для пакету та системи випадкових слотів, щоб затримати його відправку (для зменшення ймовірності колізій).'; @override String get repeater_cliHelpSetDirectTxDelay => @@ -2602,7 +2635,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_cliHelpRegion => - '(тільки серійний) Перелічує всі визначені регіони та поточні дозволи на оголошення «на всю мережу» (flood).'; + '(тільки серійний) Перелічує всі визначені регіони та поточні дозволи на оголошення «через всю мережу» (flood).'; @override String get repeater_cliHelpRegionLoad => @@ -2656,11 +2689,11 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_cliHelpGpsAdvert => - 'Надає конфігурацію оголошення розташування вузла:\n- none : не включати розташування в оголошення\n- share : ділитися розташуванням GPS (з SensorManager)\n- prefs : оголошувати розташування, збережене в налаштуваннях'; + 'Надає конфігурацію оголошення геопозиції вузла:\n- none : не включати геопозицію в оголошення\n- share : ділитись геопозицією GPS (з SensorManager)\n- prefs : оголошувати геопозицію, збережену в налаштуваннях'; @override String get repeater_cliHelpGpsAdvertSet => - 'Встановлює конфігурацію оголошення розташування.'; + 'Встановлює конфігурацію оголошення геопозиції.'; @override String get repeater_commandsListTitle => 'Список команд'; @@ -2679,7 +2712,7 @@ class AppLocalizationsUk extends AppLocalizations { String get repeater_bridge => 'Міст'; @override - String get repeater_logging => 'Логування'; + String get repeater_logging => 'Журналювання'; @override String get repeater_neighborsRepeaterOnly => 'Сусіди (Тільки ретранслятор)'; @@ -2790,7 +2823,7 @@ class AppLocalizationsUk extends AppLocalizations { String get channelPath_otherObservedPaths => 'Інші спостережувані шляхи'; @override - String get channelPath_repeaterHops => 'Стрибки ретранслятора'; + String get channelPath_repeaterHops => 'Переходи через ретранслятори'; @override String get channelPath_noHopDetails => @@ -2822,7 +2855,7 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get channelPath_noLocationData => 'Немає даних про розташування'; + String get channelPath_noLocationData => 'Немає даних про геопозицію'; @override String channelPath_timeWithDate(int day, int month, String time) { @@ -2838,19 +2871,19 @@ class AppLocalizationsUk extends AppLocalizations { String get channelPath_unknownPath => 'Невідомий'; @override - String get channelPath_floodPath => 'На всю мережу'; + String get channelPath_floodPath => 'Через всю мережу'; @override - String get channelPath_directPath => 'Прямий'; + String get channelPath_directPath => 'Напряму'; @override String channelPath_observedZeroOf(int total) { - return '0 з $total стрибків'; + return '0 з $total переходів'; } @override String channelPath_observedSomeOf(int observed, int total) { - return '$observed з $total стрибків'; + return '$observed з $total переходів'; } @override @@ -2878,7 +2911,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get channelPath_noHopDetailsAvailable => - 'Деталі стрибків недоступні для цього пакету.'; + 'Деталі переходів недоступні для цього пакету.'; @override String get channelPath_unknownRepeater => 'Невідомий ретранслятор'; @@ -2891,17 +2924,17 @@ class AppLocalizationsUk extends AppLocalizations { @override String get community_createDesc => - 'Створити нову спільноту та поділитися через QR-код.'; + 'Створити нову спільноту та поділитись через QR-код.'; @override - String get community_join => 'Приєднатися'; + String get community_join => 'Приєднатись'; @override - String get community_joinTitle => 'Приєднатися до спільноти'; + String get community_joinTitle => 'Приєднатись до спільноти'; @override String community_joinConfirmation(String name) { - return 'Ви бажаєте приєднатися до спільноти «$name»?'; + return 'Ви бажаєте приєднатись до спільноти «$name»?'; } @override @@ -2937,16 +2970,16 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get community_qrTitle => 'Поділитися спільнотою'; + String get community_qrTitle => 'Поділитись спільнотою'; @override String community_qrInstructions(String name) { - return 'Відскануйте цей QR-код, щоб приєднатися до $name'; + return 'Відскануйте цей QR-код, щоб приєднатись до $name'; } @override String get community_hashtagPrivacyHint => - 'Канали хештегів спільноти доступні лише членам спільноти'; + 'Хештег-канали спільноти доступні лише членам спільноти'; @override String get community_invalidQrCode => 'Недійсний QR-код спільноти'; @@ -3003,11 +3036,11 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get community_regenerateSecret => 'Перегенерувати секрет'; + String get community_regenerateSecret => 'Перегенерувати секретний ключ'; @override String community_regenerateSecretConfirm(String name) { - return 'Перегенерувати секретний ключ для «$name»? Всі учасники повинні будуть відсканувати новий QR-код, щоб продовжити спілкування.'; + return 'Перегенерувати секретний ключ для «$name»? Усі учасники матимуть відсканувати новий QR-код, щоб продовжити спілкування.'; } @override @@ -3015,20 +3048,20 @@ class AppLocalizationsUk extends AppLocalizations { @override String community_secretRegenerated(String name) { - return 'Секретний пароль для «$name» перегенеровано'; + return 'Секретний ключ для «$name» перегенеровано'; } @override - String get community_updateSecret => 'Оновити секрет'; + String get community_updateSecret => 'Оновити секретний ключ'; @override String community_secretUpdated(String name) { - return 'Зміну секрету для «$name» оновлено'; + return 'Секретний ключ для «$name» оновлено'; } @override String community_scanToUpdateSecret(String name) { - return 'Відскануйте новий QR-код, щоб оновити пароль для «$name»'; + return 'Відскануйте новий QR-код, щоб оновити секретний ключ для «$name»'; } @override @@ -3036,7 +3069,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get community_addHashtagChannelDesc => - 'Додати канал хештегу для цієї спільноти'; + 'Додати хештег-канал для цієї спільноти'; @override String get community_selectCommunity => 'Вибрати спільноту'; @@ -3046,7 +3079,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get community_regularHashtagDesc => - 'Публічний хештег (будь-хто може приєднатися)'; + 'Публічний хештег (будь-хто може приєднатись)'; @override String get community_communityHashtag => 'Хештег спільноти'; @@ -3070,7 +3103,7 @@ class AppLocalizationsUk extends AppLocalizations { String get listFilter_latestMessages => 'Останні повідомлення'; @override - String get listFilter_heardRecently => 'Нещодавно чули'; + String get listFilter_heardRecently => 'Нещодавно почуті'; @override String get listFilter_az => 'А-Я'; @@ -3109,17 +3142,17 @@ class AppLocalizationsUk extends AppLocalizations { String get pathTrace_you => 'Ви'; @override - String get pathTrace_failed => 'Відстеження шляху не вдалося.'; + String get pathTrace_failed => 'Відстеження шляху не вдалось.'; @override String get pathTrace_notAvailable => 'Трасування шляху недоступне.'; @override - String get pathTrace_refreshTooltip => 'Оновити Path Trace'; + String get pathTrace_refreshTooltip => 'Оновити трасування шляху'; @override String get pathTrace_someHopsNoLocation => - 'Одне або більше хмелів відсутнє місце розташування!'; + 'Один або декілька переходів не мають даних про геопозицію!'; @override String get pathTrace_clearTooltip => 'Очистити шлях'; @@ -3155,7 +3188,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String losCustomPointLabel(int index) { - return 'Спеціальний $index'; + return 'Власна точка $index'; } @override @@ -3220,7 +3253,7 @@ class AppLocalizationsUk extends AppLocalizations { 'Недійсні дані про точки/висоту для розрахунку LOS.'; @override - String get losRenameCustomPoint => 'Перейменуйте спеціальну точку'; + String get losRenameCustomPoint => 'Перейменувати власну точку'; @override String get losPointName => 'Назва точки'; @@ -3270,10 +3303,10 @@ class AppLocalizationsUk extends AppLocalizations { String get contacts_ping => 'Пінгувати'; @override - String get contacts_repeaterPathTrace => 'Трасування шляху до повторювача'; + String get contacts_repeaterPathTrace => 'Трасування шляху до ретранслятора'; @override - String get contacts_repeaterPing => 'Пінгувати повторювач'; + String get contacts_repeaterPing => 'Пінгувати ретранслятор'; @override String get contacts_roomPathTrace => 'Трасування шляху до серверу кімнати'; @@ -3299,28 +3332,26 @@ class AppLocalizationsUk extends AppLocalizations { String get contacts_contactImported => 'Контакт було імпортовано.'; @override - String get contacts_contactImportFailed => 'Контакт не вдалося імпортувати'; + String get contacts_contactImportFailed => 'Контакт не вдалось імпортувати'; @override - String get contacts_zeroHopAdvert => 'Реклама без перехоплення'; + String get contacts_zeroHopAdvert => 'Оголошення без ретрансляції'; @override - String get contacts_floodAdvert => 'Залив реклами'; + String get contacts_floodAdvert => 'Оголошення з ретрансляцією'; @override - String get contacts_copyAdvertToClipboard => - 'Копіювати оголошення в буфер обміну'; + String get contacts_copyAdvertToClipboard => 'Копіювати оголошення'; @override - String get contacts_addContactFromClipboard => - 'Додати контакт з буфера обміну'; + String get contacts_addContactFromClipboard => 'Додати контакт з буфера'; @override String get contacts_ShareContact => 'Копіювати контакт у буфер обміну'; @override String get contacts_ShareContactZeroHop => - 'Поділитися контактом за оголошенням'; + 'Поділитись контактом за оголошенням'; @override String get contacts_zeroHopContactAdvertSent => @@ -3328,15 +3359,15 @@ class AppLocalizationsUk extends AppLocalizations { @override String get contacts_zeroHopContactAdvertFailed => - 'Не вдалося надіслати контакт.'; + 'Не вдалось надіслати контакт.'; @override String get contacts_contactAdvertCopied => - 'Рекламу скопійовано до буфера обміну.'; + 'Оголошення скопійовано до буфера обміну.'; @override String get contacts_contactAdvertCopyFailed => - 'Копіювання оголошення в буфер обміну завершилося невдало'; + 'Копіювання оголошення в буфер обміну завершилось невдало'; @override String get notification_activityTitle => 'Активність MeshCore'; @@ -3374,7 +3405,7 @@ class AppLocalizationsUk extends AppLocalizations { locale: localeName, other: 'нових вузлів', many: 'нових вузлів', - few: 'нових вузли', + few: 'нові вузли', one: 'новий вузол', ); return '$count $_temp0'; @@ -3390,25 +3421,25 @@ class AppLocalizationsUk extends AppLocalizations { @override String get settings_gpxExportRepeaters => - 'Експортувати ретранслятори / сервер кімнати до GPX'; + 'Експорт ретрансляторів і серверів кімнат у GPX'; @override String get settings_gpxExportRepeatersSubtitle => - 'Експортує ретранслятори / сервер кімнати з місцезнаходженням у файл GPX.'; + 'Експортує ретранслятори та сервери кімнат з геопозицією у файл GPX.'; @override - String get settings_gpxExportContacts => 'Експортувати супутників до GPX'; + String get settings_gpxExportContacts => 'Експорт контактів у GPX'; @override String get settings_gpxExportContactsSubtitle => - 'Експортує супутників з місцезнаходженням у файл GPX.'; + 'Експортує контакти з геопозицією у файл GPX.'; @override - String get settings_gpxExportAll => 'Експортувати всі контакти до GPX'; + String get settings_gpxExportAll => 'Експорт усіх контактів у GPX'; @override String get settings_gpxExportAllSubtitle => - 'Експортує всі контакти з місцем розташування у файл GPX.'; + 'Експортує всі контакти з геопозицією у файл GPX.'; @override String get settings_gpxExportSuccess => 'Успішно експортовано файл GPX.'; @@ -3421,14 +3452,14 @@ class AppLocalizationsUk extends AppLocalizations { 'Не підтримується на вашому пристрої/операційній системі'; @override - String get settings_gpxExportError => 'Сталася помилка під час експорту.'; + String get settings_gpxExportError => 'Сталась помилка під час експорту.'; @override String get settings_gpxExportRepeatersRoom => - 'Місцезнаходження повторювача та сервера кімнати'; + 'Геопозиції ретрансляторів та серверів кімнат'; @override - String get settings_gpxExportChat => 'Місця супутників'; + String get settings_gpxExportChat => 'Геопозиції контактів'; @override String get settings_gpxExportAllContacts => 'Усі місця контактів'; @@ -3442,7 +3473,7 @@ class AppLocalizationsUk extends AppLocalizations { 'експорт даних карти meshcore-open у форматі GPX'; @override - String get snrIndicator_nearByRepeaters => 'Ближні ретранслятори'; + String get snrIndicator_nearByRepeaters => 'Найближчі ретранслятори'; @override String get snrIndicator_lastSeen => 'Останній раз бачили'; @@ -3463,15 +3494,15 @@ class AppLocalizationsUk extends AppLocalizations { @override String get contactsSettings_autoAddUsersSubtitle => - 'Дозволити супутникові автоматично додавати виявлених користувачів'; + 'Дозволити пристрою-компаньйону автоматично додавати виявлених користувачів'; @override String get contactsSettings_autoAddRepeatersTitle => - 'Автоматично додавати повторювачі'; + 'Автоматично додавати ретранслятори'; @override String get contactsSettings_autoAddRepeatersSubtitle => - 'Дозволити супутнику автоматично додавати виявлені ретранслятори'; + 'Дозволити пристрою-компаньйону автоматично додавати виявлені ретранслятори'; @override String get contactsSettings_autoAddRoomServersTitle => @@ -3479,15 +3510,15 @@ class AppLocalizationsUk extends AppLocalizations { @override String get contactsSettings_autoAddRoomServersSubtitle => - 'Дозволити супровіднику автоматично додавати виявлені сервери кімнат.'; + 'Дозволити пристрою-компаньйону автоматично додавати виявлені сервери кімнат.'; @override String get contactsSettings_autoAddSensorsTitle => - 'Автоматично додавати датчики'; + 'Автоматично додавати сенсори'; @override String get contactsSettings_autoAddSensorsSubtitle => - 'Дозволити супровіднику автоматично додавати виявлені сенсори'; + 'Дозволити пристрою-компаньйону автоматично додавати виявлені сенсори'; @override String get contactsSettings_overwriteOldestTitle => 'Перезаписати найстаріше'; @@ -3537,33 +3568,33 @@ class AppLocalizationsUk extends AppLocalizations { @override String get appSettings_jumpToOldestUnreadSubtitle => - 'При відкритті чату з не прочитаними повідомленнями, прокрутіть до першого не прочитаного повідомлення, а не до останнього.'; + 'При відкритті чату з непрочитаними повідомленнями, прокрутіть до першого непрочитаного повідомлення, а не до останнього.'; @override - String get appSettings_languageHu => 'Угорський'; + String get appSettings_languageHu => 'Угорська'; @override String get appSettings_languageJa => 'Японська'; @override - String get appSettings_languageKo => 'Кореєська'; + String get appSettings_languageKo => 'Корейська'; @override String get radioStats_tooltip => 'Статистика радіо та мережі'; @override - String get radioStats_screenTitle => 'Дані про радіостанції'; + String get radioStats_screenTitle => 'Статистика радіо'; @override String get radioStats_notConnected => - 'Підключіться до пристрою, щоб переглядати статистику радіопередач.'; + 'Підключіться до пристрою, щоб переглядати статистику радіо.'; @override String get radioStats_firmwareTooOld => - 'Статистика радіо приймача вимагає супутнього програмного забезпечення версії 8 або новішої.'; + 'Статистика радіо вимагає прошивки пристрою-компаньйона версії 8 або новішої.'; @override - String get radioStats_waiting => 'Очікую на отримання даних…'; + String get radioStats_waiting => 'Очікування даних…'; @override String radioStats_noiseFloor(int noiseDbm) { @@ -3582,12 +3613,12 @@ class AppLocalizationsUk extends AppLocalizations { @override String radioStats_txAir(int seconds) { - return 'Час трансляції на телеканалі TX (загальний): $seconds секунд'; + return 'Час в ефірі TX (загальний): $seconds секунд'; } @override String radioStats_rxAir(int seconds) { - return 'Загальний час використання RX: $seconds секунд'; + return 'Час в ефірі RX (загальний): $seconds секунд'; } @override @@ -3603,11 +3634,11 @@ class AppLocalizationsUk extends AppLocalizations { String get radioStats_stripWaiting => 'Отримано статистику радіо…'; @override - String get radioStats_settingsTile => 'Дані про радіостанції'; + String get radioStats_settingsTile => 'Статистика радіо'; @override String get radioStats_settingsSubtitle => - 'Рівень шуму, RSSI, SNR та час, протягом якого пристрій використовує радіоканал.'; + 'Рівень шуму, RSSI, SNR та час в ефірі.'; @override String get translation_title => 'Переклад'; @@ -3630,14 +3661,14 @@ class AppLocalizationsUk extends AppLocalizations { String get translation_targetLanguage => 'Цільова мова'; @override - String get translation_useAppLanguage => 'Використовуйте мову додатку'; + String get translation_useAppLanguage => 'Використовувати мову застосунку'; @override String get translation_downloadedModelLabel => 'Завантажений шаблон'; @override String get translation_presetModelLabel => - 'Заздалегідь налаштований модель від Hugging Face'; + 'Попередньо налаштована модель з Hugging Face'; @override String get translation_manualUrlLabel => @@ -3673,7 +3704,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String translation_downloadFailed(String error) { - return 'Не вдалося завантажити: $error'; + return 'Не вдалось завантажити: $error'; } @override @@ -3718,4 +3749,40 @@ class AppLocalizationsUk extends AppLocalizations { @override String get translation_systemLanguage => 'Мова системи'; + + @override + String get background_serviceTitle => 'MeshCore працює'; + + @override + String get background_serviceText => 'Підтримує з\'єднання BLE'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Видалено $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Не вдалось видалити: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Не вдалось оновити канал: $error'; + } + + @override + String get contact_typeChat => 'Чат'; + + @override + String get contact_typeRepeater => 'Ретранслятор'; + + @override + String get contact_typeRoom => 'Кімната'; + + @override + String get contact_typeSensor => 'Сенсор'; + + @override + String get contact_typeUnknown => 'Невідомо'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 4f38c64a..c28b980e 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -44,6 +44,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get common_close => '关闭'; + @override + String get common_done => 'Done'; + @override String get common_edit => '编辑'; @@ -418,6 +421,13 @@ class AppLocalizationsZh extends AppLocalizations { @override String get settings_actions => '操作'; + @override + String get settings_deleteAllPaths => 'Delete All Paths'; + + @override + String get settings_deleteAllPathsSubtitle => + 'Clear all path data from contacts.'; + @override String get settings_sendAdvertisement => '发送广播'; @@ -1023,6 +1033,11 @@ class AppLocalizationsZh extends AppLocalizations { @override String get channels_public => '公共'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => '私有'; @@ -1535,6 +1550,24 @@ class AppLocalizationsZh extends AppLocalizations { @override String get map_flags => '标志'; + @override + String get map_type => 'Type'; + + @override + String get map_path => 'Path'; + + @override + String get map_location => 'Location'; + + @override + String get map_estLocation => 'Est. Location'; + + @override + String get map_publicKey => 'Public Key'; + + @override + String get map_publicKeyPrefixHint => 'e.g. ab12'; + @override String get map_shareMarkerHere => '在此分享标记'; @@ -3424,4 +3457,40 @@ class AppLocalizationsZh extends AppLocalizations { @override String get translation_systemLanguage => '系统语言'; + + @override + String get background_serviceTitle => 'MeshCore running'; + + @override + String get background_serviceText => 'Keeping BLE connected'; + + @override + String appSettings_translationModelDeleted(String name) { + return 'Deleted $name'; + } + + @override + String appSettings_translationModelDeleteFailed(String error) { + return 'Failed to delete: $error'; + } + + @override + String channels_channelUpdateFailed(String error) { + return 'Failed to update channel: $error'; + } + + @override + String get contact_typeChat => 'Chat'; + + @override + String get contact_typeRepeater => 'Repeater'; + + @override + String get contact_typeRoom => 'Room'; + + @override + String get contact_typeSensor => 'Sensor'; + + @override + String get contact_typeUnknown => 'Unknown'; } diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 96bdb845..9dd31bd9 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -104,6 +104,8 @@ "settings_privacyModeEnabled": "Privacy modus is ingeschakeld", "settings_privacyModeDisabled": "Privacy modus is uitgeschakeld", "settings_actions": "Acties", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "Verzend Advertentie", "settings_sendAdvertisementSubtitle": "Nu aanwezigheid uitzenden", "settings_advertisementSent": "Advertentie verzonden", @@ -2073,5 +2075,44 @@ "repeater_guestTools": "Gastenfuncties", "room_guest": "Informatie over de server", "chat_sendMessage": "Verzend bericht", - "repeater_guest": "Informatie over herhalingsapparatuur" + "repeater_guest": "Informatie over herhalingsapparatuur", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index b62a78af..b9180716 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -104,6 +104,8 @@ "settings_privacyModeEnabled": "Tryb prywatności włączony", "settings_privacyModeDisabled": "Tryb prywatności wyłączony", "settings_actions": "Działania", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "Wyślij rozgłoszenie", "settings_sendAdvertisementSubtitle": "Nadaj obecność teraz", "settings_advertisementSent": "Rozgłoszenie wysłane", @@ -2111,5 +2113,44 @@ "chat_sendMessage": "Wyślij wiadomość", "repeater_guestTools": "Narzędzia dla gości", "repeater_guest": "Informacje dotyczące urządzenia powtarzającego", - "room_guest": "Informacje o serwerze" + "room_guest": "Informacje o serwerze", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index bf3e8936..5de4b127 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -104,6 +104,8 @@ "settings_privacyModeEnabled": "Modo de privacidade ativado", "settings_privacyModeDisabled": "Modo de privacidade desativado", "settings_actions": "Ações", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "Enviar Publicidade", "settings_sendAdvertisementSubtitle": "Presença de transmissão agora", "settings_advertisementSent": "Anúncio enviado", @@ -2073,5 +2075,44 @@ "room_guest": "Informações do Servidor", "chat_sendMessage": "Enviar mensagem", "repeater_guest": "Informações sobre repetidores", - "repeater_guestTools": "Ferramentas para hóspedes" + "repeater_guestTools": "Ferramentas para hóspedes", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index a83d1394..07bf5259 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -81,6 +81,8 @@ "settings_privacyModeEnabled": "Режим конфиденциальности включен", "settings_privacyModeDisabled": "Режим конфиденциальности выключен", "settings_actions": "Действия", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "Отправить анонсирование", "settings_sendAdvertisementSubtitle": "Отправить анонсирование о присутствии сейчас", "settings_advertisementSent": "Анонсирование отправлено", @@ -1313,5 +1315,44 @@ "chat_sendMessage": "Отправить сообщение", "repeater_guest": "Информация о ретрансляторе", "room_guest": "Информация о сервере", - "repeater_guestTools": "Инструменты для гостей" + "repeater_guestTools": "Инструменты для гостей", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index e4466c30..61fc64f7 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -104,6 +104,8 @@ "settings_privacyModeEnabled": "Ochranný režim je povolený.", "settings_privacyModeDisabled": "Ochranný režim je vypnutý", "settings_actions": "Možné akcie", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "Odoslať reklamu", "settings_sendAdvertisementSubtitle": "Momentálne priezornejšie.", "settings_advertisementSent": "Reklama odeslaná", @@ -2073,5 +2075,44 @@ "chat_sendMessage": "Odoslať správu", "repeater_guest": "Informácie o opakovači", "room_guest": "Informácie o serveri", - "repeater_guestTools": "Nástroje pre hostí" + "repeater_guestTools": "Nástroje pre hostí", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index f6a317ef..f6339142 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -104,6 +104,8 @@ "settings_privacyModeEnabled": "Privatni način je omogočen.", "settings_privacyModeDisabled": "Privatni način je onemogočen.", "settings_actions": "Akcije", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "Pošlji Oglas", "settings_sendAdvertisementSubtitle": "Trenutna prisotnost v oddajah", "settings_advertisementSent": "Oglas poslan", @@ -2073,5 +2075,44 @@ "repeater_guest": "Informacije o ponovljalniku", "chat_sendMessage": "Pošlji sporočilo", "room_guest": "Informacije o strežniku", - "repeater_guestTools": "Naložila za goste" + "repeater_guestTools": "Naložila za goste", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index eab348c7..d5947cba 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -104,6 +104,8 @@ "settings_privacyModeEnabled": "Privatläget är aktiverat", "settings_privacyModeDisabled": "Privatläge är avstängt", "settings_actions": "Åtgärder", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "Skicka Annons", "settings_sendAdvertisementSubtitle": "Sändning finns nu", "settings_advertisementSent": "Annons skickad", @@ -2073,5 +2075,44 @@ "repeater_guest": "Information om repetorer", "chat_sendMessage": "Skicka meddelande", "repeater_guestTools": "Gästverktyg", - "room_guest": "Information om servern" + "room_guest": "Information om servern", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index a005b369..f0cfd360 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1,5 +1,5 @@ { - "channels_channelDeleteFailed": "Не вдалося видалити канал \"{name}\"", + "channels_channelDeleteFailed": "Не вдалось видалити канал \"{name}\"", "@channels_channelDeleteFailed": { "placeholders": { "name": { @@ -18,6 +18,7 @@ "common_save": "Зберегти", "common_delete": "Видалити", "common_close": "Закрити", + "common_done": "Готово", "common_edit": "Редагувати", "common_add": "Додати", "common_settings": "Налаштування", @@ -26,7 +27,7 @@ "common_disconnected": "Відключено", "common_create": "Створити", "common_continue": "Продовжити", - "common_share": "Поділитися", + "common_share": "Поділитись", "common_copy": "Копіювати", "common_retry": "Повторити", "common_hide": "Приховати", @@ -81,7 +82,7 @@ "device_meshcore": "MeshCore", "settings_title": "Налаштування", "settings_deviceInfo": "Інформація про пристрій", - "settings_appSettings": "Налаштування програми", + "settings_appSettings": "Налаштування застосунку", "settings_appSettingsSubtitle": "Сповіщення, повідомлення та налаштування карти", "settings_nodeSettings": "Налаштування вузла", "settings_nodeName": "Ім'я вузла", @@ -91,19 +92,21 @@ "settings_radioSettings": "Налаштування радіо", "settings_radioSettingsSubtitle": "Частота, потужність, коефіцієнт розширення", "settings_radioSettingsUpdated": "Налаштування радіо оновлено", - "settings_location": "Розташування", - "settings_locationSubtitle": "GPS координати", - "settings_locationUpdated": "Розташування оновлено", + "settings_location": "Геопозиція", + "settings_locationSubtitle": "GPS-координати", + "settings_locationUpdated": "Геопозицію оновлено", "settings_locationBothRequired": "Введіть широту та довготу.", "settings_locationInvalid": "Некоректна широта або довгота.", "settings_latitude": "Широта", "settings_longitude": "Довгота", "settings_privacyMode": "Режим приватності", - "settings_privacyModeSubtitle": "Приховати ім'я/розташування в оголошеннях", - "settings_privacyModeToggle": "Увімкніть режим приватності, щоб приховати своє ім'я та місцезнаходження в оголошеннях.", + "settings_privacyModeSubtitle": "Приховати ім'я/геопозицію в оголошеннях", + "settings_privacyModeToggle": "Увімкніть режим приватності, щоб приховати своє ім'я та геопозицію в оголошеннях.", "settings_privacyModeEnabled": "Режим приватності увімкнено", "settings_privacyModeDisabled": "Режим приватності вимкнено", "settings_actions": "Дії", + "settings_deleteAllPaths": "Видалити всі шляхи", + "settings_deleteAllPathsSubtitle": "Очистити всі дані шляхів у контактах.", "settings_sendAdvertisement": "Оголосити себе", "settings_sendAdvertisementSubtitle": "Транслювати присутність зараз", "settings_advertisementSent": "Оголошення надіслано", @@ -118,9 +121,9 @@ "settings_debug": "Налагодження", "settings_bleDebugLog": "Журнал налагодження BLE", "settings_bleDebugLogSubtitle": "Команди BLE, відповіді та необроблені дані", - "settings_appDebugLog": "Журнал налагодження програми", - "settings_appDebugLogSubtitle": "Повідомлення налагодження програми", - "settings_about": "Про програму", + "settings_appDebugLog": "Журнал налагодження застосунку", + "settings_appDebugLogSubtitle": "Повідомлення налагодження застосунку", + "settings_about": "Про застосунок", "settings_aboutVersion": "MeshCore Open v{version}", "@settings_aboutVersion": { "placeholders": { @@ -156,7 +159,7 @@ } } }, - "appSettings_title": "Налаштування програми", + "appSettings_title": "Налаштування застосунку", "appSettings_appearance": "Вигляд", "appSettings_theme": "Тема", "appSettings_themeSystem": "Системна", @@ -194,9 +197,9 @@ "appSettings_clearPathOnMaxRetry": "Очищати шлях після макс. спроб", "appSettings_clearPathOnMaxRetrySubtitle": "Скидати шлях до контакту після 5 невдалих спроб надсилання", "appSettings_pathsWillBeCleared": "Шляхи будуть очищені після 5 невдалих спроб.", - "appSettings_pathsWillNotBeCleared": "Шляхи не будуть очищатися автоматично.", + "appSettings_pathsWillNotBeCleared": "Шляхи не будуть очищатись автоматично.", "appSettings_autoRouteRotation": "Авторотація маршруту", - "appSettings_autoRouteRotationSubtitle": "Чергувати найкращі шляхи та режим «на всю мережу» (flood)", + "appSettings_autoRouteRotationSubtitle": "Чергувати найкращі шляхи та режим «через всю мережу» (flood)", "appSettings_autoRouteRotationEnabled": "Авторотація маршрутизації увімкнена", "appSettings_autoRouteRotationDisabled": "Авторотація маршрутизації вимкнена", "appSettings_battery": "Батарея", @@ -251,10 +254,10 @@ } }, "appSettings_debugCard": "Налагодження", - "appSettings_appDebugLogging": "Логування налагодження програми", - "appSettings_appDebugLoggingSubtitle": "Записувати повідомлення налагодження програми в лог для усунення несправностей.", - "appSettings_appDebugLoggingEnabled": "Логування налагодження програми увімкнено", - "appSettings_appDebugLoggingDisabled": "Налагодження програми вимкнено.", + "appSettings_appDebugLogging": "Журналювання налагодження застосунку", + "appSettings_appDebugLoggingSubtitle": "Записувати повідомлення налагодження застосунку в журнал для усунення несправностей.", + "appSettings_appDebugLoggingEnabled": "Журналювання налагодження застосунку увімкнено", + "appSettings_appDebugLoggingDisabled": "Журналювання налагодження застосунку вимкнено.", "contacts_title": "Контакти", "contacts_noContacts": "Контактів не знайдено.", "contacts_contactsWillAppear": "Контакти з'являться, коли пристрої надішлють оголошення.", @@ -338,8 +341,9 @@ } } }, - "channels_hashtagChannel": "Канал з хештегом", + "channels_hashtagChannel": "Хештег-канал", "channels_public": "Публічний", + "channels_via": "через {path}", "channels_private": "Приватний", "channels_publicChannel": "Публічний канал", "channels_privateChannel": "Приватний канал", @@ -371,7 +375,7 @@ "channels_pskHex": "PSK (Hex)", "channels_generateRandomPsk": "Згенерувати випадковий ключ PSK", "channels_enterChannelName": "Будь ласка, введіть назву каналу", - "channels_pskMustBe32Hex": "PSK має складатися з 32 шістнадцяткових символів.", + "channels_pskMustBe32Hex": "PSK має складатись з 32 шістнадцяткових символів.", "channels_channelAdded": "Канал «{name}» додано", "@channels_channelAdded": { "placeholders": { @@ -422,7 +426,7 @@ } } }, - "chat_location": "Розташування", + "chat_location": "Геопозиція", "chat_sendMessageTo": "Надіслати повідомлення {contactName}", "@chat_sendMessageTo": { "placeholders": { @@ -466,17 +470,17 @@ "gifPicker_searchHint": "Пошук GIF...", "gifPicker_poweredBy": "На базі GIPHY", "gifPicker_noGifsFound": "GIF не знайдено", - "gifPicker_failedLoad": "Не вдалося завантажити GIF-файли", + "gifPicker_failedLoad": "Не вдалось завантажити GIF-файли", "gifPicker_failedSearch": "Пошук GIF не вдався", "gifPicker_noInternet": "Немає інтернет-з'єднання", - "debugLog_appTitle": "Журнал налагодження програми", + "debugLog_appTitle": "Журнал налагодження застосунку", "debugLog_bleTitle": "Журнал налагодження BLE", "debugLog_copyLog": "Копіювати журнал", "debugLog_clearLog": "Очистити журнал", "debugLog_copied": "Журнал налагодження скопійовано", "debugLog_bleCopied": "Журнал BLE скопійовано", "debugLog_noEntries": "Поки що немає записів журналу налагодження.", - "debugLog_enableInSettings": "Увімкніть налагодження програми в налаштуваннях", + "debugLog_enableInSettings": "Увімкніть налагодження застосунку в налаштуваннях", "debugLog_frames": "Кадри", "debugLog_rawLogRx": "Необроблений лог - RX", "debugLog_noBleActivity": "Поки що немає активності BLE.", @@ -546,12 +550,12 @@ "chat_pathManagement": "Керування шляхами", "chat_routingMode": "Режим маршрутизації", "chat_autoUseSavedPath": "Авто (використовувати збережений шлях)", - "chat_forceFloodMode": "Примусово на всю мережу", - "chat_recentAckPaths": "Недавні шляхи ACK (натисніть, щоб використати):", + "chat_forceFloodMode": "Примусово через всю мережу", + "chat_recentAckPaths": "Підтверджені шляхи (натисніть, щоб використати):", "chat_pathHistoryFull": "Історія шляхів заповнена. Видаліть записи, щоб додати нові.", - "chat_hopSingular": "Стрибок", - "chat_hopPlural": "стрибків", - "chat_hopsCount": "{count} {count, plural, =1{стрибок} few{стрибки} many{стрибків} other{стрибків}}", + "chat_hopSingular": "Перехід", + "chat_hopPlural": "переходів", + "chat_hopsCount": "{count} {count, plural, =1{перехід} few{переходи} many{переходів} other{переходів}}", "@chat_hopsCount": { "placeholders": { "count": { @@ -568,11 +572,11 @@ "chat_clearPath": "Очистити шлях", "chat_clearPathSubtitle": "Примусово повторити пошук при наступному надсиланні", "chat_pathCleared": "Шлях очищено. Наступне повідомлення оновить маршрут.", - "chat_floodModeSubtitle": "Використовувати перемикач маршрутизації в панелі програми", - "chat_floodModeEnabled": "Увімкнено режим «на всю мережу». Перемикайте через іконку маршрутизації на панелі інструментів.", + "chat_floodModeSubtitle": "Використовувати перемикач маршрутизації в панелі застосунку", + "chat_floodModeEnabled": "Увімкнено режим «через всю мережу». Перемикайте через іконку маршрутизації на панелі інструментів.", "chat_fullPath": "Повний шлях", "chat_pathDetailsNotAvailable": "Деталі шляху ще недоступні. Спробуйте надіслати повідомлення для оновлення.", - "chat_pathSetHops": "Шлях встановлено: {hopCount} {hopCount, plural, =1{стрибок} few{стрибки} many{стрибків} other{стрибків}} - {status}", + "chat_pathSetHops": "Шлях встановлено: {hopCount} {hopCount, plural, =1{перехід} few{переходи} many{переходів} other{переходів}} - {status}", "@chat_pathSetHops": { "placeholders": { "hopCount": { @@ -590,9 +594,9 @@ "chat_path": "Шлях", "chat_publicKey": "Відкритий ключ", "chat_compressOutgoingMessages": "Стискати вихідні повідомлення", - "chat_floodForced": "На всю мережу (примусово)", - "chat_directForced": "Прямий (примусово)", - "chat_hopsForced": "{count} стрибків (примусово)", + "chat_floodForced": "Через всю мережу (примусово)", + "chat_directForced": "Напряму (примусово)", + "chat_hopsForced": "{count} переходів (примусово)", "@chat_hopsForced": { "placeholders": { "count": { @@ -600,9 +604,9 @@ } } }, - "chat_floodAuto": "На всю мережу (авто)", - "chat_direct": "Прямий", - "chat_poiShared": "Точкою інтересу поділилися", + "chat_floodAuto": "Через всю мережу (авто)", + "chat_direct": "Напряму", + "chat_poiShared": "Поділилися точкою інтересу", "chat_unread": "Непрочитано: {count}", "@chat_unread": { "placeholders": { @@ -614,7 +618,7 @@ "chat_openLink": "Відкрити посилання?", "chat_openLinkConfirmation": "Ви хочете відкрити це посилання у браузері?", "chat_open": "Відкрити", - "chat_couldNotOpenLink": "Не вдалося відкрити посилання: {url}", + "chat_couldNotOpenLink": "Не вдалось відкрити посилання: {url}", "@chat_couldNotOpenLink": { "placeholders": { "url": { @@ -624,8 +628,8 @@ }, "chat_invalidLink": "Невірний формат посилання", "map_title": "Карта вузлів", - "map_noNodesWithLocation": "Немає вузлів з даними про розташування", - "map_nodesNeedGps": "Вузли повинні надавати свої GPS координати,\nщоб з'явитися на карті.", + "map_noNodesWithLocation": "Немає вузлів з даними про геопозицію", + "map_nodesNeedGps": "Вузли мають надавати свої GPS координати,\nщоб з'явитись на карті.", "map_nodesCount": "Вузли: {count}", "@map_nodesCount": { "placeholders": { @@ -650,19 +654,25 @@ "map_pinPrivate": "Замок (Приватний)", "map_pinPublic": "Ключ (Публічний)", "map_lastSeen": "Останній раз бачили", - "map_disconnectConfirm": "Ви впевнені, що хочете відключитися від цього пристрою?", + "map_disconnectConfirm": "Ви впевнені, що хочете відключитись від цього пристрою?", "map_from": "Від", "map_source": "Джерело", "map_flags": "Прапорці", - "map_shareMarkerHere": "Поділитися маркером тут", + "map_type": "Тип", + "map_path": "Шлях", + "map_location": "Геопозиція", + "map_estLocation": "Орієнтовна геопозиція", + "map_publicKey": "Публічний ключ", + "map_publicKeyPrefixHint": "напр. ab12", + "map_shareMarkerHere": "Поділитись маркером тут", "map_pinLabel": "Мітка піна", "map_label": "Мітка", "map_pointOfInterest": "Точка інтересу", "map_sendToContact": "Надіслати контакту", "map_sendToChannel": "Надіслати в канал", "map_noChannelsAvailable": "Немає доступних каналів", - "map_publicLocationShare": "Поділитися в публічному місці", - "map_publicLocationShareConfirm": "Ви збираєтеся поділитися розташуванням у {channelLabel}. Цей канал публічний, і кожен, хто має ключ PSK, може це побачити.", + "map_publicLocationShare": "Поділитись в публічному місці", + "map_publicLocationShareConfirm": "Ви збираєтесь поділитись геопозицією у {channelLabel}. Цей канал публічний, і кожен, хто має ключ PSK, може це побачити.", "@map_publicLocationShareConfirm": { "placeholders": { "channelLabel": { @@ -670,7 +680,7 @@ } } }, - "map_connectToShareMarkers": "Підключіться до пристрою, щоб поділитися маркерами", + "map_connectToShareMarkers": "Підключіться до пристрою, щоб поділитись маркерами", "map_filterNodes": "Фільтрувати вузли", "map_nodeTypes": "Типи вузлів", "map_chatNodes": "Вузли чату", @@ -683,7 +693,7 @@ "map_showSharedMarkers": "Показувати спільні маркери", "map_lastSeenTime": "Час останньої активності", "map_sharedPin": "Спільний пін", - "map_joinRoom": "Приєднатися до кімнати", + "map_joinRoom": "Приєднатись до кімнати", "map_manageRepeater": "Керувати ретранслятором", "mapCache_title": "Офлайн-кеш карти", "mapCache_selectAreaFirst": "Спершу виберіть область для кешування", @@ -806,7 +816,7 @@ "time_minutes": "хвилин", "time_allTime": "Весь час", "dialog_disconnect": "Відключити", - "dialog_disconnectConfirm": "Ви впевнені, що хочете відключитися від цього пристрою?", + "dialog_disconnectConfirm": "Ви впевнені, що хочете відключитись від цього пристрою?", "login_repeaterLogin": "Вхід у ретранслятор", "login_roomLogin": "Вхід у кімнату", "login_password": "Пароль", @@ -818,7 +828,7 @@ "login_routing": "Маршрутизація", "login_routingMode": "Режим маршрутизації", "login_autoUseSavedPath": "Авто (використовувати збережений шлях)", - "login_forceFloodMode": "Примусово на всю мережу", + "login_forceFloodMode": "Примусово через всю мережу", "login_managePaths": "Керувати шляхами", "login_login": "Вхід", "login_attempt": "Спроба {current}/{max}", @@ -851,7 +861,7 @@ } } }, - "path_usingHopsPath": "Використання шляху з {count} {count, plural, =1{стрибком} few{стрибками} many{стрибками} other{стрибками}}", + "path_usingHopsPath": "Використання шляху з {count} {count, plural, =1{переходом} few{переходами} many{переходами} other{переходами}}", "@path_usingHopsPath": { "placeholders": { "count": { @@ -861,10 +871,10 @@ }, "path_enterCustomPath": "Ввести власний шлях", "path_currentPathLabel": "Поточний шлях", - "path_hexPrefixInstructions": "Введіть 2-символьні hex-префікси для кожного стрибка, розділені комами.", + "path_hexPrefixInstructions": "Введіть 2-символьні hex-префікси для кожного переходу, розділені комами.", "path_hexPrefixExample": "Приклад: A1,F2,3C (кожен вузол використовує перший байт свого відкритого ключа).", "path_labelHexPrefixes": "Hex-префікси", - "path_helperMaxHops": "Макс. 64 стрибки. Кожен префікс - 2 шістнадцяткові символи (1 байт)", + "path_helperMaxHops": "Макс. 64 переходи. Кожен префікс — 2 шістнадцяткові символи (1 байт)", "path_selectFromContacts": "Вибрати з контактів:", "path_noRepeatersFound": "Ретрансляторів або серверів кімнат не знайдено.", "path_customPathsRequire": "Власні шляхи вимагають проміжних вузлів, які можуть передавати повідомлення.", @@ -876,7 +886,7 @@ } } }, - "path_tooLong": "Шлях занадто довгий. Максимум 64 стрибки.", + "path_tooLong": "Шлях занадто довгий. Максимум 64 переходи.", "path_setPath": "Встановити шлях", "repeater_management": "Керування ретранслятором", "repeater_managementTools": "Інструменти керування", @@ -891,7 +901,7 @@ "repeater_statusTitle": "Статус ретранслятора", "repeater_routingMode": "Режим маршрутизації", "repeater_autoUseSavedPath": "Авто (використовувати збережений шлях)", - "repeater_forceFloodMode": "Примусово на всю мережу", + "repeater_forceFloodMode": "Примусово через всю мережу", "repeater_pathManagement": "Керування шляхами", "repeater_refresh": "Оновити", "repeater_statusRequestTimeout": "Час очікування запиту статусу вичерпано.", @@ -936,7 +946,7 @@ } } }, - "repeater_packetTxTotal": "Всього: {total}, На всю мережу: {flood}, Прямі: {direct}", + "repeater_packetTxTotal": "Всього: {total}, Через всю мережу: {flood}, Прямі: {direct}", "@repeater_packetTxTotal": { "placeholders": { "total": { @@ -950,7 +960,7 @@ } } }, - "repeater_packetRxTotal": "Всього: {total}, На всю мережу: {flood}, Прямі: {direct}", + "repeater_packetRxTotal": "Всього: {total}, Через всю мережу: {flood}, Прямі: {direct}", "@repeater_packetRxTotal": { "placeholders": { "total": { @@ -964,7 +974,7 @@ } } }, - "repeater_duplicatesFloodDirect": "На всю мережу: {flood}, Прямі: {direct}", + "repeater_duplicatesFloodDirect": "Через всю мережу: {flood}, Прямі: {direct}", "@repeater_duplicatesFloodDirect": { "placeholders": { "flood": { @@ -999,7 +1009,7 @@ "repeater_bandwidth": "Смуга пропускання", "repeater_spreadingFactor": "Коефіцієнт розширення", "repeater_codingRate": "Швидкість кодування", - "repeater_locationSettings": "Налаштування розташування", + "repeater_locationSettings": "Налаштування геопозиції", "repeater_latitude": "Широта", "repeater_latitudeHelper": "Десяткові градуси (наприклад, 37.7749)", "repeater_longitude": "Довгота", @@ -1010,9 +1020,9 @@ "repeater_guestAccess": "Гостьовий доступ", "repeater_guestAccessSubtitle": "Дозволити гостьовий доступ лише для читання", "repeater_privacyMode": "Режим приватності", - "repeater_privacyModeSubtitle": "Приховати ім'я/розташування в оголошеннях", + "repeater_privacyModeSubtitle": "Приховати ім'я/геопозицію в оголошеннях", "repeater_advertisementSettings": "Налаштування оголошень", - "repeater_localAdvertInterval": "Інтервал локальних оголошень (0 стрибків)", + "repeater_localAdvertInterval": "Інтервал локальних оголошень (без ретрансляції)", "repeater_localAdvertIntervalMinutes": "{minutes} хвилин", "@repeater_localAdvertIntervalMinutes": { "placeholders": { @@ -1021,7 +1031,7 @@ } } }, - "repeater_floodAdvertInterval": "Інтервал оголошень на всю мережу (flood)", + "repeater_floodAdvertInterval": "Інтервал оголошень через всю мережу (flood)", "repeater_floodAdvertIntervalHours": "{hours} годин", "@repeater_floodAdvertIntervalHours": { "placeholders": { @@ -1035,9 +1045,9 @@ "repeater_rebootRepeater": "Перезавантажити ретранслятор", "repeater_rebootRepeaterSubtitle": "Скинути пристрій ретранслятора", "repeater_rebootRepeaterConfirm": "Ви впевнені, що хочете перезавантажити цей ретранслятор?", - "repeater_regenerateIdentityKey": "Перегенерувати ключ ідентичності", + "repeater_regenerateIdentityKey": "Перегенерувати ключ ідентифікації", "repeater_regenerateIdentityKeySubtitle": "Згенерувати нову пару ключів (публічний/приватний)", - "repeater_regenerateIdentityKeyConfirm": "Це створить нову ідентичність для ретранслятора. Продовжити?", + "repeater_regenerateIdentityKeyConfirm": "Це створить нову ідентифікацію для ретранслятора. Продовжити?", "repeater_eraseFileSystem": "Очистити файлову систему", "repeater_eraseFileSystemSubtitle": "Відформатувати файлову систему ретранслятора", "repeater_eraseFileSystemConfirm": "УВАГА: Це видалить всі дані з ретранслятора. Це не можна скасувати!", @@ -1071,7 +1081,7 @@ "repeater_refreshBasicSettings": "Оновити основні налаштування", "repeater_refreshRadioSettings": "Оновити налаштування радіо", "repeater_refreshTxPower": "Оновити потужність TX", - "repeater_refreshLocationSettings": "Оновити налаштування розташування", + "repeater_refreshLocationSettings": "Оновити налаштування геопозиції", "repeater_refreshPacketForwarding": "Оновити пересилання пакетів", "repeater_refreshGuestAccess": "Оновити гостьовий доступ", "repeater_refreshPrivacyMode": "Оновити режим приватності", @@ -1128,19 +1138,19 @@ "repeater_cliHelpSetTx": "Встановлює потужність передачі LoRa в дБм (для застосування потрібне перезавантаження).", "repeater_cliHelpSetRepeat": "Вмикає або вимикає роль ретранслятора для цього вузла.", "repeater_cliHelpSetAllowReadOnly": "(Сервер кімнати) Якщо «увімкнено», порожній пароль дозволить вхід, але не дозволить публікувати в кімнаті. (тільки читання)", - "repeater_cliHelpSetFloodMax": "Встановлює максимальну кількість стрибків для вхідних пакетів flood (якщо >= max, пакет не пересилається).", + "repeater_cliHelpSetFloodMax": "Встановлює максимальну кількість переходів для вхідних пакетів flood (якщо >= max, пакет не пересилається).", "repeater_cliHelpSetIntThresh": "Встановлює поріг інтерференції (в дБ). Значення за замовчуванням — 14. Встановлення на 0 вимикає виявлення інтерференції каналу.", "repeater_cliHelpSetAgcResetInterval": "Встановлює інтервал скидання автоматичного контролера посилення (AGC). Встановіть 0 для вимкнення.", "repeater_cliHelpSetMultiAcks": "Вмикає або вимикає функціональність подвійних ACK.", "repeater_cliHelpSetAdvertInterval": "Встановлює інтервал таймера для надсилання локального пакету оголошення (без ретрансляції). Встановіть 0 для вимкнення.", - "repeater_cliHelpSetFloodAdvertInterval": "Встановлює інтервал таймера в годинах для надсилання пакету оголошення на всю мережу. Встановіть 0 для вимкнення.", + "repeater_cliHelpSetFloodAdvertInterval": "Встановлює інтервал таймера в годинах для надсилання пакету оголошення через всю мережу. Встановіть 0 для вимкнення.", "repeater_cliHelpSetGuestPassword": "Встановлює/оновлює гостьовий пароль. (для ретрансляторів гостьові підключення можуть надсилати запит «Get Stats»)", "repeater_cliHelpSetName": "Встановлює ім'я для оголошення.", "repeater_cliHelpSetLat": "Встановлює широту для карти оголошень. (десяткові градуси)", "repeater_cliHelpSetLon": "Встановлює довготу для карти оголошень. (десяткові градуси)", "repeater_cliHelpSetRadio": "Повністю встановлює нові параметри радіо та зберігає їх у налаштуваннях. Потребує команди «перезавантаження» для застосування.", "repeater_cliHelpSetRxDelay": "Базові (експериментальні) параметри для застосування невеликої затримки до отриманих пакетів залежно від сили сигналу/оцінки. Встановіть 0 для вимкнення.", - "repeater_cliHelpSetTxDelay": "Встановлює множник для часу роботи в режимі «на всю мережу» (flood) для пакету та системи випадкових слотів, щоб затримати його відправку (для зменшення ймовірності колізій).", + "repeater_cliHelpSetTxDelay": "Встановлює множник для часу роботи в режимі «через всю мережу» (flood) для пакету та системи випадкових слотів, щоб затримати його відправку (для зменшення ймовірності колізій).", "repeater_cliHelpSetDirectTxDelay": "Те саме, що й txdelay, але для застосування випадкової затримки при пересиланні пакетів у прямому режимі.", "repeater_cliHelpSetBridgeEnabled": "Увімкнути/Вимкнути міст.", "repeater_cliHelpSetBridgeDelay": "Встановити затримку перед пересиланням пакетів.", @@ -1156,7 +1166,7 @@ "repeater_cliHelpLogErase": "Видаляє журнали пакетів з файлової системи.", "repeater_cliHelpNeighbors": "Показує список інших вузлів-ретрансляторів, почутих через оголошення без ретрансляції. Кожен рядок — id-hex-префікс:timestamp:snr-помножено-на-4", "repeater_cliHelpNeighborRemove": "Видаляє перший відповідний запис (за префіксом публічного ключа (hex)) зі списку сусідів.", - "repeater_cliHelpRegion": "(тільки серійний) Перелічує всі визначені регіони та поточні дозволи на оголошення «на всю мережу» (flood).", + "repeater_cliHelpRegion": "(тільки серійний) Перелічує всі визначені регіони та поточні дозволи на оголошення «через всю мережу» (flood).", "repeater_cliHelpRegionLoad": "ПРИМІТКА: це спеціальний виклик кількох команд. Кожна наступна команда — це назва регіону (з відступом пробілами для позначення ієрархії батьків, мінімум один пробіл). Завершується надсиланням порожнього рядка/команди.", "repeater_cliHelpRegionGet": "Шукає регіон із заданим префіксом назви (або «» для глобальної області). Відповідає: «-> ім'я-регіону (ім'я-батька) 'F'»", "repeater_cliHelpRegionPut": "Додає або оновлює визначення регіону з заданою назвою.", @@ -1170,14 +1180,14 @@ "repeater_cliHelpGpsOnOff": "Увімкнути/вимкнути GPS.", "repeater_cliHelpGpsSync": "Синхронізує час вузла з годинником GPS.", "repeater_cliHelpGpsSetLoc": "Встановлює позицію вузла за координатами GPS і зберігає в налаштуваннях.", - "repeater_cliHelpGpsAdvert": "Надає конфігурацію оголошення розташування вузла:\n- none : не включати розташування в оголошення\n- share : ділитися розташуванням GPS (з SensorManager)\n- prefs : оголошувати розташування, збережене в налаштуваннях", - "repeater_cliHelpGpsAdvertSet": "Встановлює конфігурацію оголошення розташування.", + "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_logging": "Журналювання", "repeater_neighborsRepeaterOnly": "Сусіди (Тільки ретранслятор)", "repeater_regionManagementRepeaterOnly": "Керування регіонами (Тільки ретранслятор)", "repeater_regionNote": "Команди регіонів були введені для керування визначеннями та дозволами регіонів.", @@ -1248,7 +1258,7 @@ "channelPath_title": "Шлях пакету", "channelPath_viewMap": "Показати карту", "channelPath_otherObservedPaths": "Інші спостережувані шляхи", - "channelPath_repeaterHops": "Стрибки ретранслятора", + "channelPath_repeaterHops": "Переходи через ретранслятори", "channelPath_noHopDetails": "Деталі відправки не надані для цього пакету.", "channelPath_messageDetails": "Деталі повідомлення", "channelPath_senderLabel": "Відправник", @@ -1267,7 +1277,7 @@ } } }, - "channelPath_noLocationData": "Немає даних про розташування", + "channelPath_noLocationData": "Немає даних про геопозицію", "channelPath_timeWithDate": "{day}/{month} {time}", "@channelPath_timeWithDate": { "placeholders": { @@ -1291,9 +1301,9 @@ } }, "channelPath_unknownPath": "Невідомий", - "channelPath_floodPath": "На всю мережу", - "channelPath_directPath": "Прямий", - "channelPath_observedZeroOf": "0 з {total} стрибків", + "channelPath_floodPath": "Через всю мережу", + "channelPath_directPath": "Напряму", + "channelPath_observedZeroOf": "0 з {total} переходів", "@channelPath_observedZeroOf": { "placeholders": { "total": { @@ -1301,7 +1311,7 @@ } } }, - "channelPath_observedSomeOf": "{observed} з {total} стрибків", + "channelPath_observedSomeOf": "{observed} з {total} переходів", "@channelPath_observedSomeOf": { "placeholders": { "observed": { @@ -1342,12 +1352,12 @@ } } }, - "channelPath_noHopDetailsAvailable": "Деталі стрибків недоступні для цього пакету.", + "channelPath_noHopDetailsAvailable": "Деталі переходів недоступні для цього пакету.", "channelPath_unknownRepeater": "Невідомий ретранслятор", "listFilter_tooltip": "Фільтр та сортування", "listFilter_sortBy": "Сортувати за", "listFilter_latestMessages": "Останні повідомлення", - "listFilter_heardRecently": "Нещодавно чули", + "listFilter_heardRecently": "Нещодавно почуті", "listFilter_az": "А-Я", "listFilter_filters": "Фільтри", "listFilter_all": "Все", @@ -1364,20 +1374,20 @@ } }, "repeater_neighbors": "Сусіди", - "repeater_neighborsSubtitle": "Показати сусідів нульового стрибка.", + "repeater_neighborsSubtitle": "Показати сусідів, доступних без ретрансляції.", "neighbors_receivedData": "Дані сусідів отримано", "neighbors_requestTimedOut": "Час запиту сусідів вичерпано.", "neighbors_errorLoading": "Помилка завантаження сусідів: {error}", "neighbors_repeatersNeighbors": "Ретранслятори-сусіди", "neighbors_noData": "Дані про сусідів недоступні.", "channels_createPrivateChannelDesc": "Захищено секретним ключем.", - "channels_joinPrivateChannel": "Приєднатися до приватного каналу", + "channels_joinPrivateChannel": "Приєднатись до приватного каналу", "channels_createPrivateChannel": "Створити приватний канал", "channels_joinPrivateChannelDesc": "Ввести секретний ключ вручну.", - "channels_joinPublicChannel": "Приєднатися до публічного каналу", - "channels_joinPublicChannelDesc": "Будь-хто може приєднатися до цього каналу.", - "channels_joinHashtagChannel": "Приєднатися до каналу з хештегом", - "channels_joinHashtagChannelDesc": "Будь-хто може приєднатися до каналів #hashtag.", + "channels_joinPublicChannel": "Приєднатись до публічного каналу", + "channels_joinPublicChannelDesc": "Будь-хто може приєднатись до цього каналу.", + "channels_joinHashtagChannel": "Приєднатись до хештег-каналу", + "channels_joinHashtagChannelDesc": "Будь-хто може приєднатись до хештег-каналів.", "channels_scanQrCode": "Сканувати QR-код", "channels_scanQrCodeComingSoon": "Скоро буде", "channels_enterHashtag": "Введіть хештег", @@ -1399,7 +1409,7 @@ "neighbors_unknownContact": "Невідомий відкритий ключ {pubkey}", "neighbors_heardAgo": "Почуто: {time} тому", "settings_locationGPSEnable": "Увімкнути GPS", - "settings_locationGPSEnableSubtitle": "Вмикає автоматичне оновлення місцезнаходження через GPS.", + "settings_locationGPSEnableSubtitle": "Вмикає автоматичне оновлення геопозиції через GPS.", "settings_locationIntervalSec": "Інтервал для GPS (Секунди)", "settings_locationIntervalInvalid": "Інтервал має бути не менше 60 секунд і менше 86400 секунд.", "contacts_manageRoom": "Керувати сервером кімнати", @@ -1463,10 +1473,10 @@ "common_ok": "ОК", "community_title": "Спільнота", "community_create": "Створити спільноту", - "community_createDesc": "Створити нову спільноту та поділитися через QR-код.", - "community_join": "Приєднатися", - "community_joinTitle": "Приєднатися до спільноти", - "community_joinConfirmation": "Ви бажаєте приєднатися до спільноти «{name}»?", + "community_createDesc": "Створити нову спільноту та поділитись через QR-код.", + "community_join": "Приєднатись", + "community_joinTitle": "Приєднатись до спільноти", + "community_joinConfirmation": "Ви бажаєте приєднатись до спільноти «{name}»?", "community_scanQr": "Сканувати QR спільноти", "community_scanInstructions": "Наведіть камеру на QR-код спільноти.", "community_showQr": "Показати QR-код", @@ -1476,9 +1486,9 @@ "community_enterName": "Введіть назву спільноти", "community_created": "Спільноту «{name}» створено", "community_joined": "Приєднався до спільноти «{name}»", - "community_qrTitle": "Поділитися спільнотою", - "community_qrInstructions": "Відскануйте цей QR-код, щоб приєднатися до {name}", - "community_hashtagPrivacyHint": "Канали хештегів спільноти доступні лише членам спільноти", + "community_qrTitle": "Поділитись спільнотою", + "community_qrInstructions": "Відскануйте цей QR-код, щоб приєднатись до {name}", + "community_hashtagPrivacyHint": "Хештег-канали спільноти доступні лише членам спільноти", "community_invalidQrCode": "Недійсний QR-код спільноти", "community_alreadyMember": "Вже учасник", "community_alreadyMemberMessage": "Ви вже є учасником «{name}».", @@ -1499,10 +1509,10 @@ }, "community_deleted": "Спільноту «{name}» покинуто", "community_addHashtagChannel": "Додати хештег спільноти", - "community_addHashtagChannelDesc": "Додати канал хештегу для цієї спільноти", + "community_addHashtagChannelDesc": "Додати хештег-канал для цієї спільноти", "community_selectCommunity": "Вибрати спільноту", "community_regularHashtag": "Звичайний хештег", - "community_regularHashtagDesc": "Публічний хештег (будь-хто може приєднатися)", + "community_regularHashtagDesc": "Публічний хештег (будь-хто може приєднатись)", "community_communityHashtag": "Хештег спільноти", "community_communityHashtagDesc": "Ексклюзивно для членів спільноти", "community_forCommunity": "Для {name}", @@ -1534,13 +1544,13 @@ } } }, - "community_regenerateSecret": "Перегенерувати секрет", - "community_regenerateSecretConfirm": "Перегенерувати секретний ключ для «{name}»? Всі учасники повинні будуть відсканувати новий QR-код, щоб продовжити спілкування.", + "community_regenerateSecret": "Перегенерувати секретний ключ", + "community_regenerateSecretConfirm": "Перегенерувати секретний ключ для «{name}»? Усі учасники матимуть відсканувати новий QR-код, щоб продовжити спілкування.", "community_regenerate": "Перегенерувати", - "community_secretRegenerated": "Секретний пароль для «{name}» перегенеровано", - "community_scanToUpdateSecret": "Відскануйте новий QR-код, щоб оновити пароль для «{name}»", - "community_updateSecret": "Оновити секрет", - "community_secretUpdated": "Зміну секрету для «{name}» оновлено", + "community_secretRegenerated": "Секретний ключ для «{name}» перегенеровано", + "community_scanToUpdateSecret": "Відскануйте новий QR-код, щоб оновити секретний ключ для «{name}»", + "community_updateSecret": "Оновити секретний ключ", + "community_secretUpdated": "Секретний ключ для «{name}» оновлено", "@contacts_pathTraceTo": { "placeholders": { "name": { @@ -1549,76 +1559,76 @@ } }, "pathTrace_you": "Ви", - "pathTrace_failed": "Відстеження шляху не вдалося.", + "pathTrace_failed": "Відстеження шляху не вдалось.", "pathTrace_notAvailable": "Трасування шляху недоступне.", - "pathTrace_refreshTooltip": "Оновити Path Trace", + "pathTrace_refreshTooltip": "Оновити трасування шляху", "contacts_pathTrace": "Трасування шляхів", "contacts_ping": "Пінгувати", - "contacts_repeaterPathTrace": "Трасування шляху до повторювача", - "contacts_repeaterPing": "Пінгувати повторювач", + "contacts_repeaterPathTrace": "Трасування шляху до ретранслятора", + "contacts_repeaterPing": "Пінгувати ретранслятор", "contacts_roomPathTrace": "Трасування шляху до серверу кімнати", "contacts_roomPing": "Пінг сервера кімнати", "contacts_chatTraceRoute": "Трасування шляху", "contacts_pathTraceTo": "Відстежити маршрут до {name}", "contacts_invalidAdvertFormat": "Недійсні контактні дані", "contacts_contactImported": "Контакт було імпортовано.", - "contacts_contactImportFailed": "Контакт не вдалося імпортувати", - "contacts_zeroHopAdvert": "Реклама без перехоплення", - "contacts_floodAdvert": "Залив реклами", - "contacts_copyAdvertToClipboard": "Копіювати оголошення в буфер обміну", + "contacts_contactImportFailed": "Контакт не вдалось імпортувати", + "contacts_zeroHopAdvert": "Оголошення без ретрансляції", + "contacts_floodAdvert": "Оголошення з ретрансляцією", + "contacts_copyAdvertToClipboard": "Копіювати оголошення", "contacts_clipboardEmpty": "Буфер обміну порожній", "appSettings_languageRu": "Російська", "appSettings_enableMessageTracing": "Увімкнути відстеження повідомлень", "appSettings_enableMessageTracingSubtitle": "Показувати детальні метадані про маршрутизацію та час для повідомлень", "contacts_ShareContact": "Копіювати контакт у буфер обміну", - "contacts_zeroHopContactAdvertFailed": "Не вдалося надіслати контакт.", - "contacts_contactAdvertCopied": "Рекламу скопійовано до буфера обміну.", - "contacts_contactAdvertCopyFailed": "Копіювання оголошення в буфер обміну завершилося невдало", + "contacts_zeroHopContactAdvertFailed": "Не вдалось надіслати контакт.", + "contacts_contactAdvertCopied": "Оголошення скопійовано до буфера обміну.", + "contacts_contactAdvertCopyFailed": "Копіювання оголошення в буфер обміну завершилось невдало", "contacts_zeroHopContactAdvertSent": "Відправлено контакт за оголошенням", - "contacts_addContactFromClipboard": "Додати контакт з буфера обміну", - "contacts_ShareContactZeroHop": "Поділитися контактом за оголошенням", + "contacts_addContactFromClipboard": "Додати контакт з буфера", + "contacts_ShareContactZeroHop": "Поділитись контактом за оголошенням", "notification_activityTitle": "Активність MeshCore", "notification_messagesCount": "{count} {count, plural, =1{повідомлення} few{повідомлення} many{повідомлень} other{повідомлень}}", "notification_channelMessagesCount": "{count} {count, plural, =1{повідомлення каналу} few{повідомлення каналу} many{повідомлень каналу} other{повідомлень каналу}}", - "notification_newNodesCount": "{count} {count, plural, =1{новий вузол} few{нових вузли} many{нових вузлів} other{нових вузлів}}", + "notification_newNodesCount": "{count} {count, plural, =1{новий вузол} few{нові вузли} many{нових вузлів} other{нових вузлів}}", "notification_newTypeDiscovered": "Виявлено новий {contactType}", "notification_receivedNewMessage": "Отримано нове повідомлення", - "settings_gpxExportRepeaters": "Експортувати ретранслятори / сервер кімнати до GPX", - "settings_gpxExportRepeatersSubtitle": "Експортує ретранслятори / сервер кімнати з місцезнаходженням у файл GPX.", + "settings_gpxExportRepeaters": "Експорт ретрансляторів і серверів кімнат у GPX", + "settings_gpxExportRepeatersSubtitle": "Експортує ретранслятори та сервери кімнат з геопозицією у файл GPX.", "settings_gpxExportSuccess": "Успішно експортовано файл GPX.", "settings_gpxExportNoContacts": "Немає контактів для експорту.", "settings_gpxExportNotAvailable": "Не підтримується на вашому пристрої/операційній системі", - "settings_gpxExportError": "Сталася помилка під час експорту.", - "settings_gpxExportAllSubtitle": "Експортує всі контакти з місцем розташування у файл GPX.", - "settings_gpxExportAll": "Експортувати всі контакти до GPX", - "settings_gpxExportContactsSubtitle": "Експортує супутників з місцезнаходженням у файл GPX.", - "settings_gpxExportContacts": "Експортувати супутників до GPX", - "settings_gpxExportRepeatersRoom": "Місцезнаходження повторювача та сервера кімнати", - "settings_gpxExportChat": "Місця супутників", + "settings_gpxExportError": "Сталась помилка під час експорту.", + "settings_gpxExportAllSubtitle": "Експортує всі контакти з геопозицією у файл GPX.", + "settings_gpxExportAll": "Експорт усіх контактів у GPX", + "settings_gpxExportContactsSubtitle": "Експортує контакти з геопозицією у файл GPX.", + "settings_gpxExportContacts": "Експорт контактів у GPX", + "settings_gpxExportRepeatersRoom": "Геопозиції ретрансляторів та серверів кімнат", + "settings_gpxExportChat": "Геопозиції контактів", "settings_gpxExportShareText": "Дані карти експортовані з meshcore-open", "settings_gpxExportAllContacts": "Усі місця контактів", "settings_gpxExportShareSubject": "експорт даних карти meshcore-open у форматі GPX", - "pathTrace_someHopsNoLocation": "Одне або більше хмелів відсутнє місце розташування!", + "pathTrace_someHopsNoLocation": "Один або декілька переходів не мають даних про геопозицію!", "map_tapToAdd": "Натисніть на вузли, щоб додати їх до шляху", "map_runTrace": "Виконати трасування шляху", "pathTrace_clearTooltip": "Очистити шлях", "map_removeLast": "Видалити останній", - "map_pathTraceCancelled": "Відмінується трасування шляху", + "map_pathTraceCancelled": "Трасування шляху скасовано.", "scanner_enableBluetooth": "Увімкніть Bluetooth", "scanner_bluetoothOffMessage": "Будь ласка, увімкніть Bluetooth, щоб сканувати пристрої.", "scanner_chromeRequired": "Потрібен браузер Chrome", - "scanner_chromeRequiredMessage": "Для підтримки Bluetooth у цьому веб-додатку потрібен Google Chrome або браузер на базі Chromium.", + "scanner_chromeRequiredMessage": "Для підтримки Bluetooth у цьому вебзастосунку потрібен Google Chrome або браузер на базі Chromium.", "scanner_bluetoothOff": "Bluetooth вимкнено", "snrIndicator_lastSeen": "Останній раз бачили", - "snrIndicator_nearByRepeaters": "Ближні ретранслятори", + "snrIndicator_nearByRepeaters": "Найближчі ретранслятори", "chat_ShowAllPaths": "Показати всі шляхи", "settings_clientRepeatFreqWarning": "Повтор без підключення до мережі вимагає частоти 433, 869 або 918 МГц.", "settings_clientRepeatSubtitle": "Дозвольте цьому пристрою повторювати пакети даних для інших пристроїв.", "settings_clientRepeat": "Автономна система", "settings_aboutOpenMeteoAttribution": "Дані про висоту LOS: Open-Meteo (CC BY 4.0)", - "appSettings_unitsTitle": "одиниці", - "appSettings_unitsMetric": "Метричний (м / км)", - "appSettings_unitsImperial": "Імперська (ft / mi)", + "appSettings_unitsTitle": "Одиниці", + "appSettings_unitsMetric": "Метричні (м / км)", + "appSettings_unitsImperial": "Імперські (ft / mi)", "map_lineOfSight": "Пряма видимість", "map_losScreenTitle": "Пряма видимість", "losSelectStartEnd": "Виберіть початковий і кінцевий вузли для LOS.", @@ -1636,7 +1646,7 @@ "losMenuSubtitle": "Торкніться вузлів або утримуйте карту, щоб отримати власні точки", "losShowDisplayNodes": "Показати вузли відображення", "losCustomPoints": "Користувальницькі точки", - "losCustomPointLabel": "Спеціальний {index}", + "losCustomPointLabel": "Власна точка {index}", "@losCustomPointLabel": { "placeholders": { "index": { @@ -1725,7 +1735,7 @@ }, "losErrorElevationUnavailable": "Дані про висоту недоступні для одного чи кількох зразків.", "losErrorInvalidInput": "Недійсні дані про точки/висоту для розрахунку LOS.", - "losRenameCustomPoint": "Перейменуйте спеціальну точку", + "losRenameCustomPoint": "Перейменувати власну точку", "losPointName": "Назва точки", "losShowPanelTooltip": "Показати панель LOS", "losHidePanelTooltip": "Приховати панель LOS", @@ -1805,19 +1815,19 @@ "contacts_unread": "Непрочитане", "settings_contactSettingsSubtitle": "Налаштування для додавання контактів", "settings_contactSettings": "Налаштування контактів", - "contactsSettings_autoAddUsersSubtitle": "Дозволити супутникові автоматично додавати виявлених користувачів", - "contactsSettings_autoAddRepeatersTitle": "Автоматично додавати повторювачі", - "contactsSettings_autoAddRepeatersSubtitle": "Дозволити супутнику автоматично додавати виявлені ретранслятори", + "contactsSettings_autoAddUsersSubtitle": "Дозволити пристрою-компаньйону автоматично додавати виявлених користувачів", + "contactsSettings_autoAddRepeatersTitle": "Автоматично додавати ретранслятори", + "contactsSettings_autoAddRepeatersSubtitle": "Дозволити пристрою-компаньйону автоматично додавати виявлені ретранслятори", "contactsSettings_autoAddRoomServersTitle": "Автоматично додавати сервери кімнат", "contactsSettings_otherTitle": "Інші налаштування, пов'язані з контактами", "contactsSettings_autoAddTitle": "Автоматичне виявлення", "contactsSettings_autoAddUsersTitle": "Автоматично додавати користувачів", "contactsSettings_title": "Налаштування контактів", - "contactsSettings_autoAddRoomServersSubtitle": "Дозволити супровіднику автоматично додавати виявлені сервери кімнат.", - "contactsSettings_autoAddSensorsTitle": "Автоматично додавати датчики", + "contactsSettings_autoAddRoomServersSubtitle": "Дозволити пристрою-компаньйону автоматично додавати виявлені сервери кімнат.", + "contactsSettings_autoAddSensorsTitle": "Автоматично додавати сенсори", "discoveredContacts_searchHint": "Знайти виявлені контакти", "discoveredContacts_contactAdded": "Контакт додано", - "contactsSettings_autoAddSensorsSubtitle": "Дозволити супровіднику автоматично додавати виявлені сенсори", + "contactsSettings_autoAddSensorsSubtitle": "Дозволити пристрою-компаньйону автоматично додавати виявлені сенсори", "contactsSettings_overwriteOldestTitle": "Перезаписати найстаріше", "discoveredContacts_Title": "Виявлені контакти", "discoveredContacts_noMatching": "Відповідних контактів не знайдено", @@ -1828,9 +1838,9 @@ "common_deleteAll": "Видалити все", "discoveredContacts_deleteContactAll": "Видалити всі виявлені контакти", "discoveredContacts_deleteContactAllContent": "Ви впевнені, що хочете видалити всі виявлені контакти?", - "map_showGuessedLocations": "Показати місцезнаходження передбачених вузлів", - "map_guessedLocation": "Визначено місцезнаходження", - "usbScreenSubtitle": "Виберіть виявлене серійне пристрій і підключіть його безпосередньо до вашого вузла MeshCore.", + "map_showGuessedLocations": "Показати геопозиції передбачених вузлів", + "map_guessedLocation": "Передбачена геопозиція", + "usbScreenSubtitle": "Виберіть виявлений USB-пристрій і підключіть його безпосередньо до вашого вузла MeshCore.", "usbScreenTitle": "Підключити через USB", "connectionChoiceBluetoothLabel": "Bluetooth", "connectionChoiceUsbLabel": "USB", @@ -1842,8 +1852,8 @@ "usbErrorInvalidPort": "Виберіть дійсний USB-пристрій.", "usbErrorBusy": "Ще один запит на підключення через USB вже обробляється.", "usbErrorNotConnected": "Немає підключених пристроїв USB.", - "usbErrorOpenFailed": "Не вдалося відкрити вибране USB-пристрій.", - "usbErrorConnectFailed": "Не вдалося підключитися до вибраного USB-пристрою.", + "usbErrorOpenFailed": "Не вдалось відкрити вибране USB-пристрій.", + "usbErrorConnectFailed": "Не вдалось підключитись до вибраного USB-пристрою.", "usbErrorUnsupported": "Підтримка USB-серіального інтерфейсу не реалізована на цій платформі.", "usbErrorAlreadyActive": "USB-з'єднання вже встановлено.", "usbErrorNoDeviceSelected": "Не було вибрано жодного пристрою USB.", @@ -1858,9 +1868,9 @@ }, "usbStatus_searching": "Пошук пристроїв USB...", "usbStatus_notConnected": "Виберіть пристрій USB", - "usbConnectionFailed": "Не вдалося встановити з'єднання через USB: {error}", + "usbConnectionFailed": "Не вдалось встановити з'єднання через USB: {error}", "usbStatus_connecting": "Підключення до USB-пристрою...", - "usbErrorConnectTimedOut": "З'єднання не вдалося встановити. Переконайтеся, що пристрій має встановлене програмне забезпечення USB Companion.", + "usbErrorConnectTimedOut": "З'єднання не вдалось встановити. Переконайтесь, що пристрій має встановлене програмне забезпечення USB Companion.", "@tcpStatus_connectingTo": { "placeholders": { "endpoint": { @@ -1878,18 +1888,18 @@ "connectionChoiceTcpLabel": "TCP", "tcpHostHint": "192.168.40.10", "tcpHostLabel": "IP-адреса", - "tcpScreenTitle": "З'єднатися через протокол TCP", + "tcpScreenTitle": "Підключитись через TCP", "tcpPortLabel": "Порт", "tcpPortHint": "5000", "tcpStatus_notConnected": "Введіть кінцеву точку та підключіться", "tcpStatus_connectingTo": "Підключення до {endpoint}...", "tcpErrorHostRequired": "Необхідно вказати IP-адресу.", - "tcpErrorPortInvalid": "Порт повинен бути в межах від 1 до 65535.", + "tcpErrorPortInvalid": "Порт має бути в межах від 1 до 65535.", "tcpErrorUnsupported": "Транспорт TCP не підтримується на цій платформі.", - "tcpErrorTimedOut": "З'єднання TCP завершилося через закінчення часу очікування.", - "tcpConnectionFailed": "Не вдалося встановити з'єднання TCP: {error}", - "map_showDiscoveryContacts": "Показати контакти Відкриття", - "map_setAsMyLocation": "Встановити моє місцезнаходження", + "tcpErrorTimedOut": "З'єднання TCP завершилось через закінчення часу очікування.", + "tcpConnectionFailed": "Не вдалось встановити з'єднання TCP: {error}", + "map_showDiscoveryContacts": "Показати виявлені контакти", + "map_setAsMyLocation": "Встановити мою геопозицію", "@path_routeWeight": { "placeholders": { "weight": { @@ -1900,12 +1910,12 @@ } } }, - "settings_privacySubtitle": "Керуйте інформацією, яку буде спільно використовуватися", + "settings_privacySubtitle": "Керуйте інформацією, яка буде спільно використовуватись", "settings_privacy": "Налаштування приватності", "settings_telemetryBaseMode": "Режим базової телеметрії", "settings_telemetryLocationMode": "Режим місця телеметрії", - "settings_advertLocation": "Розміщення реклами", - "settings_advertLocationSubtitle": "Включити місце розташування в оголошення", + "settings_advertLocation": "Геопозиція в оголошенні", + "settings_advertLocationSubtitle": "Включити геопозицію в оголошення", "settings_privacySettingsDescription": "Виберіть, яку інформацію ваш пристрій буде передавати іншим.", "settings_allowAll": "Дозволити все", "settings_denyAll": "Відхилити все", @@ -1913,8 +1923,8 @@ "settings_telemetryEnvironmentMode": "Режим середовища телеметрії", "contact_info": "Контактна інформація", "contact_teleBaseSubtitle": "Дозволити спільний доступ до рівня заряду батареї та базової телеметрії", - "contact_teleLoc": "Розташування телеметрії", - "contact_teleBase": "Базовий телебачення", + "contact_teleLoc": "Геопозиція телеметрії", + "contact_teleBase": "Базова телеметрія", "contact_teleLocSubtitle": "Дозволити спільне використання даних про місцеположення", "contact_settings": "Налаштування контактів", "contact_telemetry": "Телеметрія", @@ -1942,8 +1952,8 @@ "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрії оновлено", "settings_multiAck": "Багатократне підтвердження: {value}", - "map_showOverlaps": "Перекриття ключа повторювача", - "map_runTraceWithReturnPath": "Повернутися назад тим же шляхом", + "map_showOverlaps": "Перекриття ключів ретрансляторів", + "map_runTraceWithReturnPath": "Повернутись назад тим же шляхом", "@radioStats_noiseFloor": { "placeholders": { "noiseDbm": { @@ -1987,26 +1997,26 @@ } }, "chat_sendCooldown": "Будь ласка, зачекайте трохи, перш ніж відправляти знову.", - "appSettings_languageHu": "Угорський", - "appSettings_jumpToOldestUnreadSubtitle": "При відкритті чату з не прочитаними повідомленнями, прокрутіть до першого не прочитаного повідомлення, а не до останнього.", + "appSettings_languageHu": "Угорська", + "appSettings_jumpToOldestUnreadSubtitle": "При відкритті чату з непрочитаними повідомленнями, прокрутіть до першого непрочитаного повідомлення, а не до останнього.", "appSettings_jumpToOldestUnread": "Перейти до найстарішого непрочитаного повідомлення", "appSettings_languageJa": "Японська", - "appSettings_languageKo": "Кореєська", + "appSettings_languageKo": "Корейська", "radioStats_tooltip": "Статистика радіо та мережі", - "radioStats_screenTitle": "Дані про радіостанції", - "radioStats_notConnected": "Підключіться до пристрою, щоб переглядати статистику радіопередач.", - "radioStats_firmwareTooOld": "Статистика радіо приймача вимагає супутнього програмного забезпечення версії 8 або новішої.", - "radioStats_waiting": "Очікую на отримання даних…", + "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_txAir": "Час в ефірі TX (загальний): {seconds} секунд", + "radioStats_rxAir": "Час в ефірі RX (загальний): {seconds} секунд", "radioStats_chartCaption": "Рівень шуму (дБм) на основі останніх вимірювань.", "radioStats_stripNoise": "Рівень шуму: {noiseDbm} дБм", "radioStats_stripWaiting": "Отримано статистику радіо…", - "radioStats_settingsTile": "Дані про радіостанції", - "radioStats_settingsSubtitle": "Рівень шуму, RSSI, SNR та час, протягом якого пристрій використовує радіоканал.", + "radioStats_settingsTile": "Статистика радіо", + "radioStats_settingsSubtitle": "Рівень шуму, RSSI, SNR та час в ефірі.", "@translation_downloadFailed": { "placeholders": { "error": { @@ -2020,9 +2030,9 @@ "translation_enableSubtitle": "Перекладати отримані повідомлення та дозволяти попередній переклад перед відправкою.", "translation_composerSubtitle": "Контролює стан ікон перекладу, який використовується за замовчуванням.", "translation_targetLanguage": "Цільова мова", - "translation_useAppLanguage": "Використовуйте мову додатку", + "translation_useAppLanguage": "Використовувати мову застосунку", "translation_downloadedModelLabel": "Завантажений шаблон", - "translation_presetModelLabel": "Заздалегідь налаштований модель від Hugging Face", + "translation_presetModelLabel": "Попередньо налаштована модель з Hugging Face", "translation_manualUrlLabel": "Посилання на веб-сторінку з інструкцією", "translation_downloadModel": "Завантажити модель", "translation_downloading": "Завантаження...", @@ -2033,7 +2043,7 @@ "translation_deleteModel": "Видалити модель", "translation_modelDownloaded": "Модель перекладу завантажена.", "translation_downloadStopped": "Завантаження призупинено.", - "translation_downloadFailed": "Не вдалося завантажити: {error}", + "translation_downloadFailed": "Не вдалось завантажити: {error}", "translation_enterUrlFirst": "Спочатку введіть URL моделі.", "@scanner_linuxPairingPinPrompt": { "placeholders": { @@ -2061,7 +2071,7 @@ "scanner_linuxPairingPinPrompt": "Введіть PIN для {deviceName} (залиште порожнім, якщо його немає).", "scanner_linuxPairingHidePin": "Приховати PIN", "repeater_cliQuickClockSync": "Синхронізація годинника", - "repeater_cliQuickDiscovery": "Відкрити сусідів", + "repeater_cliQuickDiscovery": "Виявити сусідів", "@repeater_clockSyncAfterLogin": { "description": "Repeater setting: auto sync device clock after successful login" }, @@ -2070,8 +2080,18 @@ }, "repeater_clockSyncAfterLoginSubtitle": "Автоматично надсилати повідомлення \"синхронізація годин\" після успішного входу.", "repeater_clockSyncAfterLogin": "Синхронізація годин після входу", - "repeater_guestTools": "Інструменти для гостей", + "repeater_guestTools": "Гостьові інструменти", "repeater_guest": "Інформація про ретранслятор", "room_guest": "Інформація про сервер кімнати", - "chat_sendMessage": "Надіслати повідомлення" + "chat_sendMessage": "Надіслати повідомлення", + "background_serviceTitle": "MeshCore працює", + "background_serviceText": "Підтримує з'єднання BLE", + "appSettings_translationModelDeleted": "Видалено {name}", + "appSettings_translationModelDeleteFailed": "Не вдалось видалити: {error}", + "channels_channelUpdateFailed": "Не вдалось оновити канал: {error}", + "contact_typeChat": "Чат", + "contact_typeRepeater": "Ретранслятор", + "contact_typeRoom": "Кімната", + "contact_typeSensor": "Сенсор", + "contact_typeUnknown": "Невідомо" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9dc2325c..03fa71fd 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -109,6 +109,8 @@ "settings_privacyModeEnabled": "隐私模式已启用", "settings_privacyModeDisabled": "隐私模式已关闭", "settings_actions": "操作", + "settings_deleteAllPaths": "Delete All Paths", + "settings_deleteAllPathsSubtitle": "Clear all path data from contacts.", "settings_sendAdvertisement": "发送广播", "settings_sendAdvertisementSubtitle": "立即发送广播", "settings_advertisementSent": "已发送广播", @@ -2078,5 +2080,44 @@ "repeater_guestTools": "访客工具", "repeater_guest": "重复器信息", "chat_sendMessage": "发送消息", - "room_guest": "服务器信息" + "room_guest": "服务器信息", + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}" } diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 2699f939..020e4290 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -2,6 +2,7 @@ import 'dart:typed_data'; import 'package:meshcore_open/utils/app_logger.dart'; import '../connector/meshcore_protocol.dart'; +import '../l10n/app_localizations.dart'; class Contact { final Uint8List publicKey; @@ -56,15 +57,30 @@ class Contact { } } - String get pathLabel { - if (pathOverride != null) { - if (pathOverride! < 0) return 'Flood (forced)'; - if (pathOverride == 0) return 'Direct (forced)'; - return '$pathOverride hops (forced)'; + String typeLabelLocalized(AppLocalizations l10n) { + switch (type) { + case advTypeChat: + return l10n.contact_typeChat; + case advTypeRepeater: + return l10n.contact_typeRepeater; + case advTypeRoom: + return l10n.contact_typeRoom; + case advTypeSensor: + return l10n.contact_typeSensor; + default: + return l10n.contact_typeUnknown; } - if (pathLength < 0) return 'Flood'; - if (pathLength == 0) return 'Direct'; - return '$pathLength hops'; + } + + String pathLabel(AppLocalizations l10n) { + if (pathOverride != null) { + if (pathOverride! < 0) return l10n.chat_floodForced; + if (pathOverride == 0) return l10n.chat_directForced; + return l10n.chat_hopsForced(pathOverride!); + } + if (pathLength < 0) return l10n.channelPath_floodPath; + if (pathLength == 0) return l10n.chat_direct; + return l10n.chat_hopsCount(pathLength); } bool get hasLocation { diff --git a/lib/screens/app_settings_screen.dart b/lib/screens/app_settings_screen.dart index 80d8adbc..5d81e1ed 100644 --- a/lib/screens/app_settings_screen.dart +++ b/lib/screens/app_settings_screen.dart @@ -1237,15 +1237,20 @@ class AppSettingsScreen extends StatelessWidget { if (!context.mounted) return; showDismissibleSnackBar( context, - // TODO: l10n - content: Text('Deleted ${translationModelFriendlyName(model)}.'), + content: Text( + context.l10n.appSettings_translationModelDeleted( + translationModelFriendlyName(model), + ), + ), ); } catch (error) { if (!context.mounted) return; showDismissibleSnackBar( context, - content: Text('Delete failed: $error'), - ); // TODO: l10n + content: Text( + context.l10n.appSettings_translationModelDeleteFailed('$error'), + ), + ); } } diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index b203cbbe..0454f17f 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -555,7 +555,9 @@ class _ChannelChatScreenState extends State { ? const EdgeInsets.symmetric(horizontal: 8) : EdgeInsets.zero, child: Text( - 'via ${_formatPathPrefixes(displayPath)}', + context.l10n.channels_via( + _formatPathPrefixes(displayPath), + ), style: TextStyle( fontSize: 11, color: Colors.grey[600], diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index 44c7a69c..220a5af8 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -1492,7 +1492,7 @@ class _ChannelsScreenState extends State if (!context.mounted) return; showDismissibleSnackBar( context, - content: Text('Failed to update channel: $e'), + content: Text(context.l10n.channels_channelUpdateFailed('$e')), ); } }, diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index ffa8344b..2d5a4483 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -1168,8 +1168,8 @@ class _ChatScreenState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildInfoRow(context.l10n.chat_type, contact.typeLabel), - _buildInfoRow(context.l10n.chat_path, contact.pathLabel), + _buildInfoRow(context.l10n.chat_type, contact.typeLabelLocalized(context.l10n)), + _buildInfoRow(context.l10n.chat_path, contact.pathLabel(context.l10n)), _buildInfoRow( context.l10n.contact_lastSeen, _formatContactLastMessage(contact.lastMessageAt), diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index 54d32990..5b855cdf 100644 --- a/lib/screens/contacts_screen.dart +++ b/lib/screens/contacts_screen.dart @@ -1122,7 +1122,9 @@ class _ContactsScreenState extends State return CheckboxListTile( value: isSelected, title: Text(contact.name), - subtitle: Text(contact.typeLabel), + subtitle: Text( + contact.typeLabelLocalized(context.l10n), + ), onChanged: (value) { setDialogState(() { if (value == true) { @@ -1464,7 +1466,7 @@ class _ContactTile extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - contact.pathLabel, + contact.pathLabel(context.l10n), maxLines: 1, overflow: TextOverflow.ellipsis, ), diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 6a8acda7..afc592f2 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -1425,23 +1425,23 @@ class _MapScreenState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildInfoRow('Type', contact.typeLabel), - _buildInfoRow('Path', contact.pathLabel), + _buildInfoRow(context.l10n.map_type, contact.typeLabelLocalized(context.l10n)), + _buildInfoRow(context.l10n.map_path, contact.pathLabel(context.l10n)), if (contact.hasLocation) _buildInfoRow( - 'Location', + context.l10n.map_location, '${contact.latitude!.toStringAsFixed(6)}, ${contact.longitude!.toStringAsFixed(6)}', ) else if (guessedPosition != null) _buildInfoRow( - 'Est. Location', + context.l10n.map_estLocation, '~${guessedPosition.latitude.toStringAsFixed(6)}, ${guessedPosition.longitude.toStringAsFixed(6)}', ), _buildInfoRow( context.l10n.map_lastSeen, _formatLastSeen(contact.lastSeen), ), - _buildInfoRow('Public Key', contact.publicKeyHex), + _buildInfoRow(context.l10n.map_publicKey, contact.publicKeyHex), ], ), actions: [ @@ -1550,7 +1550,7 @@ class _MapScreenState extends State { _buildInfoRow(context.l10n.map_from, marker.fromName), _buildInfoRow(context.l10n.map_source, marker.sourceLabel), _buildInfoRow( - 'Location', + context.l10n.map_location, '${marker.position.latitude.toStringAsFixed(6)}, ${marker.position.longitude.toStringAsFixed(6)}', ), if (marker.flags.isNotEmpty) @@ -2019,7 +2019,7 @@ class _MapScreenState extends State { enabled: settings.mapKeyPrefixEnabled, decoration: InputDecoration( labelText: context.l10n.map_publicKeyPrefix, - hintText: 'e.g. ab12', + hintText: context.l10n.map_publicKeyPrefixHint, border: const OutlineInputBorder(), isDense: true, ), diff --git a/lib/screens/repeater_hub_screen.dart b/lib/screens/repeater_hub_screen.dart index 0dc141c7..0fa87cce 100644 --- a/lib/screens/repeater_hub_screen.dart +++ b/lib/screens/repeater_hub_screen.dart @@ -93,7 +93,7 @@ class RepeaterHubScreen extends StatelessWidget { ), const SizedBox(height: 8), Text( - repeater.pathLabel, + repeater.pathLabel(context.l10n), style: TextStyle(fontSize: 14, color: Colors.grey[600]), ), if (repeater.hasLocation) ...[ diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 47b9b9c6..546bc4de 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -341,9 +341,9 @@ class _SettingsScreenState extends State { ), ListTile( leading: const Icon(Icons.delete_outline, color: Colors.red), - title: Text("Delete All Paths"), + title: Text(l10n.settings_deleteAllPaths), subtitle: Text( - "Clear all path data from contacts.", + l10n.settings_deleteAllPathsSubtitle, style: TextStyle(color: Colors.red[700]), ), onTap: () => connector.deleteAllPaths(), diff --git a/lib/services/background_service.dart b/lib/services/background_service.dart index 6202b3be..04468eff 100644 --- a/lib/services/background_service.dart +++ b/lib/services/background_service.dart @@ -1,6 +1,9 @@ -import '../utils/platform_info.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; +import '../l10n/app_localizations.dart'; +import '../utils/platform_info.dart'; + class BackgroundService { bool _initialized = false; @@ -34,13 +37,25 @@ class BackgroundService { } final running = await FlutterForegroundTask.isRunningService; if (running) return; + final l10n = await _loadLocalizations(); await FlutterForegroundTask.startService( - notificationTitle: 'MeshCore running', - notificationText: 'Keeping BLE connected', + notificationTitle: l10n.background_serviceTitle, + notificationText: l10n.background_serviceText, callback: startCallback, ); } + Future _loadLocalizations() async { + final supported = AppLocalizations.supportedLocales; + final system = + WidgetsBinding.instance.platformDispatcher.locale; + final match = basicLocaleListResolution( + [system], + supported, + ); + return AppLocalizations.delegate.load(match); + } + Future stop() async { if (!PlatformInfo.isAndroid) return; final running = await FlutterForegroundTask.isRunningService; diff --git a/lib/widgets/path_management_dialog.dart b/lib/widgets/path_management_dialog.dart index 094805a1..bc7623e0 100644 --- a/lib/widgets/path_management_dialog.dart +++ b/lib/widgets/path_management_dialog.dart @@ -147,7 +147,7 @@ class _PathManagementDialogState extends State<_PathManagementDialog> { context, availableContacts: availableContacts, initialPath: pathForInput.isEmpty ? null : pathForInput, - currentPathLabel: currentContact.pathLabel, + currentPathLabel: currentContact.pathLabel(l10n), onRefresh: connector.isConnected ? connector.getContacts : null, ); @@ -236,7 +236,7 @@ class _PathManagementDialogState extends State<_PathManagementDialog> { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - l10n.path_currentPath(currentContact.pathLabel), + l10n.path_currentPath(currentContact.pathLabel(l10n)), style: const TextStyle(fontSize: 12, color: Colors.grey), ), const SizedBox(height: 12), diff --git a/lib/widgets/path_selection_dialog.dart b/lib/widgets/path_selection_dialog.dart index 7a890ec5..e061e731 100644 --- a/lib/widgets/path_selection_dialog.dart +++ b/lib/widgets/path_selection_dialog.dart @@ -311,7 +311,7 @@ class _PathSelectionDialogState extends State { style: const TextStyle(fontSize: 14), ), subtitle: Text( - '${contact.typeLabel} • ${contact.publicKeyHex.substring(0, 2)}', + '${contact.typeLabelLocalized(l10n)} • ${contact.publicKeyHex.substring(0, 2)}', style: const TextStyle(fontSize: 10), ), trailing: isSelected diff --git a/lib/widgets/qr_code_display.dart b/lib/widgets/qr_code_display.dart index e8f4795d..584eabee 100644 --- a/lib/widgets/qr_code_display.dart +++ b/lib/widgets/qr_code_display.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:qr_flutter/qr_flutter.dart'; +import '../l10n/l10n.dart'; + /// A reusable QR code display widget for sharing data. /// /// Features: @@ -197,7 +199,7 @@ class QrCodeShareDialog extends StatelessWidget { width: double.infinity, child: FilledButton( onPressed: () => Navigator.pop(context), - child: const Text('Done'), + child: Text(context.l10n.common_done), ), ), ], diff --git a/lib/widgets/repeater_login_dialog.dart b/lib/widgets/repeater_login_dialog.dart index d38bd031..d2cfce09 100644 --- a/lib/widgets/repeater_login_dialog.dart +++ b/lib/widgets/repeater_login_dialog.dart @@ -467,7 +467,7 @@ class _RepeaterLoginDialogState extends State { ), const SizedBox(height: 4), Text( - repeater.pathLabel, + repeater.pathLabel(context.l10n), style: const TextStyle(fontSize: 11, color: Colors.grey), ), const SizedBox(height: 8), diff --git a/lib/widgets/room_login_dialog.dart b/lib/widgets/room_login_dialog.dart index 4d7f29ed..1a5ff7fc 100644 --- a/lib/widgets/room_login_dialog.dart +++ b/lib/widgets/room_login_dialog.dart @@ -393,7 +393,7 @@ class _RoomLoginDialogState extends State { ), const SizedBox(height: 4), Text( - repeater.pathLabel, + repeater.pathLabel(context.l10n), style: const TextStyle(fontSize: 11, color: Colors.grey), ), const SizedBox(height: 8), From 066aba7c5d70f83004a4d62bf9fd3e1a10c3aace Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 23 Apr 2026 17:58:15 -0700 Subject: [PATCH 07/32] #401 Refactor multi-ACK localization strings and settings UI - Updated localization files for multiple languages to change the representation of multi-ACK settings from a string with a placeholder to a simple string. - Removed unnecessary placeholder definitions for multi-ACK in localization files. - Adjusted the settings screen to replace the slider for multi-ACK with a switch, simplifying the user interface. - Updated the Podfile.lock to remove the wakelock_plus dependency. --- lib/l10n/app_bg.arb | 11 ++--------- lib/l10n/app_de.arb | 11 ++--------- lib/l10n/app_en.arb | 9 +-------- lib/l10n/app_es.arb | 11 ++--------- lib/l10n/app_fr.arb | 11 ++--------- lib/l10n/app_hu.arb | 11 ++--------- lib/l10n/app_it.arb | 11 ++--------- lib/l10n/app_ja.arb | 11 ++--------- lib/l10n/app_ko.arb | 11 ++--------- lib/l10n/app_localizations.dart | 4 ++-- lib/l10n/app_localizations_bg.dart | 4 +--- lib/l10n/app_localizations_de.dart | 4 +--- lib/l10n/app_localizations_en.dart | 4 +--- lib/l10n/app_localizations_es.dart | 4 +--- lib/l10n/app_localizations_fr.dart | 4 +--- lib/l10n/app_localizations_hu.dart | 4 +--- lib/l10n/app_localizations_it.dart | 4 +--- lib/l10n/app_localizations_ja.dart | 4 +--- lib/l10n/app_localizations_ko.dart | 4 +--- lib/l10n/app_localizations_nl.dart | 4 +--- lib/l10n/app_localizations_pl.dart | 4 +--- lib/l10n/app_localizations_pt.dart | 4 +--- lib/l10n/app_localizations_ru.dart | 4 +--- lib/l10n/app_localizations_sk.dart | 4 +--- lib/l10n/app_localizations_sl.dart | 4 +--- lib/l10n/app_localizations_sv.dart | 4 +--- lib/l10n/app_localizations_uk.dart | 4 +--- lib/l10n/app_localizations_zh.dart | 4 +--- lib/l10n/app_nl.arb | 11 ++--------- lib/l10n/app_pl.arb | 11 ++--------- lib/l10n/app_pt.arb | 9 +-------- lib/l10n/app_ru.arb | 11 ++--------- lib/l10n/app_sk.arb | 9 +-------- lib/l10n/app_sl.arb | 11 ++--------- lib/l10n/app_sv.arb | 11 ++--------- lib/l10n/app_uk.arb | 11 ++--------- lib/l10n/app_zh.arb | 9 +-------- lib/screens/settings_screen.dart | 24 +++++++++--------------- macos/Podfile.lock | 6 ------ 39 files changed, 61 insertions(+), 235 deletions(-) diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 7ac54177..7260c253 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -1922,13 +1922,6 @@ "contact_teleLocSubtitle": "Позволи споделяне на данни за местоположение", "contact_teleLoc": "Местоположение на телеметрията", "contact_teleEnvSubtitle": "Позволи споделяне на данни от средносферните датчици", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_initialRouteWeight": "Първоначална тежест на маршрута", "appSettings_maxRouteWeight": "Максимално допустимо тегло на маршрута", "appSettings_initialRouteWeightSubtitle": "Начално тегло за новооткрити маршрути", @@ -1940,7 +1933,6 @@ "appSettings_maxMessageRetries": "Максимален брой опити за изпращане на съобщение", "appSettings_maxMessageRetriesSubtitle": "Брой опити за повторно изпращане, преди съобщението да бъде маркирано като неуспешно.", "path_routeWeight": "{weight}/{max}", - "settings_multiAck": "Мулти-потвърди: {value}", "settings_telemetryModeUpdated": "Режим на телеметрията е обновен", "map_showOverlaps": "Покриване на ключа на повтаряча", "map_runTraceWithReturnPath": "Върни се по същия път.", @@ -2073,5 +2065,6 @@ "chat_sendMessage": "Изпратете съобщение", "room_guest": "Информация за сървъра на стаята", "repeater_guest": "Информация за ретранслаторите", - "repeater_guestTools": "Инструменти за гости" + "repeater_guestTools": "Инструменти за гости", + "settings_multiAck": "Множество потвърждения" } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 46955052..bf8ad2eb 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1950,13 +1950,6 @@ "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", @@ -1969,7 +1962,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}", "map_showOverlaps": "Überlappungen der Repeater-Taste", "map_runTraceWithReturnPath": "Auf dem gleichen Pfad zurückkehren.", "@radioStats_noiseFloor": { @@ -2101,5 +2093,6 @@ "repeater_guest": "Informationen zu Repeatern", "repeater_guestTools": "Gastwerkzeuge", "chat_sendMessage": "Nachricht senden", - "room_guest": "Informationen zum Room Server" + "room_guest": "Informationen zum Room Server", + "settings_multiAck": "Mehrere Bestätigungen" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8ad6bf37..606410cb 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -178,14 +178,7 @@ "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_multiAck": "Multi-ACKs", "settings_telemetryModeUpdated": "Telemetry mode updated", "settings_actions": "Actions", "settings_sendAdvertisement": "Send Advertisement", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index ac9527bd..b0585680 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1950,13 +1950,6 @@ "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", @@ -1969,7 +1962,6 @@ "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}", "map_showOverlaps": "Superposiciones de tecla repetidora", "map_runTraceWithReturnPath": "Volver atrás por el mismo camino.", "@radioStats_noiseFloor": { @@ -2101,5 +2093,6 @@ "repeater_guest": "Información sobre repetidores", "chat_sendMessage": "Enviar mensaje", "repeater_guestTools": "Herramientas para invitados", - "room_guest": "Información del servidor" + "room_guest": "Información del servidor", + "settings_multiAck": "Múltiples respuestas de confirmación" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index a942aa2b..fa375a40 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1922,13 +1922,6 @@ "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", @@ -1940,7 +1933,6 @@ "appSettings_maxMessageRetries": "Nombre maximal de tentatives de récupération de messages", "appSettings_maxMessageRetriesSubtitle": "Nombre de tentatives de relance avant de marquer un message comme ayant échoué.", "path_routeWeight": "{weight}/{max}", - "settings_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.", @@ -2073,5 +2065,6 @@ "repeater_guestTools": "Outils pour les invités", "chat_sendMessage": "Envoyer un message", "room_guest": "Informations sur le serveur", - "repeater_guest": "Informations sur les répéteurs" + "repeater_guest": "Informations sur les répéteurs", + "settings_multiAck": "Plusieurs accusés de réception" } diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index 6f43463c..ae6acfb2 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -2012,13 +2012,6 @@ "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.", @@ -2030,7 +2023,6 @@ "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", @@ -2111,5 +2103,6 @@ "repeater_guestTools": "Vendégek számára elérhető eszközök", "room_guest": "Szoba szerver információk", "chat_sendMessage": "Üzenet küldése", - "repeater_guest": "Adatok a repeaterről" + "repeater_guest": "Adatok a repeaterről", + "settings_multiAck": "Többszörös visszaigazolások" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 387c8cff..35001e61 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -1922,13 +1922,6 @@ "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.", @@ -1941,7 +1934,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}", "map_showOverlaps": "Sovrapposizioni della chiave ripetitore", "map_runTraceWithReturnPath": "Tornare indietro sullo stesso percorso", "@radioStats_noiseFloor": { @@ -2073,5 +2065,6 @@ "repeater_guest": "Informazioni sul ripetitore", "repeater_guestTools": "Strumenti per gli ospiti", "chat_sendMessage": "Invia messaggio", - "room_guest": "Informazioni sul server" + "room_guest": "Informazioni sul server", + "settings_multiAck": "ACK multipli" } diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index b63f146d..7ac449db 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -2012,13 +2012,6 @@ "radioStats_stripWaiting": "ラジオの統計情報を取得中…", "radioStats_settingsTile": "ラジオの統計", "radioStats_settingsSubtitle": "ノイズレベル、RSSI、SNR、および通信時間", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "settings_privacy": "プライバシー設定", "settings_privacySubtitle": "共有する情報の内容を管理する。", "settings_denyAll": "すべてを否定", @@ -2030,7 +2023,6 @@ "settings_telemetryEnvironmentMode": "テレメトリ環境モード", "settings_advertLocation": "広告掲載場所", "settings_advertLocationSubtitle": "広告に場所を記載してください。", - "settings_multiAck": "複数のACK:{value}", "settings_telemetryModeUpdated": "テレメトリモードが更新されました", "contact_info": "連絡先", "contact_settings": "連絡設定", @@ -2111,5 +2103,6 @@ "room_guest": "ルームサーバーに関する情報", "chat_sendMessage": "メッセージを送信する", "repeater_guest": "繰り返し送信に関する情報", - "repeater_guestTools": "ゲスト向けツール" + "repeater_guestTools": "ゲスト向けツール", + "settings_multiAck": "複数のACK(応答)" } diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 40721fe1..54073db7 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -2012,13 +2012,6 @@ "radioStats_stripWaiting": "라디오 통계 가져오기…", "radioStats_settingsTile": "라디오 통계", "radioStats_settingsSubtitle": "잡음 수준, RSSI, 신호 대 잡음비, 통신 시간", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "settings_privacy": "개인 정보 설정", "settings_privacySubtitle": "어떤 정보를 공유할지 통제하세요.", "settings_privacySettingsDescription": "어떤 정보를 기기가 다른 사람들과 공유할지 선택하세요.", @@ -2030,7 +2023,6 @@ "settings_telemetryEnvironmentMode": "텔레메트리 환경 모드", "settings_advertLocation": "광고 위치", "settings_advertLocationSubtitle": "광고에 위치 정보를 포함하세요.", - "settings_multiAck": "다중 ACK: {value}", "settings_telemetryModeUpdated": "텔레메트리 모드 업데이트 완료", "contact_info": "연락처", "contact_settings": "연락처 설정", @@ -2111,5 +2103,6 @@ "repeater_guestTools": "손님용 도구", "chat_sendMessage": "메시지를 보내기", "repeater_guest": "반복 장비 정보", - "room_guest": "서버 정보" + "room_guest": "서버 정보", + "settings_multiAck": "다중 ACK" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 2c1342d0..09075570 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -901,8 +901,8 @@ abstract class AppLocalizations { /// No description provided for @settings_multiAck. /// /// In en, this message translates to: - /// **'Multi-ACKs: {value}'** - String settings_multiAck(String value); + /// **'Multi-ACKs'** + String get settings_multiAck; /// No description provided for @settings_telemetryModeUpdated. /// diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index b3e12799..01973dff 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -437,9 +437,7 @@ class AppLocalizationsBg extends AppLocalizations { 'Включи местоположение в обявата'; @override - String settings_multiAck(String value) { - return 'Мулти-потвърди: $value'; - } + String get settings_multiAck => 'Множество потвърждения'; @override String get settings_telemetryModeUpdated => 'Режим на телеметрията е обновен'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index d7c16914..60cd3252 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -435,9 +435,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Ort in der Anzeige einbeziehen'; @override - String settings_multiAck(String value) { - return 'Mehrfach-Bestätigungen: $value'; - } + String get settings_multiAck => 'Mehrere Bestätigungen'; @override String get settings_telemetryModeUpdated => 'Telemetriemodus aktualisiert'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index a2a88b0d..353e5bcb 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -427,9 +427,7 @@ class AppLocalizationsEn extends AppLocalizations { String get settings_advertLocationSubtitle => 'Include location in advert.'; @override - String settings_multiAck(String value) { - return 'Multi-ACKs: $value'; - } + String get settings_multiAck => 'Multi-ACKs'; @override String get settings_telemetryModeUpdated => 'Telemetry mode updated'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index a1270124..d656ac49 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -434,9 +434,7 @@ class AppLocalizationsEs extends AppLocalizations { String get settings_advertLocationSubtitle => 'Incluir ubicación en anuncio'; @override - String settings_multiAck(String value) { - return 'Multi-ACKs: $value'; - } + String get settings_multiAck => 'Múltiples respuestas de confirmación'; @override String get settings_telemetryModeUpdated => 'Modo de telemetría actualizado'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index a0063914..acdc9454 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -438,9 +438,7 @@ class AppLocalizationsFr extends AppLocalizations { 'Inclure l\'emplacement dans l\'annonce'; @override - String settings_multiAck(String value) { - return 'Multi-ACKs : $value'; - } + String get settings_multiAck => 'Plusieurs accusés de réception'; @override String get settings_telemetryModeUpdated => diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 1ad8558c..24c9ac66 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -437,9 +437,7 @@ class AppLocalizationsHu extends AppLocalizations { 'A hirdetés tartalmazza a helyszínt.'; @override - String settings_multiAck(String value) { - return 'Többszöri visszaigazolások: $value'; - } + String get settings_multiAck => 'Többszörös visszaigazolások'; @override String get settings_telemetryModeUpdated => 'A telemetriamód frissítve'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 3a55559a..abb1427d 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -437,9 +437,7 @@ class AppLocalizationsIt extends AppLocalizations { 'Includi la posizione nell\'annuncio'; @override - String settings_multiAck(String value) { - return 'Multi-ACKs: $value'; - } + String get settings_multiAck => 'ACK multipli'; @override String get settings_telemetryModeUpdated => 'Modalità telemetria aggiornata'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index afb8c29b..60018262 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -414,9 +414,7 @@ class AppLocalizationsJa extends AppLocalizations { String get settings_advertLocationSubtitle => '広告に場所を記載してください。'; @override - String settings_multiAck(String value) { - return '複数のACK:$value'; - } + String get settings_multiAck => '複数のACK(応答)'; @override String get settings_telemetryModeUpdated => 'テレメトリモードが更新されました'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index ff4bd261..8f74af8f 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -414,9 +414,7 @@ class AppLocalizationsKo extends AppLocalizations { String get settings_advertLocationSubtitle => '광고에 위치 정보를 포함하세요.'; @override - String settings_multiAck(String value) { - return '다중 ACK: $value'; - } + String get settings_multiAck => '다중 ACK'; @override String get settings_telemetryModeUpdated => '텔레메트리 모드 업데이트 완료'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index dd770e15..d1ebea4a 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -432,9 +432,7 @@ class AppLocalizationsNl extends AppLocalizations { 'Locatie opnemen in advertentie'; @override - String settings_multiAck(String value) { - return 'Multi-ACKs: $value'; - } + String get settings_multiAck => 'Meerdere bevestigingen'; @override String get settings_telemetryModeUpdated => 'Telemetrie-modus bijgewerkt'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 357dd7e3..918860ed 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -439,9 +439,7 @@ class AppLocalizationsPl extends AppLocalizations { 'Uwzględnij lokalizację w ogłoszeniu'; @override - String settings_multiAck(String value) { - return 'Wielokrotne ACK: $value'; - } + String get settings_multiAck => 'Wielokrotne potwierdzenia odbioru'; @override String get settings_telemetryModeUpdated => diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 2dfcd8bd..5584da93 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -436,9 +436,7 @@ class AppLocalizationsPt extends AppLocalizations { 'Incluir localização no anúncio'; @override - String settings_multiAck(String value) { - return 'Multi-ACKs: $value'; - } + String get settings_multiAck => 'Multi-ACKs'; @override String get settings_telemetryModeUpdated => 'Modo de telemetria atualizado'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 4fac42ce..4b7e3d44 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -436,9 +436,7 @@ class AppLocalizationsRu extends AppLocalizations { 'Включить местоположение в объявление'; @override - String settings_multiAck(String value) { - return 'Мульти-ACK: $value'; - } + String get settings_multiAck => 'Несколько подтверждений'; @override String get settings_telemetryModeUpdated => 'Режим телеметрии обновлен'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index c42e0249..f855652d 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -430,9 +430,7 @@ class AppLocalizationsSk extends AppLocalizations { String get settings_advertLocationSubtitle => 'Zahrnúť polohu do inzerátu'; @override - String settings_multiAck(String value) { - return 'Viaceré ACK: $value'; - } + String get settings_multiAck => 'Viaceré ACK'; @override String get settings_telemetryModeUpdated => diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 2d89aa41..2f3b30c0 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -430,9 +430,7 @@ class AppLocalizationsSl extends AppLocalizations { String get settings_advertLocationSubtitle => 'Vključi lokacijo v oglas.'; @override - String settings_multiAck(String value) { - return 'Večkratni potrditvi: $value'; - } + String get settings_multiAck => 'Več potrdil'; @override String get settings_telemetryModeUpdated => 'Način telemetrije posodobljen'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 38e08939..00816f2a 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -428,9 +428,7 @@ class AppLocalizationsSv extends AppLocalizations { String get settings_advertLocationSubtitle => 'Inkludera plats i annonsen'; @override - String settings_multiAck(String value) { - return 'Multi-ACKs: $value'; - } + String get settings_multiAck => 'Flera bekräftelser'; @override String get settings_telemetryModeUpdated => 'Telemetri-läge uppdaterat'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 8ed4b9ff..f43fd53e 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -432,9 +432,7 @@ class AppLocalizationsUk extends AppLocalizations { 'Включити місце розташування в оголошення'; @override - String settings_multiAck(String value) { - return 'Багатократне підтвердження: $value'; - } + String get settings_multiAck => 'Багато підтверджень'; @override String get settings_telemetryModeUpdated => 'Режим телеметрії оновлено'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 4f38c64a..6181e8af 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -408,9 +408,7 @@ class AppLocalizationsZh extends AppLocalizations { String get settings_advertLocationSubtitle => '在广告中包含位置'; @override - String settings_multiAck(String value) { - return '多重ACK:$value'; - } + String get settings_multiAck => '多重ACK'; @override String get settings_telemetryModeUpdated => '遥测模式已更新'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 96bdb845..fa3eb37b 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -1922,13 +1922,6 @@ "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", @@ -1941,7 +1934,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}", "map_showOverlaps": "Herhalingssleutel overlapt", "map_runTraceWithReturnPath": "Terugkeren op hetzelfde pad.", "@radioStats_noiseFloor": { @@ -2073,5 +2065,6 @@ "repeater_guestTools": "Gastenfuncties", "room_guest": "Informatie over de server", "chat_sendMessage": "Verzend bericht", - "repeater_guest": "Informatie over herhalingsapparatuur" + "repeater_guest": "Informatie over herhalingsapparatuur", + "settings_multiAck": "Meerdere bevestigingen" } diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index b62a78af..870be9e6 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1960,13 +1960,6 @@ "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", @@ -1979,7 +1972,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": "Wielokrotne ACK: {value}", "map_showOverlaps": "Nakładające się klucze przekaźników", "map_runTraceWithReturnPath": "Wróć tą samą ścieżką", "@radioStats_noiseFloor": { @@ -2111,5 +2103,6 @@ "chat_sendMessage": "Wyślij wiadomość", "repeater_guestTools": "Narzędzia dla gości", "repeater_guest": "Informacje dotyczące urządzenia powtarzającego", - "room_guest": "Informacje o serwerze" + "room_guest": "Informacje o serwerze", + "settings_multiAck": "Wielokrotne potwierdzenia odbioru" } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index bf3e8936..634cf1c0 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -1922,13 +1922,6 @@ "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.", @@ -1941,7 +1934,7 @@ "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", "map_showOverlaps": "Sobreposições da Chave Repeater", "map_runTraceWithReturnPath": "Retornar ao mesmo caminho.", "@radioStats_noiseFloor": { diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index a83d1394..1ad0f1b9 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -1162,13 +1162,6 @@ "contact_clearChat": "Очистить чат", "contact_lastSeen": "Последний раз видели", "contact_teleBaseSubtitle": "Разрешить обмен уровнем заряда батареи и базовой телеметрией", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_maxRouteWeight": "Максимальный допустимый вес маршрута", "appSettings_maxRouteWeightSubtitle": "Максимальный вес, который может быть перевезён по определённому маршруту при успешных доставках.", "appSettings_initialRouteWeightSubtitle": "Начальный вес для новых, только что открытых маршрутов", @@ -1181,7 +1174,6 @@ "appSettings_maxMessageRetriesSubtitle": "Количество попыток повторной отправки сообщения перед тем, как пометить его как неудачное.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрии обновлен", - "settings_multiAck": "Мульти-ACK: {value}", "map_showOverlaps": "Перекрытия ключа повтора", "map_runTraceWithReturnPath": "Вернуться обратно по тому же пути", "@radioStats_noiseFloor": { @@ -1313,5 +1305,6 @@ "chat_sendMessage": "Отправить сообщение", "repeater_guest": "Информация о ретрансляторе", "room_guest": "Информация о сервере", - "repeater_guestTools": "Инструменты для гостей" + "repeater_guestTools": "Инструменты для гостей", + "settings_multiAck": "Несколько подтверждений" } diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index e4466c30..9cd6072c 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -1922,13 +1922,6 @@ "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", @@ -1941,7 +1934,7 @@ "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", "map_showOverlaps": "Prekrývanie opakovača kľúča", "map_runTraceWithReturnPath": "Vráťte sa späť po tej istej ceste.", "@radioStats_noiseFloor": { diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index f6a317ef..cace8c6b 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -1922,13 +1922,6 @@ "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", @@ -1940,7 +1933,6 @@ "appSettings_maxMessageRetries": "Najve število poskusov pošiljanja sporočil", "appSettings_maxMessageRetriesSubtitle": "Število poskusov ponovnega poslanja, preden se sporočilo označuje kot neuspešno", "path_routeWeight": "{weight}/{max}", - "settings_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.", @@ -2073,5 +2065,6 @@ "repeater_guest": "Informacije o ponovljalniku", "chat_sendMessage": "Pošlji sporočilo", "room_guest": "Informacije o strežniku", - "repeater_guestTools": "Naložila za goste" + "repeater_guestTools": "Naložila za goste", + "settings_multiAck": "Več potrdil" } diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index eab348c7..aaa6ea75 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -1922,13 +1922,6 @@ "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.", @@ -1941,7 +1934,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}", "map_showOverlaps": "Repeater-nyckelöverlappningar", "map_runTraceWithReturnPath": "Gå tillbaka på samma väg", "@radioStats_noiseFloor": { @@ -2073,5 +2065,6 @@ "repeater_guest": "Information om repetorer", "chat_sendMessage": "Skicka meddelande", "repeater_guestTools": "Gästverktyg", - "room_guest": "Information om servern" + "room_guest": "Information om servern", + "settings_multiAck": "Flera bekräftelser" } diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index a005b369..489b8a2e 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1922,13 +1922,6 @@ "contact_lastSeen": "Останній раз бачили", "contact_teleEnv": "Середовище телеметрії", "contact_teleEnvSubtitle": "Дозволити спільний доступ до даних датчиків середовища", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_initialRouteWeight": "Початкова вартість маршруту", "appSettings_initialRouteWeightSubtitle": "Початкова вага для нових відкритих шляхів", "appSettings_maxRouteWeight": "Максимальна вага маршруту", @@ -1941,7 +1934,6 @@ "appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрії оновлено", - "settings_multiAck": "Багатократне підтвердження: {value}", "map_showOverlaps": "Перекриття ключа повторювача", "map_runTraceWithReturnPath": "Повернутися назад тим же шляхом", "@radioStats_noiseFloor": { @@ -2073,5 +2065,6 @@ "repeater_guestTools": "Інструменти для гостей", "repeater_guest": "Інформація про ретранслятор", "room_guest": "Інформація про сервер кімнати", - "chat_sendMessage": "Надіслати повідомлення" + "chat_sendMessage": "Надіслати повідомлення", + "settings_multiAck": "Багато підтверджень" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9dc2325c..9d6a8bfd 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1927,13 +1927,6 @@ "contact_settings": "联系人设置", "contact_teleLocSubtitle": "允许共享位置数据", "contact_telemetry": "遥测数据", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_maxRouteWeight": "最大路径重量", "appSettings_initialRouteWeightSubtitle": "新发现路径的初始重量", "appSettings_initialRouteWeight": "初始路线权重", @@ -1945,7 +1938,7 @@ "appSettings_maxMessageRetries": "最大消息重试次数", "appSettings_maxMessageRetriesSubtitle": "在将消息标记为失败之前,允许尝试的次数", "path_routeWeight": "{weight}/{max}", - "settings_multiAck": "多重ACK:{value}", + "settings_multiAck": "多重ACK", "settings_telemetryModeUpdated": "遥测模式已更新", "map_showOverlaps": "重复键重叠", "map_runTraceWithReturnPath": "沿着相同的路径返回", diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 47b9b9c6..9a1aca63 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -1011,6 +1011,15 @@ void _privacySettings(BuildContext context, MeshCoreConnector connector) { }, ), const SizedBox(height: 8), + SwitchListTile( + title: Text(l10n.settings_multiAck), + value: multiAcks == 1, + onChanged: (value) { + setDialogState(() => multiAcks = value ? 1 : 0); + }, + contentPadding: EdgeInsets.zero, + ), + const SizedBox(height: 16), DropdownButtonFormField( initialValue: telemetryMode, decoration: InputDecoration( @@ -1052,21 +1061,6 @@ void _privacySettings(BuildContext context, MeshCoreConnector connector) { } }, ), - 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()); - }, - ), ], ), ), diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 58b4d017..481f1bcc 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -22,8 +22,6 @@ PODS: - FlutterMacOS - url_launcher_macos (0.0.1): - FlutterMacOS - - wakelock_plus (0.0.1): - - FlutterMacOS DEPENDENCIES: - flserial (from `Flutter/ephemeral/.symlinks/plugins/flserial/macos`) @@ -36,7 +34,6 @@ DEPENDENCIES: - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - - wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`) EXTERNAL SOURCES: flserial: @@ -59,8 +56,6 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos - wakelock_plus: - :path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos SPEC CHECKSUMS: flserial: 3c161e076dfc73458ec5803e7a9a9d2bb85fadf6 @@ -73,7 +68,6 @@ SPEC CHECKSUMS: shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd - wakelock_plus: 917609be14d812ddd9e9528876538b2263aaa03b PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 From 54e0dae17273e41b281177d56d83677796bd013c Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 23 Apr 2026 17:58:40 -0700 Subject: [PATCH 08/32] Add placeholder for multi-ACKs setting in localization --- lib/l10n/app_en.arb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 606410cb..27ae16ac 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -178,7 +178,14 @@ "settings_telemetryEnvironmentMode": "Telemetry Environment Mode", "settings_advertLocation": "Advert Location", "settings_advertLocationSubtitle": "Include location in advert.", - "settings_multiAck": "Multi-ACKs", + "settings_multiAck": "Multi-ACKs", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "settings_telemetryModeUpdated": "Telemetry mode updated", "settings_actions": "Actions", "settings_sendAdvertisement": "Send Advertisement", From e53c493e789716d582744ced0bdef367934886e4 Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 23 Apr 2026 18:01:35 -0700 Subject: [PATCH 09/32] update TS --- lib/l10n/app_en.arb | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 27ae16ac..606410cb 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -178,14 +178,7 @@ "settings_telemetryEnvironmentMode": "Telemetry Environment Mode", "settings_advertLocation": "Advert Location", "settings_advertLocationSubtitle": "Include location in advert.", - "settings_multiAck": "Multi-ACKs", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, + "settings_multiAck": "Multi-ACKs", "settings_telemetryModeUpdated": "Telemetry mode updated", "settings_actions": "Actions", "settings_sendAdvertisement": "Send Advertisement", From 6ae3f612aed471ad61ce7b87b9bd540cb47d09f3 Mon Sep 17 00:00:00 2001 From: Serge Tarkovski Date: Fri, 24 Apr 2026 13:58:31 +0300 Subject: [PATCH 10/32] Localize Score, fix login dialog overflow, use locale-aware date format in channel chat --- lib/l10n/app_bg.arb | 3 ++- lib/l10n/app_de.arb | 3 ++- lib/l10n/app_en.arb | 1 + lib/l10n/app_es.arb | 3 ++- lib/l10n/app_fr.arb | 3 ++- lib/l10n/app_hu.arb | 3 ++- lib/l10n/app_it.arb | 3 ++- lib/l10n/app_ja.arb | 3 ++- lib/l10n/app_ko.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_hu.dart | 3 +++ lib/l10n/app_localizations_it.dart | 3 +++ lib/l10n/app_localizations_ja.dart | 3 +++ lib/l10n/app_localizations_ko.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/screens/channel_chat_screen.dart | 11 +++++++---- lib/widgets/path_management_dialog.dart | 2 +- 39 files changed, 103 insertions(+), 22 deletions(-) diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index e1a955bd..963be1c5 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -2114,5 +2114,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 885d2de8..4864daa6 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -2142,5 +2142,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 5b8b2c47..b0f03b5a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -784,6 +784,7 @@ } }, "chat_successes": "successes", + "chat_score": "Score", "chat_removePath": "Remove path", "chat_noPathHistoryYet": "No path history yet.\nSend a message to discover paths.", "chat_pathActions": "Path Actions:", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index e830630a..b7e71b49 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -2142,5 +2142,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index ad74eb25..ad15c87f 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -2114,5 +2114,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index 89d7389c..f341fd96 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -2152,5 +2152,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 13069a5b..f6724f39 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -2114,5 +2114,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index b6736b18..973107ab 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -2152,5 +2152,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index d2e46a40..fe639e8a 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -2152,5 +2152,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index d12fe26d..45b3b46c 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2686,6 +2686,12 @@ abstract class AppLocalizations { /// **'successes'** String get chat_successes; + /// No description provided for @chat_score. + /// + /// In en, this message translates to: + /// **'Score'** + String get chat_score; + /// No description provided for @chat_removePath. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 81df50a3..9db1c889 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -1473,6 +1473,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get chat_successes => 'Успехи'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Премахни пътя'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index aec7223e..e29a0fe8 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1472,6 +1472,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get chat_successes => 'Erfolgreich'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Pfad entfernen'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 00926c02..faa40f02 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1445,6 +1445,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get chat_successes => 'successes'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Remove path'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index c9bf19aa..65a36922 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1470,6 +1470,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get chat_successes => 'Éxitos'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Eliminar ruta'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index f88101fa..df53a2f0 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1477,6 +1477,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get chat_successes => 'Succès'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Supprimer le chemin'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index f6642caf..5e939d72 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -1482,6 +1482,9 @@ class AppLocalizationsHu extends AppLocalizations { @override String get chat_successes => 'sikerek'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Törölje a elérési útvonalat'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index a4d04705..7f285c39 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1471,6 +1471,9 @@ class AppLocalizationsIt extends AppLocalizations { @override String get chat_successes => 'successi'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Rimuovi percorso'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index d93481be..17e97591 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -1409,6 +1409,9 @@ class AppLocalizationsJa extends AppLocalizations { @override String get chat_successes => '成功事例'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'パスを削除する'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index e8d5d64a..d4872b12 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -1405,6 +1405,9 @@ class AppLocalizationsKo extends AppLocalizations { @override String get chat_successes => '성공 사례'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => '경로 제거'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index db1701ed..448053ec 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1459,6 +1459,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get chat_successes => 'Succesvol'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Pad verwijderen'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index aa8db9cf..f092c155 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1483,6 +1483,9 @@ class AppLocalizationsPl extends AppLocalizations { @override String get chat_successes => 'Sukcesy'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Usuń ścieżkę'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 483447dd..8c870505 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1470,6 +1470,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get chat_successes => 'Sucessos'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Remover caminho'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 743ffbe2..a77f1f4f 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1473,6 +1473,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get chat_successes => 'успешно'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Удалить маршрут'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 6deb5e3e..222251b2 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -1460,6 +1460,9 @@ class AppLocalizationsSk extends AppLocalizations { @override String get chat_successes => 'Úspechy'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Odstrániť cestu'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index eee54439..4053b915 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -1457,6 +1457,9 @@ class AppLocalizationsSl extends AppLocalizations { @override String get chat_successes => 'Uspešni'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Izbriši pot'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index cc4b77c5..b29fdf4e 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -1452,6 +1452,9 @@ class AppLocalizationsSv extends AppLocalizations { @override String get chat_successes => 'framgångar'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Ta bort sökväg'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 834db157..b0c62127 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -1468,6 +1468,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get chat_successes => 'Успішно'; + @override + String get chat_score => 'Оцінка'; + @override String get chat_removePath => 'Видалити шлях'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index c28b980e..164abb90 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1385,6 +1385,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get chat_successes => '成功'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => '移除路径'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 9dd31bd9..eaa593b8 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -2114,5 +2114,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index b9180716..ac678c11 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -2152,5 +2152,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 5de4b127..7993f029 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -2114,5 +2114,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 07bf5259..5124659e 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -1354,5 +1354,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 61fc64f7..26dd1e73 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -2114,5 +2114,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index f6339142..3e0fdf65 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -2114,5 +2114,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index d5947cba..73b7227a 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -2114,5 +2114,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index f0cfd360..b47bfcbf 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -564,6 +564,7 @@ } }, "chat_successes": "Успішно", + "chat_score": "Оцінка", "chat_removePath": "Видалити шлях", "chat_noPathHistoryYet": "Історія шляхів недоступна.\nНадішліть повідомлення, щоб виявити шляхи.", "chat_pathActions": "Дії зі шляхом:", @@ -827,7 +828,7 @@ "login_roomDescription": "Введіть пароль кімнати для доступу до налаштувань та статусу.", "login_routing": "Маршрутизація", "login_routingMode": "Режим маршрутизації", - "login_autoUseSavedPath": "Авто (використовувати збережений шлях)", + "login_autoUseSavedPath": "Авто (збережений шлях)", "login_forceFloodMode": "Примусово через всю мережу", "login_managePaths": "Керувати шляхами", "login_login": "Вхід", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 03fa71fd..b88a9af9 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -2119,5 +2119,6 @@ "contact_typeRoom": "Room", "contact_typeSensor": "Sensor", "contact_typeUnknown": "Unknown", - "channels_via": "via {path}" + "channels_via": "via {path}", + "chat_score": "Score" } diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index 0454f17f..6bd89c2c 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; +import 'package:intl/intl.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; @@ -578,7 +579,7 @@ class _ChannelChatScreenState extends State { mainAxisSize: MainAxisSize.min, children: [ Text( - _formatTime(message.timestamp), + _formatTime(context, message.timestamp), style: TextStyle( fontSize: 11, color: Colors.grey[600], @@ -1227,14 +1228,16 @@ class _ChannelChatScreenState extends State { ); } - String _formatTime(DateTime time) { + String _formatTime(BuildContext context, DateTime time) { final now = DateTime.now(); final diff = now.difference(time); + final locale = Localizations.localeOf(context).toString(); + final hm = DateFormat.Hm(locale).format(time); if (diff.inDays > 0) { - return '${time.day}/${time.month} ${time.hour}:${time.minute.toString().padLeft(2, '0')}'; + return '${DateFormat.Md(locale).format(time)} $hm'; } else { - return '${time.hour}:${time.minute.toString().padLeft(2, '0')}'; + return hm; } } diff --git a/lib/widgets/path_management_dialog.dart b/lib/widgets/path_management_dialog.dart index bc7623e0..c63ce76e 100644 --- a/lib/widgets/path_management_dialog.dart +++ b/lib/widgets/path_management_dialog.dart @@ -303,7 +303,7 @@ class _PathManagementDialogState extends State<_PathManagementDialog> { ), isThreeLine: true, subtitle: Text( - '${(path.tripTimeMs / 1000).toStringAsFixed(2)}s • ${_formatRelativeTime(context, path.timestamp)}\n${path.successCount} ${l10n.chat_successes} • Score: ${path.routeWeight.toStringAsFixed(1)}', + '${(path.tripTimeMs / 1000).toStringAsFixed(2)}s • ${_formatRelativeTime(context, path.timestamp)}\n${path.successCount} ${l10n.chat_successes} • ${l10n.chat_score}: ${path.routeWeight.toStringAsFixed(1)}', style: const TextStyle(fontSize: 11), ), trailing: Row( From b7d0db8d1c61b34a223e6290144120943c3c6cd8 Mon Sep 17 00:00:00 2001 From: Serge Tarkovski Date: Sat, 25 Apr 2026 00:29:20 +0300 Subject: [PATCH 11/32] Refactor: move Contact UI labels to l10n extension; rename raw getter to typeLabelRaw --- lib/connector/meshcore_connector.dart | 8 +++--- lib/l10n/contact_localization.dart | 36 +++++++++++++++++++++++++ lib/main.dart | 3 +++ lib/models/contact.dart | 32 +++------------------- lib/screens/chat_screen.dart | 3 ++- lib/screens/contacts_screen.dart | 3 ++- lib/screens/map_screen.dart | 3 ++- lib/screens/repeater_hub_screen.dart | 1 + lib/services/background_service.dart | 12 +++++++++ lib/utils/gpx_export.dart | 6 ++--- lib/widgets/path_management_dialog.dart | 1 + lib/widgets/path_selection_dialog.dart | 3 ++- lib/widgets/repeater_login_dialog.dart | 1 + lib/widgets/room_login_dialog.dart | 1 + 14 files changed, 74 insertions(+), 39 deletions(-) create mode 100644 lib/l10n/contact_localization.dart diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index fceee150..ec887eaf 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -3997,7 +3997,7 @@ class MeshCoreConnector extends ChangeNotifier { ); } else { appLogger.info( - "Discovered contact ${contact.name} (type ${contact.typeLabel}) not added due to auto-add settings", + "Discovered contact ${contact.name} (type ${contact.typeLabelRaw}) not added due to auto-add settings", tag: 'Connector', ); return; @@ -4019,7 +4019,7 @@ class MeshCoreConnector extends ChangeNotifier { if (settings.notificationsEnabled && settings.notifyOnNewAdvert) { _notificationService.showAdvertNotification( contactName: contact.name, - contactType: contact.typeLabel, + contactType: contact.typeLabelRaw, contactId: contact.publicKeyHex, ); } @@ -4094,7 +4094,7 @@ class MeshCoreConnector extends ChangeNotifier { if (settings.notificationsEnabled && settings.notifyOnNewAdvert) { _notificationService.showAdvertNotification( contactName: contact.name, - contactType: contact.typeLabel, + contactType: contact.typeLabelRaw, contactId: contact.publicKeyHex, ); } @@ -6025,7 +6025,7 @@ class MeshCoreConnector extends ChangeNotifier { if (settings.notificationsEnabled && settings.notifyOnNewAdvert) { _notificationService.showAdvertNotification( contactName: contact.name, - contactType: contact.typeLabel, + contactType: contact.typeLabelRaw, contactId: contact.publicKeyHex, ); } diff --git a/lib/l10n/contact_localization.dart b/lib/l10n/contact_localization.dart new file mode 100644 index 00000000..d8344a32 --- /dev/null +++ b/lib/l10n/contact_localization.dart @@ -0,0 +1,36 @@ +import '../connector/meshcore_protocol.dart'; +import '../models/contact.dart'; +import 'app_localizations.dart'; + +/// UI-level localization helpers for [Contact]. +/// +/// Kept out of the model layer so `Contact` does not depend on +/// `AppLocalizations`. Use these from widgets/screens; for logs and +/// non-UI export use `Contact.typeLabelRaw`. +extension ContactLocalization on Contact { + String typeLabel(AppLocalizations l10n) { + switch (type) { + case advTypeChat: + return l10n.contact_typeChat; + case advTypeRepeater: + return l10n.contact_typeRepeater; + case advTypeRoom: + return l10n.contact_typeRoom; + case advTypeSensor: + return l10n.contact_typeSensor; + default: + return l10n.contact_typeUnknown; + } + } + + String pathLabel(AppLocalizations l10n) { + if (pathOverride != null) { + if (pathOverride! < 0) return l10n.chat_floodForced; + if (pathOverride == 0) return l10n.chat_directForced; + return l10n.chat_hopsForced(pathOverride!); + } + if (pathLength < 0) return l10n.channelPath_floodPath; + if (pathLength == 0) return l10n.chat_direct; + return l10n.chat_hopsCount(pathLength); + } +} diff --git a/lib/main.dart b/lib/main.dart index 3e57eb10..cd622811 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -59,6 +59,9 @@ void main() async { final notificationService = NotificationService(); await notificationService.initialize(); await backgroundService.initialize(); + backgroundService.setLanguageOverrideProvider( + () => appSettingsService.settings.languageOverride, + ); _registerThirdPartyLicenses(); await chatTextScaleService.initialize(); diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 020e4290..b5df2af1 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -2,7 +2,6 @@ import 'dart:typed_data'; import 'package:meshcore_open/utils/app_logger.dart'; import '../connector/meshcore_protocol.dart'; -import '../l10n/app_localizations.dart'; class Contact { final Uint8List publicKey; @@ -42,7 +41,10 @@ class Contact { String get publicKeyHex => pubKeyToHex(publicKey); - String get typeLabel { + /// Non-localized type label, intended for logs and non-UI exports + /// (e.g. GPX). For UI use the `typeLabel(l10n)` extension in + /// `lib/l10n/contact_localization.dart`. + String get typeLabelRaw { switch (type) { case advTypeChat: return 'Chat'; @@ -57,32 +59,6 @@ class Contact { } } - String typeLabelLocalized(AppLocalizations l10n) { - switch (type) { - case advTypeChat: - return l10n.contact_typeChat; - case advTypeRepeater: - return l10n.contact_typeRepeater; - case advTypeRoom: - return l10n.contact_typeRoom; - case advTypeSensor: - return l10n.contact_typeSensor; - default: - return l10n.contact_typeUnknown; - } - } - - String pathLabel(AppLocalizations l10n) { - if (pathOverride != null) { - if (pathOverride! < 0) return l10n.chat_floodForced; - if (pathOverride == 0) return l10n.chat_directForced; - return l10n.chat_hopsForced(pathOverride!); - } - if (pathLength < 0) return l10n.channelPath_floodPath; - if (pathLength == 0) return l10n.chat_direct; - return l10n.chat_hopsCount(pathLength); - } - bool get hasLocation { const double epsilon = 1e-6; final lat = latitude ?? 0.0; diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index 2d5a4483..ec311f84 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -20,6 +20,7 @@ import '../helpers/gif_helper.dart'; import '../helpers/path_helper.dart'; import '../models/channel_message.dart'; import '../models/contact.dart'; +import '../l10n/contact_localization.dart'; import '../models/message.dart'; import '../models/path_history.dart'; import '../models/translation_support.dart'; @@ -1168,7 +1169,7 @@ class _ChatScreenState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildInfoRow(context.l10n.chat_type, contact.typeLabelLocalized(context.l10n)), + _buildInfoRow(context.l10n.chat_type, contact.typeLabel(context.l10n)), _buildInfoRow(context.l10n.chat_path, contact.pathLabel(context.l10n)), _buildInfoRow( context.l10n.contact_lastSeen, diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index 5b855cdf..39ccfcd9 100644 --- a/lib/screens/contacts_screen.dart +++ b/lib/screens/contacts_screen.dart @@ -13,6 +13,7 @@ import '../connector/meshcore_connector.dart'; import '../l10n/l10n.dart'; import '../connector/meshcore_protocol.dart'; import '../models/contact.dart'; +import '../l10n/contact_localization.dart'; import '../models/contact_group.dart'; import '../services/ui_view_state_service.dart'; import '../utils/contact_search.dart'; @@ -1123,7 +1124,7 @@ class _ContactsScreenState extends State value: isSelected, title: Text(contact.name), subtitle: Text( - contact.typeLabelLocalized(context.l10n), + contact.typeLabel(context.l10n), ), onChanged: (value) { setDialogState(() { diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index afc592f2..9d5c20a5 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -16,6 +16,7 @@ import '../connector/meshcore_protocol.dart'; import '../models/app_settings.dart'; import '../models/channel.dart'; import '../models/contact.dart'; +import '../l10n/contact_localization.dart'; import '../services/app_settings_service.dart'; import '../services/path_history_service.dart'; import '../services/map_marker_service.dart'; @@ -1425,7 +1426,7 @@ class _MapScreenState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildInfoRow(context.l10n.map_type, contact.typeLabelLocalized(context.l10n)), + _buildInfoRow(context.l10n.map_type, contact.typeLabel(context.l10n)), _buildInfoRow(context.l10n.map_path, contact.pathLabel(context.l10n)), if (contact.hasLocation) _buildInfoRow( diff --git a/lib/screens/repeater_hub_screen.dart b/lib/screens/repeater_hub_screen.dart index 0fa87cce..5b1e30ca 100644 --- a/lib/screens/repeater_hub_screen.dart +++ b/lib/screens/repeater_hub_screen.dart @@ -3,6 +3,7 @@ import 'package:meshcore_open/connector/meshcore_protocol.dart'; import 'package:provider/provider.dart'; import '../l10n/l10n.dart'; import '../models/contact.dart'; +import '../l10n/contact_localization.dart'; import '../services/app_settings_service.dart'; import 'repeater_status_screen.dart'; import 'repeater_cli_screen.dart'; diff --git a/lib/services/background_service.dart b/lib/services/background_service.dart index 04468eff..45be09b8 100644 --- a/lib/services/background_service.dart +++ b/lib/services/background_service.dart @@ -6,6 +6,14 @@ import '../utils/platform_info.dart'; class BackgroundService { bool _initialized = false; + String? Function()? _languageOverrideProvider; + + /// Allows the app to expose its current language override (e.g. from + /// AppSettingsService) so the foreground notification matches the app UI + /// language instead of only the system locale. + void setLanguageOverrideProvider(String? Function()? provider) { + _languageOverrideProvider = provider; + } Future initialize() async { if (!PlatformInfo.isAndroid || _initialized) return; @@ -47,6 +55,10 @@ class BackgroundService { Future _loadLocalizations() async { final supported = AppLocalizations.supportedLocales; + final override = _languageOverrideProvider?.call(); + if (override != null && override.isNotEmpty) { + return AppLocalizations.delegate.load(Locale(override)); + } final system = WidgetsBinding.instance.platformDispatcher.locale; final match = basicLocaleListResolution( diff --git a/lib/utils/gpx_export.dart b/lib/utils/gpx_export.dart index 296cc3ae..1b82a72d 100644 --- a/lib/utils/gpx_export.dart +++ b/lib/utils/gpx_export.dart @@ -72,7 +72,7 @@ class GpxExport { contact.name, contact.latitude!, contact.longitude!, - "Type: ${contact.typeLabel}\nPublic Key: ${contact.publicKeyHex}", + "Type: ${contact.typeLabelRaw}\nPublic Key: ${contact.publicKeyHex}", url, ); } @@ -91,7 +91,7 @@ class GpxExport { contact.name, contact.latitude!, contact.longitude!, - "Type: ${contact.typeLabel}\nPublic Key: ${contact.publicKeyHex}", + "Type: ${contact.typeLabelRaw}\nPublic Key: ${contact.publicKeyHex}", url, ); } @@ -110,7 +110,7 @@ class GpxExport { contact.name, contact.latitude ?? 0.0, contact.longitude ?? 0.0, - "Type: ${contact.typeLabel}\nPublic Key: ${contact.publicKeyHex}", + "Type: ${contact.typeLabelRaw}\nPublic Key: ${contact.publicKeyHex}", url, ); } diff --git a/lib/widgets/path_management_dialog.dart b/lib/widgets/path_management_dialog.dart index c63ce76e..a2122f46 100644 --- a/lib/widgets/path_management_dialog.dart +++ b/lib/widgets/path_management_dialog.dart @@ -9,6 +9,7 @@ import 'package:provider/provider.dart'; import '../connector/meshcore_connector.dart'; import '../l10n/l10n.dart'; import '../models/contact.dart'; +import '../l10n/contact_localization.dart'; import '../helpers/path_helper.dart'; import '../services/path_history_service.dart'; import '../helpers/snack_bar_builder.dart'; diff --git a/lib/widgets/path_selection_dialog.dart b/lib/widgets/path_selection_dialog.dart index e061e731..44ae58f9 100644 --- a/lib/widgets/path_selection_dialog.dart +++ b/lib/widgets/path_selection_dialog.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:meshcore_open/connector/meshcore_protocol.dart'; import '../l10n/l10n.dart'; import '../models/contact.dart'; +import '../l10n/contact_localization.dart'; import '../helpers/snack_bar_builder.dart'; class PathSelectionDialog extends StatefulWidget { @@ -311,7 +312,7 @@ class _PathSelectionDialogState extends State { style: const TextStyle(fontSize: 14), ), subtitle: Text( - '${contact.typeLabelLocalized(l10n)} • ${contact.publicKeyHex.substring(0, 2)}', + '${contact.typeLabel(l10n)} • ${contact.publicKeyHex.substring(0, 2)}', style: const TextStyle(fontSize: 10), ), trailing: isSelected diff --git a/lib/widgets/repeater_login_dialog.dart b/lib/widgets/repeater_login_dialog.dart index d2cfce09..0973faec 100644 --- a/lib/widgets/repeater_login_dialog.dart +++ b/lib/widgets/repeater_login_dialog.dart @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:provider/provider.dart'; import '../l10n/l10n.dart'; import '../models/contact.dart'; +import '../l10n/contact_localization.dart'; import '../services/storage_service.dart'; import '../connector/meshcore_connector.dart'; import '../connector/meshcore_protocol.dart'; diff --git a/lib/widgets/room_login_dialog.dart b/lib/widgets/room_login_dialog.dart index 1a5ff7fc..2641c023 100644 --- a/lib/widgets/room_login_dialog.dart +++ b/lib/widgets/room_login_dialog.dart @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:provider/provider.dart'; import '../l10n/l10n.dart'; import '../models/contact.dart'; +import '../l10n/contact_localization.dart'; import '../services/storage_service.dart'; import '../connector/meshcore_connector.dart'; import '../connector/meshcore_protocol.dart'; From f8d00caae08c31fd98ba91fdea69b10b1edb0e92 Mon Sep 17 00:00:00 2001 From: Serge Tarkovski Date: Sat, 25 Apr 2026 01:03:11 +0300 Subject: [PATCH 12/32] Validate locale override and use preferred locale list for fallback --- lib/screens/channel_chat_screen.dart | 13 +++++++++++-- lib/services/background_service.dart | 17 ++++++++++------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index 6bd89c2c..330e3f03 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -57,6 +57,10 @@ class _ChannelChatScreenState extends State { DateTime? _lastChannelSendAt; bool _channelSkipNextBottomSnap = false; + String? _cachedFormatLocale; + late DateFormat _hmFormat; + late DateFormat _mdFormat; + @override void initState() { super.initState(); @@ -1232,10 +1236,15 @@ class _ChannelChatScreenState extends State { final now = DateTime.now(); final diff = now.difference(time); final locale = Localizations.localeOf(context).toString(); - final hm = DateFormat.Hm(locale).format(time); + if (locale != _cachedFormatLocale) { + _cachedFormatLocale = locale; + _hmFormat = DateFormat.Hm(locale); + _mdFormat = DateFormat.Md(locale); + } + final hm = _hmFormat.format(time); if (diff.inDays > 0) { - return '${DateFormat.Md(locale).format(time)} $hm'; + return '${_mdFormat.format(time)} $hm'; } else { return hm; } diff --git a/lib/services/background_service.dart b/lib/services/background_service.dart index 45be09b8..336ecdd4 100644 --- a/lib/services/background_service.dart +++ b/lib/services/background_service.dart @@ -57,14 +57,17 @@ class BackgroundService { final supported = AppLocalizations.supportedLocales; final override = _languageOverrideProvider?.call(); if (override != null && override.isNotEmpty) { - return AppLocalizations.delegate.load(Locale(override)); + final overrideLocale = Locale(override); + final isSupported = supported.any( + (l) => l.languageCode == overrideLocale.languageCode, + ); + if (isSupported) { + return AppLocalizations.delegate.load(overrideLocale); + } } - final system = - WidgetsBinding.instance.platformDispatcher.locale; - final match = basicLocaleListResolution( - [system], - supported, - ); + final preferred = + WidgetsBinding.instance.platformDispatcher.locales; + final match = basicLocaleListResolution(preferred, supported); return AppLocalizations.delegate.load(match); } From 92d3009eb444a320f4394357c01b9367e8c61ca7 Mon Sep 17 00:00:00 2001 From: Serge Tarkovski Date: Sat, 25 Apr 2026 01:32:43 +0300 Subject: [PATCH 13/32] Fix swapped url/desc args in GPX export and add ContactLocalization unit tests --- lib/utils/gpx_export.dart | 6 +- test/l10n/contact_localization_test.dart | 105 +++++++++++++++++++++++ 2 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 test/l10n/contact_localization_test.dart diff --git a/lib/utils/gpx_export.dart b/lib/utils/gpx_export.dart index 1b82a72d..e3a42898 100644 --- a/lib/utils/gpx_export.dart +++ b/lib/utils/gpx_export.dart @@ -72,8 +72,8 @@ class GpxExport { contact.name, contact.latitude!, contact.longitude!, - "Type: ${contact.typeLabelRaw}\nPublic Key: ${contact.publicKeyHex}", url, + "Type: ${contact.typeLabelRaw}\nPublic Key: ${contact.publicKeyHex}", ); } } @@ -91,8 +91,8 @@ class GpxExport { contact.name, contact.latitude!, contact.longitude!, - "Type: ${contact.typeLabelRaw}\nPublic Key: ${contact.publicKeyHex}", url, + "Type: ${contact.typeLabelRaw}\nPublic Key: ${contact.publicKeyHex}", ); } } @@ -110,8 +110,8 @@ class GpxExport { contact.name, contact.latitude ?? 0.0, contact.longitude ?? 0.0, - "Type: ${contact.typeLabelRaw}\nPublic Key: ${contact.publicKeyHex}", url, + "Type: ${contact.typeLabelRaw}\nPublic Key: ${contact.publicKeyHex}", ); } } diff --git a/test/l10n/contact_localization_test.dart b/test/l10n/contact_localization_test.dart new file mode 100644 index 00000000..87df23a7 --- /dev/null +++ b/test/l10n/contact_localization_test.dart @@ -0,0 +1,105 @@ +import 'dart:typed_data'; + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:meshcore_open/connector/meshcore_protocol.dart'; +import 'package:meshcore_open/l10n/app_localizations.dart'; +import 'package:meshcore_open/l10n/contact_localization.dart'; +import 'package:meshcore_open/models/contact.dart'; + +Contact _contact({ + int type = advTypeChat, + int pathLength = 0, + int? pathOverride, +}) { + return Contact( + publicKey: Uint8List.fromList(List.generate(32, (i) => i + 1)), + name: 'Node', + type: type, + pathLength: pathLength, + path: Uint8List(0), + pathOverride: pathOverride, + lastSeen: DateTime.fromMillisecondsSinceEpoch(0), + ); +} + +void main() { + late AppLocalizations l10n; + + setUpAll(() async { + l10n = await AppLocalizations.delegate.load(const Locale('en')); + }); + + group('Contact.typeLabel', () { + test('chat', () { + expect(_contact(type: advTypeChat).typeLabel(l10n), 'Chat'); + }); + + test('repeater', () { + expect( + _contact(type: advTypeRepeater).typeLabel(l10n), + 'Repeater', + ); + }); + + test('room', () { + expect(_contact(type: advTypeRoom).typeLabel(l10n), 'Room'); + }); + + test('sensor', () { + expect(_contact(type: advTypeSensor).typeLabel(l10n), 'Sensor'); + }); + + test('unknown type falls back', () { + expect(_contact(type: 99).typeLabel(l10n), 'Unknown'); + }); + }); + + group('Contact.pathLabel (override)', () { + test('override < 0 -> flood (forced)', () { + expect( + _contact(pathOverride: -1).pathLabel(l10n), + 'Flood (forced)', + ); + }); + + test('override == 0 -> direct (forced)', () { + expect( + _contact(pathOverride: 0).pathLabel(l10n), + 'Direct (forced)', + ); + }); + + test('override > 0 -> hops (forced)', () { + expect( + _contact(pathOverride: 3).pathLabel(l10n), + '3 hops (forced)', + ); + }); + + test('override takes precedence over pathLength', () { + expect( + _contact(pathLength: 5, pathOverride: -1).pathLabel(l10n), + 'Flood (forced)', + ); + }); + }); + + group('Contact.pathLabel (auto)', () { + test('pathLength < 0 -> flood', () { + expect(_contact(pathLength: -1).pathLabel(l10n), 'Flood'); + }); + + test('pathLength == 0 -> direct', () { + expect(_contact(pathLength: 0).pathLabel(l10n), 'Direct'); + }); + + test('pathLength == 1 -> singular hop', () { + expect(_contact(pathLength: 1).pathLabel(l10n), '1 hop'); + }); + + test('pathLength > 1 -> plural hops', () { + expect(_contact(pathLength: 4).pathLabel(l10n), '4 hops'); + }); + }); +} From 4e368d562d3868fda7cd539d2609377ed2748f97 Mon Sep 17 00:00:00 2001 From: just-stuff-tm Date: Sat, 25 Apr 2026 08:56:28 -0400 Subject: [PATCH 14/32] add selectable LOS obstruction pinning for repeater placement --- .contextstream/config.json | 7 + lib/l10n/app_bg.arb | 42 +- lib/l10n/app_de.arb | 42 +- lib/l10n/app_en.arb | 40 ++ lib/l10n/app_es.arb | 42 +- lib/l10n/app_fr.arb | 42 +- lib/l10n/app_hu.arb | 42 +- lib/l10n/app_it.arb | 42 +- lib/l10n/app_ja.arb | 42 +- lib/l10n/app_ko.arb | 42 +- lib/l10n/app_localizations.dart | 41 ++ lib/l10n/app_localizations_bg.dart | 31 ++ lib/l10n/app_localizations_de.dart | 31 ++ lib/l10n/app_localizations_en.dart | 31 ++ lib/l10n/app_localizations_es.dart | 31 ++ lib/l10n/app_localizations_fr.dart | 31 ++ lib/l10n/app_localizations_hu.dart | 31 ++ lib/l10n/app_localizations_it.dart | 31 ++ lib/l10n/app_localizations_ja.dart | 30 ++ lib/l10n/app_localizations_ko.dart | 30 ++ lib/l10n/app_localizations_nl.dart | 31 ++ lib/l10n/app_localizations_pl.dart | 31 ++ lib/l10n/app_localizations_pt.dart | 31 ++ lib/l10n/app_localizations_ru.dart | 32 ++ lib/l10n/app_localizations_sk.dart | 31 ++ lib/l10n/app_localizations_sl.dart | 31 ++ lib/l10n/app_localizations_sv.dart | 31 ++ lib/l10n/app_localizations_uk.dart | 31 ++ lib/l10n/app_localizations_zh.dart | 30 ++ lib/l10n/app_nl.arb | 42 +- lib/l10n/app_pl.arb | 42 +- lib/l10n/app_pt.arb | 42 +- lib/l10n/app_ru.arb | 42 +- lib/l10n/app_sk.arb | 42 +- lib/l10n/app_sl.arb | 42 +- lib/l10n/app_sv.arb | 42 +- lib/l10n/app_uk.arb | 42 +- lib/l10n/app_zh.arb | 42 +- lib/screens/line_of_sight_map_screen.dart | 426 ++++++++++++++++-- lib/services/line_of_sight_service.dart | 49 +- test/services/line_of_sight_service_test.dart | 24 + 41 files changed, 1811 insertions(+), 46 deletions(-) create mode 100644 .contextstream/config.json diff --git a/.contextstream/config.json b/.contextstream/config.json new file mode 100644 index 00000000..412e2fb3 --- /dev/null +++ b/.contextstream/config.json @@ -0,0 +1,7 @@ +{ + "workspace_id": "56872cc9-7375-4423-92d5-6cdebf6564dc", + "workspace_name": ".contextstream-global", + "project_id": "5b1fe101-ea6b-4fe3-9de5-c87722c38084", + "project_name": "meshcore-open", + "associated_at": "2026-04-25T12:13:54.086Z" +} \ No newline at end of file diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 7260c253..b37529ba 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -2066,5 +2066,45 @@ "room_guest": "Информация за сървъра на стаята", "repeater_guest": "Информация за ретранслаторите", "repeater_guestTools": "Инструменти за гости", - "settings_multiAck": "Множество потвърждения" + "settings_multiAck": "Множество потвърждения", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Избрано препятствие", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losBlockedSpotsHint": "Кликнете върху блокираната точка, за да я отбележите на картата.", + "losBlockedSpotsTitle": "Ограничени места", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index bf8ad2eb..86846e98 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -2094,5 +2094,45 @@ "repeater_guestTools": "Gastwerkzeuge", "chat_sendMessage": "Nachricht senden", "room_guest": "Informationen zum Room Server", - "settings_multiAck": "Mehrere Bestätigungen" + "settings_multiAck": "Mehrere Bestätigungen", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsTitle": "Reservierte Plätze", + "losSelectedObstructionTitle": "Ausgewählte Behinderung", + "losBlockedSpotChip": "{distance} • {distanceUnit} • {obstruction} {heightUnit}", + "losBlockedSpotsHint": "Klicken Sie auf einen blockierten Bereich, um ihn auf der Karte hervorzuheben.", + "losSelectedObstructionDetails": "Blockiert durch {obstruction} in einer Höhe von {heightUnit}, {distanceFromA} von A und {distanceFromB} von B ({distanceUnit})." } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 606410cb..dfe81683 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1875,6 +1875,46 @@ "losLegendRadioHorizon": "Radio horizon", "losLegendLosBeam": "LOS beam", "losLegendTerrain": "Terrain", + "losBlockedSpotsTitle": "Blocked spots", + "losBlockedSpotsHint": "Tap a blocked spot to highlight it on the map.", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Selected obstruction", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).", + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, "losFrequencyLabel": "Frequency", "losFrequencyInfoTooltip": "View calculation details", "losFrequencyDialogTitle": "Radio horizon calculation", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index b0585680..04497cbf 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -2094,5 +2094,45 @@ "chat_sendMessage": "Enviar mensaje", "repeater_guestTools": "Herramientas para invitados", "room_guest": "Información del servidor", - "settings_multiAck": "Múltiples respuestas de confirmación" + "settings_multiAck": "Múltiples respuestas de confirmación", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsTitle": "Espacios ocupados", + "losBlockedSpotsHint": "Seleccione un punto bloqueado para resaltarlo en el mapa.", + "losSelectedObstructionTitle": "Obstrucción seleccionada", + "losSelectedObstructionDetails": "Bloqueado por {obstruction} a una altura de {heightUnit}, a {distanceFromA} metros de A y a {distanceFromB} metros de B ({distanceUnit}).", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index fa375a40..59ca46ac 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -2066,5 +2066,45 @@ "chat_sendMessage": "Envoyer un message", "room_guest": "Informations sur le serveur", "repeater_guest": "Informations sur les répéteurs", - "settings_multiAck": "Plusieurs accusés de réception" + "settings_multiAck": "Plusieurs accusés de réception", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Obstruction sélectionnée", + "losBlockedSpotsTitle": "Places occupés", + "losBlockedSpotsHint": "Sélectionnez un emplacement bloqué pour le mettre en évidence sur la carte.", + "losSelectedObstructionDetails": "Bloqué par {obstruction}, à une hauteur de {heightUnit}, à une distance de {distanceFromA} par rapport à A et à une distance de {distanceFromB} par rapport à B ({distanceUnit}).", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}" } diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index ae6acfb2..597faf62 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -2104,5 +2104,45 @@ "room_guest": "Szoba szerver információk", "chat_sendMessage": "Üzenet küldése", "repeater_guest": "Adatok a repeaterről", - "settings_multiAck": "Többszörös visszaigazolások" + "settings_multiAck": "Többszörös visszaigazolások", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Kiválasztott akadály", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losBlockedSpotsHint": "A blokkolt területet megjelölve, hogy a térképen kiemeljük.", + "losBlockedSpotsTitle": "Foglalhatatlan területek", + "losSelectedObstructionDetails": "Elakadt a {obstruction} miatt, {heightUnit} magasságban, {distanceFromA} méterrel A-tól és {distanceFromB} méterrel B-től ({distanceUnit})." } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 35001e61..9db535dd 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -2066,5 +2066,45 @@ "repeater_guestTools": "Strumenti per gli ospiti", "chat_sendMessage": "Invia messaggio", "room_guest": "Informazioni sul server", - "settings_multiAck": "ACK multipli" + "settings_multiAck": "ACK multipli", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Ostacolo selezionato", + "losBlockedSpotsHint": "Tocca un punto bloccato sulla mappa per evidenziarlo.", + "losBlockedSpotsTitle": "Posti occupati", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 7ac449db..4a45f4cd 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -2104,5 +2104,45 @@ "chat_sendMessage": "メッセージを送信する", "repeater_guest": "繰り返し送信に関する情報", "repeater_guestTools": "ゲスト向けツール", - "settings_multiAck": "複数のACK(応答)" + "settings_multiAck": "複数のACK(応答)", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsHint": "地図上で、特定された場所を強調するために、該当する場所をタップしてください。", + "losSelectedObstructionTitle": "選択された障害", + "losBlockedSpotsTitle": "利用できない場所", + "losSelectedObstructionDetails": "{obstruction} によって {heightUnit} の高さで、A地点から {distanceFromA}、B地点から {distanceFromB} ({distanceUnit}) の距離で塞がれています。", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}" } diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 54073db7..69fd99f9 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -2104,5 +2104,45 @@ "chat_sendMessage": "메시지를 보내기", "repeater_guest": "반복 장비 정보", "room_guest": "서버 정보", - "settings_multiAck": "다중 ACK" + "settings_multiAck": "다중 ACK", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsHint": "지도에서 특정 위치를 강조하려면 해당 위치를 클릭하세요.", + "losBlockedSpotsTitle": "차단된 공간", + "losSelectedObstructionTitle": "선택된 장애물", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 09075570..d090bc13 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -5640,6 +5640,47 @@ abstract class AppLocalizations { /// **'Terrain'** String get losLegendTerrain; + /// No description provided for @losBlockedSpotsTitle. + /// + /// In en, this message translates to: + /// **'Blocked spots'** + String get losBlockedSpotsTitle; + + /// No description provided for @losBlockedSpotsHint. + /// + /// In en, this message translates to: + /// **'Tap a blocked spot to highlight it on the map.'** + String get losBlockedSpotsHint; + + /// No description provided for @losBlockedSpotChip. + /// + /// In en, this message translates to: + /// **'{distance} {distanceUnit} • {obstruction} {heightUnit}'** + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ); + + /// No description provided for @losSelectedObstructionTitle. + /// + /// In en, this message translates to: + /// **'Selected obstruction'** + String get losSelectedObstructionTitle; + + /// No description provided for @losSelectedObstructionDetails. + /// + /// In en, this message translates to: + /// **'Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).'** + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ); + /// No description provided for @losFrequencyLabel. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 01973dff..8ee18b67 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -3236,6 +3236,37 @@ class AppLocalizationsBg extends AppLocalizations { @override String get losLegendTerrain => 'Терен'; + @override + String get losBlockedSpotsTitle => 'Ограничени места'; + + @override + String get losBlockedSpotsHint => + 'Кликнете върху блокираната точка, за да я отбележите на картата.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Избрано препятствие'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Честота'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 60cd3252..70d09b31 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -3241,6 +3241,37 @@ class AppLocalizationsDe extends AppLocalizations { @override String get losLegendTerrain => 'Gelände'; + @override + String get losBlockedSpotsTitle => 'Reservierte Plätze'; + + @override + String get losBlockedSpotsHint => + 'Klicken Sie auf einen blockierten Bereich, um ihn auf der Karte hervorzuheben.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance • $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Ausgewählte Behinderung'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blockiert durch $obstruction in einer Höhe von $heightUnit, $distanceFromA von A und $distanceFromB von B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frequenz'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 353e5bcb..eeb9091b 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -3180,6 +3180,37 @@ class AppLocalizationsEn extends AppLocalizations { @override String get losLegendTerrain => 'Terrain'; + @override + String get losBlockedSpotsTitle => 'Blocked spots'; + + @override + String get losBlockedSpotsHint => + 'Tap a blocked spot to highlight it on the map.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Selected obstruction'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frequency'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index d656ac49..824daaba 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -3235,6 +3235,37 @@ class AppLocalizationsEs extends AppLocalizations { @override String get losLegendTerrain => 'Terreno'; + @override + String get losBlockedSpotsTitle => 'Espacios ocupados'; + + @override + String get losBlockedSpotsHint => + 'Seleccione un punto bloqueado para resaltarlo en el mapa.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Obstrucción seleccionada'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Bloqueado por $obstruction a una altura de $heightUnit, a $distanceFromA metros de A y a $distanceFromB metros de B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frecuencia'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index acdc9454..1ad69397 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -3253,6 +3253,37 @@ class AppLocalizationsFr extends AppLocalizations { @override String get losLegendTerrain => 'Terrain'; + @override + String get losBlockedSpotsTitle => 'Places occupés'; + + @override + String get losBlockedSpotsHint => + 'Sélectionnez un emplacement bloqué pour le mettre en évidence sur la carte.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Obstruction sélectionnée'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Bloqué par $obstruction, à une hauteur de $heightUnit, à une distance de $distanceFromA par rapport à A et à une distance de $distanceFromB par rapport à B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Fréquence'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 24c9ac66..34c25bd5 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -3249,6 +3249,37 @@ class AppLocalizationsHu extends AppLocalizations { @override String get losLegendTerrain => 'Terület'; + @override + String get losBlockedSpotsTitle => 'Foglalhatatlan területek'; + + @override + String get losBlockedSpotsHint => + 'A blokkolt területet megjelölve, hogy a térképen kiemeljük.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Kiválasztott akadály'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Elakadt a $obstruction miatt, $heightUnit magasságban, $distanceFromA méterrel A-tól és $distanceFromB méterrel B-től ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Hatósság'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index abb1427d..21f9e52b 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -3238,6 +3238,37 @@ class AppLocalizationsIt extends AppLocalizations { @override String get losLegendTerrain => 'Terreno'; + @override + String get losBlockedSpotsTitle => 'Posti occupati'; + + @override + String get losBlockedSpotsHint => + 'Tocca un punto bloccato sulla mappa per evidenziarlo.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Ostacolo selezionato'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frequenza'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 60018262..d2d0511d 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -3090,6 +3090,36 @@ class AppLocalizationsJa extends AppLocalizations { @override String get losLegendTerrain => '地形'; + @override + String get losBlockedSpotsTitle => '利用できない場所'; + + @override + String get losBlockedSpotsHint => '地図上で、特定された場所を強調するために、該当する場所をタップしてください。'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => '選択された障害'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return '$obstruction によって $heightUnit の高さで、A地点から $distanceFromA、B地点から $distanceFromB ($distanceUnit) の距離で塞がれています。'; + } + @override String get losFrequencyLabel => '周波数'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 8f74af8f..b2bd9131 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -3090,6 +3090,36 @@ class AppLocalizationsKo extends AppLocalizations { @override String get losLegendTerrain => '지형'; + @override + String get losBlockedSpotsTitle => '차단된 공간'; + + @override + String get losBlockedSpotsHint => '지도에서 특정 위치를 강조하려면 해당 위치를 클릭하세요.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => '선택된 장애물'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => '빈도'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index d1ebea4a..fadc7b1e 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -3220,6 +3220,37 @@ class AppLocalizationsNl extends AppLocalizations { @override String get losLegendTerrain => 'Terrein'; + @override + String get losBlockedSpotsTitle => 'Geplande plaatsen'; + + @override + String get losBlockedSpotsHint => + 'Tik op een geblokkeerd gebied om het op de kaart te markeren.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Geselecteerde obstakel'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frequentie'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 918860ed..cdfef437 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -3245,6 +3245,37 @@ class AppLocalizationsPl extends AppLocalizations { @override String get losLegendTerrain => 'Teren'; + @override + String get losBlockedSpotsTitle => 'Zablokowane miejsca'; + + @override + String get losBlockedSpotsHint => + 'Kliknij zablokowane miejsce, aby je zaznaczyć na mapie.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Wybór przeszkody'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Częstotliwość'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 5584da93..2b7d1109 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -3234,6 +3234,37 @@ class AppLocalizationsPt extends AppLocalizations { @override String get losLegendTerrain => 'Terreno'; + @override + String get losBlockedSpotsTitle => 'Locais ocupados'; + + @override + String get losBlockedSpotsHint => + 'Toque em um ponto bloqueado para destacá-lo no mapa.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Obstrução selecionada'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frequência'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 4b7e3d44..76cf8e34 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -3239,6 +3239,38 @@ class AppLocalizationsRu extends AppLocalizations { @override String get losLegendTerrain => 'Рельеф'; + @override + String get losBlockedSpotsTitle => 'Зарезервированные места'; + + @override + String get losBlockedSpotsHint => + 'Щелкните по заблокированной области, чтобы выделить ее на карте.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => + 'Выбранный объект, препятствующий движению'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Частота'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index f855652d..10407978 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -3214,6 +3214,37 @@ class AppLocalizationsSk extends AppLocalizations { @override String get losLegendTerrain => 'Terén'; + @override + String get losBlockedSpotsTitle => 'Zablokované miesta'; + + @override + String get losBlockedSpotsHint => + 'Kliknite na zablokované miesto, aby ste ho zvýraznili na mape.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Vybraná prekážka'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frekvencia'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 2f3b30c0..157dce80 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -3215,6 +3215,37 @@ class AppLocalizationsSl extends AppLocalizations { @override String get losLegendTerrain => 'Teren'; + @override + String get losBlockedSpotsTitle => 'Zasedena parkirišča'; + + @override + String get losBlockedSpotsHint => + 'Dotaknite blokirano točko, da jo označite na zemljeplati.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Izbrano ovire'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frekvenca'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 00816f2a..f3259c5f 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -3197,6 +3197,37 @@ class AppLocalizationsSv extends AppLocalizations { @override String get losLegendTerrain => 'Terräng'; + @override + String get losBlockedSpotsTitle => 'Reserverade platser'; + + @override + String get losBlockedSpotsHint => + 'Klicka på en markerad plats för att framhäva den på kartan.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Vald hinder'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frekvens'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index f43fd53e..8f83263d 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -3242,6 +3242,37 @@ class AppLocalizationsUk extends AppLocalizations { @override String get losLegendTerrain => 'Рельєф'; + @override + String get losBlockedSpotsTitle => 'Заблоковані місця'; + + @override + String get losBlockedSpotsHint => + 'Натисніть на заблоковане місце, щоб виділити його на карті.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Вибраний об\'єкт перешкоди'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Частота'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 6181e8af..4cef6064 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -3014,6 +3014,36 @@ class AppLocalizationsZh extends AppLocalizations { @override String get losLegendTerrain => '地形'; + @override + String get losBlockedSpotsTitle => '被占用区域'; + + @override + String get losBlockedSpotsHint => '点击地图上的某个被遮盖的区域,以突出显示该区域。'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => '选择性阻碍'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => '频率'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index fa3eb37b..fa8bc465 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -2066,5 +2066,45 @@ "room_guest": "Informatie over de server", "chat_sendMessage": "Verzend bericht", "repeater_guest": "Informatie over herhalingsapparatuur", - "settings_multiAck": "Meerdere bevestigingen" + "settings_multiAck": "Meerdere bevestigingen", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Geselecteerde obstakel", + "losBlockedSpotsHint": "Tik op een geblokkeerd gebied om het op de kaart te markeren.", + "losBlockedSpotsTitle": "Geplande plaatsen", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 870be9e6..2c971bed 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -2104,5 +2104,45 @@ "repeater_guestTools": "Narzędzia dla gości", "repeater_guest": "Informacje dotyczące urządzenia powtarzającego", "room_guest": "Informacje o serwerze", - "settings_multiAck": "Wielokrotne potwierdzenia odbioru" + "settings_multiAck": "Wielokrotne potwierdzenia odbioru", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Wybór przeszkody", + "losBlockedSpotsTitle": "Zablokowane miejsca", + "losBlockedSpotsHint": "Kliknij zablokowane miejsce, aby je zaznaczyć na mapie.", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 634cf1c0..2b8efabe 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -2066,5 +2066,45 @@ "room_guest": "Informações do Servidor", "chat_sendMessage": "Enviar mensagem", "repeater_guest": "Informações sobre repetidores", - "repeater_guestTools": "Ferramentas para hóspedes" + "repeater_guestTools": "Ferramentas para hóspedes", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsTitle": "Locais ocupados", + "losBlockedSpotsHint": "Toque em um ponto bloqueado para destacá-lo no mapa.", + "losSelectedObstructionTitle": "Obstrução selecionada", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 1ad0f1b9..9ab27fec 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -1306,5 +1306,45 @@ "repeater_guest": "Информация о ретрансляторе", "room_guest": "Информация о сервере", "repeater_guestTools": "Инструменты для гостей", - "settings_multiAck": "Несколько подтверждений" + "settings_multiAck": "Несколько подтверждений", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsHint": "Щелкните по заблокированной области, чтобы выделить ее на карте.", + "losBlockedSpotsTitle": "Зарезервированные места", + "losSelectedObstructionTitle": "Выбранный объект, препятствующий движению", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 9cd6072c..daddb249 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -2066,5 +2066,45 @@ "chat_sendMessage": "Odoslať správu", "repeater_guest": "Informácie o opakovači", "room_guest": "Informácie o serveri", - "repeater_guestTools": "Nástroje pre hostí" + "repeater_guestTools": "Nástroje pre hostí", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsTitle": "Zablokované miesta", + "losBlockedSpotsHint": "Kliknite na zablokované miesto, aby ste ho zvýraznili na mape.", + "losSelectedObstructionTitle": "Vybraná prekážka", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index cace8c6b..5969b918 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -2066,5 +2066,45 @@ "chat_sendMessage": "Pošlji sporočilo", "room_guest": "Informacije o strežniku", "repeater_guestTools": "Naložila za goste", - "settings_multiAck": "Več potrdil" + "settings_multiAck": "Več potrdil", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsHint": "Dotaknite blokirano točko, da jo označite na zemljeplati.", + "losSelectedObstructionTitle": "Izbrano ovire", + "losBlockedSpotsTitle": "Zasedena parkirišča", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index aaa6ea75..5ed2561a 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -2066,5 +2066,45 @@ "chat_sendMessage": "Skicka meddelande", "repeater_guestTools": "Gästverktyg", "room_guest": "Information om servern", - "settings_multiAck": "Flera bekräftelser" + "settings_multiAck": "Flera bekräftelser", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losBlockedSpotsTitle": "Reserverade platser", + "losSelectedObstructionTitle": "Vald hinder", + "losBlockedSpotsHint": "Klicka på en markerad plats för att framhäva den på kartan.", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index 489b8a2e..1bf2b584 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -2066,5 +2066,45 @@ "repeater_guest": "Інформація про ретранслятор", "room_guest": "Інформація про сервер кімнати", "chat_sendMessage": "Надіслати повідомлення", - "settings_multiAck": "Багато підтверджень" + "settings_multiAck": "Багато підтверджень", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsHint": "Натисніть на заблоковане місце, щоб виділити його на карті.", + "losBlockedSpotsTitle": "Заблоковані місця", + "losSelectedObstructionTitle": "Вибраний об'єкт перешкоди", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9d6a8bfd..ee8285c0 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -2071,5 +2071,45 @@ "repeater_guestTools": "访客工具", "repeater_guest": "重复器信息", "chat_sendMessage": "发送消息", - "room_guest": "服务器信息" + "room_guest": "服务器信息", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsTitle": "被占用区域", + "losBlockedSpotsHint": "点击地图上的某个被遮盖的区域,以突出显示该区域。", + "losSelectedObstructionTitle": "选择性阻碍", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/screens/line_of_sight_map_screen.dart b/lib/screens/line_of_sight_map_screen.dart index ec8a391f..e88f4b9e 100644 --- a/lib/screens/line_of_sight_map_screen.dart +++ b/lib/screens/line_of_sight_map_screen.dart @@ -62,6 +62,7 @@ class _LineOfSightMapScreenState extends State { bool _loading = false; String? _error; LineOfSightPathResult? _result; + LineOfSightObstruction? _selectedObstruction; LineOfSightEndpoint? _start; LineOfSightEndpoint? _end; final List _customEndpoints = []; @@ -111,6 +112,7 @@ class _LineOfSightMapScreenState extends State { if (start == null || end == null) { setState(() { _result = null; + _selectedObstruction = null; _error = _errorSelectStartEnd; }); return; @@ -142,6 +144,7 @@ class _LineOfSightMapScreenState extends State { } setState(() { _result = result; + _selectedObstruction = _defaultObstructionFor(result); }); } catch (e) { if (!mounted) return; @@ -156,6 +159,7 @@ class _LineOfSightMapScreenState extends State { } setState(() { _result = null; + _selectedObstruction = null; _error = context.l10n.losRunFailed(e.toString()); }); } finally { @@ -184,6 +188,7 @@ class _LineOfSightMapScreenState extends State { void _selectFromMap(LineOfSightEndpoint endpoint) { setState(() { _result = null; + _selectedObstruction = null; _error = null; if (_start == null || (_start != null && _end != null)) { _start = endpoint; @@ -241,6 +246,7 @@ class _LineOfSightMapScreenState extends State { _start = null; _end = null; _result = null; + _selectedObstruction = null; _error = _errorSelectStartEnd; }); } @@ -251,6 +257,7 @@ class _LineOfSightMapScreenState extends State { if (identical(_start, endpoint)) _start = null; if (identical(_end, endpoint)) _end = null; _result = null; + _selectedObstruction = null; }); } @@ -377,7 +384,9 @@ class _LineOfSightMapScreenState extends State { ), if (_result != null && _result!.segments.isNotEmpty) PolylineLayer(polylines: _buildSegmentPolylines(_result!)), - MarkerLayer(markers: _buildMarkers(endpoints)), + MarkerLayer( + markers: _buildMarkers(endpoints, _primaryObstructions()), + ), ], ), if (_showHud) @@ -445,6 +454,8 @@ class _LineOfSightMapScreenState extends State { ); final displayFrequencyMHz = segment?.frequencyMHz ?? reportedFrequencyMHz; final kFactorUsed = segment?.usedKFactor; + final obstructions = + segment?.obstructions ?? const []; final endpoints = _visibleEndpoints(); final distanceUnit = isImperial ? 'mi' : 'km'; final heightUnit = isImperial ? 'ft' : 'm'; @@ -463,31 +474,7 @@ class _LineOfSightMapScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (segment != null) - SizedBox( - height: 160, - width: double.infinity, - child: CustomPaint( - painter: _LosProfilePainter( - samples: segment.samples, - distanceUnit: distanceUnit, - heightUnit: heightUnit, - badgeTextStyle: - Theme.of(context).textTheme.labelSmall?.copyWith( - color: Colors.white70, - fontSize: 10, - fontWeight: FontWeight.w600, - ) ?? - const TextStyle( - color: Colors.white70, - fontSize: 10, - fontWeight: FontWeight.w600, - ), - terrainLabel: context.l10n.losLegendTerrain, - losBeamLabel: context.l10n.losLegendLosBeam, - radioHorizonLabel: context.l10n.losLegendRadioHorizon, - ), - ), - ) + _buildProfileView(segment, distanceUnit, heightUnit, isImperial) else SizedBox( height: 44, @@ -519,6 +506,96 @@ class _LineOfSightMapScreenState extends State { fontWeight: FontWeight.w600, ), ), + if (obstructions.isNotEmpty) ...[ + const SizedBox(height: 8), + Text( + context.l10n.losBlockedSpotsTitle, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 4), + Text( + context.l10n.losBlockedSpotsHint, + style: TextStyle(fontSize: 11, color: Colors.grey[700]), + ), + const SizedBox(height: 6), + Wrap( + spacing: 6, + runSpacing: 6, + children: [ + for (final obstruction in obstructions) + ChoiceChip( + label: Text( + _obstructionChipLabel(obstruction, isImperial), + style: const TextStyle(fontSize: 11), + ), + selected: + _selectedObstruction?.sampleIndex == + obstruction.sampleIndex, + onSelected: (_) => _selectObstruction(obstruction), + ), + ], + ), + if (_selectedObstruction != null) ...[ + const SizedBox(height: 8), + DecoratedBox( + decoration: BoxDecoration( + color: Colors.orange.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: Colors.deepOrangeAccent.withValues(alpha: 0.45), + ), + ), + child: Padding( + padding: const EdgeInsets.all(10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.l10n.losSelectedObstructionTitle, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 4), + Text( + context.l10n.losSelectedObstructionDetails( + _formatHeightValue( + _selectedObstruction!.obstructionMeters, + isImperial, + ), + heightUnit, + _formatDistanceValue( + _selectedObstruction!.distanceMeters, + isImperial, + ), + distanceUnit, + _formatDistanceValue( + segment!.totalDistanceMeters - + _selectedObstruction!.distanceMeters, + isImperial, + ), + ), + style: const TextStyle(fontSize: 11), + ), + const SizedBox(height: 4), + Text( + '${_selectedObstruction!.point.latitude.toStringAsFixed(5)}, ' + '${_selectedObstruction!.point.longitude.toStringAsFixed(5)}', + style: TextStyle( + fontSize: 11, + color: Colors.grey[700], + ), + ), + ], + ), + ), + ), + ], + ], const SizedBox(height: 4), if (displayFrequencyMHz != null) Padding( @@ -605,6 +682,7 @@ class _LineOfSightMapScreenState extends State { _showDisplayNodes = value; _sanitizeSelection(); _result = null; + _selectedObstruction = null; }); }, ), @@ -655,6 +733,7 @@ class _LineOfSightMapScreenState extends State { setState(() { _start = value; _result = null; + _selectedObstruction = null; }); if (_start != null && _end != null) { _runLos(); @@ -670,6 +749,7 @@ class _LineOfSightMapScreenState extends State { setState(() { _end = value; _result = null; + _selectedObstruction = null; }); if (_start != null && _end != null) { _runLos(); @@ -769,6 +849,179 @@ class _LineOfSightMapScreenState extends State { return _result!.segments.first.result; } + List _primaryObstructions() { + return _primarySegmentResult()?.obstructions ?? const []; + } + + LineOfSightObstruction? _defaultObstructionFor( + LineOfSightPathResult? result, + ) { + if (result == null || result.segments.isEmpty) return null; + final obstructions = result.segments.first.result.obstructions; + if (obstructions.isEmpty) return null; + return obstructions.reduce( + (current, next) => + next.obstructionMeters > current.obstructionMeters ? next : current, + ); + } + + void _selectObstruction(LineOfSightObstruction obstruction) { + setState(() { + _selectedObstruction = obstruction; + }); + } + + String _formatDistanceValue(double meters, bool isImperial) { + final value = isImperial ? (meters / 1000.0) * _kmToMiles : meters / 1000.0; + return value.toStringAsFixed(2); + } + + String _formatHeightValue(double meters, bool isImperial) { + final value = isImperial ? meters * _metersToFeet : meters; + return value.toStringAsFixed(1); + } + + String _obstructionChipLabel( + LineOfSightObstruction obstruction, + bool isImperial, + ) { + final distanceUnit = isImperial ? 'mi' : 'km'; + final heightUnit = isImperial ? 'ft' : 'm'; + return context.l10n.losBlockedSpotChip( + _formatDistanceValue(obstruction.distanceMeters, isImperial), + distanceUnit, + _formatHeightValue(obstruction.obstructionMeters, isImperial), + heightUnit, + ); + } + + Widget _buildProfileView( + LineOfSightResult segment, + String distanceUnit, + String heightUnit, + bool isImperial, + ) { + if (segment.samples.length < 2) { + return SizedBox( + height: 160, + width: double.infinity, + child: CustomPaint( + painter: _LosProfilePainter( + samples: segment.samples, + distanceUnit: distanceUnit, + heightUnit: heightUnit, + badgeTextStyle: + Theme.of(context).textTheme.labelSmall?.copyWith( + color: Colors.white70, + fontSize: 10, + fontWeight: FontWeight.w600, + ) ?? + const TextStyle( + color: Colors.white70, + fontSize: 10, + fontWeight: FontWeight.w600, + ), + terrainLabel: context.l10n.losLegendTerrain, + losBeamLabel: context.l10n.losLegendLosBeam, + radioHorizonLabel: context.l10n.losLegendRadioHorizon, + selectedSampleIndex: _selectedObstruction?.sampleIndex, + ), + ), + ); + } + return SizedBox( + height: 160, + width: double.infinity, + child: LayoutBuilder( + builder: (context, constraints) { + final size = Size(constraints.maxWidth, 160); + final geometry = _LosProfileGeometry( + samples: segment.samples, + size: size, + ); + return Stack( + clipBehavior: Clip.none, + children: [ + Positioned.fill( + child: CustomPaint( + painter: _LosProfilePainter( + samples: segment.samples, + distanceUnit: distanceUnit, + heightUnit: heightUnit, + badgeTextStyle: + Theme.of(context).textTheme.labelSmall?.copyWith( + color: Colors.white70, + fontSize: 10, + fontWeight: FontWeight.w600, + ) ?? + const TextStyle( + color: Colors.white70, + fontSize: 10, + fontWeight: FontWeight.w600, + ), + terrainLabel: context.l10n.losLegendTerrain, + losBeamLabel: context.l10n.losLegendLosBeam, + radioHorizonLabel: context.l10n.losLegendRadioHorizon, + selectedSampleIndex: _selectedObstruction?.sampleIndex, + ), + ), + ), + for (final obstruction in segment.obstructions) + Builder( + builder: (context) { + final sample = segment.samples[obstruction.sampleIndex]; + final position = geometry.mapPoint( + sample.distanceMeters, + sample.terrainMeters, + ); + final isSelected = + _selectedObstruction?.sampleIndex == + obstruction.sampleIndex; + final markerSize = isSelected ? 18.0 : 14.0; + final left = (position.dx - markerSize / 2) + .clamp(0.0, math.max(0.0, size.width - markerSize)) + .toDouble(); + final top = (position.dy - markerSize / 2) + .clamp(0.0, math.max(0.0, size.height - markerSize)) + .toDouble(); + return Positioned( + left: left, + top: top, + child: Tooltip( + message: _obstructionChipLabel(obstruction, isImperial), + child: GestureDetector( + onTap: () => _selectObstruction(obstruction), + child: Container( + width: markerSize, + height: markerSize, + decoration: BoxDecoration( + color: isSelected + ? Colors.amberAccent + : Colors.deepOrangeAccent, + shape: BoxShape.circle, + border: Border.all( + color: isSelected + ? Colors.white + : Colors.black87, + width: isSelected ? 2 : 1.5, + ), + boxShadow: const [ + BoxShadow(color: Colors.black45, blurRadius: 4), + ], + ), + ), + ), + ), + ); + }, + ), + ], + ); + }, + ), + ); + } + String _profileStats(LineOfSightResult result, bool isImperial) { final distance = isImperial ? (result.totalDistanceMeters / 1000.0) * _kmToMiles @@ -820,8 +1073,51 @@ class _LineOfSightMapScreenState extends State { return polylines; } - List _buildMarkers(List endpoints) { + List _buildMarkers( + List endpoints, + List obstructions, + ) { return [ + for (final obstruction in obstructions) + Marker( + point: obstruction.point, + width: 52, + height: 52, + child: GestureDetector( + onTap: () => _selectObstruction(obstruction), + child: Center( + child: Container( + width: + _selectedObstruction?.sampleIndex == obstruction.sampleIndex + ? 36 + : 24, + height: + _selectedObstruction?.sampleIndex == obstruction.sampleIndex + ? 36 + : 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.transparent, + border: Border.all( + color: + _selectedObstruction?.sampleIndex == + obstruction.sampleIndex + ? Colors.amberAccent + : Colors.deepOrangeAccent, + width: + _selectedObstruction?.sampleIndex == + obstruction.sampleIndex + ? 4 + : 3, + ), + boxShadow: const [ + BoxShadow(color: Colors.black26, blurRadius: 6), + ], + ), + ), + ), + ), + ), for (final endpoint in endpoints) Marker( point: endpoint.point, @@ -1010,6 +1306,51 @@ class _LineOfSightMapScreenState extends State { } } +class _LosProfileGeometry { + static const horizontalPadding = 12.0; + static const verticalPadding = 12.0; + + final List samples; + final Size size; + late final double minY = samples + .map( + (s) => math.min( + math.min(s.terrainMeters, s.lineHeightMeters), + s.refractedHeightMeters, + ), + ) + .reduce(math.min); + late final double maxY = samples + .map( + (s) => math.max( + math.max(s.terrainMeters, s.lineHeightMeters), + s.refractedHeightMeters, + ), + ) + .reduce(math.max); + late final double ySpan = math.max(1.0, maxY - minY); + late final double maxDist = math.max(1.0, samples.last.distanceMeters); + late final double chartWidth = math.max( + 1.0, + size.width - horizontalPadding * 2, + ); + late final double chartHeight = math.max( + 1.0, + size.height - verticalPadding * 2, + ); + + _LosProfileGeometry({required this.samples, required this.size}); + + Offset mapPoint(double distanceMeters, double elevationMeters) { + final px = horizontalPadding + (distanceMeters / maxDist) * chartWidth; + final py = + size.height - + verticalPadding - + ((elevationMeters - minY) / ySpan) * chartHeight; + return Offset(px, py); + } +} + class _LosProfilePainter extends CustomPainter { final List samples; final String distanceUnit; @@ -1018,6 +1359,7 @@ class _LosProfilePainter extends CustomPainter { final String terrainLabel; final String losBeamLabel; final String radioHorizonLabel; + final int? selectedSampleIndex; const _LosProfilePainter({ required this.samples, @@ -1027,6 +1369,7 @@ class _LosProfilePainter extends CustomPainter { required this.terrainLabel, required this.losBeamLabel, required this.radioHorizonLabel, + this.selectedSampleIndex, }); @override @@ -1212,6 +1555,32 @@ class _LosProfilePainter extends CustomPainter { ..color = horizonFillColor ..style = PaintingStyle.fill, ); + + if (selectedSampleIndex != null && + selectedSampleIndex! >= 0 && + selectedSampleIndex! < samples.length) { + final selectedSample = samples[selectedSampleIndex!]; + final selectedPoint = mapPoint( + selectedSample.distanceMeters, + selectedSample.terrainMeters, + ); + canvas.drawLine( + Offset(selectedPoint.dx, verticalPadding), + Offset(selectedPoint.dx, size.height - verticalPadding), + Paint() + ..color = Colors.amberAccent.withValues(alpha: 0.7) + ..strokeWidth = 1.5, + ); + canvas.drawCircle(selectedPoint, 7, Paint()..color = Colors.amberAccent); + canvas.drawCircle( + selectedPoint, + 8.5, + Paint() + ..color = Colors.white + ..style = PaintingStyle.stroke + ..strokeWidth = 1.5, + ); + } } @override @@ -1222,7 +1591,8 @@ class _LosProfilePainter extends CustomPainter { oldDelegate.badgeTextStyle != badgeTextStyle || oldDelegate.terrainLabel != terrainLabel || oldDelegate.losBeamLabel != losBeamLabel || - oldDelegate.radioHorizonLabel != radioHorizonLabel; + oldDelegate.radioHorizonLabel != radioHorizonLabel || + oldDelegate.selectedSampleIndex != selectedSampleIndex; } void _drawUnitBadge(Canvas canvas, Size size) { diff --git a/lib/services/line_of_sight_service.dart b/lib/services/line_of_sight_service.dart index 7f056c81..e8d149ad 100644 --- a/lib/services/line_of_sight_service.dart +++ b/lib/services/line_of_sight_service.dart @@ -24,6 +24,26 @@ class LineOfSightSample { }); } +class LineOfSightObstruction { + final int sampleIndex; + final LatLng point; + final double distanceMeters; + final double clearanceMeters; + final double obstructionMeters; + final double terrainMeters; + final double lineHeightMeters; + + const LineOfSightObstruction({ + required this.sampleIndex, + required this.point, + required this.distanceMeters, + required this.clearanceMeters, + required this.obstructionMeters, + required this.terrainMeters, + required this.lineHeightMeters, + }); +} + class LineOfSightResult { final bool hasData; final bool isClear; @@ -31,6 +51,7 @@ class LineOfSightResult { final double maxObstructionMeters; final double? firstObstructionDistanceMeters; final List samples; + final List obstructions; final String? errorMessage; final double usedKFactor; final double? frequencyMHz; @@ -42,6 +63,7 @@ class LineOfSightResult { required this.maxObstructionMeters, required this.firstObstructionDistanceMeters, required this.samples, + required this.obstructions, required this.usedKFactor, this.frequencyMHz, this.errorMessage, @@ -56,7 +78,8 @@ class LineOfSightResult { isClear = false, maxObstructionMeters = 0, firstObstructionDistanceMeters = null, - samples = const []; + samples = const [], + obstructions = const []; } class LineOfSightPathSegment { @@ -191,6 +214,7 @@ class LineOfSightService { maxObstructionMeters: 0, firstObstructionDistanceMeters: null, samples: const [], + obstructions: const [], usedKFactor: kFactor, frequencyMHz: frequencyMHz, ); @@ -249,7 +273,9 @@ class LineOfSightService { var maxObstructionMeters = 0.0; double? firstObstructionDistanceMeters; final samples = []; + final obstructions = []; var isClear = true; + LineOfSightObstruction? clusterWorstObstruction; for (int i = 0; i < points.length; i++) { final fraction = points.length == 1 ? 0.0 : i / (points.length - 1); @@ -274,6 +300,23 @@ class LineOfSightService { maxObstructionMeters = obstruction; } firstObstructionDistanceMeters ??= distanceFromStart; + final candidate = LineOfSightObstruction( + sampleIndex: i, + point: points[i], + distanceMeters: distanceFromStart, + clearanceMeters: clearance, + obstructionMeters: obstruction, + terrainMeters: terrainHeight, + lineHeightMeters: lineHeight, + ); + if (clusterWorstObstruction == null || + candidate.obstructionMeters > + clusterWorstObstruction.obstructionMeters) { + clusterWorstObstruction = candidate; + } + } else if (clusterWorstObstruction != null) { + obstructions.add(clusterWorstObstruction); + clusterWorstObstruction = null; } samples.add( @@ -286,6 +329,9 @@ class LineOfSightService { ), ); } + if (clusterWorstObstruction != null) { + obstructions.add(clusterWorstObstruction); + } return LineOfSightResult( hasData: true, @@ -294,6 +340,7 @@ class LineOfSightService { maxObstructionMeters: maxObstructionMeters, firstObstructionDistanceMeters: firstObstructionDistanceMeters, samples: samples, + obstructions: obstructions, usedKFactor: kFactor, frequencyMHz: frequencyMHz, ); diff --git a/test/services/line_of_sight_service_test.dart b/test/services/line_of_sight_service_test.dart index 267a70ba..bd649f2a 100644 --- a/test/services/line_of_sight_service_test.dart +++ b/test/services/line_of_sight_service_test.dart @@ -23,6 +23,7 @@ void main() { expect(result.isClear, isTrue); expect(result.maxObstructionMeters, equals(0)); expect(result.firstObstructionDistanceMeters, isNull); + expect(result.obstructions, isEmpty); }); test( @@ -44,9 +45,32 @@ void main() { expect(result.isClear, isFalse); expect(result.maxObstructionMeters, greaterThan(0)); expect(result.firstObstructionDistanceMeters, isNotNull); + expect(result.obstructions, hasLength(1)); + expect(result.obstructions.single.sampleIndex, equals(10)); + expect(result.obstructions.single.point, equals(points[10])); }, ); + test('computeFromElevations groups contiguous blocked samples', () { + final points = makePoints(21); + final elevations = List.filled(points.length, 100); + elevations[9] = 220; + elevations[10] = 320; + elevations[11] = 240; + + final result = LineOfSightService.computeFromElevations( + points: points, + elevations: elevations, + startAntennaHeightMeters: 1.5, + endAntennaHeightMeters: 1.5, + kFactor: 4.0 / 3.0, + ); + + expect(result.obstructions, hasLength(1)); + expect(result.obstructions.single.sampleIndex, equals(10)); + expect(result.obstructions.single.obstructionMeters, greaterThan(0)); + }); + test('analyzePath summarizes clear and blocked segments', () async { final service = LineOfSightService( elevationDataSource: (points) async { From 46683e0ec297cc63459bd168577de3aafa5307a5 Mon Sep 17 00:00:00 2001 From: just_stuff_tm <133525672+just-stuff-tm@users.noreply.github.com> Date: Sat, 25 Apr 2026 09:04:00 -0400 Subject: [PATCH 15/32] Delete .contextstream/config.json --- .contextstream/config.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .contextstream/config.json diff --git a/.contextstream/config.json b/.contextstream/config.json deleted file mode 100644 index 412e2fb3..00000000 --- a/.contextstream/config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "workspace_id": "56872cc9-7375-4423-92d5-6cdebf6564dc", - "workspace_name": ".contextstream-global", - "project_id": "5b1fe101-ea6b-4fe3-9de5-c87722c38084", - "project_name": "meshcore-open", - "associated_at": "2026-04-25T12:13:54.086Z" -} \ No newline at end of file From 7f353490cf720b082ca386b62fe28a39c69b6096 Mon Sep 17 00:00:00 2001 From: just-stuff-tm Date: Sat, 25 Apr 2026 09:07:28 -0400 Subject: [PATCH 16/32] contextstream/ is added to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 88295e7c..fa4d28d6 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ keystore.properties # IDE .vscode/launch.json .vscode/settings.json +.contextstream/ # Cloudflare Wrangler .wrangler From fcf10b4a7317d638337d8f1d6004e27ff038ecae Mon Sep 17 00:00:00 2001 From: just-stuff-tm Date: Sat, 25 Apr 2026 09:11:49 -0400 Subject: [PATCH 17/32] added strings translategemma didnt translate to proper locallization --- untranslated.json | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/untranslated.json b/untranslated.json index 9e26dfee..6c2e251d 100644 --- a/untranslated.json +++ b/untranslated.json @@ -1 +1,38 @@ -{} \ No newline at end of file +{ + "bg": [ + "losSelectedObstructionDetails" + ], + "it": [ + "losSelectedObstructionDetails" + ], + "ko": [ + "losSelectedObstructionDetails" + ], + "nl": [ + "losSelectedObstructionDetails" + ], + "pl": [ + "losSelectedObstructionDetails" + ], + "pt": [ + "losSelectedObstructionDetails" + ], + "ru": [ + "losSelectedObstructionDetails" + ], + "sk": [ + "losSelectedObstructionDetails" + ], + "sl": [ + "losSelectedObstructionDetails" + ], + "sv": [ + "losSelectedObstructionDetails" + ], + "uk": [ + "losSelectedObstructionDetails" + ], + "zh": [ + "losSelectedObstructionDetails" + ] +} From 7db3a127230391cf21b1455f8e170f3d9121ea84 Mon Sep 17 00:00:00 2001 From: ericz Date: Sun, 26 Apr 2026 00:13:26 +0200 Subject: [PATCH 18/32] squashed commit for: deduplicate markers, allow for updates on position with same label with drawing line, get marker back after deletion in map through tabbing on icon in poi-message. --- lib/l10n/app_bg.arb | 1 + lib/l10n/app_de.arb | 1 + lib/l10n/app_en.arb | 1 + lib/l10n/app_es.arb | 1 + lib/l10n/app_fr.arb | 1 + lib/l10n/app_hu.arb | 1 + lib/l10n/app_it.arb | 1 + lib/l10n/app_ja.arb | 1 + lib/l10n/app_ko.arb | 1 + 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_hu.dart | 3 + lib/l10n/app_localizations_it.dart | 3 + lib/l10n/app_localizations_ja.dart | 3 + lib/l10n/app_localizations_ko.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 | 1 + lib/l10n/app_pl.arb | 1 + lib/l10n/app_pt.arb | 1 + lib/l10n/app_ru.arb | 1 + lib/l10n/app_sk.arb | 1 + lib/l10n/app_sl.arb | 1 + lib/l10n/app_sv.arb | 1 + lib/l10n/app_uk.arb | 1 + lib/l10n/app_zh.arb | 1 + lib/screens/channel_chat_screen.dart | 27 ++++- lib/screens/chat_screen.dart | 32 +++++- lib/screens/map_screen.dart | 152 +++++++++++++++++++++++---- 40 files changed, 259 insertions(+), 30 deletions(-) diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 7260c253..c4af0584 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Покажи споделени маркери", "map_lastSeenTime": "Последна видяна дата", "map_sharedPin": "Споделено копие", + "map_sharedAt": "Споделено", "map_joinRoom": "Присъедини се към стаята", "map_manageRepeater": "Управление на Повтарящ се Елемент", "mapCache_title": "Кеш на офлайн карти", diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index bf8ad2eb..7d9edd58 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Zeige gemeinsam genutzte Marker", "map_lastSeenTime": "Letzte Sichtung", "map_sharedPin": "Gemeinsames Passwort", + "map_sharedAt": "Geteilt", "map_joinRoom": "Beitreten Sie dem Raum", "map_manageRepeater": "Repeater verwalten", "mapCache_title": "Offline-Karten-Cache", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 606410cb..9fd22e91 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -896,6 +896,7 @@ "map_guessedLocation": "Guessed location", "map_lastSeenTime": "Last Seen Time", "map_sharedPin": "Shared pin", + "map_sharedAt": "Shared", "map_joinRoom": "Join Room", "map_manageRepeater": "Manage Repeater", "map_tapToAdd": "Tap on nodes to add them to the path.", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index b0585680..859b13b8 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Mostrar marcadores compartidos", "map_lastSeenTime": "Última vez que se vio", "map_sharedPin": "Pin compartido", + "map_sharedAt": "Compartido", "map_joinRoom": "Únete a la sala", "map_manageRepeater": "Gestionar Repetidor", "mapCache_title": "Caché de Mapa Offline", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index fa375a40..9fc51c81 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Afficher les marqueurs partagés", "map_lastSeenTime": "Dernière fois vu", "map_sharedPin": "Clé partagée", + "map_sharedAt": "Partagé", "map_joinRoom": "Rejoindre le room server", "map_manageRepeater": "Gérer le répéteur", "mapCache_title": "Cache de Carte Hors Ligne", diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index ae6acfb2..de00ac49 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -861,6 +861,7 @@ "map_guessedLocation": "Tippolt hely", "map_lastSeenTime": "Utoljára megjelent idő", "map_sharedPin": "Gemeinsames PIN-kód", + "map_sharedAt": "Megosztva", "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.", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 35001e61..0ea9ab03 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Mostra i segnaposto condivisi", "map_lastSeenTime": "Ultimo Tempo di Visualizzazione", "map_sharedPin": "Condividi PIN", + "map_sharedAt": "Condiviso", "map_joinRoom": "Unisciti alla stanza", "map_manageRepeater": "Gestisci Ripetitore", "mapCache_title": "Cache Mappa Offline", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 7ac449db..517ef5f3 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -861,6 +861,7 @@ "map_guessedLocation": "推測された場所", "map_lastSeenTime": "最後に確認された時間", "map_sharedPin": "共有パスワード", + "map_sharedAt": "共有済み", "map_joinRoom": "部屋に参加する", "map_manageRepeater": "リピーターの管理", "map_tapToAdd": "ノードをクリックして、パスに追加します。", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 54073db7..81b37f78 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -861,6 +861,7 @@ "map_guessedLocation": "추측된 위치", "map_lastSeenTime": "마지막으로 확인된 시간", "map_sharedPin": "공유 비밀번호", + "map_sharedAt": "공유됨", "map_joinRoom": "방에 참여", "map_manageRepeater": "리피터 관리", "map_tapToAdd": "노드에 클릭하여 경로에 추가합니다.", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 09075570..b8f35eab 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3130,6 +3130,12 @@ abstract class AppLocalizations { /// **'Shared pin'** String get map_sharedPin; + /// No description provided for @map_sharedAt. + /// + /// In en, this message translates to: + /// **'Shared'** + String get map_sharedAt; + /// No description provided for @map_joinRoom. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 01973dff..1ac0074d 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -1724,6 +1724,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get map_sharedPin => 'Споделено копие'; + @override + String get map_sharedAt => 'Споделено'; + @override String get map_joinRoom => 'Присъедини се към стаята'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 60cd3252..59542752 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1721,6 +1721,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get map_sharedPin => 'Gemeinsames Passwort'; + @override + String get map_sharedAt => 'Geteilt'; + @override String get map_joinRoom => 'Beitreten Sie dem Raum'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 353e5bcb..6611bd0c 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1690,6 +1690,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get map_sharedPin => 'Shared pin'; + @override + String get map_sharedAt => 'Shared'; + @override String get map_joinRoom => 'Join Room'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index d656ac49..c5029a76 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1720,6 +1720,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get map_sharedPin => 'Pin compartido'; + @override + String get map_sharedAt => 'Compartido'; + @override String get map_joinRoom => 'Únete a la sala'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index acdc9454..91d6836c 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1730,6 +1730,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get map_sharedPin => 'Clé partagée'; + @override + String get map_sharedAt => 'Partagé'; + @override String get map_joinRoom => 'Rejoindre le room server'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 24c9ac66..609d4e18 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -1733,6 +1733,9 @@ class AppLocalizationsHu extends AppLocalizations { @override String get map_sharedPin => 'Gemeinsames PIN-kód'; + @override + String get map_sharedAt => 'Megosztva'; + @override String get map_joinRoom => 'Csatlakozás a szobához'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index abb1427d..a906b7ff 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1721,6 +1721,9 @@ class AppLocalizationsIt extends AppLocalizations { @override String get map_sharedPin => 'Condividi PIN'; + @override + String get map_sharedAt => 'Condiviso'; + @override String get map_joinRoom => 'Unisciti alla stanza'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 60018262..a222cd5f 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -1648,6 +1648,9 @@ class AppLocalizationsJa extends AppLocalizations { @override String get map_sharedPin => '共有パスワード'; + @override + String get map_sharedAt => '共有済み'; + @override String get map_joinRoom => '部屋に参加する'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 8f74af8f..3a23f2c8 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -1644,6 +1644,9 @@ class AppLocalizationsKo extends AppLocalizations { @override String get map_sharedPin => '공유 비밀번호'; + @override + String get map_sharedAt => '공유됨'; + @override String get map_joinRoom => '방에 참여'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index d1ebea4a..9470ad92 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1709,6 +1709,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get map_sharedPin => 'Gedeelde pin'; + @override + String get map_sharedAt => 'Gedeeld'; + @override String get map_joinRoom => 'Kamer Toetreden'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 918860ed..223a2685 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1733,6 +1733,9 @@ class AppLocalizationsPl extends AppLocalizations { @override String get map_sharedPin => 'Udostępniona pinezka'; + @override + String get map_sharedAt => 'Udostępnione'; + @override String get map_joinRoom => 'Dołącz do pokoju'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 5584da93..18a6c694 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1721,6 +1721,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get map_sharedPin => 'Pin compartilhado'; + @override + String get map_sharedAt => 'Compartilhado'; + @override String get map_joinRoom => 'Junte-se à Sala'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 4b7e3d44..ba4970ff 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1724,6 +1724,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get map_sharedPin => 'Общая метка'; + @override + String get map_sharedAt => 'Поделено'; + @override String get map_joinRoom => 'Присоединиться к комнате'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index f855652d..f135fdd6 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -1710,6 +1710,9 @@ class AppLocalizationsSk extends AppLocalizations { @override String get map_sharedPin => 'Zdieľaný PIN'; + @override + String get map_sharedAt => 'Zdieľané'; + @override String get map_joinRoom => 'Pripojiť miestnosť'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 2f3b30c0..311946a9 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -1705,6 +1705,9 @@ class AppLocalizationsSl extends AppLocalizations { @override String get map_sharedPin => 'Deljeno naslovno geslo'; + @override + String get map_sharedAt => 'Deljeno'; + @override String get map_joinRoom => 'Pridružiti sobo'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 00816f2a..bb1cc1b0 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -1699,6 +1699,9 @@ class AppLocalizationsSv extends AppLocalizations { @override String get map_sharedPin => 'Delad PIN'; + @override + String get map_sharedAt => 'Delad'; + @override String get map_joinRoom => 'Gå med i rum'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index f43fd53e..c27b1d05 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -1719,6 +1719,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get map_sharedPin => 'Спільний пін'; + @override + String get map_sharedAt => 'Поділено'; + @override String get map_joinRoom => 'Приєднатися до кімнати'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 6181e8af..e415081e 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1616,6 +1616,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get map_sharedPin => '共享标记'; + @override + String get map_sharedAt => '已分享'; + @override String get map_joinRoom => '加入房间'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index fa3eb37b..932f9995 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Toon gedeelde markeringen", "map_lastSeenTime": "Laatste Bekeken Tijd", "map_sharedPin": "Gedeelde pin", + "map_sharedAt": "Gedeeld", "map_joinRoom": "Kamer Toetreden", "map_manageRepeater": "Beheer Repeater", "mapCache_title": "Offline Kaarten Cache", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 870be9e6..1509340a 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -692,6 +692,7 @@ "map_showSharedMarkers": "Pokaż udostępnione znaczniki.", "map_lastSeenTime": "Ostatni raz widziany", "map_sharedPin": "Udostępniona pinezka", + "map_sharedAt": "Udostępnione", "map_joinRoom": "Dołącz do pokoju", "map_manageRepeater": "Zarządzaj przekaźnikiem", "mapCache_title": "Pamięć podręczna map offline", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 634cf1c0..189f5b9b 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Mostrar marcadores compartilhados", "map_lastSeenTime": "Último Tempo de Visualização", "map_sharedPin": "Pin compartilhado", + "map_sharedAt": "Compartilhado", "map_joinRoom": "Junte-se à Sala", "map_manageRepeater": "Gerenciar Repetidor", "mapCache_title": "Cache de Mapa Offline", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 1ad0f1b9..fc7830b8 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -397,6 +397,7 @@ "map_showSharedMarkers": "Показывать общие метки", "map_lastSeenTime": "Время последнего появления", "map_sharedPin": "Общая метка", + "map_sharedAt": "Поделено", "map_joinRoom": "Присоединиться к комнате", "map_manageRepeater": "Управление репитером", "mapCache_title": "Кэш офлайн-карты", diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 9cd6072c..c6002ff5 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Zobraziť zdieľané značky", "map_lastSeenTime": "Posledný čas sledovania", "map_sharedPin": "Zdieľaný PIN", + "map_sharedAt": "Zdieľané", "map_joinRoom": "Pripojiť miestnosť", "map_manageRepeater": "Spravovať Opakovanie", "mapCache_title": "Offline Mapa Pamäť", diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index cace8c6b..4ad9d9e6 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Pokaži skupno označenja", "map_lastSeenTime": "Datum zadnjega vpogleda", "map_sharedPin": "Deljeno naslovno geslo", + "map_sharedAt": "Deljeno", "map_joinRoom": "Pridružiti sobo", "map_manageRepeater": "Upravljajte Ponovitve", "mapCache_title": "Omrezni predpomnilnik zemljeških zemljejevskih slik", diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index aaa6ea75..25a6dc26 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Visa delade markörer", "map_lastSeenTime": "Senaste Visats Tid", "map_sharedPin": "Delad PIN", + "map_sharedAt": "Delad", "map_joinRoom": "Gå med i rum", "map_manageRepeater": "Hantera Upprepare", "mapCache_title": "Offline Kartcache", diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index 489b8a2e..1ac0b48a 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -683,6 +683,7 @@ "map_showSharedMarkers": "Показувати спільні маркери", "map_lastSeenTime": "Час останньої активності", "map_sharedPin": "Спільний пін", + "map_sharedAt": "Поділено", "map_joinRoom": "Приєднатися до кімнати", "map_manageRepeater": "Керувати ретранслятором", "mapCache_title": "Офлайн-кеш карти", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9d6a8bfd..4eb539d6 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -709,6 +709,7 @@ "map_showSharedMarkers": "显示共享标记", "map_lastSeenTime": "最后在线时间", "map_sharedPin": "共享标记", + "map_sharedAt": "已分享", "map_joinRoom": "加入房间", "map_manageRepeater": "管理转发节点", "mapCache_title": "离线地图缓存", diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index b203cbbe..e89eb158 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -445,6 +445,7 @@ class _ChannelChatScreenState extends State { poi, isOutgoing, textScale, + message.senderName, trailing: (!enableTracing && isOutgoing) ? Padding( padding: const EdgeInsets.only(bottom: 2), @@ -815,21 +816,23 @@ class _ChannelChatScreenState extends State { _PoiInfo? _parsePoiMessage(String text) { final trimmed = text.trim(); final match = RegExp( - r'm:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|', + r'm:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|(.*)', ).firstMatch(trimmed); if (match == null) return null; final lat = double.tryParse(match.group(1) ?? ''); final lon = double.tryParse(match.group(2) ?? ''); if (lat == null || lon == null) return null; final label = match.group(3) ?? ''; - return _PoiInfo(lat: lat, lon: lon, label: label); + final flags = match.group(4) ?? ''; + return _PoiInfo(lat: lat, lon: lon, label: label, flags: flags); } Widget _buildPoiMessage( BuildContext context, _PoiInfo poi, bool isOutgoing, - double textScale, { + double textScale, + String senderName, { Widget? trailing, }) { final colorScheme = Theme.of(context).colorScheme; @@ -849,12 +852,22 @@ class _ChannelChatScreenState extends State { padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 32, minHeight: 32), onPressed: () { + final selfName = context.read().selfName ?? 'Me'; + final fromName = isOutgoing ? selfName : senderName; + final key = buildSharedMarkerKey( + sourceId: 'channel:${widget.channel.index}', + label: poi.label, + fromName: fromName, + flags: poi.flags, + isChannel: true, + ); Navigator.push( context, MaterialPageRoute( builder: (context) => MapScreen( highlightPosition: LatLng(poi.lat, poi.lon), highlightLabel: poi.label, + highlightMarkerKey: key, ), ), ); @@ -1512,6 +1525,12 @@ class _PoiInfo { final double lat; final double lon; final String label; + final String flags; - const _PoiInfo({required this.lat, required this.lon, required this.label}); + const _PoiInfo({ + required this.lat, + required this.lon, + required this.label, + required this.flags, + }); } diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index ffa8344b..129f6925 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -479,6 +479,7 @@ class _ChatScreenState extends State { senderName: resolvedContact.type == advTypeRoom ? "${contact.name} [$fourByteHex]" : contact.name, + sourceId: widget.contact.publicKeyHex, isRoomServer: resolvedContact.type == advTypeRoom, textScale: textScale, onTap: () => _openMessagePath(message, contact), @@ -1568,10 +1569,12 @@ class _MessageBubble extends StatelessWidget { final VoidCallback? onLongPress; final void Function(Message message, String emoji)? onRetryReaction; final double textScale; + final String sourceId; const _MessageBubble({ required this.message, required this.senderName, + required this.sourceId, required this.isRoomServer, required this.textScale, this.onTap, @@ -1678,6 +1681,7 @@ class _MessageBubble extends StatelessWidget { textColor, metaColor, textScale, + senderName, trailing: (!enableTracing && isOutgoing) ? Padding( padding: const EdgeInsets.only(bottom: 2), @@ -1862,14 +1866,15 @@ class _MessageBubble extends StatelessWidget { _PoiInfo? _parsePoiMessage(String text) { final trimmed = text.trim(); final match = RegExp( - r'^m:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|.*$', + r'^m:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|(.*)$', ).firstMatch(trimmed); if (match == null) return null; final lat = double.tryParse(match.group(1) ?? ''); final lon = double.tryParse(match.group(2) ?? ''); if (lat == null || lon == null) return null; final label = match.group(3) ?? ''; - return _PoiInfo(lat: lat, lon: lon, label: label); + final flags = match.group(4) ?? ''; + return _PoiInfo(lat: lat, lon: lon, label: label, flags: flags); } Widget _buildPoiMessage( @@ -1877,7 +1882,8 @@ class _MessageBubble extends StatelessWidget { _PoiInfo poi, Color textColor, Color metaColor, - double textScale, { + double textScale, + String senderName, { Widget? trailing, }) { return Row( @@ -1887,13 +1893,23 @@ class _MessageBubble extends StatelessWidget { icon: Icon(Icons.location_on_outlined, color: textColor), padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 32, minHeight: 32), - onPressed: () { + onPressed: () async { + final selfName = context.read().selfName ?? 'Me'; + final fromName = message.isOutgoing ? selfName : senderName; + final key = buildSharedMarkerKey( + sourceId: sourceId, + label: poi.label, + fromName: fromName, + flags: poi.flags, + isChannel: false, + ); Navigator.push( context, MaterialPageRoute( builder: (context) => MapScreen( highlightPosition: LatLng(poi.lat, poi.lon), highlightLabel: poi.label, + highlightMarkerKey: key, ), ), ); @@ -2079,6 +2095,12 @@ class _PoiInfo { final double lat; final double lon; final String label; + final String flags; - const _PoiInfo({required this.lat, required this.lon, required this.label}); + const _PoiInfo({ + required this.lat, + required this.lon, + required this.label, + required this.flags, + }); } diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 6a8acda7..1f8bce26 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -37,6 +37,7 @@ import 'line_of_sight_map_screen.dart'; class MapScreen extends StatefulWidget { final LatLng? highlightPosition; final String? highlightLabel; + final String? highlightMarkerKey; final double highlightZoom; final bool hideBackButton; @@ -44,6 +45,7 @@ class MapScreen extends StatefulWidget { super.key, this.highlightPosition, this.highlightLabel, + this.highlightMarkerKey, this.highlightZoom = 15.0, this.hideBackButton = false, }); @@ -94,6 +96,19 @@ class _MapScreenState extends State { _removedMarkerIds = ids; _removedMarkersLoaded = true; }); + // If this screen was opened to highlight a marker, and that marker + // was previously removed, re-enable it now that we've loaded the saved + // removed IDs. + if (widget.highlightMarkerKey != null && + _removedMarkerIds.contains(widget.highlightMarkerKey)) { + final updated = Set.from(_removedMarkerIds); + updated.remove(widget.highlightMarkerKey); + if (!mounted) return; + setState(() { + _removedMarkerIds = updated; + }); + await _markerService.saveRemovedIds(updated); + } } bool _checkLocationPlausibility(double lat, double lon) { @@ -229,6 +244,24 @@ class _MapScreenState extends State { : [], ); + // Collect polylines for shared markers' history with dashed lines + final List sharedMarkerPolylines = []; + for (final marker in sharedMarkers) { + if (marker.history.isNotEmpty) { + final points = List.from(marker.history); + points.add(marker.position); + sharedMarkerPolylines.add( + Polyline( + points: points, + color: marker.isChannel + ? (marker.isPublicChannel ? Colors.orange : Colors.purple) + : Colors.blue, + strokeWidth: 3, + ), + ); + } + } + // Calculate center and zoom of all nodes, or default to (0, 0) LatLng center = const LatLng(0, 0); double initialZoom = 10.0; @@ -475,6 +508,8 @@ class _MapScreenState extends State { ), if (_polylines.isNotEmpty && _isBuildingPathTrace) PolylineLayer(polylines: _polylines), + if (sharedMarkerPolylines.isNotEmpty) + PolylineLayer(polylines: sharedMarkerPolylines), MarkerLayer( markers: [ if (highlightPosition != null) @@ -1239,28 +1274,37 @@ class _MapScreenState extends State { } List<_SharedMarker> _collectSharedMarkers(MeshCoreConnector connector) { - final markers = <_SharedMarker>[]; + // Build a _SharedMarker per message (history empty), grouped by dedupe key. + // Afterwards pick the latest per key and fill its history from older ones. + final updatesByKey = >{}; final selfName = connector.selfName ?? 'Me'; + void addUpdate(_SharedMarker update) { + (updatesByKey[update.id] ??= <_SharedMarker>[]).add(update); + } + for (final contact in connector.contacts) { final messages = connector.getMessages(contact); for (final message in messages) { final payload = _parseMarkerText(message.text); if (payload == null) continue; final fromName = message.isOutgoing ? selfName : contact.name; - final id = _buildMarkerId( + final key = buildSharedMarkerKey( sourceId: contact.publicKeyHex, - timestamp: message.timestamp, - text: message.text, + label: payload.label, + fromName: fromName, + flags: payload.flags, + isChannel: false, ); - markers.add( + addUpdate( _SharedMarker( - id: id, + id: key, position: payload.position, label: payload.label, flags: payload.flags, fromName: fromName, sourceLabel: contact.name, + timestamp: message.timestamp, isChannel: false, isPublicChannel: false, ), @@ -1274,14 +1318,16 @@ class _MapScreenState extends State { for (final message in messages) { final payload = _parseMarkerText(message.text); if (payload == null) continue; - final id = _buildMarkerId( + final key = buildSharedMarkerKey( sourceId: 'channel:${channel.index}', - timestamp: message.timestamp, - text: message.text, + label: payload.label, + fromName: message.senderName, + flags: payload.flags, + isChannel: true, ); - markers.add( + addUpdate( _SharedMarker( - id: id, + id: key, position: payload.position, label: payload.label, flags: payload.flags, @@ -1289,6 +1335,7 @@ class _MapScreenState extends State { sourceLabel: channel.name.isEmpty ? 'Channel ${channel.index}' : channel.name, + timestamp: message.timestamp, isChannel: true, isPublicChannel: isPublic, ), @@ -1296,6 +1343,24 @@ class _MapScreenState extends State { } } + final markers = <_SharedMarker>[]; + updatesByKey.forEach((_, updates) { + updates.sort((a, b) => a.timestamp.compareTo(b.timestamp)); + final latest = updates.last; + // History: older positions, drop consecutive duplicates at same position. + final history = []; + for (var i = 0; i < updates.length - 1; i++) { + final p = updates[i].position; + if (history.isEmpty || + history.last.latitude != p.latitude || + history.last.longitude != p.longitude) { + history.add(p); + } + } + markers.add(latest.copyWithHistory(history)); + }); + + markers.sort((a, b) => b.timestamp.compareTo(a.timestamp)); return markers; } @@ -1320,14 +1385,6 @@ class _MapScreenState extends State { ); } - String _buildMarkerId({ - required String sourceId, - required DateTime timestamp, - required String text, - }) { - return '$sourceId|${timestamp.millisecondsSinceEpoch}|$text'; - } - Marker _buildSharedMarker(_SharedMarker marker) { final markerColor = marker.isChannel ? (marker.isPublicChannel ? Colors.orange : Colors.purple) @@ -1337,7 +1394,15 @@ class _MapScreenState extends State { width: 60, height: 60, child: GestureDetector( - onTap: () => _showMarkerInfo(marker), + onTap: () async { + if (_removedMarkerIds.contains(marker.id)) { + setState(() { + _removedMarkerIds.remove(marker.id); + }); + await _markerService.saveRemovedIds(_removedMarkerIds); + } + _showMarkerInfo(marker); + }, child: Column( children: [ Container( @@ -1542,13 +1607,19 @@ class _MapScreenState extends State { showDialog( context: context, builder: (dialogContext) => AlertDialog( - title: Text(marker.label), + title: Text( + marker.label.isEmpty ? context.l10n.map_sharedPin : marker.label, + ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildInfoRow(context.l10n.map_from, marker.fromName), _buildInfoRow(context.l10n.map_source, marker.sourceLabel), + _buildInfoRow( + context.l10n.map_sharedAt, + _formatLastSeen(marker.timestamp), + ), _buildInfoRow( 'Location', '${marker.position.latitude.toStringAsFixed(6)}, ${marker.position.longitude.toStringAsFixed(6)}', @@ -1715,6 +1786,10 @@ class _MapScreenState extends State { String defaultLabel, ) async { final controller = TextEditingController(text: defaultLabel); + controller.selection = TextSelection( + baseOffset: 0, + extentOffset: controller.text.length, + ); return showDialog( context: context, builder: (dialogContext) => AlertDialog( @@ -2322,6 +2397,22 @@ class _MarkerPayload { }); } +/// Build a normalized dedupe key for shared markers. +/// Keeps the same algorithm previously present in both chat and map screens. +String buildSharedMarkerKey({ + required String sourceId, + required String label, + required String fromName, + required String flags, + required bool isChannel, +}) { + final normalizedLabel = label.trim().toLowerCase(); + final normalizedFrom = fromName.trim().toLowerCase(); + final normalizedFlags = flags.trim().toLowerCase(); + final scope = isChannel ? 'ch' : 'dm'; + return '$scope|$sourceId|$normalizedFrom|$normalizedLabel|$normalizedFlags'; +} + class _SharedMarker { final String id; final LatLng position; @@ -2329,8 +2420,10 @@ class _SharedMarker { final String flags; final String fromName; final String sourceLabel; + final DateTime timestamp; final bool isChannel; final bool isPublicChannel; + final List history; _SharedMarker({ required this.id, @@ -2339,7 +2432,24 @@ class _SharedMarker { required this.flags, required this.fromName, required this.sourceLabel, + required this.timestamp, required this.isChannel, required this.isPublicChannel, + this.history = const [], }); + + _SharedMarker copyWithHistory(List newHistory) { + return _SharedMarker( + id: id, + position: position, + label: label, + flags: flags, + fromName: fromName, + sourceLabel: sourceLabel, + timestamp: timestamp, + isChannel: isChannel, + isPublicChannel: isPublicChannel, + history: newHistory, + ); + } } From 94d9afe8b1d1ba6f7b3773c0cbf48e0f680f3084 Mon Sep 17 00:00:00 2001 From: ericz Date: Sun, 26 Apr 2026 01:23:33 +0200 Subject: [PATCH 19/32] consolidate parsing in single parseMarkerText in map_screen.dart --- lib/screens/channel_chat_screen.dart | 37 +++---------------- lib/screens/chat_screen.dart | 35 ++---------------- lib/screens/map_screen.dart | 53 ++++++++++++++-------------- 3 files changed, 33 insertions(+), 92 deletions(-) diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index e89eb158..f7821a14 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -4,7 +4,6 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; -import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; import '../connector/meshcore_connector.dart'; @@ -357,7 +356,7 @@ class _ChannelChatScreenState extends State { final enableTracing = settingsService.settings.enableMessageTracing; final isOutgoing = message.isOutgoing; final gifId = GifHelper.parseGif(message.text); - final poi = _parsePoiMessage(message.text); + final poi = parseMarkerText(message.text); final translatedDisplayText = message.translatedText != null && message.translatedText!.trim().isNotEmpty @@ -702,7 +701,7 @@ class _ChannelChatScreenState extends State { final previewTextColor = colorScheme.onSurface.withValues(alpha: 0.7); final gifId = GifHelper.parseGif(replyText); - final poi = _parsePoiMessage(replyText); + final poi = parseMarkerText(replyText); Widget contentPreview; if (gifId != null) { @@ -813,23 +812,9 @@ class _ChannelChatScreenState extends State { ); } - _PoiInfo? _parsePoiMessage(String text) { - final trimmed = text.trim(); - final match = RegExp( - r'm:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|(.*)', - ).firstMatch(trimmed); - if (match == null) return null; - final lat = double.tryParse(match.group(1) ?? ''); - final lon = double.tryParse(match.group(2) ?? ''); - if (lat == null || lon == null) return null; - final label = match.group(3) ?? ''; - final flags = match.group(4) ?? ''; - return _PoiInfo(lat: lat, lon: lon, label: label, flags: flags); - } - Widget _buildPoiMessage( BuildContext context, - _PoiInfo poi, + MarkerPayload poi, bool isOutgoing, double textScale, String senderName, { @@ -865,7 +850,7 @@ class _ChannelChatScreenState extends State { context, MaterialPageRoute( builder: (context) => MapScreen( - highlightPosition: LatLng(poi.lat, poi.lon), + highlightPosition: poi.position, highlightLabel: poi.label, highlightMarkerKey: key, ), @@ -1520,17 +1505,3 @@ class _SwipeReplyBubbleState extends State<_SwipeReplyBubble> { ); } } - -class _PoiInfo { - final double lat; - final double lon; - final String label; - final String flags; - - const _PoiInfo({ - required this.lat, - required this.lon, - required this.label, - required this.flags, - }); -} diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index 129f6925..fe7b0086 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -9,7 +9,6 @@ import 'package:meshcore_open/screens/path_trace_map.dart'; import 'package:provider/provider.dart'; import '../utils/platform_info.dart'; -import 'package:latlong2/latlong.dart'; import '../connector/meshcore_connector.dart'; import '../connector/meshcore_protocol.dart'; @@ -1589,7 +1588,7 @@ class _MessageBubble extends StatelessWidget { final isOutgoing = message.isOutgoing; final colorScheme = Theme.of(context).colorScheme; final gifId = GifHelper.parseGif(message.text); - final poi = _parsePoiMessage(message.text); + final poi = parseMarkerText(message.text); final isFailed = message.status == MessageStatus.failed; final bubbleColor = isFailed ? colorScheme.errorContainer @@ -1863,23 +1862,9 @@ class _MessageBubble extends StatelessWidget { ); } - _PoiInfo? _parsePoiMessage(String text) { - final trimmed = text.trim(); - final match = RegExp( - r'^m:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|(.*)$', - ).firstMatch(trimmed); - if (match == null) return null; - final lat = double.tryParse(match.group(1) ?? ''); - final lon = double.tryParse(match.group(2) ?? ''); - if (lat == null || lon == null) return null; - final label = match.group(3) ?? ''; - final flags = match.group(4) ?? ''; - return _PoiInfo(lat: lat, lon: lon, label: label, flags: flags); - } - Widget _buildPoiMessage( BuildContext context, - _PoiInfo poi, + MarkerPayload poi, Color textColor, Color metaColor, double textScale, @@ -1907,7 +1892,7 @@ class _MessageBubble extends StatelessWidget { context, MaterialPageRoute( builder: (context) => MapScreen( - highlightPosition: LatLng(poi.lat, poi.lon), + highlightPosition: poi.position, highlightLabel: poi.label, highlightMarkerKey: key, ), @@ -2090,17 +2075,3 @@ class _MessageBubble extends StatelessWidget { return '$hour:$minute'; } } - -class _PoiInfo { - final double lat; - final double lon; - final String label; - final String flags; - - const _PoiInfo({ - required this.lat, - required this.lon, - required this.label, - required this.flags, - }); -} diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 1f8bce26..fb48f838 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -1286,7 +1286,7 @@ class _MapScreenState extends State { for (final contact in connector.contacts) { final messages = connector.getMessages(contact); for (final message in messages) { - final payload = _parseMarkerText(message.text); + final payload = parseMarkerText(message.text); if (payload == null) continue; final fromName = message.isOutgoing ? selfName : contact.name; final key = buildSharedMarkerKey( @@ -1300,7 +1300,9 @@ class _MapScreenState extends State { _SharedMarker( id: key, position: payload.position, - label: payload.label, + label: payload.label.isEmpty + ? context.l10n.map_sharedPin + : payload.label, flags: payload.flags, fromName: fromName, sourceLabel: contact.name, @@ -1316,7 +1318,7 @@ class _MapScreenState extends State { final isPublic = _isPublicChannel(channel); final messages = connector.getChannelMessages(channel); for (final message in messages) { - final payload = _parseMarkerText(message.text); + final payload = parseMarkerText(message.text); if (payload == null) continue; final key = buildSharedMarkerKey( sourceId: 'channel:${channel.index}', @@ -1329,7 +1331,9 @@ class _MapScreenState extends State { _SharedMarker( id: key, position: payload.position, - label: payload.label, + label: payload.label.isEmpty + ? context.l10n.map_sharedPin + : payload.label, flags: payload.flags, fromName: message.senderName, sourceLabel: channel.name.isEmpty @@ -1364,27 +1368,6 @@ class _MapScreenState extends State { return markers; } - _MarkerPayload? _parseMarkerText(String text) { - final trimmed = text.trim(); - if (!trimmed.startsWith('m:')) return null; - - final parts = trimmed.substring(2).split('|'); - if (parts.isEmpty) return null; - final coords = parts[0].split(','); - if (coords.length != 2) return null; - final lat = double.tryParse(coords[0].trim()); - final lon = double.tryParse(coords[1].trim()); - if (lat == null || lon == null) return null; - - final label = parts.length > 1 ? parts[1].trim() : ''; - final flags = parts.length > 2 ? parts[2].trim() : ''; - return _MarkerPayload( - position: LatLng(lat, lon), - label: label.isEmpty ? context.l10n.map_sharedPin : label, - flags: flags, - ); - } - Marker _buildSharedMarker(_SharedMarker marker) { final markerColor = marker.isChannel ? (marker.isPublicChannel ? Colors.orange : Colors.purple) @@ -2385,18 +2368,34 @@ class _GuessedLocation { }); } -class _MarkerPayload { +class MarkerPayload { final LatLng position; final String label; final String flags; - _MarkerPayload({ + MarkerPayload({ required this.position, required this.label, required this.flags, }); } +/// Parse a shared marker text message of the form +/// `m:,|