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/51] 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 e5b5f67..ebf8264 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 2aee61c..9fc33eb 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 0000000..5832ae8 --- /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/51] 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 b432277..fceee15 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 c6acdd2..4188ec6 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 ebf8264..dfcdcdb 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 9fc33eb..435e6c5 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 5832ae8..bfb5fcc 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/51] 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 bfb5fcc..ca43252 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/51] 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 dfcdcdb..b203cbb 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 435e6c5..ffa8344 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/51] 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 fceee15..be0d893 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 396d78b..dcb6948 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 2699f93..a126ade 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 89ca027..3f6f171 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 0e2e3ad..7883d88 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 609d0c8dbce3356d0b5164946119a098720eecf5 Mon Sep 17 00:00:00 2001 From: HDDen <62592944+HDDen@users.noreply.github.com> Date: Wed, 22 Apr 2026 03:59:43 +0300 Subject: [PATCH 06/51] Added Cyr2Lat compression by replacing 2-byte cyrillic chars by 1-byte latin --- lib/connector/meshcore_connector.dart | 67 +++++++++++++- lib/helpers/cyr2lat.dart | 63 +++++++++++++ lib/l10n/app_bg.arb | 10 +++ lib/l10n/app_de.arb | 10 +++ lib/l10n/app_en.arb | 15 ++++ lib/l10n/app_es.arb | 10 +++ lib/l10n/app_fr.arb | 10 +++ lib/l10n/app_hu.arb | 10 +++ lib/l10n/app_it.arb | 10 +++ lib/l10n/app_ja.arb | 10 +++ lib/l10n/app_ko.arb | 10 +++ lib/l10n/app_localizations.dart | 60 +++++++++++++ lib/l10n/app_localizations_bg.dart | 37 ++++++++ lib/l10n/app_localizations_de.dart | 36 ++++++++ lib/l10n/app_localizations_en.dart | 36 ++++++++ lib/l10n/app_localizations_es.dart | 37 ++++++++ lib/l10n/app_localizations_fr.dart | 38 ++++++++ lib/l10n/app_localizations_hu.dart | 36 ++++++++ lib/l10n/app_localizations_it.dart | 38 ++++++++ lib/l10n/app_localizations_ja.dart | 33 +++++++ lib/l10n/app_localizations_ko.dart | 33 +++++++ lib/l10n/app_localizations_nl.dart | 37 ++++++++ lib/l10n/app_localizations_pl.dart | 37 ++++++++ lib/l10n/app_localizations_pt.dart | 37 ++++++++ lib/l10n/app_localizations_ru.dart | 35 ++++++++ lib/l10n/app_localizations_sk.dart | 37 ++++++++ lib/l10n/app_localizations_sl.dart | 36 ++++++++ lib/l10n/app_localizations_sv.dart | 36 ++++++++ lib/l10n/app_localizations_uk.dart | 35 ++++++++ lib/l10n/app_localizations_zh.dart | 32 +++++++ lib/l10n/app_nl.arb | 10 +++ lib/l10n/app_pl.arb | 10 +++ lib/l10n/app_pt.arb | 10 +++ lib/l10n/app_ru.arb | 10 +++ lib/l10n/app_sk.arb | 10 +++ lib/l10n/app_sl.arb | 10 +++ lib/l10n/app_sv.arb | 10 +++ lib/l10n/app_uk.arb | 10 +++ lib/l10n/app_zh.arb | 10 +++ lib/models/app_settings.dart | 38 +++++++- lib/screens/app_settings_screen.dart | 113 ++++++++++++++++++++++++ lib/screens/channel_chat_screen.dart | 17 +++- lib/screens/channels_screen.dart | 26 +++++- lib/screens/chat_screen.dart | 60 ++++++++++++- lib/services/app_settings_service.dart | 7 ++ lib/storage/channel_settings_store.dart | 41 ++++++++- lib/storage/contact_settings_store.dart | 39 ++++++++ 47 files changed, 1350 insertions(+), 12 deletions(-) create mode 100644 lib/helpers/cyr2lat.dart diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index fceee15..6e3a1b9 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -16,6 +16,7 @@ import '../models/message.dart'; import '../models/path_selection.dart'; import '../models/translation_support.dart'; import '../helpers/reaction_helper.dart'; +import '../helpers/cyr2lat.dart'; import '../helpers/smaz.dart'; import '../services/app_debug_log_service.dart'; import '../services/ble_debug_log_service.dart'; @@ -283,9 +284,11 @@ class MeshCoreConnector extends ChangeNotifier { final UnreadStore _unreadStore = UnreadStore(); List _cachedChannels = []; final Map _channelSmazEnabled = {}; + final Map _channelCyr2LatEnabled = {}; bool _lastSentWasCliCommand = false; // Track if last sent message was a CLI command final Map _contactSmazEnabled = {}; + final Map _contactCyr2LatEnabled = {}; final Set _knownContactKeys = {}; final Map _contactUnreadCount = {}; final Map _repeaterBatterySnapshots = {}; @@ -607,6 +610,18 @@ class MeshCoreConnector extends ChangeNotifier { _ensureContactSmazSettingLoaded(contactKeyHex); } + bool isChannelCyr2LatEnabled(int channelIndex) { + return _channelCyr2LatEnabled[channelIndex] ?? false; + } + + bool isContactCyr2LatEnabled(String contactKeyHex) { + return _contactCyr2LatEnabled[contactKeyHex] ?? false; + } + + void ensureContactCyr2LatSettingLoaded(String contactKeyHex) { + _ensureContactCyr2LatSettingLoaded(contactKeyHex); + } + Future loadUnreadState() async { _contactUnreadCount ..clear() @@ -682,6 +697,10 @@ class MeshCoreConnector extends ChangeNotifier { Future setChannelSmazEnabled(int channelIndex, bool enabled) async { _channelSmazEnabled[channelIndex] = enabled; + if (enabled) { + _channelCyr2LatEnabled[channelIndex] = false; + await _channelSettingsStore.saveCyr2LatEnabled(channelIndex, false); + } await _channelSettingsStore.saveSmazEnabled(channelIndex, enabled); notifyListeners(); } @@ -692,6 +711,25 @@ class MeshCoreConnector extends ChangeNotifier { notifyListeners(); } + Future setChannelCyr2LatEnabled(int channelIndex, bool enabled) async { + _channelCyr2LatEnabled[channelIndex] = enabled; + if (enabled) { + _channelSmazEnabled[channelIndex] = false; + await _channelSettingsStore.saveSmazEnabled(channelIndex, false); + } + await _channelSettingsStore.saveCyr2LatEnabled(channelIndex, enabled); + notifyListeners(); + } + + Future setContactCyr2LatEnabled( + String contactKeyHex, + bool enabled, + ) async { + _contactCyr2LatEnabled[contactKeyHex] = enabled; + await _contactSettingsStore.saveCyr2LatEnabled(contactKeyHex, enabled); + notifyListeners(); + } + Future _loadChannelOrder() async { _channelOrder = await _channelOrderStore.loadChannelOrder(); _applyChannelOrder(); @@ -828,6 +866,7 @@ class MeshCoreConnector extends ChangeNotifier { ..addAll(cached); for (final contact in cached) { _ensureContactSmazSettingLoaded(contact.publicKeyHex); + _ensureContactCyr2LatSettingLoaded(contact.publicKeyHex); } } @@ -840,9 +879,12 @@ class MeshCoreConnector extends ChangeNotifier { Future loadChannelSettings({int? maxChannels}) async { _channelSmazEnabled.clear(); + _channelCyr2LatEnabled.clear(); final channelCount = maxChannels ?? _maxChannels; for (int i = 0; i < channelCount; i++) { _channelSmazEnabled[i] = await _channelSettingsStore.loadSmazEnabled(i); + _channelCyr2LatEnabled[i] = await _channelSettingsStore + .loadCyr2LatEnabled(i); } } @@ -4432,6 +4474,15 @@ class MeshCoreConnector extends ChangeNotifier { }); } + void _ensureContactCyr2LatSettingLoaded(String contactKeyHex) { + if (_contactCyr2LatEnabled.containsKey(contactKeyHex)) return; + _contactSettingsStore.loadCyr2LatEnabled(contactKeyHex).then((enabled) { + if (_contactCyr2LatEnabled[contactKeyHex] == enabled) return; + _contactCyr2LatEnabled[contactKeyHex] = enabled; + notifyListeners(); + }); + } + /// Prepares contact outbound text by applying SMAZ encoding if enabled. /// This should be used to transform text before computing ACK hashes. String prepareContactOutboundText(Contact contact, String text) { @@ -4440,8 +4491,12 @@ class MeshCoreConnector extends ChangeNotifier { trimmed.startsWith('g:') || trimmed.startsWith('m:') || trimmed.startsWith('V1|'); - if (!isStructuredPayload && isContactSmazEnabled(contact.publicKeyHex)) { - return Smaz.encodeIfSmaller(text); + if (!isStructuredPayload) { + if (isContactSmazEnabled(contact.publicKeyHex)) { + return Smaz.encodeIfSmaller(text); + } else if (isContactCyr2LatEnabled(contact.publicKeyHex)) { + return Cyr2Lat.encode(text); + } } return text; } @@ -4450,8 +4505,12 @@ class MeshCoreConnector extends ChangeNotifier { final trimmed = text.trim(); final isStructuredPayload = trimmed.startsWith('g:') || trimmed.startsWith('m:'); - if (!isStructuredPayload && isChannelSmazEnabled(channelIndex)) { - return Smaz.encodeIfSmaller(text); + if (!isStructuredPayload) { + if (isChannelSmazEnabled(channelIndex)) { + return Smaz.encodeIfSmaller(text); + } else if (isChannelCyr2LatEnabled(channelIndex)) { + return Cyr2Lat.encode(text); + } } return text; } diff --git a/lib/helpers/cyr2lat.dart b/lib/helpers/cyr2lat.dart new file mode 100644 index 0000000..a7ec5dd --- /dev/null +++ b/lib/helpers/cyr2lat.dart @@ -0,0 +1,63 @@ +class Cyr2Lat { + static Map _charMap = { + 'А': 'A', + 'В': 'B', + 'Е': 'E', + 'Ё': 'E', + 'З': '3', + 'К': 'K', + 'М': 'M', + 'Н': 'H', + 'О': 'O', + 'Р': 'P', + 'С': 'C', + 'Т': 'T', + 'Х': 'X', + 'Ь': 'b', + 'а': 'a', + 'е': 'e', + 'ё': 'e', + 'о': 'o', + 'р': 'p', + 'с': 'c', + 'у': 'y', + 'х': 'x', + }; + + static final RegExp _prefixRegExp = RegExp(r'\@\[[\S\s]+\] '); + + static void setCharMap(Map charMap) { + _charMap = Map.from(charMap); + } + + static String encode(String text) { + if (text.isEmpty) return text; + final buffer = StringBuffer(); + + final senderName = extractSenderName(text); + final msgText = removeSenderName(text); + + for (final rune in msgText.runes) { + final char = String.fromCharCode(rune); + buffer.write(_charMap[char] ?? char); + } + + return senderName + buffer.toString(); + } + + static String removeSenderName(String text) { + final match = _prefixRegExp.matchAsPrefix(text); + if (match != null) { + return text.substring(match.end); + } + return text; + } + + static String extractSenderName(String text) { + final match = _prefixRegExp.matchAsPrefix(text); + if (match != null) { + return match.group(0) ?? ''; + } + return ''; + } +} diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 7ac5417..8f59d98 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "Компресия SMAZ", + "channels_cyr2latCompression": "Компресия Cyr2Lat", + "channels_cyr2latCompressionDscr": "Заменя някои кирилични символи с латиница при изпращане.", + "channels_cyr2latSettingsHeading": "Настройки на Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Списък със замествания", + "channels_cyr2latSettingsDscr": "Редактиране на JSON конфигурацията за заместване на символи", + "channels_cyr2latSettingsDialogHint": "JSON карта за замествания", + "channels_cyr2latSettingsDialogSuccess": "Списъкът със замествания е актуализиран", + "channels_cyr2latSettingsDialogWrongJSON": "Неправилен JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Възстановяване на първоначалните настройки", + "channels_cyr2latSettingsDialogResetted": "Настройките за заместване на Cyr2Lat са възстановени към първоначалните", "channels_channelUpdated": "Каналът \"{name}\" е актуализиран", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 4695505..e0cc58b 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "SMAZ-Komprimierung", + "channels_cyr2latCompression": "Cyr2Lat-Komprimierung", + "channels_cyr2latCompressionDscr": "Ersetzt einige kyrillische Zeichen durch lateinische Zeichen, wenn sie gesendet werden.", + "channels_cyr2latSettingsHeading": "Cyr2Lat-Einstellungen", + "channels_cyr2latSettingsSubheading": "Ersetzungsliste", + "channels_cyr2latSettingsDscr": "JSON-Konfiguration für die Zeichenersetzung bearbeiten", + "channels_cyr2latSettingsDialogHint": "JSON-Ersetzungstabelle", + "channels_cyr2latSettingsDialogSuccess": "Ersetzungsliste aktualisiert", + "channels_cyr2latSettingsDialogWrongJSON": "Ungültiges JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Auf Standard zurücksetzen", + "channels_cyr2latSettingsDialogResetted": "Die Cyr2Lat-Ersetzungseinstellungen wurden auf die Standardeinstellungen zurückgesetzt", "channels_channelUpdated": "Kanal \"{name}\" aktualisiert", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8ad6bf3..03612b8 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -580,6 +580,21 @@ } }, "channels_smazCompression": "SMAZ compression", + "channels_cyr2latCompression": "Cyr2Lat compression", + "channels_cyr2latCompressionDscr": "Replaces some Cyrillic characters with Latin characters when sending.", + "channels_cyr2latSettingsHeading": "Cyr2Lat Setup", + "channels_cyr2latSettingsSubheading": "List of replacements", + "channels_cyr2latSettingsDscr": "Edit the JSON configuration of character replacement", + "channels_cyr2latSettingsDialogHint": "JSON replacement map", + "channels_cyr2latSettingsDialogSuccess": "The list of replacements has been updated", + "channels_cyr2latSettingsDialogWrongJSON": "Invalid JSON: {error}", + "@channels_cyr2latSettingsDialogWrongJSON": { + "placeholders": { + "error": {} + } + }, + "channels_cyr2latSettingsDialogReset": "Reset to default", + "channels_cyr2latSettingsDialogResetted": "Cyr2Lat replacement settings reset to default", "channels_channelUpdated": "Channel \"{name}\" updated", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index ac9527b..bcb1b70 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "Compresión SMAZ", + "channels_cyr2latCompression": "Compresión Cyr2Lat", + "channels_cyr2latCompressionDscr": "Reemplaza algunos caracteres cirílicos con caracteres latinos al enviar.", + "channels_cyr2latSettingsHeading": "Configuración de Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Lista de sustituciones", + "channels_cyr2latSettingsDscr": "Editar la configuración JSON de sustitución de caracteres", + "channels_cyr2latSettingsDialogHint": "Mapa JSON de sustituciones", + "channels_cyr2latSettingsDialogSuccess": "Lista de sustituciones actualizada", + "channels_cyr2latSettingsDialogWrongJSON": "JSON incorrecto: {error}", + "channels_cyr2latSettingsDialogReset": "Restablecer valores predeterminados", + "channels_cyr2latSettingsDialogResetted": "La configuración de sustituciones de Cyr2Lat se ha restablecido a los valores predeterminados", "channels_channelUpdated": "Canal \"{name}\" actualizado", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index a942aa2..d0813a2 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "Compression SMAZ", + "channels_cyr2latCompression": "Compression Cyr2Lat", + "channels_cyr2latCompressionDscr": "Remplace certains caractères cyrilliques par des caractères latins lors de l'envoi.", + "channels_cyr2latSettingsHeading" : "Paramètres Cyr2Lat", + "channels_cyr2latSettingsSubheading" : "Liste des remplacements", + "channels_cyr2latSettingsDscr" : "Modifier la configuration JSON des remplacements de caractères", + "channels_cyr2latSettingsDialogHint": "Tableau de remplacement JSON", + "channels_cyr2latSettingsDialogSuccess": "Liste de remplacement mise à jour", + "channels_cyr2latSettingsDialogWrongJSON": "JSON incorrect : {error}", + "channels_cyr2latSettingsDialogReset": "Réinitialiser les paramètres par défaut", + "channels_cyr2latSettingsDialogResetted": "Les paramètres de remplacement Cyr2Lat ont été réinitialisés aux valeurs par défaut", "channels_channelUpdated": "Le canal \"{name}\" a été mis à jour", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index 6f43463..45a40a5 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -548,6 +548,16 @@ } }, "channels_smazCompression": "SMAZ kompresszió", + "channels_cyr2latCompression": "Cyr2Lat kompresszió", + "channels_cyr2latCompressionDscr": "Néhány Cirill betűt Latin betűkkel helyettesít küldéskor.", + "channels_cyr2latSettingsHeading": "Cyr2Lat beállítások", + "channels_cyr2latSettingsSubheading": "Helyettesítési lista", + "channels_cyr2latSettingsDscr": "A karakterhelyettesítési JSON-konfiguráció szerkesztése", + "channels_cyr2latSettingsDialogHint": "JSON-csere táblázat", + "channels_cyr2latSettingsDialogSuccess": "A csere lista frissítve", + "channels_cyr2latSettingsDialogWrongJSON": "Hibás JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Alapértelmezett értékekre állítás", + "channels_cyr2latSettingsDialogResetted": "A Cyr2Lat helyettesítési beállítások alapértelmezett értékekre lettek állítva", "channels_channelUpdated": "A {name} csatorna frissítve", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 387c8cf..5f78fdc 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "Compressione SMAZ", + "channels_cyr2latCompression": "Compressione Cyr2Lat", + "channels_cyr2latCompressionDscr": "Sostituisce alcuni caratteri cirillici con caratteri latini durante l'invio.", + "channels_cyr2latSettingsHeading": "Impostazioni Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Elenco delle sostituzioni", + "channels_cyr2latSettingsDscr": "Modifica la configurazione JSON delle sostituzioni dei caratteri", + "channels_cyr2latSettingsDialogHint": "Mappa JSON delle sostituzioni", + "channels_cyr2latSettingsDialogSuccess": "Elenco delle sostituzioni aggiornato", + "channels_cyr2latSettingsDialogWrongJSON": "JSON non corretto: {error}", + "channels_cyr2latSettingsDialogReset": "Ripristina impostazioni predefinite", + "channels_cyr2latSettingsDialogResetted": "Le impostazioni di sostituzione Cyr2Lat sono state ripristinate alle impostazioni predefinite", "channels_channelUpdated": "Canale \"{name}\" aggiornato", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index b63f146..ae3d689 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -548,6 +548,16 @@ } }, "channels_smazCompression": "SMAZ 圧縮", + "channels_cyr2latCompression": "Cyr2Lat 圧縮", + "channels_cyr2latCompressionDscr": "送信時に一部のキリル文字をラテン文字に置き換えます。", + "channels_cyr2latSettingsHeading": "cyr2latの設定", + "channels_cyr2latSettingsSubheading": "置換リスト", + "channels_cyr2latSettingsDscr": "文字置換のJSON設定を編集する", + "channels_cyr2latSettingsDialogHint": "JSON置換マップ", + "channels_cyr2latSettingsDialogSuccess": "置換リストが更新されました", + "channels_cyr2latSettingsDialogWrongJSON": "不正なJSON: {error}", + "channels_cyr2latSettingsDialogReset": "初期設定に戻す", + "channels_cyr2latSettingsDialogResetted": "cyr2latの置換設定が初期設定に戻されました", "channels_channelUpdated": "チャンネル「{name}」が更新されました", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 40721fe..982bd42 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -548,6 +548,16 @@ } }, "channels_smazCompression": "SMAZ 압축", + "channels_cyr2latCompression": "Cyr2Lat 압축", + "channels_cyr2latCompressionDscr": "보낼 때 일부 키릴 문자를 라틴 문자로 바꿉니다.", + "channels_cyr2latSettingsHeading": "Cyr2Lat 설정", + "channels_cyr2latSettingsSubheading": "변환 목록", + "channels_cyr2latSettingsDscr": "문자 변환 JSON 구성 편집", + "channels_cyr2latSettingsDialogHint": "JSON 변환 맵", + "channels_cyr2latSettingsDialogSuccess": "변환 목록이 업데이트되었습니다", + "channels_cyr2latSettingsDialogWrongJSON": "잘못된 JSON: {error}", + "channels_cyr2latSettingsDialogReset": "초기값으로 초기화", + "channels_cyr2latSettingsDialogResetted": "Cyr2Lat 변환 설정이 초기값으로 초기화되었습니다", "channels_channelUpdated": "채널 \"{name}\"이 업데이트되었습니다.", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 2c1342d..419d8c9 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2176,6 +2176,66 @@ abstract class AppLocalizations { /// **'SMAZ compression'** String get channels_smazCompression; + /// No description provided for @channels_cyr2latCompression. + /// + /// In en, this message translates to: + /// **'Cyr2Lat compression'** + String get channels_cyr2latCompression; + + /// No description provided for @channels_cyr2latCompressionDscr. + /// + /// In en, this message translates to: + /// **'Replaces some Cyrillic characters with Latin characters when sending.'** + String get channels_cyr2latCompressionDscr; + + /// No description provided for @channels_cyr2latSettingsHeading. + /// + /// In en, this message translates to: + /// **'Cyr2Lat Setup'** + String get channels_cyr2latSettingsHeading; + + /// No description provided for @channels_cyr2latSettingsSubheading. + /// + /// In en, this message translates to: + /// **'List of replacements'** + String get channels_cyr2latSettingsSubheading; + + /// No description provided for @channels_cyr2latSettingsDscr. + /// + /// In en, this message translates to: + /// **'Edit the JSON configuration of character replacement'** + String get channels_cyr2latSettingsDscr; + + /// No description provided for @channels_cyr2latSettingsDialogHint. + /// + /// In en, this message translates to: + /// **'JSON replacement map'** + String get channels_cyr2latSettingsDialogHint; + + /// No description provided for @channels_cyr2latSettingsDialogSuccess. + /// + /// In en, this message translates to: + /// **'The list of replacements has been updated'** + String get channels_cyr2latSettingsDialogSuccess; + + /// No description provided for @channels_cyr2latSettingsDialogWrongJSON. + /// + /// In en, this message translates to: + /// **'Invalid JSON: {error}'** + String channels_cyr2latSettingsDialogWrongJSON(Object error); + + /// No description provided for @channels_cyr2latSettingsDialogReset. + /// + /// In en, this message translates to: + /// **'Reset to default'** + String get channels_cyr2latSettingsDialogReset; + + /// No description provided for @channels_cyr2latSettingsDialogResetted. + /// + /// In en, this message translates to: + /// **'Cyr2Lat replacement settings reset to default'** + String get channels_cyr2latSettingsDialogResetted; + /// No description provided for @channels_channelUpdated. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index b3e1279..a34ae45 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -1174,6 +1174,43 @@ class AppLocalizationsBg extends AppLocalizations { @override String get channels_smazCompression => 'Компресия SMAZ'; + @override + String get channels_cyr2latCompression => 'Компресия Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Заменя някои кирилични символи с латиница при изпращане.'; + + @override + String get channels_cyr2latSettingsHeading => 'Настройки на Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Списък със замествания'; + + @override + String get channels_cyr2latSettingsDscr => + 'Редактиране на JSON конфигурацията за заместване на символи'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON карта за замествания'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Списъкът със замествания е актуализиран'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Неправилен JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Възстановяване на първоначалните настройки'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Настройките за заместване на Cyr2Lat са възстановени към първоначалните'; + @override String channels_channelUpdated(String name) { return 'Каналът \"$name\" е актуализиран'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index d7c1691..7f6cfb2 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1170,6 +1170,42 @@ class AppLocalizationsDe extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ-Komprimierung'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat-Komprimierung'; + + @override + String get channels_cyr2latCompressionDscr => + 'Ersetzt einige kyrillische Zeichen durch lateinische Zeichen, wenn sie gesendet werden.'; + + @override + String get channels_cyr2latSettingsHeading => 'Cyr2Lat-Einstellungen'; + + @override + String get channels_cyr2latSettingsSubheading => 'Ersetzungsliste'; + + @override + String get channels_cyr2latSettingsDscr => + 'JSON-Konfiguration für die Zeichenersetzung bearbeiten'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON-Ersetzungstabelle'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Ersetzungsliste aktualisiert'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Ungültiges JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => 'Auf Standard zurücksetzen'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Die Cyr2Lat-Ersetzungseinstellungen wurden auf die Standardeinstellungen zurückgesetzt'; + @override String channels_channelUpdated(String name) { return 'Kanal \"$name\" aktualisiert'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index a2a88b0..c63079e 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1150,6 +1150,42 @@ class AppLocalizationsEn extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ compression'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat compression'; + + @override + String get channels_cyr2latCompressionDscr => + 'Replaces some Cyrillic characters with Latin characters when sending.'; + + @override + String get channels_cyr2latSettingsHeading => 'Cyr2Lat Setup'; + + @override + String get channels_cyr2latSettingsSubheading => 'List of replacements'; + + @override + String get channels_cyr2latSettingsDscr => + 'Edit the JSON configuration of character replacement'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON replacement map'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'The list of replacements has been updated'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Invalid JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => 'Reset to default'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Cyr2Lat replacement settings reset to default'; + @override String channels_channelUpdated(String name) { return 'Channel \"$name\" updated'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index a127012..4f2980c 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1172,6 +1172,43 @@ class AppLocalizationsEs extends AppLocalizations { @override String get channels_smazCompression => 'Compresión SMAZ'; + @override + String get channels_cyr2latCompression => 'Compresión Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Reemplaza algunos caracteres cirílicos con caracteres latinos al enviar.'; + + @override + String get channels_cyr2latSettingsHeading => 'Configuración de Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Lista de sustituciones'; + + @override + String get channels_cyr2latSettingsDscr => + 'Editar la configuración JSON de sustitución de caracteres'; + + @override + String get channels_cyr2latSettingsDialogHint => 'Mapa JSON de sustituciones'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Lista de sustituciones actualizada'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'JSON incorrecto: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Restablecer valores predeterminados'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'La configuración de sustituciones de Cyr2Lat se ha restablecido a los valores predeterminados'; + @override String channels_channelUpdated(String name) { return 'Canal \"$name\" actualizado'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index a006391..5710566 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1177,6 +1177,44 @@ class AppLocalizationsFr extends AppLocalizations { @override String get channels_smazCompression => 'Compression SMAZ'; + @override + String get channels_cyr2latCompression => 'Compression Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Remplace certains caractères cyrilliques par des caractères latins lors de l\'envoi.'; + + @override + String get channels_cyr2latSettingsHeading => 'Paramètres Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Liste des remplacements'; + + @override + String get channels_cyr2latSettingsDscr => + 'Modifier la configuration JSON des remplacements de caractères'; + + @override + String get channels_cyr2latSettingsDialogHint => + 'Tableau de remplacement JSON'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Liste de remplacement mise à jour'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'JSON incorrect : $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Réinitialiser les paramètres par défaut'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Les paramètres de remplacement Cyr2Lat ont été réinitialisés aux valeurs par défaut'; + @override String channels_channelUpdated(String name) { return 'Le canal \"$name\" a été mis à jour'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 1ad8558..690fed1 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -1177,6 +1177,42 @@ class AppLocalizationsHu extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ kompresszió'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat kompresszió'; + + @override + String get channels_cyr2latCompressionDscr => + 'Néhány Cirill betűt Latin betűkkel helyettesít küldéskor.'; + + @override + String get channels_cyr2latSettingsHeading => 'Cyr2Lat beállítások'; + + @override + String get channels_cyr2latSettingsSubheading => 'Helyettesítési lista'; + + @override + String get channels_cyr2latSettingsDscr => + 'A karakterhelyettesítési JSON-konfiguráció szerkesztése'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON-csere táblázat'; + + @override + String get channels_cyr2latSettingsDialogSuccess => 'A csere lista frissítve'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Hibás JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Alapértelmezett értékekre állítás'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'A Cyr2Lat helyettesítési beállítások alapértelmezett értékekre lettek állítva'; + @override String channels_channelUpdated(String name) { return 'A $name csatorna frissítve'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 3a55559..beea60d 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1173,6 +1173,44 @@ class AppLocalizationsIt extends AppLocalizations { @override String get channels_smazCompression => 'Compressione SMAZ'; + @override + String get channels_cyr2latCompression => 'Compressione Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Sostituisce alcuni caratteri cirillici con caratteri latini durante l\'invio.'; + + @override + String get channels_cyr2latSettingsHeading => 'Impostazioni Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Elenco delle sostituzioni'; + + @override + String get channels_cyr2latSettingsDscr => + 'Modifica la configurazione JSON delle sostituzioni dei caratteri'; + + @override + String get channels_cyr2latSettingsDialogHint => + 'Mappa JSON delle sostituzioni'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Elenco delle sostituzioni aggiornato'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'JSON non corretto: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Ripristina impostazioni predefinite'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Le impostazioni di sostituzione Cyr2Lat sono state ripristinate alle impostazioni predefinite'; + @override String channels_channelUpdated(String name) { return 'Canale \"$name\" aggiornato'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index afb8c29..6dadfa3 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -1117,6 +1117,39 @@ class AppLocalizationsJa extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ 圧縮'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat 圧縮'; + + @override + String get channels_cyr2latCompressionDscr => '送信時に一部のキリル文字をラテン文字に置き換えます。'; + + @override + String get channels_cyr2latSettingsHeading => 'cyr2latの設定'; + + @override + String get channels_cyr2latSettingsSubheading => '置換リスト'; + + @override + String get channels_cyr2latSettingsDscr => '文字置換のJSON設定を編集する'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON置換マップ'; + + @override + String get channels_cyr2latSettingsDialogSuccess => '置換リストが更新されました'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return '不正なJSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => '初期設定に戻す'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'cyr2latの置換設定が初期設定に戻されました'; + @override String channels_channelUpdated(String name) { return 'チャンネル「$name」が更新されました'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index ff4bd26..358c7b1 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -1112,6 +1112,39 @@ class AppLocalizationsKo extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ 압축'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat 압축'; + + @override + String get channels_cyr2latCompressionDscr => '보낼 때 일부 키릴 문자를 라틴 문자로 바꿉니다.'; + + @override + String get channels_cyr2latSettingsHeading => 'Cyr2Lat 설정'; + + @override + String get channels_cyr2latSettingsSubheading => '변환 목록'; + + @override + String get channels_cyr2latSettingsDscr => '문자 변환 JSON 구성 편집'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON 변환 맵'; + + @override + String get channels_cyr2latSettingsDialogSuccess => '변환 목록이 업데이트되었습니다'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return '잘못된 JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => '초기값으로 초기화'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Cyr2Lat 변환 설정이 초기값으로 초기화되었습니다'; + @override String channels_channelUpdated(String name) { return '채널 \"$name\"이 업데이트되었습니다.'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index dd770e1..7890853 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1161,6 +1161,43 @@ class AppLocalizationsNl extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ compressie'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat compressie'; + + @override + String get channels_cyr2latCompressionDscr => + 'Vervangt sommige Cyrillische tekens door Latijnse tekens bij het verzenden.'; + + @override + String get channels_cyr2latSettingsHeading => 'Instellingen Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Lijst met vervangingen'; + + @override + String get channels_cyr2latSettingsDscr => + 'Bewerk de JSON-configuratie voor tekenvervanging'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON-vervangingskaart'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Lijst met vervangingen bijgewerkt'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Onjuiste JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Terugzetten naar standaard'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'De instellingen voor Cyr2Lat-vervangingen zijn teruggezet naar de standaardinstellingen'; + @override String channels_channelUpdated(String name) { return 'Kanaal \"$name\" is bijgewerkt'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 357dd7e..fcb236f 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1181,6 +1181,43 @@ class AppLocalizationsPl extends AppLocalizations { @override String get channels_smazCompression => 'Kompresja SMAZ'; + @override + String get channels_cyr2latCompression => 'Kompresja Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Zastępuje niektóre znaki cyrylicy alfabetem łacińskim podczas wysyłania.'; + + @override + String get channels_cyr2latSettingsHeading => 'Ustawienia Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Lista zamian'; + + @override + String get channels_cyr2latSettingsDscr => + 'Edytuj konfigurację JSON zamiany znaków'; + + @override + String get channels_cyr2latSettingsDialogHint => 'Mapa zamian JSON'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Lista zamian zaktualizowana'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Nieprawidłowy JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Przywróć ustawienia domyślne'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Ustawienia zamiany Cyr2Lat zostały przywrócone do wartości domyślnych'; + @override String channels_channelUpdated(String name) { return 'Kanał \"$name\" został zaktualizowany'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 2dfcd8b..97c5a50 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1172,6 +1172,43 @@ class AppLocalizationsPt extends AppLocalizations { @override String get channels_smazCompression => 'Compressão SMAZ'; + @override + String get channels_cyr2latCompression => 'Compressão Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Substitui alguns caracteres cirílicos por caracteres latinos ao enviar.'; + + @override + String get channels_cyr2latSettingsHeading => 'Configuração do Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Lista de substituições'; + + @override + String get channels_cyr2latSettingsDscr => + 'Editar a configuração JSON de substituição de caracteres'; + + @override + String get channels_cyr2latSettingsDialogHint => 'Mapa de substituições JSON'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Lista de substituições atualizada'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'JSON incorreto: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Redefinir para os valores iniciais'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'As configurações de substituição Cyr2Lat foram redefinidas para os valores iniciais'; + @override String channels_channelUpdated(String name) { return 'Canal \"$name\" atualizado'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 4fac42c..a4d6dd0 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1172,6 +1172,41 @@ class AppLocalizationsRu extends AppLocalizations { @override String get channels_smazCompression => 'Сжатие SMAZ'; + @override + String get channels_cyr2latCompression => 'Сжатие Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Заменяет некоторые кириллические символы на латиницу при отправке.'; + + @override + String get channels_cyr2latSettingsHeading => 'Настройка Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Список замен'; + + @override + String get channels_cyr2latSettingsDscr => + 'Редактировать JSON-конфигурацию замены символов'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON-карта замен'; + + @override + String get channels_cyr2latSettingsDialogSuccess => 'Список замен обновлён'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Некорректный JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => 'Сбросить к начальным'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Настройки замен Cyr2Lat сброшены к начальным'; + @override String channels_channelUpdated(String name) { return 'Канал \"$name\" обновлён'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index c42e024..5f523de 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -1161,6 +1161,43 @@ class AppLocalizationsSk extends AppLocalizations { @override String get channels_smazCompression => 'Odstránenie kompresie SMAZ'; + @override + String get channels_cyr2latCompression => 'Odstránenie kompresie Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Pri odosielaní nahradí niektoré znaky cyriliky latinskými znakmi.'; + + @override + String get channels_cyr2latSettingsHeading => 'Nastavenia Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Zoznam nahradení'; + + @override + String get channels_cyr2latSettingsDscr => + 'Upravte konfiguráciu JSON pre nahradenie znakov'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON mapa nahradení'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Zoznam nahradení bol aktualizovaný'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Nesprávny JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Obnoviť predvolené nastavenia'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Nastavenia nahradzovania Cyr2Lat boli obnovené na predvolené'; + @override String channels_channelUpdated(String name) { return 'Kanál \"$name\" bol aktualizovaný'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 2d89aa4..91df3cf 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -1159,6 +1159,42 @@ class AppLocalizationsSl extends AppLocalizations { @override String get channels_smazCompression => 'Kompresija SMAZ'; + @override + String get channels_cyr2latCompression => 'Kompresija Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Pri pošiljanju nekatere cirilice nadomesti z latiničnimi.'; + + @override + String get channels_cyr2latSettingsHeading => 'Nastavitve Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Seznam zamenjav'; + + @override + String get channels_cyr2latSettingsDscr => + 'Uredi JSON-konfiguracijo zamenjav znakov'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON-tabela zamenjav'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Seznam zamenjav je posodobljen'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Nepravilen JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => 'Ponastavi na privzete'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Nastavitve zamenjav Cyr2Lat so ponastavljene na privzete'; + @override String channels_channelUpdated(String name) { return 'Kanal $name je bil posodobljen'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 38e0893..2273c42 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -1151,6 +1151,42 @@ class AppLocalizationsSv extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ-komprimering'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat-komprimering'; + + @override + String get channels_cyr2latCompressionDscr => + 'Ersätter vissa kyrilliska tecken med latinska tecken när du skickar.'; + + @override + String get channels_cyr2latSettingsHeading => 'Inställningar för Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Ersättningslista'; + + @override + String get channels_cyr2latSettingsDscr => + 'Redigera JSON-konfigurationen för teckenersättning'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON-ersättningskarta'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Ersättningslistan har uppdaterats'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Felaktig JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => 'Återställ till standard'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Inställningarna för Cyr2Lat-ersättningar har återställts till standard'; + @override String channels_channelUpdated(String name) { return 'Kanalen \"$name\" har uppdaterats'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 8ed4b9f..639cbd1 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -1166,6 +1166,41 @@ class AppLocalizationsUk extends AppLocalizations { @override String get channels_smazCompression => 'Стиснення SMAZ'; + @override + String get channels_cyr2latCompression => 'Стиснення Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Замінює деякі кириличні символи на латиницю при відправці.'; + + @override + String get channels_cyr2latSettingsHeading => 'Налаштування Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Список замін'; + + @override + String get channels_cyr2latSettingsDscr => + 'Редагувати JSON-конфігурацію заміни символів'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON-карта замін'; + + @override + String get channels_cyr2latSettingsDialogSuccess => 'Список замін оновлено'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Некоректний JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => 'Скинути до початкових'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Налаштування замін Cyr2Lat скинуто до початкових'; + @override String channels_channelUpdated(String name) { return 'Канал «$name» оновлено'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 4f38c64..0ccb9c5 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1099,6 +1099,38 @@ class AppLocalizationsZh extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ 压缩'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat 压缩'; + + @override + String get channels_cyr2latCompressionDscr => '发送时将一些西里尔字符替换为拉丁字符。'; + + @override + String get channels_cyr2latSettingsHeading => 'Cyr2Lat 設定'; + + @override + String get channels_cyr2latSettingsSubheading => '替換清單'; + + @override + String get channels_cyr2latSettingsDscr => '編輯 JSON 字元替換設定檔'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON 替換映射表'; + + @override + String get channels_cyr2latSettingsDialogSuccess => '替換清單已更新'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'JSON 格式錯誤:$error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => '還原為預設值'; + + @override + String get channels_cyr2latSettingsDialogResetted => 'Cyr2Lat 替換設定已還原為預設值'; + @override String channels_channelUpdated(String name) { return '频道 \"$name\" 已更新'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 96bdb84..8ffadd0 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "SMAZ compressie", + "channels_cyr2latCompression": "Cyr2Lat compressie", + "channels_cyr2latCompressionDscr": "Vervangt sommige Cyrillische tekens door Latijnse tekens bij het verzenden.", + "channels_cyr2latSettingsHeading": "Instellingen Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Lijst met vervangingen", + "channels_cyr2latSettingsDscr": "Bewerk de JSON-configuratie voor tekenvervanging", + "channels_cyr2latSettingsDialogHint": "JSON-vervangingskaart", + "channels_cyr2latSettingsDialogSuccess": "Lijst met vervangingen bijgewerkt", + "channels_cyr2latSettingsDialogWrongJSON": "Onjuiste JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Terugzetten naar standaard", + "channels_cyr2latSettingsDialogResetted": "De instellingen voor Cyr2Lat-vervangingen zijn teruggezet naar de standaardinstellingen", "channels_channelUpdated": "Kanaal \"{name}\" is bijgewerkt", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index b62a78a..d44f8c8 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -398,6 +398,16 @@ } }, "channels_smazCompression": "Kompresja SMAZ", + "channels_cyr2latCompression": "Kompresja Cyr2Lat", + "channels_cyr2latCompressionDscr": "Zastępuje niektóre znaki cyrylicy alfabetem łacińskim podczas wysyłania.", + "channels_cyr2latSettingsHeading": "Ustawienia Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Lista zamian", + "channels_cyr2latSettingsDscr": "Edytuj konfigurację JSON zamiany znaków", + "channels_cyr2latSettingsDialogHint": "Mapa zamian JSON", + "channels_cyr2latSettingsDialogSuccess": "Lista zamian zaktualizowana", + "channels_cyr2latSettingsDialogWrongJSON": "Nieprawidłowy JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Przywróć ustawienia domyślne", + "channels_cyr2latSettingsDialogResetted": "Ustawienia zamiany Cyr2Lat zostały przywrócone do wartości domyślnych", "channels_channelUpdated": "Kanał \"{name}\" został zaktualizowany", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index bf3e893..6784082 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "Compressão SMAZ", + "channels_cyr2latCompression": "Compressão Cyr2Lat", + "channels_cyr2latCompressionDscr": "Substitui alguns caracteres cirílicos por caracteres latinos ao enviar.", + "channels_cyr2latSettingsHeading": "Configuração do Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Lista de substituições", + "channels_cyr2latSettingsDscr": "Editar a configuração JSON de substituição de caracteres", + "channels_cyr2latSettingsDialogHint": "Mapa de substituições JSON", + "channels_cyr2latSettingsDialogSuccess": "Lista de substituições atualizada", + "channels_cyr2latSettingsDialogWrongJSON": "JSON incorreto: {error}", + "channels_cyr2latSettingsDialogReset": "Redefinir para os valores iniciais", + "channels_cyr2latSettingsDialogResetted": "As configurações de substituição Cyr2Lat foram redefinidas para os valores iniciais", "channels_channelUpdated": "Canal \"{name}\" atualizado", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index a83d139..a2052b0 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -252,6 +252,16 @@ "channels_channelAdded": "Канал \"{name}\" добавлен", "channels_editChannelTitle": "Изменить канал {index}", "channels_smazCompression": "Сжатие SMAZ", + "channels_cyr2latCompression": "Сжатие Cyr2Lat", + "channels_cyr2latCompressionDscr": "Заменяет некоторые кириллические символы на латиницу при отправке.", + "channels_cyr2latSettingsHeading": "Настройка Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Список замен", + "channels_cyr2latSettingsDscr": "Редактировать JSON-конфигурацию замены символов", + "channels_cyr2latSettingsDialogHint": "JSON-карта замен", + "channels_cyr2latSettingsDialogSuccess": "Список замен обновлён", + "channels_cyr2latSettingsDialogWrongJSON": "Некорректный JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Сбросить к начальным", + "channels_cyr2latSettingsDialogResetted": "Настройки замен Cyr2Lat сброшены к начальным", "channels_channelUpdated": "Канал \"{name}\" обновлён", "channels_publicChannelAdded": "Публичный канал добавлен", "channels_sortBy": "Сортировка", diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index e4466c3..27db3e7 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "Odstránenie kompresie SMAZ", + "channels_cyr2latCompression": "Odstránenie kompresie Cyr2Lat", + "channels_cyr2latCompressionDscr": "Pri odosielaní nahradí niektoré znaky cyriliky latinskými znakmi.", + "channels_cyr2latSettingsHeading": "Nastavenia Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Zoznam nahradení", + "channels_cyr2latSettingsDscr": "Upravte konfiguráciu JSON pre nahradenie znakov", + "channels_cyr2latSettingsDialogHint": "JSON mapa nahradení", + "channels_cyr2latSettingsDialogSuccess": "Zoznam nahradení bol aktualizovaný", + "channels_cyr2latSettingsDialogWrongJSON": "Nesprávny JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Obnoviť predvolené nastavenia", + "channels_cyr2latSettingsDialogResetted": "Nastavenia nahradzovania Cyr2Lat boli obnovené na predvolené", "channels_channelUpdated": "Kanál \"{name}\" bol aktualizovaný", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index f6a317e..b941fe7 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "Kompresija SMAZ", + "channels_cyr2latCompression": "Kompresija Cyr2Lat", + "channels_cyr2latCompressionDscr": "Pri pošiljanju nekatere cirilice nadomesti z latiničnimi.", + "channels_cyr2latSettingsHeading": "Nastavitve Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Seznam zamenjav", + "channels_cyr2latSettingsDscr": "Uredi JSON-konfiguracijo zamenjav znakov", + "channels_cyr2latSettingsDialogHint": "JSON-tabela zamenjav", + "channels_cyr2latSettingsDialogSuccess": "Seznam zamenjav je posodobljen", + "channels_cyr2latSettingsDialogWrongJSON": "Nepravilen JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Ponastavi na privzete", + "channels_cyr2latSettingsDialogResetted": "Nastavitve zamenjav Cyr2Lat so ponastavljene na privzete", "channels_channelUpdated": "Kanal {name} je bil posodobljen", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index eab348c..ca9abac 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "SMAZ-komprimering", + "channels_cyr2latCompression": "Cyr2Lat-komprimering", + "channels_cyr2latCompressionDscr": "Ersätter vissa kyrilliska tecken med latinska tecken när du skickar.", + "channels_cyr2latSettingsHeading": "Inställningar för Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Ersättningslista", + "channels_cyr2latSettingsDscr": "Redigera JSON-konfigurationen för teckenersättning", + "channels_cyr2latSettingsDialogHint": "JSON-ersättningskarta", + "channels_cyr2latSettingsDialogSuccess": "Ersättningslistan har uppdaterats", + "channels_cyr2latSettingsDialogWrongJSON": "Felaktig JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Återställ till standard", + "channels_cyr2latSettingsDialogResetted": "Inställningarna för Cyr2Lat-ersättningar har återställts till standard", "channels_channelUpdated": "Kanalen \"{name}\" har uppdaterats", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index a005b36..826d3b8 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -389,6 +389,16 @@ } }, "channels_smazCompression": "Стиснення SMAZ", + "channels_cyr2latCompression": "Стиснення Cyr2Lat", + "channels_cyr2latCompressionDscr": "Замінює деякі кириличні символи на латиницю при відправці.", + "channels_cyr2latSettingsHeading": "Налаштування Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Список замін", + "channels_cyr2latSettingsDscr": "Редагувати JSON-конфігурацію заміни символів", + "channels_cyr2latSettingsDialogHint": "JSON-карта замін", + "channels_cyr2latSettingsDialogSuccess": "Список замін оновлено", + "channels_cyr2latSettingsDialogWrongJSON": "Некоректний JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Скинути до початкових", + "channels_cyr2latSettingsDialogResetted": "Налаштування замін Cyr2Lat скинуто до початкових", "channels_channelUpdated": "Канал «{name}» оновлено", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9dc2325..6932a4b 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -403,6 +403,16 @@ } }, "channels_smazCompression": "SMAZ 压缩", + "channels_cyr2latCompression": "Cyr2Lat 压缩", + "channels_cyr2latCompressionDscr": "发送时将一些西里尔字符替换为拉丁字符。", + "channels_cyr2latSettingsHeading": "Cyr2Lat 設定", + "channels_cyr2latSettingsSubheading": "替換清單", + "channels_cyr2latSettingsDscr": "編輯 JSON 字元替換設定檔", + "channels_cyr2latSettingsDialogHint": "JSON 替換映射表", + "channels_cyr2latSettingsDialogSuccess": "替換清單已更新", + "channels_cyr2latSettingsDialogWrongJSON": "JSON 格式錯誤:{error}", + "channels_cyr2latSettingsDialogReset": "還原為預設值", + "channels_cyr2latSettingsDialogResetted": "Cyr2Lat 替換設定已還原為預設值", "channels_channelUpdated": "频道 \"{name}\" 已更新", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/models/app_settings.dart b/lib/models/app_settings.dart index d3d3421..d7c176e 100644 --- a/lib/models/app_settings.dart +++ b/lib/models/app_settings.dart @@ -13,6 +13,31 @@ extension UnitSystemValue on UnitSystem { } } +const Map defaultCyr2LatCharMap = { + 'А': 'A', + 'В': 'B', + 'Е': 'E', + 'Ё': 'E', + 'З': '3', + 'К': 'K', + 'М': 'M', + 'Н': 'H', + 'О': 'O', + 'Р': 'P', + 'С': 'C', + 'Т': 'T', + 'Х': 'X', + 'Ь': 'b', + 'а': 'a', + 'е': 'e', + 'ё': 'e', + 'о': 'o', + 'р': 'p', + 'с': 'c', + 'у': 'y', + 'х': 'x', +}; + class AppSettings { static const Object _unset = Object(); @@ -57,6 +82,7 @@ class AppSettings { final String? translationModelSourceUrl; final String? translationSelectedModelId; final List translationDownloadedModels; + final Map cyr2latCharMap; AppSettings({ this.clearPathOnMaxRetry = false, @@ -100,10 +126,12 @@ class AppSettings { this.translationModelSourceUrl, this.translationSelectedModelId, List? translationDownloadedModels, + Map? cyr2latCharMap, }) : batteryChemistryByDeviceId = batteryChemistryByDeviceId ?? {}, batteryChemistryByRepeaterId = batteryChemistryByRepeaterId ?? {}, mutedChannels = mutedChannels ?? {}, - translationDownloadedModels = translationDownloadedModels ?? const []; + translationDownloadedModels = translationDownloadedModels ?? const [], + cyr2latCharMap = cyr2latCharMap ?? defaultCyr2LatCharMap; Map toJson() { return { @@ -150,6 +178,7 @@ class AppSettings { 'translation_downloaded_models': translationDownloadedModels .map((model) => model.toJson()) .toList(), + 'cyr2lat_char_map': cyr2latCharMap, }; } @@ -237,6 +266,11 @@ class AppSettings { ) .toList() ?? const [], + cyr2latCharMap: + (json['cyr2lat_char_map'] as Map?)?.map( + (key, value) => MapEntry(key.toString(), value.toString()), + ) ?? + defaultCyr2LatCharMap, ); } @@ -282,6 +316,7 @@ class AppSettings { Object? translationModelSourceUrl = _unset, Object? translationSelectedModelId = _unset, List? translationDownloadedModels, + Map? cyr2latCharMap, }) { return AppSettings( clearPathOnMaxRetry: clearPathOnMaxRetry ?? this.clearPathOnMaxRetry, @@ -345,6 +380,7 @@ class AppSettings { : translationSelectedModelId as String?, translationDownloadedModels: translationDownloadedModels ?? this.translationDownloadedModels, + cyr2latCharMap: cyr2latCharMap ?? this.cyr2latCharMap, ); } } diff --git a/lib/screens/app_settings_screen.dart b/lib/screens/app_settings_screen.dart index 80d8adb..425248d 100644 --- a/lib/screens/app_settings_screen.dart +++ b/lib/screens/app_settings_screen.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -60,6 +61,8 @@ class AppSettingsScreen extends StatelessWidget { const SizedBox(height: 16), _buildMapSettingsCard(context, settingsService), const SizedBox(height: 16), + _buildCyr2LatCard(context, settingsService), + const SizedBox(height: 16), _buildDebugCard(context, settingsService), ], ); @@ -1255,6 +1258,116 @@ class AppSettingsScreen extends StatelessWidget { return '${sizeMb.toStringAsFixed(1)} MB • $source'; } + Widget _buildCyr2LatCard( + BuildContext context, + AppSettingsService settingsService, + ) { + return Card( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), + child: Text( + context.l10n.channels_cyr2latSettingsHeading, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + ), + ListTile( + leading: const Icon(Icons.translate), + + title: Text(context.l10n.channels_cyr2latSettingsSubheading), + subtitle: Text(context.l10n.channels_cyr2latSettingsDscr), + trailing: const Icon(Icons.chevron_right), + onTap: () => _showCyr2LatDialog(context, settingsService), + ), + ], + ), + ); + } + + void _showCyr2LatDialog( + BuildContext context, + AppSettingsService settingsService, + ) { + final controller = TextEditingController( + text: const JsonEncoder.withIndent( + ' ', + ).convert(settingsService.settings.cyr2latCharMap), + ); + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(context.l10n.channels_cyr2latSettingsDscr), + content: SingleChildScrollView( + child: TextField( + controller: controller, + maxLines: 20, + decoration: InputDecoration( + border: const OutlineInputBorder(), + hintText: context.l10n.channels_cyr2latSettingsDialogHint, + ), + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(context.l10n.common_cancel), + ), + TextButton( + onPressed: () { + try { + final json = + jsonDecode(controller.text) as Map; + final map = json.map( + (key, value) => MapEntry(key, value.toString()), + ); + final newSettings = settingsService.settings.copyWith( + cyr2latCharMap: map, + ); + settingsService.updateSettings(newSettings); + Navigator.pop(context); + showDismissibleSnackBar( + context, + content: Text( + context.l10n.channels_cyr2latSettingsDialogSuccess, + ), + ); + } catch (e) { + showDismissibleSnackBar( + context, + content: Text( + context.l10n.channels_cyr2latSettingsDialogWrongJSON( + e.toString(), + ), + ), + ); + } + }, + child: Text(context.l10n.common_save), + ), + TextButton( + onPressed: () { + final newSettings = settingsService.settings.copyWith( + cyr2latCharMap: defaultCyr2LatCharMap, + ); + settingsService.updateSettings(newSettings); + Navigator.pop(context); + showDismissibleSnackBar( + context, + content: Text( + context.l10n.channels_cyr2latSettingsDialogResetted, + ), + ); + }, + child: Text(context.l10n.channels_cyr2latSettingsDialogReset), + ), + ], + ), + ); + } + Widget _buildDebugCard( BuildContext context, AppSettingsService settingsService, diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index b203cbb..4f43e9c 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -11,6 +11,7 @@ import '../connector/meshcore_connector.dart'; import '../utils/platform_info.dart'; import '../helpers/chat_scroll_controller.dart'; import '../connector/meshcore_protocol.dart'; +import '../helpers/cyr2lat.dart'; import '../helpers/gif_helper.dart'; import '../helpers/reaction_helper.dart'; import '../helpers/snack_bar_builder.dart'; @@ -1100,7 +1101,12 @@ class _ChannelChatScreenState extends State { hintText: context.l10n.chat_typeMessage, onSubmitted: (_) => _sendMessage(), encoder: - connector.isChannelSmazEnabled(widget.channel.index) + (connector.isChannelSmazEnabled( + widget.channel.index, + ) || + connector.isChannelCyr2LatEnabled( + widget.channel.index, + )) ? (text) => connector.prepareChannelOutboundText( widget.channel.index, text, @@ -1213,6 +1219,15 @@ class _ChannelChatScreenState extends State { return; } + // When messageText is transformed with cyr2lat, it (generally) hasn't visual differences, + // but we getting messages doubles in chat screen (source text and transformed). + // To prevent, we'll perform transform of source before pass to main sender logic. + // We can pass whole text, senderName will be kept intact + if (connector.isChannelCyr2LatEnabled(widget.channel.index)) { + messageText = Cyr2Lat.encode(messageText); + } + // end transform + _textController.clear(); _cancelReply(); _textFieldFocusNode.requestFocus(); diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index 44c7a69..868aac7 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -1404,6 +1404,7 @@ class _ChannelsScreenState extends State final nameController = TextEditingController(text: channel.name); final pskController = TextEditingController(text: channel.pskHex); bool smazEnabled = connector.isChannelSmazEnabled(channel.index); + bool cyr2latEnabled = connector.isChannelCyr2LatEnabled(channel.index); showDialog( context: context, @@ -1449,7 +1450,26 @@ class _ChannelsScreenState extends State contentPadding: EdgeInsets.zero, title: Text(dialogContext.l10n.channels_smazCompression), value: smazEnabled, - onChanged: (value) => setState(() => smazEnabled = value), + onChanged: (value) => setState(() { + smazEnabled = value; + if (smazEnabled) { + cyr2latEnabled = false; + } + }), + ), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: Text(dialogContext.l10n.channels_cyr2latCompression), + subtitle: Text( + dialogContext.l10n.channels_cyr2latCompressionDscr, + ), + value: cyr2latEnabled, + onChanged: (value) => setState(() { + cyr2latEnabled = value; + if (cyr2latEnabled) { + smazEnabled = false; + } + }), ), ], ), @@ -1482,6 +1502,10 @@ class _ChannelsScreenState extends State channel.index, smazEnabled, ); + await connector.setChannelCyr2LatEnabled( + channel.index, + cyr2latEnabled, + ); if (!context.mounted) return; showDismissibleSnackBar( context, diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index ffa8344..b9cdf0e 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -13,6 +13,7 @@ import 'package:latlong2/latlong.dart'; import '../connector/meshcore_connector.dart'; import '../connector/meshcore_protocol.dart'; +import '../helpers/cyr2lat.dart'; import '../helpers/reaction_helper.dart'; import '../widgets/message_status_icon.dart'; import '../helpers/chat_scroll_controller.dart'; @@ -574,9 +575,12 @@ class _ChatScreenState extends State { hintText: context.l10n.chat_typeMessage, onSubmitted: (_) => _sendMessage(connector), encoder: - connector.isContactSmazEnabled( - widget.contact.publicKeyHex, - ) + (connector.isContactSmazEnabled( + widget.contact.publicKeyHex, + ) || + connector.isContactCyr2LatEnabled( + widget.contact.publicKeyHex, + )) ? (text) => connector.prepareContactOutboundText( widget.contact, text, @@ -695,6 +699,18 @@ class _ChatScreenState extends State { return; } + // This is only for cyr2lat compression - to see the message being sent in the same format as the other person will receive + try { + if (connector.isContactCyr2LatEnabled( + _resolveContact(connector).publicKeyHex, + )) { + outgoingText = Cyr2Lat.encode(outgoingText); + } + } catch (_) { + // TODO maybe log + } + // end transform + _textController.clear(); _textFieldFocusNode.requestFocus(); connector.sendMessage( @@ -1196,8 +1212,12 @@ class _ChatScreenState extends State { void _showContactSettings(BuildContext context) { final connector = Provider.of(context, listen: false); connector.ensureContactSmazSettingLoaded(widget.contact.publicKeyHex); + connector.ensureContactCyr2LatSettingLoaded(widget.contact.publicKeyHex); final contact = widget.contact; bool smazEnabled = connector.isContactSmazEnabled(contact.publicKeyHex); + bool cyr2latEnabled = connector.isContactCyr2LatEnabled( + contact.publicKeyHex, + ); bool teleBaseEnabled = contact.teleBaseEnabled; bool teleLocEnabled = contact.teleLocEnabled; bool teleEnvEnabled = contact.teleEnvEnabled; @@ -1228,7 +1248,39 @@ class _ChatScreenState extends State { contact.publicKeyHex, value, ); - setDialogState(() => smazEnabled = value); + connector.setContactCyr2LatEnabled( + contact.publicKeyHex, + false, + ); + setDialogState(() { + smazEnabled = value; + if (smazEnabled) { + cyr2latEnabled = false; + } + }); + }, + ), + const Divider(height: 8), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: Text(context.l10n.channels_cyr2latCompression), + subtitle: Text(context.l10n.channels_cyr2latCompressionDscr), + value: cyr2latEnabled, + onChanged: (value) { + connector.setContactCyr2LatEnabled( + contact.publicKeyHex, + value, + ); + connector.setContactSmazEnabled( + contact.publicKeyHex, + false, + ); + setDialogState(() { + cyr2latEnabled = value; + if (cyr2latEnabled) { + smazEnabled = false; + } + }); }, ), const Divider(height: 8), diff --git a/lib/services/app_settings_service.dart b/lib/services/app_settings_service.dart index 283ce72..820fe0e 100644 --- a/lib/services/app_settings_service.dart +++ b/lib/services/app_settings_service.dart @@ -4,6 +4,7 @@ import '../models/app_settings.dart'; import '../models/translation_support.dart'; import '../storage/prefs_manager.dart'; import '../utils/app_logger.dart'; +import '../helpers/cyr2lat.dart'; class AppSettingsService extends ChangeNotifier { static const String _settingsKey = 'app_settings'; @@ -32,16 +33,22 @@ class AppSettingsService extends ChangeNotifier { try { final json = jsonDecode(jsonStr) as Map; _settings = AppSettings.fromJson(json); + Cyr2Lat.setCharMap(_settings.cyr2latCharMap); notifyListeners(); } catch (e) { // If parsing fails, use defaults _settings = AppSettings(); + Cyr2Lat.setCharMap(_settings.cyr2latCharMap); } + } else { + _settings = AppSettings(); + Cyr2Lat.setCharMap(_settings.cyr2latCharMap); } } Future updateSettings(AppSettings newSettings) async { _settings = newSettings; + Cyr2Lat.setCharMap(_settings.cyr2latCharMap); notifyListeners(); final prefs = PrefsManager.instance; diff --git a/lib/storage/channel_settings_store.dart b/lib/storage/channel_settings_store.dart index 276826d..eb637ac 100644 --- a/lib/storage/channel_settings_store.dart +++ b/lib/storage/channel_settings_store.dart @@ -3,12 +3,14 @@ import 'prefs_manager.dart'; class ChannelSettingsStore { static const String _keyPrefix = 'channel_smaz_'; + static const String _cyr2latKeyPrefix = 'channel_cyr2lat_'; String publicKeyHex = ''; set setPublicKeyHex(String value) => publicKeyHex = value.length > 10 ? value.substring(0, 10) : ''; String get keyFor => '$_keyPrefix$publicKeyHex'; + String get keyForCyr2Lat => '$_cyr2latKeyPrefix$publicKeyHex'; Future loadSmazEnabled(int channelIndex) async { if (publicKeyHex.isEmpty) { @@ -20,7 +22,7 @@ class ChannelSettingsStore { final prefs = PrefsManager.instance; final key = '$keyFor$channelIndex'; final oldKey = '$_keyPrefix$channelIndex'; - bool? enabled = prefs.getBool(oldKey); + bool? enabled = prefs.getBool(key); if (enabled == null) { // Attempt migration from legacy unscoped key on first load enabled = prefs.getBool(oldKey); @@ -46,4 +48,41 @@ class ChannelSettingsStore { final key = '$keyFor$channelIndex'; await prefs.setBool(key, enabled); } + + Future loadCyr2LatEnabled(int channelIndex) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot load channel Cyr2Lat settings.', + ); + return false; + } + final prefs = PrefsManager.instance; + final key = '$keyForCyr2Lat$channelIndex'; + final oldKey = '$_cyr2latKeyPrefix$channelIndex'; + bool? enabled = prefs.getBool(key); + if (enabled == null) { + // Attempt migration from legacy unscoped key on first load + enabled = prefs.getBool(oldKey); + prefs.remove(oldKey); + if (enabled != null) { + appLogger.info( + 'Migrating channel Cyr2Lat settings from legacy key $oldKey to scoped key $key', + ); + await prefs.setBool(key, enabled); + } + } + return enabled ?? false; + } + + Future saveCyr2LatEnabled(int channelIndex, bool enabled) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot save channel Cyr2Lat settings.', + ); + return; + } + final prefs = PrefsManager.instance; + final key = '$keyForCyr2Lat$channelIndex'; + await prefs.setBool(key, enabled); + } } diff --git a/lib/storage/contact_settings_store.dart b/lib/storage/contact_settings_store.dart index 94c6430..5db15ad 100644 --- a/lib/storage/contact_settings_store.dart +++ b/lib/storage/contact_settings_store.dart @@ -3,12 +3,14 @@ import 'prefs_manager.dart'; class ContactSettingsStore { static const String _keyPrefix = 'contact_smaz_'; + static const String _cyr2latKeyPrefix = 'contact_cyr2lat_'; String publicKeyHex = ''; set setPublicKeyHex(String value) => publicKeyHex = value.length > 10 ? value.substring(0, 10) : ''; String get keyFor => '$_keyPrefix$publicKeyHex'; + String get keyForCyr2Lat => '$_cyr2latKeyPrefix$publicKeyHex'; Future loadSmazEnabled(String contactKeyHex) async { if (publicKeyHex.isEmpty) { @@ -46,4 +48,41 @@ class ContactSettingsStore { final key = '$keyFor$contactKeyHex'; await prefs.setBool(key, enabled); } + + Future loadCyr2LatEnabled(String contactKeyHex) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot load contact Cyr2Lat settings.', + ); + return false; + } + final prefs = PrefsManager.instance; + final key = '$keyForCyr2Lat$contactKeyHex'; + final oldKey = '$_cyr2latKeyPrefix$contactKeyHex'; + bool? enabled = prefs.getBool(key); + if (enabled == null) { + // Attempt migration from legacy unscoped key on first load + enabled = prefs.getBool(oldKey); + prefs.remove(oldKey); + if (enabled != null) { + appLogger.info( + 'Migrating contact Cyr2Lat settings from legacy key $oldKey to scoped key $key', + ); + await prefs.setBool(key, enabled); + } + } + return prefs.getBool(key) ?? false; + } + + Future saveCyr2LatEnabled(String contactKeyHex, bool enabled) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot save contact Cyr2Lat settings.', + ); + return; + } + final prefs = PrefsManager.instance; + final key = '$keyForCyr2Lat$contactKeyHex'; + await prefs.setBool(key, enabled); + } } From 5e446207c655b202ca9c7ad3415420c011d2c915 Mon Sep 17 00:00:00 2001 From: Serge Tarkovski Date: Thu, 23 Apr 2026 17:27:39 +0300 Subject: [PATCH 07/51] 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 | 30 +- 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, 2542 insertions(+), 416 deletions(-) diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 7ac5417..e1a955b 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 4695505..885d2de 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 8ad6bf3..5b8b2c4 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 ac9527b..e830630 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 a942aa2..ad74eb2 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 6f43463..89d7389 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 387c8cf..13069a5 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 b63f146..b6736b1 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 40721fe..d2e46a4 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 2c1342d..d12fe26 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 b3e1279..81df50a 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 d7c1691..aec7223 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 a2a88b0..00926c0 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 a127012..c9bf19a 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 a006391..f88101f 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 1ad8558..f6642ca 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 3a55559..a4d0470 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 afb8c29..d93481b 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 ff4bd26..e8d5d64 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 dd770e1..db1701e 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 357dd7e..aa8db9c 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 2dfcd8b..483447d 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 4fac42c..743ffbe 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 c42e024..6deb5e3 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 2d89aa4..eee5443 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 38e0893..cc4b77c 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 8ed4b9f..834db15 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_path => 'Шлях'; @override - String get map_setAsMyLocation => 'Встановити моє місцезнаходження'; + 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 4f38c64..c28b980 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 96bdb84..9dd31bd 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 b62a78a..b918071 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 bf3e893..5de4b12 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 a83d139..07bf525 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 e4466c3..61fc64f 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 f6a317e..f633914 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 eab348c..d5947cb 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 a005b36..f0cfd36 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 9dc2325..03fa71f 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 2699f93..020e429 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 { + 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 'Flood (forced)'; - if (pathOverride == 0) return 'Direct (forced)'; - return '$pathOverride hops (forced)'; + if (pathOverride! < 0) return l10n.chat_floodForced; + if (pathOverride == 0) return l10n.chat_directForced; + return l10n.chat_hopsForced(pathOverride!); } - if (pathLength < 0) return 'Flood'; - if (pathLength == 0) return 'Direct'; - return '$pathLength hops'; + 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 80d8adb..5d81e1e 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 b203cbb..0454f17 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 44c7a69..220a5af 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 ffa8344..2d5a448 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 54d3299..5b855cd 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 6a8acda..afc592f 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 0dc141c..0fa87cc 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 47b9b9c..546bc4d 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 6202b3b..04468ef 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 094805a..bc7623e 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 7a890ec..e061e73 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 e8f4795..584eabe 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 d38bd03..d2cfce0 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 4d7f29e..1a5ff7f 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 08/51] #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 7ac5417..7260c25 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 4695505..bf8ad2e 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 8ad6bf3..606410c 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 ac9527b..b058568 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 a942aa2..fa375a4 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 6f43463..ae6acfb 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 387c8cf..35001e6 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 b63f146..7ac449d 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 40721fe..54073db 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 2c1342d..0907557 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 b3e1279..01973df 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 d7c1691..60cd325 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 a2a88b0..353e5bc 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 a127012..d656ac4 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 a006391..acdc945 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 1ad8558..24c9ac6 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 3a55559..abb1427 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 afb8c29..6001826 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 ff4bd26..8f74af8 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 dd770e1..d1ebea4 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 357dd7e..918860e 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 2dfcd8b..5584da9 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 4fac42c..4b7e3d4 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 c42e024..f855652 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 2d89aa4..2f3b30c 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 38e0893..00816f2 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 8ed4b9f..f43fd53 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 4f38c64..6181e8a 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 96bdb84..fa3eb37 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 b62a78a..870be9e 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 bf3e893..634cf1c 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 a83d139..1ad0f1b 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 e4466c3..9cd6072 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 f6a317e..cace8c6 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 eab348c..aaa6ea7 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 a005b36..489b8a2 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 9dc2325..9d6a8bf 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 47b9b9c..9a1aca6 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 58b4d01..481f1bc 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 09/51] 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 606410c..27ae16a 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 10/51] 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 27ae16a..606410c 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 11/51] 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 e1a955b..963be1c 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 885d2de..4864daa 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 5b8b2c4..b0f03b5 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 e830630..b7e71b4 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 ad74eb2..ad15c87 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 89d7389..f341fd9 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 13069a5..f6724f3 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 b6736b1..973107a 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 d2e46a4..fe639e8 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 d12fe26..45b3b46 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 81df50a..9db1c88 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 aec7223..e29a0fe 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 00926c0..faa40f0 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 c9bf19a..65a3692 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 f88101f..df53a2f 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 f6642ca..5e939d7 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 a4d0470..7f285c3 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 d93481b..17e9759 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 e8d5d64..d4872b1 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 db1701e..448053e 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 aa8db9c..f092c15 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 483447d..8c87050 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 743ffbe..a77f1f4 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 6deb5e3..222251b 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 eee5443..4053b91 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 cc4b77c..b29fdf4 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 834db15..b0c6212 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 c28b980..164abb9 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 9dd31bd..eaa593b 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 b918071..ac678c1 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 5de4b12..7993f02 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 07bf525..5124659 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 61fc64f..26dd1e7 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 f633914..3e0fdf6 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 d5947cb..73b7227 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 f0cfd36..b47bfcb 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 03fa71f..b88a9af 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 0454f17..6bd89c2 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 bc7623e..c63ce76 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 12/51] 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 fceee15..ec887ea 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 0000000..d8344a3 --- /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 3e57eb1..cd62281 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 020e429..b5df2af 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 2d5a448..ec311f8 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 5b855cd..39ccfcd 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 afc592f..9d5c20a 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 0fa87cc..5b1e30c 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 04468ef..45be09b 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 296cc3a..1b82a72 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 c63ce76..a2122f4 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 e061e73..44ae58f 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 d2cfce0..0973fae 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 1a5ff7f..2641c02 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 13/51] 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 6bd89c2..330e3f0 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 45be09b..336ecdd 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 14/51] 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 1b82a72..e3a4289 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 0000000..87df23a --- /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 f56c28a27de69bcfc2d92d37a50c365ae09b14ec Mon Sep 17 00:00:00 2001 From: HDDen <62592944+HDDen@users.noreply.github.com> Date: Sat, 25 Apr 2026 01:30:55 +0300 Subject: [PATCH 15/51] synced with last dev, added profiles for cyr2lat replacement dictionaries --- lib/connector/meshcore_connector.dart | 89 +++++++++ lib/l10n/app_bg.arb | 12 +- lib/l10n/app_de.arb | 12 +- lib/l10n/app_en.arb | 19 +- lib/l10n/app_es.arb | 23 ++- lib/l10n/app_fr.arb | 23 ++- lib/l10n/app_hu.arb | 23 ++- lib/l10n/app_it.arb | 23 ++- lib/l10n/app_ja.arb | 23 ++- lib/l10n/app_ko.arb | 23 ++- lib/l10n/app_localizations.dart | 70 +++++-- lib/l10n/app_localizations_bg.dart | 43 ++-- lib/l10n/app_localizations_de.dart | 42 +++- lib/l10n/app_localizations_en.dart | 40 +++- lib/l10n/app_localizations_es.dart | 43 ++-- lib/l10n/app_localizations_fr.dart | 42 ++-- lib/l10n/app_localizations_hu.dart | 41 +++- lib/l10n/app_localizations_it.dart | 43 ++-- lib/l10n/app_localizations_ja.dart | 39 +++- lib/l10n/app_localizations_ko.dart | 39 +++- lib/l10n/app_localizations_nl.dart | 42 ++-- lib/l10n/app_localizations_pl.dart | 44 +++-- lib/l10n/app_localizations_pt.dart | 42 ++-- lib/l10n/app_localizations_ru.dart | 40 +++- lib/l10n/app_localizations_sk.dart | 43 ++-- lib/l10n/app_localizations_sl.dart | 42 +++- lib/l10n/app_localizations_sv.dart | 41 +++- lib/l10n/app_localizations_uk.dart | 40 +++- lib/l10n/app_localizations_zh.dart | 38 +++- lib/l10n/app_nl.arb | 23 ++- lib/l10n/app_pl.arb | 23 ++- lib/l10n/app_pt.arb | 21 +- lib/l10n/app_ru.arb | 23 ++- lib/l10n/app_sk.arb | 21 +- lib/l10n/app_sl.arb | 23 ++- lib/l10n/app_sv.arb | 23 ++- lib/l10n/app_uk.arb | 23 ++- lib/l10n/app_zh.arb | 21 +- lib/models/app_settings.dart | 110 +++++++++-- lib/screens/app_settings_screen.dart | 252 ++++++++++++++++++++---- lib/screens/channels_screen.dart | 36 ++++ lib/screens/chat_screen.dart | 37 ++++ lib/services/app_settings_service.dart | 52 +++++ lib/storage/channel_settings_store.dart | 43 ++-- lib/storage/contact_settings_store.dart | 44 +++-- 45 files changed, 1506 insertions(+), 353 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 6e3a1b9..2de74a6 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -285,10 +285,12 @@ class MeshCoreConnector extends ChangeNotifier { List _cachedChannels = []; final Map _channelSmazEnabled = {}; final Map _channelCyr2LatEnabled = {}; + final Map _channelCyr2LatProfileId = {}; bool _lastSentWasCliCommand = false; // Track if last sent message was a CLI command final Map _contactSmazEnabled = {}; final Map _contactCyr2LatEnabled = {}; + final Map _contactCyr2LatProfileId = {}; final Set _knownContactKeys = {}; final Map _contactUnreadCount = {}; final Map _repeaterBatterySnapshots = {}; @@ -611,10 +613,12 @@ class MeshCoreConnector extends ChangeNotifier { } bool isChannelCyr2LatEnabled(int channelIndex) { + _ensureChannelCyr2LatSettingLoaded(channelIndex); return _channelCyr2LatEnabled[channelIndex] ?? false; } bool isContactCyr2LatEnabled(String contactKeyHex) { + _ensureContactCyr2LatSettingLoaded(contactKeyHex); return _contactCyr2LatEnabled[contactKeyHex] ?? false; } @@ -4483,6 +4487,63 @@ class MeshCoreConnector extends ChangeNotifier { }); } + void _ensureContactCyr2LatProfileLoaded(String contactKeyHex) { + if (_contactCyr2LatProfileId.containsKey(contactKeyHex)) return; + _contactSettingsStore.loadCyr2LatProfileId(contactKeyHex).then((profileId) { + if (_contactCyr2LatProfileId[contactKeyHex] == profileId) return; + _contactCyr2LatProfileId[contactKeyHex] = profileId; + notifyListeners(); + }); + } + + void _ensureChannelCyr2LatSettingLoaded(int channelIndex) { + if (_channelCyr2LatEnabled.containsKey(channelIndex)) return; + _channelSettingsStore.loadCyr2LatEnabled(channelIndex).then((enabled) { + if (_channelCyr2LatEnabled[channelIndex] == enabled) return; + _channelCyr2LatEnabled[channelIndex] = enabled; + notifyListeners(); + }); + } + + void _ensureChannelCyr2LatProfileLoaded(int channelIndex) { + if (_channelCyr2LatProfileId.containsKey(channelIndex)) return; + _channelSettingsStore.loadCyr2LatProfileId(channelIndex).then((profileId) { + if (_channelCyr2LatProfileId[channelIndex] == profileId) return; + _channelCyr2LatProfileId[channelIndex] = profileId; + notifyListeners(); + }); + } + + String? getChannelCyr2LatProfileId(int channelIndex) { + _ensureChannelCyr2LatProfileLoaded(channelIndex); + return _channelCyr2LatProfileId[channelIndex]; + } + + Future setChannelCyr2LatProfileId( + int channelIndex, + String? profileId, + ) async { + if (_channelCyr2LatProfileId[channelIndex] == profileId) return; + _channelCyr2LatProfileId[channelIndex] = profileId; + await _channelSettingsStore.saveCyr2LatProfileId(channelIndex, profileId); + notifyListeners(); + } + + String? getContactCyr2LatProfileId(String contactKeyHex) { + _ensureContactCyr2LatProfileLoaded(contactKeyHex); + return _contactCyr2LatProfileId[contactKeyHex]; + } + + Future setContactCyr2LatProfileId( + String contactKeyHex, + String? profileId, + ) async { + if (_contactCyr2LatProfileId[contactKeyHex] == profileId) return; + _contactCyr2LatProfileId[contactKeyHex] = profileId; + await _contactSettingsStore.saveCyr2LatProfileId(contactKeyHex, profileId); + notifyListeners(); + } + /// Prepares contact outbound text by applying SMAZ encoding if enabled. /// This should be used to transform text before computing ACK hashes. String prepareContactOutboundText(Contact contact, String text) { @@ -4495,6 +4556,20 @@ class MeshCoreConnector extends ChangeNotifier { if (isContactSmazEnabled(contact.publicKeyHex)) { return Smaz.encodeIfSmaller(text); } else if (isContactCyr2LatEnabled(contact.publicKeyHex)) { + final profileId = getContactCyr2LatProfileId(contact.publicKeyHex); + final profile = profileId != null && _appSettingsService != null + ? _appSettingsService!.getCyr2LatProfileById(profileId) + : null; + if (profile != null) { + Cyr2Lat.setCharMap(profile.charMap); + } else { + // Use global profile + final globalProfile = _appSettingsService + ?.getSelectedCyr2LatProfile(); + if (globalProfile != null) { + Cyr2Lat.setCharMap(globalProfile.charMap); + } + } return Cyr2Lat.encode(text); } } @@ -4509,6 +4584,20 @@ class MeshCoreConnector extends ChangeNotifier { if (isChannelSmazEnabled(channelIndex)) { return Smaz.encodeIfSmaller(text); } else if (isChannelCyr2LatEnabled(channelIndex)) { + final profileId = getChannelCyr2LatProfileId(channelIndex); + final profile = profileId != null && _appSettingsService != null + ? _appSettingsService!.getCyr2LatProfileById(profileId) + : null; + if (profile != null) { + Cyr2Lat.setCharMap(profile.charMap); + } else { + // Use global profile + final globalProfile = _appSettingsService + ?.getSelectedCyr2LatProfile(); + if (globalProfile != null) { + Cyr2Lat.setCharMap(globalProfile.charMap); + } + } return Cyr2Lat.encode(text); } } diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 8747538..c6ee97f 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Списък със замествания", "channels_cyr2latSettingsDscr": "Редактиране на JSON конфигурацията за заместване на символи", "channels_cyr2latSettingsDialogHint": "JSON карта за замествания", - "channels_cyr2latSettingsDialogSuccess": "Списъкът със замествания е актуализиран", "channels_cyr2latSettingsDialogWrongJSON": "Неправилен JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Възстановяване на първоначалните настройки", - "channels_cyr2latSettingsDialogResetted": "Настройките за заместване на Cyr2Lat са възстановени към първоначалните", + "settings_cyr2latProfileAdd": "Добавяне на профил Cyr2Lat", + "settings_cyr2latProfileName": "Име на профила", + "settings_cyr2latProfileNameEmpty": "Името на профила не може да бъде празно", + "settings_cyr2latProfileAdded": "Профилът е добавен успешно", + "settings_cyr2latProfileUpdated": "Профилът е актуализиран успешно", + "settings_cyr2latProfileEdit": "Редактиране на Cyr2Lat профил", + "settings_cyr2latProfileDelete": "Изтриване на профил Cyr2Lat", + "settings_cyr2latProfileDeleted": "Профилът беше изтрит успешно", + "settings_cyr2latProfileDeleteDscr": "Сигурен ли сте, че искате да изтриете профила \"{name}\"?", "channels_channelUpdated": "Каналът \"{name}\" е актуализиран", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index cd591f6..d0ea612 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Ersetzungsliste", "channels_cyr2latSettingsDscr": "JSON-Konfiguration für die Zeichenersetzung bearbeiten", "channels_cyr2latSettingsDialogHint": "JSON-Ersetzungstabelle", - "channels_cyr2latSettingsDialogSuccess": "Ersetzungsliste aktualisiert", "channels_cyr2latSettingsDialogWrongJSON": "Ungültiges JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Auf Standard zurücksetzen", - "channels_cyr2latSettingsDialogResetted": "Die Cyr2Lat-Ersetzungseinstellungen wurden auf die Standardeinstellungen zurückgesetzt", + "settings_cyr2latProfileAdd": "Cyr2Lat-Profil hinzufügen", + "settings_cyr2latProfileName": "Profilname", + "settings_cyr2latProfileNameEmpty": "Der Profilname darf nicht leer sein", + "settings_cyr2latProfileAdded": "Profil erfolgreich hinzugefügt", + "settings_cyr2latProfileUpdated": "Profil erfolgreich aktualisiert", + "settings_cyr2latProfileEdit": "Cyr2Lat-Profil bearbeiten", + "settings_cyr2latProfileDelete": "Cyr2Lat-Profil löschen", + "settings_cyr2latProfileDeleted": "Profil erfolgreich gelöscht", + "settings_cyr2latProfileDeleteDscr": "Möchten Sie das Profil \"{name}\" wirklich löschen?", "channels_channelUpdated": "Kanal \"{name}\" aktualisiert", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 217f783..8d7addb 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -579,15 +579,12 @@ "channels_cyr2latSettingsSubheading": "List of replacements", "channels_cyr2latSettingsDscr": "Edit the JSON configuration of character replacement", "channels_cyr2latSettingsDialogHint": "JSON replacement map", - "channels_cyr2latSettingsDialogSuccess": "The list of replacements has been updated", "channels_cyr2latSettingsDialogWrongJSON": "Invalid JSON: {error}", "@channels_cyr2latSettingsDialogWrongJSON": { "placeholders": { "error": {} } }, - "channels_cyr2latSettingsDialogReset": "Reset to default", - "channels_cyr2latSettingsDialogResetted": "Cyr2Lat replacement settings reset to default", "channels_channelUpdated": "Channel \"{name}\" updated", "@channels_channelUpdated": { "placeholders": { @@ -596,6 +593,22 @@ } } }, + "settings_cyr2latProfileAdd": "Add Cyr2Lat Profile", + "settings_cyr2latProfileName": "Profile Name", + "settings_cyr2latProfileNameEmpty": "Profile name cannot be empty", + "settings_cyr2latProfileAdded": "Profile added successfully", + "settings_cyr2latProfileUpdated": "Profile updated successfully", + "settings_cyr2latProfileEdit": "Edit Cyr2Lat Profile", + "settings_cyr2latProfileDelete": "Delete Cyr2Lat Profile", + "settings_cyr2latProfileDeleted": "Profile deleted successfully", + "settings_cyr2latProfileDeleteDscr": "Are you sure you want to delete the profile \"{name}\"?", + "@settings_cyr2latProfileDeleteDscr": { + "placeholders": { + "name": { + "type": "String" + } + } + }, "channels_publicChannelAdded": "Public channel added", "channels_sortBy": "Sort by", "channels_sortManual": "Manual", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 41b5a55..0a949eb 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Lista de sustituciones", "channels_cyr2latSettingsDscr": "Editar la configuración JSON de sustitución de caracteres", "channels_cyr2latSettingsDialogHint": "Mapa JSON de sustituciones", - "channels_cyr2latSettingsDialogSuccess": "Lista de sustituciones actualizada", "channels_cyr2latSettingsDialogWrongJSON": "JSON incorrecto: {error}", - "channels_cyr2latSettingsDialogReset": "Restablecer valores predeterminados", - "channels_cyr2latSettingsDialogResetted": "La configuración de sustituciones de Cyr2Lat se ha restablecido a los valores predeterminados", + "settings_cyr2latProfileAdd": "Añadir perfil Cyr2Lat", + "settings_cyr2latProfileName": "Nombre del perfil", + "settings_cyr2latProfileNameEmpty": "El nombre del perfil no puede estar vacío", + "settings_cyr2latProfileAdded": "Perfil añadido correctamente", + "settings_cyr2latProfileUpdated": "Perfil actualizado correctamente", + "settings_cyr2latProfileEdit": "Editar perfil Cyr2Lat", + "settings_cyr2latProfileDelete": "Eliminar perfil Cyr2Lat", + "settings_cyr2latProfileDeleted": "Perfil eliminado correctamente", + "settings_cyr2latProfileDeleteDscr": "¿Está seguro de que desea eliminar el perfil \"{name}\"?", "channels_channelUpdated": "Canal \"{name}\" actualizado", "@channels_channelUpdated": { "placeholders": { @@ -1960,6 +1966,13 @@ "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", @@ -1972,6 +1985,7 @@ "appSettings_maxMessageRetriesSubtitle": "Número de intentos de reintento antes de marcar un mensaje como fallido.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modo de telemetría actualizado", + "settings_multiAck": "Multi-ACKs: {value}", "map_showOverlaps": "Superposiciones de tecla repetidora", "map_runTraceWithReturnPath": "Volver atrás por el mismo camino.", "@radioStats_noiseFloor": { @@ -2103,6 +2117,5 @@ "repeater_guest": "Información sobre repetidores", "chat_sendMessage": "Enviar mensaje", "repeater_guestTools": "Herramientas para invitados", - "room_guest": "Información del servidor", - "settings_multiAck": "Múltiples respuestas de confirmación" + "room_guest": "Información del servidor" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index ff2f2dc..e91fad8 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading" : "Liste des remplacements", "channels_cyr2latSettingsDscr" : "Modifier la configuration JSON des remplacements de caractères", "channels_cyr2latSettingsDialogHint": "Tableau de remplacement JSON", - "channels_cyr2latSettingsDialogSuccess": "Liste de remplacement mise à jour", "channels_cyr2latSettingsDialogWrongJSON": "JSON incorrect : {error}", - "channels_cyr2latSettingsDialogReset": "Réinitialiser les paramètres par défaut", - "channels_cyr2latSettingsDialogResetted": "Les paramètres de remplacement Cyr2Lat ont été réinitialisés aux valeurs par défaut", + "settings_cyr2latProfileAdd" : "Ajouter un profil Cyr2Lat", + "settings_cyr2latProfileName" : "Nom du profil", + "settings_cyr2latProfileNameEmpty" : "Le nom du profil ne peut pas être vide", + "settings_cyr2latProfileAdded": "Profil ajouté avec succès", + "settings_cyr2latProfileUpdated": "Profil mis à jour avec succès", + "settings_cyr2latProfileEdit": "Modifier le profil Cyr2Lat", + "settings_cyr2latProfileDelete" : "Supprimer le profil Cyr2Lat", + "settings_cyr2latProfileDeleted" : "Profil supprimé avec succès", + "settings_cyr2latProfileDeleteDscr" : "Êtes-vous sûr de vouloir supprimer le profil \"{name}\"?", "channels_channelUpdated": "Le canal \"{name}\" a été mis à jour", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "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", @@ -1943,6 +1956,7 @@ "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.", @@ -2075,6 +2089,5 @@ "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", - "settings_multiAck": "Plusieurs accusés de réception" + "repeater_guest": "Informations sur les répéteurs" } diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index 28e5afd..bd4a0bb 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -554,10 +554,16 @@ "channels_cyr2latSettingsSubheading": "Helyettesítési lista", "channels_cyr2latSettingsDscr": "A karakterhelyettesítési JSON-konfiguráció szerkesztése", "channels_cyr2latSettingsDialogHint": "JSON-csere táblázat", - "channels_cyr2latSettingsDialogSuccess": "A csere lista frissítve", "channels_cyr2latSettingsDialogWrongJSON": "Hibás JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Alapértelmezett értékekre állítás", - "channels_cyr2latSettingsDialogResetted": "A Cyr2Lat helyettesítési beállítások alapértelmezett értékekre lettek állítva", + "settings_cyr2latProfileAdd": "Cyr2Lat-profil hozzáadása", + "settings_cyr2latProfileName": "Profil neve", + "settings_cyr2latProfileNameEmpty": "A profil neve nem lehet üres", + "settings_cyr2latProfileAdded": "A profil hozzáadása sikeres", + "settings_cyr2latProfileUpdated": "A profil frissítése sikeres", + "settings_cyr2latProfileEdit": "Cyr2Lat profil szerkesztése", + "settings_cyr2latProfileDelete": "Cyr2Lat profil törlése", + "settings_cyr2latProfileDeleted": "A profil törlése sikeresen megtörtént", + "settings_cyr2latProfileDeleteDscr": "Biztosan törölni szeretné a \"{name}\" profilt?", "channels_channelUpdated": "A {name} csatorna frissítve", "@channels_channelUpdated": { "placeholders": { @@ -2022,6 +2028,13 @@ "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.", @@ -2033,6 +2046,7 @@ "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", @@ -2113,6 +2127,5 @@ "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", - "settings_multiAck": "Többszörös visszaigazolások" + "repeater_guest": "Adatok a repeaterről" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 93af4e6..414ee2b 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Elenco delle sostituzioni", "channels_cyr2latSettingsDscr": "Modifica la configurazione JSON delle sostituzioni dei caratteri", "channels_cyr2latSettingsDialogHint": "Mappa JSON delle sostituzioni", - "channels_cyr2latSettingsDialogSuccess": "Elenco delle sostituzioni aggiornato", "channels_cyr2latSettingsDialogWrongJSON": "JSON non corretto: {error}", - "channels_cyr2latSettingsDialogReset": "Ripristina impostazioni predefinite", - "channels_cyr2latSettingsDialogResetted": "Le impostazioni di sostituzione Cyr2Lat sono state ripristinate alle impostazioni predefinite", + "settings_cyr2latProfileAdd": "Aggiungi profilo Cyr2Lat", + "settings_cyr2latProfileName": "Nome profilo", + "settings_cyr2latProfileNameEmpty": "Il nome del profilo non può essere vuoto", + "settings_cyr2latProfileAdded": "Profilo aggiunto con successo", + "settings_cyr2latProfileUpdated": "Profilo aggiornato con successo", + "settings_cyr2latProfileEdit": "Modifica profilo Cyr2Lat", + "settings_cyr2latProfileDelete": "Elimina profilo Cyr2Lat", + "settings_cyr2latProfileDeleted": "Profilo eliminato con successo", + "settings_cyr2latProfileDeleteDscr": "Sei sicuro di voler eliminare il profilo \"{name}\"?", "channels_channelUpdated": "Canale \"{name}\" aggiornato", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "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.", @@ -1944,6 +1957,7 @@ "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": { @@ -2075,6 +2089,5 @@ "repeater_guest": "Informazioni sul ripetitore", "repeater_guestTools": "Strumenti per gli ospiti", "chat_sendMessage": "Invia messaggio", - "room_guest": "Informazioni sul server", - "settings_multiAck": "ACK multipli" + "room_guest": "Informazioni sul server" } diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index b00b462..4faebd7 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -554,10 +554,16 @@ "channels_cyr2latSettingsSubheading": "置換リスト", "channels_cyr2latSettingsDscr": "文字置換のJSON設定を編集する", "channels_cyr2latSettingsDialogHint": "JSON置換マップ", - "channels_cyr2latSettingsDialogSuccess": "置換リストが更新されました", "channels_cyr2latSettingsDialogWrongJSON": "不正なJSON: {error}", - "channels_cyr2latSettingsDialogReset": "初期設定に戻す", - "channels_cyr2latSettingsDialogResetted": "cyr2latの置換設定が初期設定に戻されました", + "settings_cyr2latProfileAdd": "Cyr2Latプロファイルの追加", + "settings_cyr2latProfileName": "プロファイル名", + "settings_cyr2latProfileNameEmpty": "プロファイル名は空にできません", + "settings_cyr2latProfileAdded": "プロファイルが正常に追加されました", + "settings_cyr2latProfileUpdated": "プロファイルの更新に成功しました", + "settings_cyr2latProfileEdit": "Cyr2Latプロファイルを編集", + "settings_cyr2latProfileDelete": "Cyr2Latプロファイルを削除", + "settings_cyr2latProfileDeleted": "プロファイルの削除に成功しました", + "settings_cyr2latProfileDeleteDscr": "プロファイル \"{name}\" を削除してもよろしいですか?", "channels_channelUpdated": "チャンネル「{name}」が更新されました", "@channels_channelUpdated": { "placeholders": { @@ -2022,6 +2028,13 @@ "radioStats_stripWaiting": "ラジオの統計情報を取得中…", "radioStats_settingsTile": "ラジオの統計", "radioStats_settingsSubtitle": "ノイズレベル、RSSI、SNR、および通信時間", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "settings_privacy": "プライバシー設定", "settings_privacySubtitle": "共有する情報の内容を管理する。", "settings_denyAll": "すべてを否定", @@ -2033,6 +2046,7 @@ "settings_telemetryEnvironmentMode": "テレメトリ環境モード", "settings_advertLocation": "広告掲載場所", "settings_advertLocationSubtitle": "広告に場所を記載してください。", + "settings_multiAck": "複数のACK:{value}", "settings_telemetryModeUpdated": "テレメトリモードが更新されました", "contact_info": "連絡先", "contact_settings": "連絡設定", @@ -2113,6 +2127,5 @@ "room_guest": "ルームサーバーに関する情報", "chat_sendMessage": "メッセージを送信する", "repeater_guest": "繰り返し送信に関する情報", - "repeater_guestTools": "ゲスト向けツール", - "settings_multiAck": "複数のACK(応答)" + "repeater_guestTools": "ゲスト向けツール" } diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index fd598f1..b2dc056 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -554,10 +554,16 @@ "channels_cyr2latSettingsSubheading": "변환 목록", "channels_cyr2latSettingsDscr": "문자 변환 JSON 구성 편집", "channels_cyr2latSettingsDialogHint": "JSON 변환 맵", - "channels_cyr2latSettingsDialogSuccess": "변환 목록이 업데이트되었습니다", "channels_cyr2latSettingsDialogWrongJSON": "잘못된 JSON: {error}", - "channels_cyr2latSettingsDialogReset": "초기값으로 초기화", - "channels_cyr2latSettingsDialogResetted": "Cyr2Lat 변환 설정이 초기값으로 초기화되었습니다", + "settings_cyr2latProfileAdd": "Cyr2Lat 프로필 추가", + "settings_cyr2latProfileName": "프로필 이름", + "settings_cyr2latProfileNameEmpty": "프로필 이름은 비워둘 수 없습니다", + "settings_cyr2latProfileAdded": "프로필이 성공적으로 추가되었습니다", + "settings_cyr2latProfileUpdated": "프로필이 성공적으로 업데이트되었습니다", + "settings_cyr2latProfileEdit": "Cyr2Lat 프로필 편집", + "settings_cyr2latProfileDelete": "Cyr2Lat 프로필 삭제", + "settings_cyr2latProfileDeleted": "프로필이 성공적으로 삭제되었습니다", + "settings_cyr2latProfileDeleteDscr": "\"{name}\" 프로필을 삭제하시겠습니까?", "channels_channelUpdated": "채널 \"{name}\"이 업데이트되었습니다.", "@channels_channelUpdated": { "placeholders": { @@ -2022,6 +2028,13 @@ "radioStats_stripWaiting": "라디오 통계 가져오기…", "radioStats_settingsTile": "라디오 통계", "radioStats_settingsSubtitle": "잡음 수준, RSSI, 신호 대 잡음비, 통신 시간", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "settings_privacy": "개인 정보 설정", "settings_privacySubtitle": "어떤 정보를 공유할지 통제하세요.", "settings_privacySettingsDescription": "어떤 정보를 기기가 다른 사람들과 공유할지 선택하세요.", @@ -2033,6 +2046,7 @@ "settings_telemetryEnvironmentMode": "텔레메트리 환경 모드", "settings_advertLocation": "광고 위치", "settings_advertLocationSubtitle": "광고에 위치 정보를 포함하세요.", + "settings_multiAck": "다중 ACK: {value}", "settings_telemetryModeUpdated": "텔레메트리 모드 업데이트 완료", "contact_info": "연락처", "contact_settings": "연락처 설정", @@ -2113,6 +2127,5 @@ "repeater_guestTools": "손님용 도구", "chat_sendMessage": "메시지를 보내기", "repeater_guest": "반복 장비 정보", - "room_guest": "서버 정보", - "settings_multiAck": "다중 ACK" + "room_guest": "서버 정보" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index dc641d7..e84ece6 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'** - String get settings_multiAck; + /// **'Multi-ACKs: {value}'** + String settings_multiAck(String value); /// No description provided for @settings_telemetryModeUpdated. /// @@ -2212,35 +2212,71 @@ abstract class AppLocalizations { /// **'JSON replacement map'** String get channels_cyr2latSettingsDialogHint; - /// No description provided for @channels_cyr2latSettingsDialogSuccess. - /// - /// In en, this message translates to: - /// **'The list of replacements has been updated'** - String get channels_cyr2latSettingsDialogSuccess; - /// No description provided for @channels_cyr2latSettingsDialogWrongJSON. /// /// In en, this message translates to: /// **'Invalid JSON: {error}'** String channels_cyr2latSettingsDialogWrongJSON(Object error); - /// No description provided for @channels_cyr2latSettingsDialogReset. + /// No description provided for @channels_channelUpdated. /// /// In en, this message translates to: - /// **'Reset to default'** - String get channels_cyr2latSettingsDialogReset; + /// **'Channel \"{name}\" updated'** + String channels_channelUpdated(String name); - /// No description provided for @channels_cyr2latSettingsDialogResetted. + /// No description provided for @settings_cyr2latProfileAdd. /// /// In en, this message translates to: - /// **'Cyr2Lat replacement settings reset to default'** - String get channels_cyr2latSettingsDialogResetted; + /// **'Add Cyr2Lat Profile'** + String get settings_cyr2latProfileAdd; - /// No description provided for @channels_channelUpdated. + /// No description provided for @settings_cyr2latProfileName. /// /// In en, this message translates to: - /// **'Channel \"{name}\" updated'** - String channels_channelUpdated(String name); + /// **'Profile Name'** + String get settings_cyr2latProfileName; + + /// No description provided for @settings_cyr2latProfileNameEmpty. + /// + /// In en, this message translates to: + /// **'Profile name cannot be empty'** + String get settings_cyr2latProfileNameEmpty; + + /// No description provided for @settings_cyr2latProfileAdded. + /// + /// In en, this message translates to: + /// **'Profile added successfully'** + String get settings_cyr2latProfileAdded; + + /// No description provided for @settings_cyr2latProfileUpdated. + /// + /// In en, this message translates to: + /// **'Profile updated successfully'** + String get settings_cyr2latProfileUpdated; + + /// No description provided for @settings_cyr2latProfileEdit. + /// + /// In en, this message translates to: + /// **'Edit Cyr2Lat Profile'** + String get settings_cyr2latProfileEdit; + + /// No description provided for @settings_cyr2latProfileDelete. + /// + /// In en, this message translates to: + /// **'Delete Cyr2Lat Profile'** + String get settings_cyr2latProfileDelete; + + /// No description provided for @settings_cyr2latProfileDeleted. + /// + /// In en, this message translates to: + /// **'Profile deleted successfully'** + String get settings_cyr2latProfileDeleted; + + /// No description provided for @settings_cyr2latProfileDeleteDscr. + /// + /// In en, this message translates to: + /// **'Are you sure you want to delete the profile \"{name}\"?'** + String settings_cyr2latProfileDeleteDscr(String name); /// No description provided for @channels_publicChannelAdded. /// diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 5dd7131..0c1cc08 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -437,7 +437,9 @@ class AppLocalizationsBg extends AppLocalizations { 'Включи местоположение в обявата'; @override - String get settings_multiAck => 'Множество потвърждения'; + String settings_multiAck(String value) { + return 'Мулти-потвърди: $value'; + } @override String get settings_telemetryModeUpdated => 'Режим на телеметрията е обновен'; @@ -1192,26 +1194,45 @@ class AppLocalizationsBg extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON карта за замествания'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Списъкът със замествания е актуализиран'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Неправилен JSON: $error'; } @override - String get channels_cyr2latSettingsDialogReset => - 'Възстановяване на първоначалните настройки'; + String channels_channelUpdated(String name) { + return 'Каналът \"$name\" е актуализиран'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'Настройките за заместване на Cyr2Lat са възстановени към първоначалните'; + String get settings_cyr2latProfileAdd => 'Добавяне на профил Cyr2Lat'; @override - String channels_channelUpdated(String name) { - return 'Каналът \"$name\" е актуализиран'; + String get settings_cyr2latProfileName => 'Име на профила'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Името на профила не може да бъде празно'; + + @override + String get settings_cyr2latProfileAdded => 'Профилът е добавен успешно'; + + @override + String get settings_cyr2latProfileUpdated => + 'Профилът е актуализиран успешно'; + + @override + String get settings_cyr2latProfileEdit => 'Редактиране на Cyr2Lat профил'; + + @override + String get settings_cyr2latProfileDelete => 'Изтриване на профил Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Профилът беше изтрит успешно'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Сигурен ли сте, че искате да изтриете профила \"$name\"?'; } @override diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index e3a3504..6fdbdd9 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -435,7 +435,9 @@ class AppLocalizationsDe extends AppLocalizations { 'Ort in der Anzeige einbeziehen'; @override - String get settings_multiAck => 'Mehrere Bestätigungen'; + String settings_multiAck(String value) { + return 'Mehrfach-Bestätigungen: $value'; + } @override String get settings_telemetryModeUpdated => 'Telemetriemodus aktualisiert'; @@ -1188,25 +1190,45 @@ class AppLocalizationsDe extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON-Ersetzungstabelle'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Ersetzungsliste aktualisiert'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Ungültiges JSON: $error'; } @override - String get channels_cyr2latSettingsDialogReset => 'Auf Standard zurücksetzen'; + String channels_channelUpdated(String name) { + return 'Kanal \"$name\" aktualisiert'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'Die Cyr2Lat-Ersetzungseinstellungen wurden auf die Standardeinstellungen zurückgesetzt'; + String get settings_cyr2latProfileAdd => 'Cyr2Lat-Profil hinzufügen'; @override - String channels_channelUpdated(String name) { - return 'Kanal \"$name\" aktualisiert'; + String get settings_cyr2latProfileName => 'Profilname'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Der Profilname darf nicht leer sein'; + + @override + String get settings_cyr2latProfileAdded => 'Profil erfolgreich hinzugefügt'; + + @override + String get settings_cyr2latProfileUpdated => + 'Profil erfolgreich aktualisiert'; + + @override + String get settings_cyr2latProfileEdit => 'Cyr2Lat-Profil bearbeiten'; + + @override + String get settings_cyr2latProfileDelete => 'Cyr2Lat-Profil löschen'; + + @override + String get settings_cyr2latProfileDeleted => 'Profil erfolgreich gelöscht'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Möchten Sie das Profil \"$name\" wirklich löschen?'; } @override diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 8f7cfd1..36fbe87 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -427,7 +427,9 @@ class AppLocalizationsEn extends AppLocalizations { String get settings_advertLocationSubtitle => 'Include location in advert.'; @override - String get settings_multiAck => 'Multi-ACKs'; + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } @override String get settings_telemetryModeUpdated => 'Telemetry mode updated'; @@ -1168,25 +1170,43 @@ class AppLocalizationsEn extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON replacement map'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'The list of replacements has been updated'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Invalid JSON: $error'; } @override - String get channels_cyr2latSettingsDialogReset => 'Reset to default'; + String channels_channelUpdated(String name) { + return 'Channel \"$name\" updated'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'Cyr2Lat replacement settings reset to default'; + String get settings_cyr2latProfileAdd => 'Add Cyr2Lat Profile'; @override - String channels_channelUpdated(String name) { - return 'Channel \"$name\" updated'; + String get settings_cyr2latProfileName => 'Profile Name'; + + @override + String get settings_cyr2latProfileNameEmpty => 'Profile name cannot be empty'; + + @override + String get settings_cyr2latProfileAdded => 'Profile added successfully'; + + @override + String get settings_cyr2latProfileUpdated => 'Profile updated successfully'; + + @override + String get settings_cyr2latProfileEdit => 'Edit Cyr2Lat Profile'; + + @override + String get settings_cyr2latProfileDelete => 'Delete Cyr2Lat Profile'; + + @override + String get settings_cyr2latProfileDeleted => 'Profile deleted successfully'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Are you sure you want to delete the profile \"$name\"?'; } @override diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 432cfad..469b777 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -434,7 +434,9 @@ class AppLocalizationsEs extends AppLocalizations { String get settings_advertLocationSubtitle => 'Incluir ubicación en anuncio'; @override - String get settings_multiAck => 'Múltiples respuestas de confirmación'; + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } @override String get settings_telemetryModeUpdated => 'Modo de telemetría actualizado'; @@ -1190,26 +1192,45 @@ class AppLocalizationsEs extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'Mapa JSON de sustituciones'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Lista de sustituciones actualizada'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'JSON incorrecto: $error'; } @override - String get channels_cyr2latSettingsDialogReset => - 'Restablecer valores predeterminados'; + String channels_channelUpdated(String name) { + return 'Canal \"$name\" actualizado'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'La configuración de sustituciones de Cyr2Lat se ha restablecido a los valores predeterminados'; + String get settings_cyr2latProfileAdd => 'Añadir perfil Cyr2Lat'; @override - String channels_channelUpdated(String name) { - return 'Canal \"$name\" actualizado'; + String get settings_cyr2latProfileName => 'Nombre del perfil'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'El nombre del perfil no puede estar vacío'; + + @override + String get settings_cyr2latProfileAdded => 'Perfil añadido correctamente'; + + @override + String get settings_cyr2latProfileUpdated => + 'Perfil actualizado correctamente'; + + @override + String get settings_cyr2latProfileEdit => 'Editar perfil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Eliminar perfil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Perfil eliminado correctamente'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return '¿Está seguro de que desea eliminar el perfil \"$name\"?'; } @override diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index a86c72b..addba54 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -438,7 +438,9 @@ class AppLocalizationsFr extends AppLocalizations { 'Inclure l\'emplacement dans l\'annonce'; @override - String get settings_multiAck => 'Plusieurs accusés de réception'; + String settings_multiAck(String value) { + return 'Multi-ACKs : $value'; + } @override String get settings_telemetryModeUpdated => @@ -1196,26 +1198,44 @@ class AppLocalizationsFr extends AppLocalizations { String get channels_cyr2latSettingsDialogHint => 'Tableau de remplacement JSON'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Liste de remplacement mise à jour'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'JSON incorrect : $error'; } @override - String get channels_cyr2latSettingsDialogReset => - 'Réinitialiser les paramètres par défaut'; + String channels_channelUpdated(String name) { + return 'Le canal \"$name\" a été mis à jour'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'Les paramètres de remplacement Cyr2Lat ont été réinitialisés aux valeurs par défaut'; + String get settings_cyr2latProfileAdd => 'Ajouter un profil Cyr2Lat'; @override - String channels_channelUpdated(String name) { - return 'Le canal \"$name\" a été mis à jour'; + String get settings_cyr2latProfileName => 'Nom du profil'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Le nom du profil ne peut pas être vide'; + + @override + String get settings_cyr2latProfileAdded => 'Profil ajouté avec succès'; + + @override + String get settings_cyr2latProfileUpdated => 'Profil mis à jour avec succès'; + + @override + String get settings_cyr2latProfileEdit => 'Modifier le profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Supprimer le profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Profil supprimé avec succès'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Êtes-vous sûr de vouloir supprimer le profil \"$name\"?'; } @override diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index b61afab..8285057 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -437,7 +437,9 @@ class AppLocalizationsHu extends AppLocalizations { 'A hirdetés tartalmazza a helyszínt.'; @override - String get settings_multiAck => 'Többszörös visszaigazolások'; + String settings_multiAck(String value) { + return 'Többszöri visszaigazolások: $value'; + } @override String get settings_telemetryModeUpdated => 'A telemetriamód frissítve'; @@ -1195,25 +1197,44 @@ class AppLocalizationsHu extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON-csere táblázat'; - @override - String get channels_cyr2latSettingsDialogSuccess => 'A csere lista frissítve'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Hibás JSON: $error'; } @override - String get channels_cyr2latSettingsDialogReset => - 'Alapértelmezett értékekre állítás'; + String channels_channelUpdated(String name) { + return 'A $name csatorna frissítve'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'A Cyr2Lat helyettesítési beállítások alapértelmezett értékekre lettek állítva'; + String get settings_cyr2latProfileAdd => 'Cyr2Lat-profil hozzáadása'; @override - String channels_channelUpdated(String name) { - return 'A $name csatorna frissítve'; + String get settings_cyr2latProfileName => 'Profil neve'; + + @override + String get settings_cyr2latProfileNameEmpty => 'A profil neve nem lehet üres'; + + @override + String get settings_cyr2latProfileAdded => 'A profil hozzáadása sikeres'; + + @override + String get settings_cyr2latProfileUpdated => 'A profil frissítése sikeres'; + + @override + String get settings_cyr2latProfileEdit => 'Cyr2Lat profil szerkesztése'; + + @override + String get settings_cyr2latProfileDelete => 'Cyr2Lat profil törlése'; + + @override + String get settings_cyr2latProfileDeleted => + 'A profil törlése sikeresen megtörtént'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Biztosan törölni szeretné a \"$name\" profilt?'; } @override diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 3df5b4b..567e9dc 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -437,7 +437,9 @@ class AppLocalizationsIt extends AppLocalizations { 'Includi la posizione nell\'annuncio'; @override - String get settings_multiAck => 'ACK multipli'; + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } @override String get settings_telemetryModeUpdated => 'Modalità telemetria aggiornata'; @@ -1192,26 +1194,45 @@ class AppLocalizationsIt extends AppLocalizations { String get channels_cyr2latSettingsDialogHint => 'Mappa JSON delle sostituzioni'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Elenco delle sostituzioni aggiornato'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'JSON non corretto: $error'; } @override - String get channels_cyr2latSettingsDialogReset => - 'Ripristina impostazioni predefinite'; + String channels_channelUpdated(String name) { + return 'Canale \"$name\" aggiornato'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'Le impostazioni di sostituzione Cyr2Lat sono state ripristinate alle impostazioni predefinite'; + String get settings_cyr2latProfileAdd => 'Aggiungi profilo Cyr2Lat'; @override - String channels_channelUpdated(String name) { - return 'Canale \"$name\" aggiornato'; + String get settings_cyr2latProfileName => 'Nome profilo'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Il nome del profilo non può essere vuoto'; + + @override + String get settings_cyr2latProfileAdded => 'Profilo aggiunto con successo'; + + @override + String get settings_cyr2latProfileUpdated => + 'Profilo aggiornato con successo'; + + @override + String get settings_cyr2latProfileEdit => 'Modifica profilo Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Elimina profilo Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Profilo eliminato con successo'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Sei sicuro di voler eliminare il profilo \"$name\"?'; } @override diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index e9ac268..55e755e 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -414,7 +414,9 @@ class AppLocalizationsJa extends AppLocalizations { String get settings_advertLocationSubtitle => '広告に場所を記載してください。'; @override - String get settings_multiAck => '複数のACK(応答)'; + String settings_multiAck(String value) { + return '複数のACK:$value'; + } @override String get settings_telemetryModeUpdated => 'テレメトリモードが更新されました'; @@ -1133,24 +1135,43 @@ class AppLocalizationsJa extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON置換マップ'; - @override - String get channels_cyr2latSettingsDialogSuccess => '置換リストが更新されました'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return '不正なJSON: $error'; } @override - String get channels_cyr2latSettingsDialogReset => '初期設定に戻す'; + String channels_channelUpdated(String name) { + return 'チャンネル「$name」が更新されました'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'cyr2latの置換設定が初期設定に戻されました'; + String get settings_cyr2latProfileAdd => 'Cyr2Latプロファイルの追加'; @override - String channels_channelUpdated(String name) { - return 'チャンネル「$name」が更新されました'; + String get settings_cyr2latProfileName => 'プロファイル名'; + + @override + String get settings_cyr2latProfileNameEmpty => 'プロファイル名は空にできません'; + + @override + String get settings_cyr2latProfileAdded => 'プロファイルが正常に追加されました'; + + @override + String get settings_cyr2latProfileUpdated => 'プロファイルの更新に成功しました'; + + @override + String get settings_cyr2latProfileEdit => 'Cyr2Latプロファイルを編集'; + + @override + String get settings_cyr2latProfileDelete => 'Cyr2Latプロファイルを削除'; + + @override + String get settings_cyr2latProfileDeleted => 'プロファイルの削除に成功しました'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'プロファイル \"$name\" を削除してもよろしいですか?'; } @override diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 83c9c97..b8ec8a6 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -414,7 +414,9 @@ class AppLocalizationsKo extends AppLocalizations { String get settings_advertLocationSubtitle => '광고에 위치 정보를 포함하세요.'; @override - String get settings_multiAck => '다중 ACK'; + String settings_multiAck(String value) { + return '다중 ACK: $value'; + } @override String get settings_telemetryModeUpdated => '텔레메트리 모드 업데이트 완료'; @@ -1128,24 +1130,43 @@ class AppLocalizationsKo extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON 변환 맵'; - @override - String get channels_cyr2latSettingsDialogSuccess => '변환 목록이 업데이트되었습니다'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return '잘못된 JSON: $error'; } @override - String get channels_cyr2latSettingsDialogReset => '초기값으로 초기화'; + String channels_channelUpdated(String name) { + return '채널 \"$name\"이 업데이트되었습니다.'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'Cyr2Lat 변환 설정이 초기값으로 초기화되었습니다'; + String get settings_cyr2latProfileAdd => 'Cyr2Lat 프로필 추가'; @override - String channels_channelUpdated(String name) { - return '채널 \"$name\"이 업데이트되었습니다.'; + String get settings_cyr2latProfileName => '프로필 이름'; + + @override + String get settings_cyr2latProfileNameEmpty => '프로필 이름은 비워둘 수 없습니다'; + + @override + String get settings_cyr2latProfileAdded => '프로필이 성공적으로 추가되었습니다'; + + @override + String get settings_cyr2latProfileUpdated => '프로필이 성공적으로 업데이트되었습니다'; + + @override + String get settings_cyr2latProfileEdit => 'Cyr2Lat 프로필 편집'; + + @override + String get settings_cyr2latProfileDelete => 'Cyr2Lat 프로필 삭제'; + + @override + String get settings_cyr2latProfileDeleted => '프로필이 성공적으로 삭제되었습니다'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return '\"$name\" 프로필을 삭제하시겠습니까?'; } @override diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index e259b4f..28e377c 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -432,7 +432,9 @@ class AppLocalizationsNl extends AppLocalizations { 'Locatie opnemen in advertentie'; @override - String get settings_multiAck => 'Meerdere bevestigingen'; + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } @override String get settings_telemetryModeUpdated => 'Telemetrie-modus bijgewerkt'; @@ -1179,26 +1181,44 @@ class AppLocalizationsNl extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON-vervangingskaart'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Lijst met vervangingen bijgewerkt'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Onjuiste JSON: $error'; } @override - String get channels_cyr2latSettingsDialogReset => - 'Terugzetten naar standaard'; + String channels_channelUpdated(String name) { + return 'Kanaal \"$name\" is bijgewerkt'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'De instellingen voor Cyr2Lat-vervangingen zijn teruggezet naar de standaardinstellingen'; + String get settings_cyr2latProfileAdd => 'Cyr2Lat-profiel toevoegen'; @override - String channels_channelUpdated(String name) { - return 'Kanaal \"$name\" is bijgewerkt'; + String get settings_cyr2latProfileName => 'Profielnaam'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Profielnaam mag niet leeg zijn'; + + @override + String get settings_cyr2latProfileAdded => 'Profiel succesvol toegevoegd'; + + @override + String get settings_cyr2latProfileUpdated => 'Profiel succesvol bijgewerkt'; + + @override + String get settings_cyr2latProfileEdit => 'Cyr2Lat-profiel bewerken'; + + @override + String get settings_cyr2latProfileDelete => 'Cyr2Lat-profiel verwijderen'; + + @override + String get settings_cyr2latProfileDeleted => 'Profiel succesvol verwijderd'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Weet u zeker dat u het profiel \"$name\" wilt verwijderen?'; } @override diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index f09f9e8..e49f235 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -439,7 +439,9 @@ class AppLocalizationsPl extends AppLocalizations { 'Uwzględnij lokalizację w ogłoszeniu'; @override - String get settings_multiAck => 'Wielokrotne potwierdzenia odbioru'; + String settings_multiAck(String value) { + return 'Wielokrotne ACK: $value'; + } @override String get settings_telemetryModeUpdated => @@ -1199,26 +1201,46 @@ class AppLocalizationsPl extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'Mapa zamian JSON'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Lista zamian zaktualizowana'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Nieprawidłowy JSON: $error'; } @override - String get channels_cyr2latSettingsDialogReset => - 'Przywróć ustawienia domyślne'; + String channels_channelUpdated(String name) { + return 'Kanał \"$name\" został zaktualizowany'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'Ustawienia zamiany Cyr2Lat zostały przywrócone do wartości domyślnych'; + String get settings_cyr2latProfileAdd => 'Dodaj profil Cyr2Lat'; @override - String channels_channelUpdated(String name) { - return 'Kanał \"$name\" został zaktualizowany'; + String get settings_cyr2latProfileName => 'Nazwa profilu'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Nazwa profilu nie może być pusta'; + + @override + String get settings_cyr2latProfileAdded => 'Profil dodano pomyślnie'; + + @override + String get settings_cyr2latProfileUpdated => + 'Profil został pomyślnie zaktualizowany'; + + @override + String get settings_cyr2latProfileEdit => 'Edytuj profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Usuń profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => + 'Profil został pomyślnie usunięty'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Czy na pewno chcesz usunąć profil \"$name\"?'; } @override diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 6c40393..0d8d32f 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -436,7 +436,9 @@ class AppLocalizationsPt extends AppLocalizations { 'Incluir localização no anúncio'; @override - String get settings_multiAck => 'Multi-ACKs'; + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } @override String get settings_telemetryModeUpdated => 'Modo de telemetria atualizado'; @@ -1190,26 +1192,44 @@ class AppLocalizationsPt extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'Mapa de substituições JSON'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Lista de substituições atualizada'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'JSON incorreto: $error'; } @override - String get channels_cyr2latSettingsDialogReset => - 'Redefinir para os valores iniciais'; + String channels_channelUpdated(String name) { + return 'Canal \"$name\" atualizado'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'As configurações de substituição Cyr2Lat foram redefinidas para os valores iniciais'; + String get settings_cyr2latProfileAdd => 'Adicionar perfil Cyr2Lat'; @override - String channels_channelUpdated(String name) { - return 'Canal \"$name\" atualizado'; + String get settings_cyr2latProfileName => 'Nome do perfil'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'O nome do perfil não pode estar vazio'; + + @override + String get settings_cyr2latProfileAdded => 'Perfil adicionado com sucesso'; + + @override + String get settings_cyr2latProfileUpdated => 'Perfil atualizado com sucesso'; + + @override + String get settings_cyr2latProfileEdit => 'Editar perfil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Eliminar perfil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Perfil eliminado com sucesso'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Tem a certeza de que deseja eliminar o perfil \"$name\"?'; } @override diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 42ca97c..6ae1661 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -436,7 +436,9 @@ class AppLocalizationsRu extends AppLocalizations { 'Включить местоположение в объявление'; @override - String get settings_multiAck => 'Несколько подтверждений'; + String settings_multiAck(String value) { + return 'Мульти-ACK: $value'; + } @override String get settings_telemetryModeUpdated => 'Режим телеметрии обновлен'; @@ -1190,24 +1192,44 @@ class AppLocalizationsRu extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON-карта замен'; - @override - String get channels_cyr2latSettingsDialogSuccess => 'Список замен обновлён'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Некорректный JSON: $error'; } @override - String get channels_cyr2latSettingsDialogReset => 'Сбросить к начальным'; + String channels_channelUpdated(String name) { + return 'Канал \"$name\" обновлён'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'Настройки замен Cyr2Lat сброшены к начальным'; + String get settings_cyr2latProfileAdd => 'Добавить профиль Cyr2Lat'; @override - String channels_channelUpdated(String name) { - return 'Канал \"$name\" обновлён'; + String get settings_cyr2latProfileName => 'Название профиля'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Название профиля не может быть пустым'; + + @override + String get settings_cyr2latProfileAdded => 'Профиль добавлен'; + + @override + String get settings_cyr2latProfileUpdated => 'Профиль успешно обновлен'; + + @override + String get settings_cyr2latProfileEdit => 'Редактировать профиль Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Удалить профиль Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Профиль успешно удален'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Вы действительно хотите удалить профиль \"$name\"?'; } @override diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 670f31c..0e95654 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -430,7 +430,9 @@ class AppLocalizationsSk extends AppLocalizations { String get settings_advertLocationSubtitle => 'Zahrnúť polohu do inzerátu'; @override - String get settings_multiAck => 'Viaceré ACK'; + String settings_multiAck(String value) { + return 'Viaceré ACK: $value'; + } @override String get settings_telemetryModeUpdated => @@ -1179,26 +1181,45 @@ class AppLocalizationsSk extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON mapa nahradení'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Zoznam nahradení bol aktualizovaný'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Nesprávny JSON: $error'; } @override - String get channels_cyr2latSettingsDialogReset => - 'Obnoviť predvolené nastavenia'; + String channels_channelUpdated(String name) { + return 'Kanál \"$name\" bol aktualizovaný'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'Nastavenia nahradzovania Cyr2Lat boli obnovené na predvolené'; + String get settings_cyr2latProfileAdd => 'Pridať profil Cyr2Lat'; @override - String channels_channelUpdated(String name) { - return 'Kanál \"$name\" bol aktualizovaný'; + String get settings_cyr2latProfileName => 'Názov profilu'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Názov profilu nesmie byť prázdny'; + + @override + String get settings_cyr2latProfileAdded => 'Profil bol úspešne pridaný'; + + @override + String get settings_cyr2latProfileUpdated => + 'Profil bol úspešne aktualizovaný'; + + @override + String get settings_cyr2latProfileEdit => 'Upraviť profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Odstrániť profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Profil bol úspešne odstránený'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Naozaj chcete odstrániť profil \"$name\"?'; } @override diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index f16bc4d..b6264ea 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -430,7 +430,9 @@ class AppLocalizationsSl extends AppLocalizations { String get settings_advertLocationSubtitle => 'Vključi lokacijo v oglas.'; @override - String get settings_multiAck => 'Več potrdil'; + String settings_multiAck(String value) { + return 'Večkratni potrditvi: $value'; + } @override String get settings_telemetryModeUpdated => 'Način telemetrije posodobljen'; @@ -1177,25 +1179,45 @@ class AppLocalizationsSl extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON-tabela zamenjav'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Seznam zamenjav je posodobljen'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Nepravilen JSON: $error'; } @override - String get channels_cyr2latSettingsDialogReset => 'Ponastavi na privzete'; + String channels_channelUpdated(String name) { + return 'Kanal $name je bil posodobljen'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'Nastavitve zamenjav Cyr2Lat so ponastavljene na privzete'; + String get settings_cyr2latProfileAdd => 'Dodaj profil Cyr2Lat'; @override - String channels_channelUpdated(String name) { - return 'Kanal $name je bil posodobljen'; + String get settings_cyr2latProfileName => 'Ime profila'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Ime profila ne sme biti prazno'; + + @override + String get settings_cyr2latProfileAdded => 'Profil je bil uspešno dodan'; + + @override + String get settings_cyr2latProfileUpdated => + 'Profil je bil uspešno posodobljen'; + + @override + String get settings_cyr2latProfileEdit => 'Uredi profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Izbriši profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Profil je bil uspešno izbrisan'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Ali res želite izbrisati profil \"$name\"?'; } @override diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 4d906ba..c540b99 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -428,7 +428,9 @@ class AppLocalizationsSv extends AppLocalizations { String get settings_advertLocationSubtitle => 'Inkludera plats i annonsen'; @override - String get settings_multiAck => 'Flera bekräftelser'; + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } @override String get settings_telemetryModeUpdated => 'Telemetri-läge uppdaterat'; @@ -1169,25 +1171,44 @@ class AppLocalizationsSv extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON-ersättningskarta'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Ersättningslistan har uppdaterats'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Felaktig JSON: $error'; } @override - String get channels_cyr2latSettingsDialogReset => 'Återställ till standard'; + String channels_channelUpdated(String name) { + return 'Kanalen \"$name\" har uppdaterats'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'Inställningarna för Cyr2Lat-ersättningar har återställts till standard'; + String get settings_cyr2latProfileAdd => 'Lägg till Cyr2Lat-profil'; @override - String channels_channelUpdated(String name) { - return 'Kanalen \"$name\" har uppdaterats'; + String get settings_cyr2latProfileName => 'Profilnamn'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Profilnamnet får inte vara tomt'; + + @override + String get settings_cyr2latProfileAdded => 'Profilen har lagts till'; + + @override + String get settings_cyr2latProfileUpdated => 'Profilen har uppdaterats'; + + @override + String get settings_cyr2latProfileEdit => 'Redigera Cyr2Lat-profil'; + + @override + String get settings_cyr2latProfileDelete => 'Ta bort Cyr2Lat-profil'; + + @override + String get settings_cyr2latProfileDeleted => 'Profilen har tagits bort'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Är du säker på att du vill ta bort profilen \"$name\"?'; } @override diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index e46e63c..a5d63ff 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -432,7 +432,9 @@ class AppLocalizationsUk extends AppLocalizations { 'Включити місце розташування в оголошення'; @override - String get settings_multiAck => 'Багато підтверджень'; + String settings_multiAck(String value) { + return 'Багатократне підтвердження: $value'; + } @override String get settings_telemetryModeUpdated => 'Режим телеметрії оновлено'; @@ -1184,24 +1186,44 @@ class AppLocalizationsUk extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON-карта замін'; - @override - String get channels_cyr2latSettingsDialogSuccess => 'Список замін оновлено'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Некоректний JSON: $error'; } @override - String get channels_cyr2latSettingsDialogReset => 'Скинути до початкових'; + String channels_channelUpdated(String name) { + return 'Канал «$name» оновлено'; + } @override - String get channels_cyr2latSettingsDialogResetted => - 'Налаштування замін Cyr2Lat скинуто до початкових'; + String get settings_cyr2latProfileAdd => 'Додати профіль Cyr2Lat'; @override - String channels_channelUpdated(String name) { - return 'Канал «$name» оновлено'; + String get settings_cyr2latProfileName => 'Назва профілю'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Назва профілю не може бути порожньою'; + + @override + String get settings_cyr2latProfileAdded => 'Профіль успішно додано'; + + @override + String get settings_cyr2latProfileUpdated => 'Профіль успішно оновлено'; + + @override + String get settings_cyr2latProfileEdit => 'Редагувати профіль Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Видалити профіль Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Профіль успішно видалено'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Ви впевнені, що хочете видалити профіль \"$name\"?'; } @override diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index f66ec20..0a5e256 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -408,7 +408,9 @@ class AppLocalizationsZh extends AppLocalizations { String get settings_advertLocationSubtitle => '在广告中包含位置'; @override - String get settings_multiAck => '多重ACK'; + String settings_multiAck(String value) { + return '多重ACK:$value'; + } @override String get settings_telemetryModeUpdated => '遥测模式已更新'; @@ -1115,23 +1117,43 @@ class AppLocalizationsZh extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON 替換映射表'; - @override - String get channels_cyr2latSettingsDialogSuccess => '替換清單已更新'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'JSON 格式錯誤:$error'; } @override - String get channels_cyr2latSettingsDialogReset => '還原為預設值'; + String channels_channelUpdated(String name) { + return '频道 \"$name\" 已更新'; + } @override - String get channels_cyr2latSettingsDialogResetted => 'Cyr2Lat 替換設定已還原為預設值'; + String get settings_cyr2latProfileAdd => '新增 Cyr2Lat 設定檔'; @override - String channels_channelUpdated(String name) { - return '频道 \"$name\" 已更新'; + String get settings_cyr2latProfileName => '設定檔名稱'; + + @override + String get settings_cyr2latProfileNameEmpty => '設定檔名稱不能為空'; + + @override + String get settings_cyr2latProfileAdded => '設定檔已成功新增'; + + @override + String get settings_cyr2latProfileUpdated => '設定檔已成功更新'; + + @override + String get settings_cyr2latProfileEdit => '編輯 Cyr2Lat 設定檔'; + + @override + String get settings_cyr2latProfileDelete => '刪除 Cyr2Lat 設定檔'; + + @override + String get settings_cyr2latProfileDeleted => '設定檔已成功刪除'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return '您確定要刪除設定檔 \"$name\" 嗎?'; } @override diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 913bea1..f7fc5a4 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Lijst met vervangingen", "channels_cyr2latSettingsDscr": "Bewerk de JSON-configuratie voor tekenvervanging", "channels_cyr2latSettingsDialogHint": "JSON-vervangingskaart", - "channels_cyr2latSettingsDialogSuccess": "Lijst met vervangingen bijgewerkt", "channels_cyr2latSettingsDialogWrongJSON": "Onjuiste JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Terugzetten naar standaard", - "channels_cyr2latSettingsDialogResetted": "De instellingen voor Cyr2Lat-vervangingen zijn teruggezet naar de standaardinstellingen", + "settings_cyr2latProfileAdd": "Cyr2Lat-profiel toevoegen", + "settings_cyr2latProfileName": "Profielnaam", + "settings_cyr2latProfileNameEmpty": "Profielnaam mag niet leeg zijn", + "settings_cyr2latProfileAdded": "Profiel succesvol toegevoegd", + "settings_cyr2latProfileUpdated": "Profiel succesvol bijgewerkt", + "settings_cyr2latProfileEdit": "Cyr2Lat-profiel bewerken", + "settings_cyr2latProfileDelete": "Cyr2Lat-profiel verwijderen", + "settings_cyr2latProfileDeleted": "Profiel succesvol verwijderd", + "settings_cyr2latProfileDeleteDscr": "Weet u zeker dat u het profiel \"{name}\" wilt verwijderen?", "channels_channelUpdated": "Kanaal \"{name}\" is bijgewerkt", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "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", @@ -1944,6 +1957,7 @@ "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": { @@ -2075,6 +2089,5 @@ "repeater_guestTools": "Gastenfuncties", "room_guest": "Informatie over de server", "chat_sendMessage": "Verzend bericht", - "repeater_guest": "Informatie over herhalingsapparatuur", - "settings_multiAck": "Meerdere bevestigingen" + "repeater_guest": "Informatie over herhalingsapparatuur" } diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 17b3aa6..a59b394 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -404,10 +404,16 @@ "channels_cyr2latSettingsSubheading": "Lista zamian", "channels_cyr2latSettingsDscr": "Edytuj konfigurację JSON zamiany znaków", "channels_cyr2latSettingsDialogHint": "Mapa zamian JSON", - "channels_cyr2latSettingsDialogSuccess": "Lista zamian zaktualizowana", "channels_cyr2latSettingsDialogWrongJSON": "Nieprawidłowy JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Przywróć ustawienia domyślne", - "channels_cyr2latSettingsDialogResetted": "Ustawienia zamiany Cyr2Lat zostały przywrócone do wartości domyślnych", + "settings_cyr2latProfileAdd": "Dodaj profil Cyr2Lat", + "settings_cyr2latProfileName": "Nazwa profilu", + "settings_cyr2latProfileNameEmpty": "Nazwa profilu nie może być pusta", + "settings_cyr2latProfileAdded": "Profil dodano pomyślnie", + "settings_cyr2latProfileUpdated": "Profil został pomyślnie zaktualizowany", + "settings_cyr2latProfileEdit": "Edytuj profil Cyr2Lat", + "settings_cyr2latProfileDelete": "Usuń profil Cyr2Lat", + "settings_cyr2latProfileDeleted": "Profil został pomyślnie usunięty", + "settings_cyr2latProfileDeleteDscr": "Czy na pewno chcesz usunąć profil \"{name}\"?", "channels_channelUpdated": "Kanał \"{name}\" został zaktualizowany", "@channels_channelUpdated": { "placeholders": { @@ -1970,6 +1976,13 @@ "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", @@ -1982,6 +1995,7 @@ "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": { @@ -2113,6 +2127,5 @@ "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", - "settings_multiAck": "Wielokrotne potwierdzenia odbioru" + "room_guest": "Informacje o serwerze" } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 80fdcd7..e49bc38 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Lista de substituições", "channels_cyr2latSettingsDscr": "Editar a configuração JSON de substituição de caracteres", "channels_cyr2latSettingsDialogHint": "Mapa de substituições JSON", - "channels_cyr2latSettingsDialogSuccess": "Lista de substituições atualizada", "channels_cyr2latSettingsDialogWrongJSON": "JSON incorreto: {error}", - "channels_cyr2latSettingsDialogReset": "Redefinir para os valores iniciais", - "channels_cyr2latSettingsDialogResetted": "As configurações de substituição Cyr2Lat foram redefinidas para os valores iniciais", + "settings_cyr2latProfileAdd": "Adicionar perfil Cyr2Lat", + "settings_cyr2latProfileName": "Nome do perfil", + "settings_cyr2latProfileNameEmpty": "O nome do perfil não pode estar vazio", + "settings_cyr2latProfileAdded": "Perfil adicionado com sucesso", + "settings_cyr2latProfileUpdated": "Perfil atualizado com sucesso", + "settings_cyr2latProfileEdit": "Editar perfil Cyr2Lat", + "settings_cyr2latProfileDelete": "Eliminar perfil Cyr2Lat", + "settings_cyr2latProfileDeleted": "Perfil eliminado com sucesso", + "settings_cyr2latProfileDeleteDscr": "Tem a certeza de que deseja eliminar o perfil \"{name}\"?", "channels_channelUpdated": "Canal \"{name}\" atualizado", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "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.", @@ -1944,7 +1957,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", + "settings_multiAck": "Multi-ACKs: {value}", "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 e3ea5cd..d3a0bcb 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -258,10 +258,16 @@ "channels_cyr2latSettingsSubheading": "Список замен", "channels_cyr2latSettingsDscr": "Редактировать JSON-конфигурацию замены символов", "channels_cyr2latSettingsDialogHint": "JSON-карта замен", - "channels_cyr2latSettingsDialogSuccess": "Список замен обновлён", "channels_cyr2latSettingsDialogWrongJSON": "Некорректный JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Сбросить к начальным", - "channels_cyr2latSettingsDialogResetted": "Настройки замен Cyr2Lat сброшены к начальным", + "settings_cyr2latProfileAdd": "Добавить профиль Cyr2Lat", + "settings_cyr2latProfileName": "Название профиля", + "settings_cyr2latProfileNameEmpty": "Название профиля не может быть пустым", + "settings_cyr2latProfileAdded": "Профиль добавлен", + "settings_cyr2latProfileUpdated": "Профиль успешно обновлен", + "settings_cyr2latProfileEdit": "Редактировать профиль Cyr2Lat", + "settings_cyr2latProfileDelete": "Удалить профиль Cyr2Lat", + "settings_cyr2latProfileDeleted": "Профиль успешно удален", + "settings_cyr2latProfileDeleteDscr": "Вы действительно хотите удалить профиль \"{name}\"?", "channels_channelUpdated": "Канал \"{name}\" обновлён", "channels_publicChannelAdded": "Публичный канал добавлен", "channels_sortBy": "Сортировка", @@ -1172,6 +1178,13 @@ "contact_clearChat": "Очистить чат", "contact_lastSeen": "Последний раз видели", "contact_teleBaseSubtitle": "Разрешить обмен уровнем заряда батареи и базовой телеметрией", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_maxRouteWeight": "Максимальный допустимый вес маршрута", "appSettings_maxRouteWeightSubtitle": "Максимальный вес, который может быть перевезён по определённому маршруту при успешных доставках.", "appSettings_initialRouteWeightSubtitle": "Начальный вес для новых, только что открытых маршрутов", @@ -1184,6 +1197,7 @@ "appSettings_maxMessageRetriesSubtitle": "Количество попыток повторной отправки сообщения перед тем, как пометить его как неудачное.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрии обновлен", + "settings_multiAck": "Мульти-ACK: {value}", "map_showOverlaps": "Перекрытия ключа повтора", "map_runTraceWithReturnPath": "Вернуться обратно по тому же пути", "@radioStats_noiseFloor": { @@ -1315,6 +1329,5 @@ "chat_sendMessage": "Отправить сообщение", "repeater_guest": "Информация о ретрансляторе", "room_guest": "Информация о сервере", - "repeater_guestTools": "Инструменты для гостей", - "settings_multiAck": "Несколько подтверждений" + "repeater_guestTools": "Инструменты для гостей" } diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 2718789..c7a66a5 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Zoznam nahradení", "channels_cyr2latSettingsDscr": "Upravte konfiguráciu JSON pre nahradenie znakov", "channels_cyr2latSettingsDialogHint": "JSON mapa nahradení", - "channels_cyr2latSettingsDialogSuccess": "Zoznam nahradení bol aktualizovaný", "channels_cyr2latSettingsDialogWrongJSON": "Nesprávny JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Obnoviť predvolené nastavenia", - "channels_cyr2latSettingsDialogResetted": "Nastavenia nahradzovania Cyr2Lat boli obnovené na predvolené", + "settings_cyr2latProfileAdd": "Pridať profil Cyr2Lat", + "settings_cyr2latProfileName": "Názov profilu", + "settings_cyr2latProfileNameEmpty": "Názov profilu nesmie byť prázdny", + "settings_cyr2latProfileAdded": "Profil bol úspešne pridaný", + "settings_cyr2latProfileUpdated": "Profil bol úspešne aktualizovaný", + "settings_cyr2latProfileEdit": "Upraviť profil Cyr2Lat", + "settings_cyr2latProfileDelete": "Odstrániť profil Cyr2Lat", + "settings_cyr2latProfileDeleted": "Profil bol úspešne odstránený", + "settings_cyr2latProfileDeleteDscr": "Naozaj chcete odstrániť profil \"{name}\"?", "channels_channelUpdated": "Kanál \"{name}\" bol aktualizovaný", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "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", @@ -1944,7 +1957,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", + "settings_multiAck": "Viaceré ACK: {value}", "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 da42f81..84f8068 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Seznam zamenjav", "channels_cyr2latSettingsDscr": "Uredi JSON-konfiguracijo zamenjav znakov", "channels_cyr2latSettingsDialogHint": "JSON-tabela zamenjav", - "channels_cyr2latSettingsDialogSuccess": "Seznam zamenjav je posodobljen", "channels_cyr2latSettingsDialogWrongJSON": "Nepravilen JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Ponastavi na privzete", - "channels_cyr2latSettingsDialogResetted": "Nastavitve zamenjav Cyr2Lat so ponastavljene na privzete", + "settings_cyr2latProfileAdd": "Dodaj profil Cyr2Lat", + "settings_cyr2latProfileName": "Ime profila", + "settings_cyr2latProfileNameEmpty": "Ime profila ne sme biti prazno", + "settings_cyr2latProfileAdded": "Profil je bil uspešno dodan", + "settings_cyr2latProfileUpdated": "Profil je bil uspešno posodobljen", + "settings_cyr2latProfileEdit": "Uredi profil Cyr2Lat", + "settings_cyr2latProfileDelete": "Izbriši profil Cyr2Lat", + "settings_cyr2latProfileDeleted": "Profil je bil uspešno izbrisan", + "settings_cyr2latProfileDeleteDscr": "Ali res želite izbrisati profil \"{name}\"?", "channels_channelUpdated": "Kanal {name} je bil posodobljen", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "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", @@ -1943,6 +1956,7 @@ "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.", @@ -2075,6 +2089,5 @@ "repeater_guest": "Informacije o ponovljalniku", "chat_sendMessage": "Pošlji sporočilo", "room_guest": "Informacije o strežniku", - "repeater_guestTools": "Naložila za goste", - "settings_multiAck": "Več potrdil" + "repeater_guestTools": "Naložila za goste" } diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index c714cf4..f8da46f 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Ersättningslista", "channels_cyr2latSettingsDscr": "Redigera JSON-konfigurationen för teckenersättning", "channels_cyr2latSettingsDialogHint": "JSON-ersättningskarta", - "channels_cyr2latSettingsDialogSuccess": "Ersättningslistan har uppdaterats", "channels_cyr2latSettingsDialogWrongJSON": "Felaktig JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Återställ till standard", - "channels_cyr2latSettingsDialogResetted": "Inställningarna för Cyr2Lat-ersättningar har återställts till standard", + "settings_cyr2latProfileAdd": "Lägg till Cyr2Lat-profil", + "settings_cyr2latProfileName": "Profilnamn", + "settings_cyr2latProfileNameEmpty": "Profilnamnet får inte vara tomt", + "settings_cyr2latProfileAdded": "Profilen har lagts till", + "settings_cyr2latProfileUpdated": "Profilen har uppdaterats", + "settings_cyr2latProfileEdit": "Redigera Cyr2Lat-profil", + "settings_cyr2latProfileDelete": "Ta bort Cyr2Lat-profil", + "settings_cyr2latProfileDeleted": "Profilen har tagits bort", + "settings_cyr2latProfileDeleteDscr": "Är du säker på att du vill ta bort profilen \"{name}\"?", "channels_channelUpdated": "Kanalen \"{name}\" har uppdaterats", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "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.", @@ -1944,6 +1957,7 @@ "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": { @@ -2075,6 +2089,5 @@ "repeater_guest": "Information om repetorer", "chat_sendMessage": "Skicka meddelande", "repeater_guestTools": "Gästverktyg", - "room_guest": "Information om servern", - "settings_multiAck": "Flera bekräftelser" + "room_guest": "Information om servern" } diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index cd73efe..d324598 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -395,10 +395,16 @@ "channels_cyr2latSettingsSubheading": "Список замін", "channels_cyr2latSettingsDscr": "Редагувати JSON-конфігурацію заміни символів", "channels_cyr2latSettingsDialogHint": "JSON-карта замін", - "channels_cyr2latSettingsDialogSuccess": "Список замін оновлено", "channels_cyr2latSettingsDialogWrongJSON": "Некоректний JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Скинути до початкових", - "channels_cyr2latSettingsDialogResetted": "Налаштування замін Cyr2Lat скинуто до початкових", + "settings_cyr2latProfileAdd": "Додати профіль Cyr2Lat", + "settings_cyr2latProfileName": "Назва профілю", + "settings_cyr2latProfileNameEmpty": "Назва профілю не може бути порожньою", + "settings_cyr2latProfileAdded": "Профіль успішно додано", + "settings_cyr2latProfileUpdated": "Профіль успішно оновлено", + "settings_cyr2latProfileEdit": "Редагувати профіль Cyr2Lat", + "settings_cyr2latProfileDelete": "Видалити профіль Cyr2Lat", + "settings_cyr2latProfileDeleted": "Профіль успішно видалено", + "settings_cyr2latProfileDeleteDscr": "Ви впевнені, що хочете видалити профіль \"{name}\"?", "channels_channelUpdated": "Канал «{name}» оновлено", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "contact_lastSeen": "Останній раз бачили", "contact_teleEnv": "Середовище телеметрії", "contact_teleEnvSubtitle": "Дозволити спільний доступ до даних датчиків середовища", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_initialRouteWeight": "Початкова вартість маршруту", "appSettings_initialRouteWeightSubtitle": "Початкова вага для нових відкритих шляхів", "appSettings_maxRouteWeight": "Максимальна вага маршруту", @@ -1944,6 +1957,7 @@ "appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрії оновлено", + "settings_multiAck": "Багатократне підтвердження: {value}", "map_showOverlaps": "Перекриття ключа повторювача", "map_runTraceWithReturnPath": "Повернутися назад тим же шляхом", "@radioStats_noiseFloor": { @@ -2075,6 +2089,5 @@ "repeater_guestTools": "Інструменти для гостей", "repeater_guest": "Інформація про ретранслятор", "room_guest": "Інформація про сервер кімнати", - "chat_sendMessage": "Надіслати повідомлення", - "settings_multiAck": "Багато підтверджень" + "chat_sendMessage": "Надіслати повідомлення" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 644ccad..beb4d0c 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -409,10 +409,16 @@ "channels_cyr2latSettingsSubheading": "替換清單", "channels_cyr2latSettingsDscr": "編輯 JSON 字元替換設定檔", "channels_cyr2latSettingsDialogHint": "JSON 替換映射表", - "channels_cyr2latSettingsDialogSuccess": "替換清單已更新", "channels_cyr2latSettingsDialogWrongJSON": "JSON 格式錯誤:{error}", - "channels_cyr2latSettingsDialogReset": "還原為預設值", - "channels_cyr2latSettingsDialogResetted": "Cyr2Lat 替換設定已還原為預設值", + "settings_cyr2latProfileAdd": "新增 Cyr2Lat 設定檔", + "settings_cyr2latProfileName": "設定檔名稱", + "settings_cyr2latProfileNameEmpty": "設定檔名稱不能為空", + "settings_cyr2latProfileAdded": "設定檔已成功新增", + "settings_cyr2latProfileUpdated": "設定檔已成功更新", + "settings_cyr2latProfileEdit": "編輯 Cyr2Lat 設定檔", + "settings_cyr2latProfileDelete": "刪除 Cyr2Lat 設定檔", + "settings_cyr2latProfileDeleted": "設定檔已成功刪除", + "settings_cyr2latProfileDeleteDscr": "您確定要刪除設定檔 \"{name}\" 嗎?", "channels_channelUpdated": "频道 \"{name}\" 已更新", "@channels_channelUpdated": { "placeholders": { @@ -1937,6 +1943,13 @@ "contact_settings": "联系人设置", "contact_teleLocSubtitle": "允许共享位置数据", "contact_telemetry": "遥测数据", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_maxRouteWeight": "最大路径重量", "appSettings_initialRouteWeightSubtitle": "新发现路径的初始重量", "appSettings_initialRouteWeight": "初始路线权重", @@ -1948,7 +1961,7 @@ "appSettings_maxMessageRetries": "最大消息重试次数", "appSettings_maxMessageRetriesSubtitle": "在将消息标记为失败之前,允许尝试的次数", "path_routeWeight": "{weight}/{max}", - "settings_multiAck": "多重ACK", + "settings_multiAck": "多重ACK:{value}", "settings_telemetryModeUpdated": "遥测模式已更新", "map_showOverlaps": "重复键重叠", "map_runTraceWithReturnPath": "沿着相同的路径返回", diff --git a/lib/models/app_settings.dart b/lib/models/app_settings.dart index d7c176e..4e95311 100644 --- a/lib/models/app_settings.dart +++ b/lib/models/app_settings.dart @@ -38,6 +38,42 @@ const Map defaultCyr2LatCharMap = { 'х': 'x', }; +class Cyr2LatProfile { + final String id; + final String name; + final Map charMap; + + Cyr2LatProfile({required this.id, required this.name, required this.charMap}); + + Map toJson() { + return {'id': id, 'name': name, 'char_map': charMap}; + } + + factory Cyr2LatProfile.fromJson(Map json) { + return Cyr2LatProfile( + id: json['id'] as String, + name: json['name'] as String, + charMap: + (json['char_map'] as Map?)?.map( + (key, value) => MapEntry(key.toString(), value.toString()), + ) ?? + {}, + ); + } + + Cyr2LatProfile copyWith({ + String? id, + String? name, + Map? charMap, + }) { + return Cyr2LatProfile( + id: id ?? this.id, + name: name ?? this.name, + charMap: charMap ?? this.charMap, + ); + } +} + class AppSettings { static const Object _unset = Object(); @@ -82,7 +118,16 @@ class AppSettings { final String? translationModelSourceUrl; final String? translationSelectedModelId; final List translationDownloadedModels; - final Map cyr2latCharMap; + final List cyr2latProfiles; + final String selectedCyr2latProfileId; + + Map get cyr2latCharMap { + final profile = cyr2latProfiles.firstWhere( + (p) => p.id == selectedCyr2latProfileId, + orElse: () => cyr2latProfiles.first, + ); + return profile.charMap; + } AppSettings({ this.clearPathOnMaxRetry = false, @@ -126,12 +171,22 @@ class AppSettings { this.translationModelSourceUrl, this.translationSelectedModelId, List? translationDownloadedModels, - Map? cyr2latCharMap, + List? cyr2latProfiles, + String? selectedCyr2latProfileId, }) : batteryChemistryByDeviceId = batteryChemistryByDeviceId ?? {}, batteryChemistryByRepeaterId = batteryChemistryByRepeaterId ?? {}, mutedChannels = mutedChannels ?? {}, translationDownloadedModels = translationDownloadedModels ?? const [], - cyr2latCharMap = cyr2latCharMap ?? defaultCyr2LatCharMap; + cyr2latProfiles = + cyr2latProfiles ?? + [ + Cyr2LatProfile( + id: 'default', + name: 'Default', + charMap: defaultCyr2LatCharMap, + ), + ], + selectedCyr2latProfileId = selectedCyr2latProfileId ?? 'default'; Map toJson() { return { @@ -178,7 +233,10 @@ class AppSettings { 'translation_downloaded_models': translationDownloadedModels .map((model) => model.toJson()) .toList(), - 'cyr2lat_char_map': cyr2latCharMap, + 'cyr2lat_profiles': cyr2latProfiles + .map((profile) => profile.toJson()) + .toList(), + 'selected_cyr2lat_profile_id': selectedCyr2latProfileId, }; } @@ -266,11 +324,38 @@ class AppSettings { ) .toList() ?? const [], - cyr2latCharMap: - (json['cyr2lat_char_map'] as Map?)?.map( - (key, value) => MapEntry(key.toString(), value.toString()), - ) ?? - defaultCyr2LatCharMap, + cyr2latProfiles: + (json['cyr2lat_profiles'] as List?) + ?.map( + (entry) => Cyr2LatProfile.fromJson( + Map.from(entry as Map), + ), + ) + .toList() ?? + // Backward compatibility: if old cyr2lat_char_map exists, create a profile from it + (json['cyr2lat_char_map'] != null + ? [ + Cyr2LatProfile( + id: 'migrated', + name: 'Migrated Profile', + charMap: + (json['cyr2lat_char_map'] as Map?)?.map( + (key, value) => + MapEntry(key.toString(), value.toString()), + ) ?? + defaultCyr2LatCharMap, + ), + ] + : [ + Cyr2LatProfile( + id: 'default', + name: 'Default', + charMap: defaultCyr2LatCharMap, + ), + ]), + selectedCyr2latProfileId: + json['selected_cyr2lat_profile_id'] as String? ?? + (json['cyr2lat_char_map'] != null ? 'migrated' : 'default'), ); } @@ -316,7 +401,8 @@ class AppSettings { Object? translationModelSourceUrl = _unset, Object? translationSelectedModelId = _unset, List? translationDownloadedModels, - Map? cyr2latCharMap, + List? cyr2latProfiles, + String? selectedCyr2latProfileId, }) { return AppSettings( clearPathOnMaxRetry: clearPathOnMaxRetry ?? this.clearPathOnMaxRetry, @@ -380,7 +466,9 @@ class AppSettings { : translationSelectedModelId as String?, translationDownloadedModels: translationDownloadedModels ?? this.translationDownloadedModels, - cyr2latCharMap: cyr2latCharMap ?? this.cyr2latCharMap, + cyr2latProfiles: cyr2latProfiles ?? this.cyr2latProfiles, + selectedCyr2latProfileId: + selectedCyr2latProfileId ?? this.selectedCyr2latProfileId, ); } } diff --git a/lib/screens/app_settings_screen.dart b/lib/screens/app_settings_screen.dart index 425248d..051a6f7 100644 --- a/lib/screens/app_settings_screen.dart +++ b/lib/screens/app_settings_screen.dart @@ -1262,6 +1262,7 @@ class AppSettingsScreen extends StatelessWidget { BuildContext context, AppSettingsService settingsService, ) { + final selectedProfile = settingsService.getSelectedCyr2LatProfile(); return Card( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1273,41 +1274,109 @@ class AppSettingsScreen extends StatelessWidget { style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), ), - ListTile( - leading: const Icon(Icons.translate), - - title: Text(context.l10n.channels_cyr2latSettingsSubheading), - subtitle: Text(context.l10n.channels_cyr2latSettingsDscr), - trailing: const Icon(Icons.chevron_right), - onTap: () => _showCyr2LatDialog(context, settingsService), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: DropdownButtonFormField( + initialValue: settingsService.settings.selectedCyr2latProfileId, + decoration: InputDecoration( + labelText: context.l10n.channels_cyr2latSettingsSubheading, + border: const OutlineInputBorder(), + ), + items: settingsService.settings.cyr2latProfiles.map((profile) { + return DropdownMenuItem( + value: profile.id, + child: Text(profile.name), + ); + }).toList(), + onChanged: (value) { + if (value != null) { + settingsService.setSelectedCyr2LatProfile(value); + } + }, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: () => + _showAddCyr2LatProfileDialog(context, settingsService), + icon: const Icon(Icons.add), + label: Text(context.l10n.common_add), + ), + ), + const SizedBox(width: 8), + Expanded( + child: OutlinedButton.icon( + onPressed: () => _showEditCyr2LatProfileDialog( + context, + settingsService, + selectedProfile, + ), + icon: const Icon(Icons.edit), + label: Text(context.l10n.common_edit), + ), + ), + const SizedBox(width: 8), + Expanded( + child: OutlinedButton.icon( + onPressed: + settingsService.settings.cyr2latProfiles.length > 1 + ? () => _showDeleteCyr2LatProfileDialog( + context, + settingsService, + selectedProfile, + ) + : null, + icon: const Icon(Icons.delete), + label: Text(context.l10n.common_delete), + ), + ), + ], + ), ), ], ), ); } - void _showCyr2LatDialog( + void _showAddCyr2LatProfileDialog( BuildContext context, AppSettingsService settingsService, ) { - final controller = TextEditingController( - text: const JsonEncoder.withIndent( - ' ', - ).convert(settingsService.settings.cyr2latCharMap), + final nameController = TextEditingController(); + final jsonController = TextEditingController( + text: const JsonEncoder.withIndent(' ').convert(defaultCyr2LatCharMap), ); showDialog( context: context, builder: (context) => AlertDialog( - title: Text(context.l10n.channels_cyr2latSettingsDscr), + title: Text(context.l10n.settings_cyr2latProfileAdd), content: SingleChildScrollView( - child: TextField( - controller: controller, - maxLines: 20, - decoration: InputDecoration( - border: const OutlineInputBorder(), - hintText: context.l10n.channels_cyr2latSettingsDialogHint, - ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: nameController, + decoration: InputDecoration( + labelText: context.l10n.settings_cyr2latProfileName, + border: const OutlineInputBorder(), + ), + ), + const SizedBox(height: 16), + TextField( + controller: jsonController, + maxLines: 15, + decoration: InputDecoration( + labelText: context.l10n.channels_cyr2latSettingsDialogHint, + border: const OutlineInputBorder(), + hintText: context.l10n.channels_cyr2latSettingsDscr, + ), + ), + ], ), ), actions: [ @@ -1316,24 +1385,119 @@ class AppSettingsScreen extends StatelessWidget { child: Text(context.l10n.common_cancel), ), TextButton( - onPressed: () { + onPressed: () async { + if (nameController.text.isEmpty) { + showDismissibleSnackBar( + context, + content: Text(context.l10n.settings_cyr2latProfileNameEmpty), + ); + return; + } try { final json = - jsonDecode(controller.text) as Map; + jsonDecode(jsonController.text) as Map; final map = json.map( (key, value) => MapEntry(key, value.toString()), ); - final newSettings = settingsService.settings.copyWith( - cyr2latCharMap: map, + final profile = Cyr2LatProfile( + id: DateTime.now().millisecondsSinceEpoch.toString(), + name: nameController.text, + charMap: map, ); - settingsService.updateSettings(newSettings); + await settingsService.addCyr2LatProfile(profile); + if (!context.mounted) return; Navigator.pop(context); + showDismissibleSnackBar( + context, + content: Text(context.l10n.settings_cyr2latProfileAdded), + ); + } catch (e) { showDismissibleSnackBar( context, content: Text( - context.l10n.channels_cyr2latSettingsDialogSuccess, + context.l10n.channels_cyr2latSettingsDialogWrongJSON( + e.toString(), + ), ), ); + } + }, + child: Text(context.l10n.common_save), + ), + ], + ), + ); + } + + void _showEditCyr2LatProfileDialog( + BuildContext context, + AppSettingsService settingsService, + Cyr2LatProfile profile, + ) { + final nameController = TextEditingController(text: profile.name); + final jsonController = TextEditingController( + text: const JsonEncoder.withIndent(' ').convert(profile.charMap), + ); + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(context.l10n.settings_cyr2latProfileEdit), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: nameController, + decoration: InputDecoration( + labelText: context.l10n.settings_cyr2latProfileName, + border: const OutlineInputBorder(), + ), + ), + const SizedBox(height: 16), + TextField( + controller: jsonController, + maxLines: 15, + decoration: InputDecoration( + labelText: context.l10n.channels_cyr2latSettingsDialogHint, + border: const OutlineInputBorder(), + hintText: context.l10n.channels_cyr2latSettingsDscr, + ), + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(context.l10n.common_cancel), + ), + TextButton( + onPressed: () async { + if (nameController.text.isEmpty) { + showDismissibleSnackBar( + context, + content: Text(context.l10n.settings_cyr2latProfileNameEmpty), + ); + return; + } + try { + final json = + jsonDecode(jsonController.text) as Map; + final map = json.map( + (key, value) => MapEntry(key, value.toString()), + ); + final updatedProfile = profile.copyWith( + name: nameController.text, + charMap: map, + ); + await settingsService.updateCyr2LatProfile(updatedProfile); + if (!context.mounted) return; + Navigator.pop(context); + showDismissibleSnackBar( + context, + content: Text(context.l10n.settings_cyr2latProfileUpdated), + ); } catch (e) { showDismissibleSnackBar( context, @@ -1347,21 +1511,39 @@ class AppSettingsScreen extends StatelessWidget { }, child: Text(context.l10n.common_save), ), + ], + ), + ); + } + + void _showDeleteCyr2LatProfileDialog( + BuildContext context, + AppSettingsService settingsService, + Cyr2LatProfile profile, + ) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(context.l10n.settings_cyr2latProfileDelete), + content: Text( + context.l10n.settings_cyr2latProfileDeleteDscr(profile.name), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(context.l10n.common_cancel), + ), TextButton( - onPressed: () { - final newSettings = settingsService.settings.copyWith( - cyr2latCharMap: defaultCyr2LatCharMap, - ); - settingsService.updateSettings(newSettings); + onPressed: () async { + await settingsService.removeCyr2LatProfile(profile.id); + if (!context.mounted) return; Navigator.pop(context); showDismissibleSnackBar( context, - content: Text( - context.l10n.channels_cyr2latSettingsDialogResetted, - ), + content: Text(context.l10n.settings_cyr2latProfileDeleted), ); }, - child: Text(context.l10n.channels_cyr2latSettingsDialogReset), + child: Text(context.l10n.common_delete), ), ], ), diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index 868aac7..f12018b 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -1401,10 +1401,17 @@ class _ChannelsScreenState extends State MeshCoreConnector connector, Channel channel, ) { + final appSettingsService = Provider.of( + context, + listen: false, + ); final nameController = TextEditingController(text: channel.name); final pskController = TextEditingController(text: channel.pskHex); bool smazEnabled = connector.isChannelSmazEnabled(channel.index); bool cyr2latEnabled = connector.isChannelCyr2LatEnabled(channel.index); + String? selectedCyr2LatProfileId = connector.getChannelCyr2LatProfileId( + channel.index, + ); showDialog( context: context, @@ -1471,6 +1478,31 @@ class _ChannelsScreenState extends State } }), ), + if (cyr2latEnabled) ...[ + Padding( + padding: const EdgeInsets.fromLTRB(0, 8, 0, 8), + child: DropdownButtonFormField( + initialValue: selectedCyr2LatProfileId, + decoration: InputDecoration( + labelText: dialogContext + .l10n + .channels_cyr2latSettingsSubheading, + border: const OutlineInputBorder(), + ), + items: appSettingsService.settings.cyr2latProfiles.map(( + profile, + ) { + return DropdownMenuItem( + value: profile.id, + child: Text(profile.name), + ); + }).toList(), + onChanged: (value) => setState(() { + selectedCyr2LatProfileId = value; + }), + ), + ), + ], ], ), ), @@ -1506,6 +1538,10 @@ class _ChannelsScreenState extends State channel.index, cyr2latEnabled, ); + await connector.setChannelCyr2LatProfileId( + channel.index, + selectedCyr2LatProfileId, + ); if (!context.mounted) return; showDismissibleSnackBar( context, diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index b9cdf0e..9ec8441 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -1211,6 +1211,10 @@ class _ChatScreenState extends State { void _showContactSettings(BuildContext context) { final connector = Provider.of(context, listen: false); + final appSettingsService = Provider.of( + context, + listen: false, + ); connector.ensureContactSmazSettingLoaded(widget.contact.publicKeyHex); connector.ensureContactCyr2LatSettingLoaded(widget.contact.publicKeyHex); final contact = widget.contact; @@ -1218,6 +1222,9 @@ class _ChatScreenState extends State { bool cyr2latEnabled = connector.isContactCyr2LatEnabled( contact.publicKeyHex, ); + String? selectedCyr2LatProfileId = connector.getContactCyr2LatProfileId( + contact.publicKeyHex, + ); bool teleBaseEnabled = contact.teleBaseEnabled; bool teleLocEnabled = contact.teleLocEnabled; bool teleEnvEnabled = contact.teleEnvEnabled; @@ -1283,6 +1290,36 @@ class _ChatScreenState extends State { }); }, ), + if (cyr2latEnabled) ...[ + Padding( + padding: const EdgeInsets.fromLTRB(0, 8, 0, 8), + child: DropdownButtonFormField( + initialValue: selectedCyr2LatProfileId, + decoration: InputDecoration( + labelText: + context.l10n.channels_cyr2latSettingsSubheading, + border: const OutlineInputBorder(), + ), + items: appSettingsService.settings.cyr2latProfiles.map(( + profile, + ) { + return DropdownMenuItem( + value: profile.id, + child: Text(profile.name), + ); + }).toList(), + onChanged: (value) { + connector.setContactCyr2LatProfileId( + contact.publicKeyHex, + value, + ); + setDialogState(() { + selectedCyr2LatProfileId = value; + }); + }, + ), + ), + ], const Divider(height: 8), SwitchListTile( contentPadding: EdgeInsets.zero, diff --git a/lib/services/app_settings_service.dart b/lib/services/app_settings_service.dart index 820fe0e..7b3d584 100644 --- a/lib/services/app_settings_service.dart +++ b/lib/services/app_settings_service.dart @@ -260,4 +260,56 @@ class AppSettingsService extends ChangeNotifier { _settings.copyWith(translationDownloadedModels: value), ); } + + Cyr2LatProfile getSelectedCyr2LatProfile() { + return _settings.cyr2latProfiles.firstWhere( + (p) => p.id == _settings.selectedCyr2latProfileId, + orElse: () => _settings.cyr2latProfiles.first, + ); + } + + Cyr2LatProfile? getCyr2LatProfileById(String profileId) { + return _settings.cyr2latProfiles.cast().firstWhere( + (p) => p?.id == profileId, + orElse: () => null, + ); + } + + Future setSelectedCyr2LatProfile(String profileId) async { + await updateSettings( + _settings.copyWith(selectedCyr2latProfileId: profileId), + ); + } + + Future addCyr2LatProfile(Cyr2LatProfile profile) async { + final updated = List.from(_settings.cyr2latProfiles) + ..add(profile); + await updateSettings(_settings.copyWith(cyr2latProfiles: updated)); + } + + Future updateCyr2LatProfile(Cyr2LatProfile updatedProfile) async { + final updated = _settings.cyr2latProfiles + .map((p) => p.id == updatedProfile.id ? updatedProfile : p) + .toList(); + await updateSettings(_settings.copyWith(cyr2latProfiles: updated)); + } + + Future removeCyr2LatProfile(String profileId) async { + if (_settings.cyr2latProfiles.length <= 1) { + return; // Don't remove the last profile + } + final updated = _settings.cyr2latProfiles + .where((p) => p.id != profileId) + .toList(); + var newSelectedId = _settings.selectedCyr2latProfileId; + if (newSelectedId == profileId) { + newSelectedId = updated.first.id; + } + await updateSettings( + _settings.copyWith( + cyr2latProfiles: updated, + selectedCyr2latProfileId: newSelectedId, + ), + ); + } } diff --git a/lib/storage/channel_settings_store.dart b/lib/storage/channel_settings_store.dart index eb637ac..e0b390f 100644 --- a/lib/storage/channel_settings_store.dart +++ b/lib/storage/channel_settings_store.dart @@ -58,20 +58,7 @@ class ChannelSettingsStore { } final prefs = PrefsManager.instance; final key = '$keyForCyr2Lat$channelIndex'; - final oldKey = '$_cyr2latKeyPrefix$channelIndex'; - bool? enabled = prefs.getBool(key); - if (enabled == null) { - // Attempt migration from legacy unscoped key on first load - enabled = prefs.getBool(oldKey); - prefs.remove(oldKey); - if (enabled != null) { - appLogger.info( - 'Migrating channel Cyr2Lat settings from legacy key $oldKey to scoped key $key', - ); - await prefs.setBool(key, enabled); - } - } - return enabled ?? false; + return prefs.getBool(key) ?? false; } Future saveCyr2LatEnabled(int channelIndex, bool enabled) async { @@ -85,4 +72,32 @@ class ChannelSettingsStore { final key = '$keyForCyr2Lat$channelIndex'; await prefs.setBool(key, enabled); } + + Future loadCyr2LatProfileId(int channelIndex) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot load channel settings.', + ); + return null; + } + final prefs = PrefsManager.instance; + final key = '${keyForCyr2Lat}profile_$channelIndex'; + return prefs.getString(key); + } + + Future saveCyr2LatProfileId(int channelIndex, String? profileId) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot save channel settings.', + ); + return; + } + final prefs = PrefsManager.instance; + final key = '${keyForCyr2Lat}profile_$channelIndex'; + if (profileId == null) { + await prefs.remove(key); + } else { + await prefs.setString(key, profileId); + } + } } diff --git a/lib/storage/contact_settings_store.dart b/lib/storage/contact_settings_store.dart index 5db15ad..682f1af 100644 --- a/lib/storage/contact_settings_store.dart +++ b/lib/storage/contact_settings_store.dart @@ -58,19 +58,6 @@ class ContactSettingsStore { } final prefs = PrefsManager.instance; final key = '$keyForCyr2Lat$contactKeyHex'; - final oldKey = '$_cyr2latKeyPrefix$contactKeyHex'; - bool? enabled = prefs.getBool(key); - if (enabled == null) { - // Attempt migration from legacy unscoped key on first load - enabled = prefs.getBool(oldKey); - prefs.remove(oldKey); - if (enabled != null) { - appLogger.info( - 'Migrating contact Cyr2Lat settings from legacy key $oldKey to scoped key $key', - ); - await prefs.setBool(key, enabled); - } - } return prefs.getBool(key) ?? false; } @@ -85,4 +72,35 @@ class ContactSettingsStore { final key = '$keyForCyr2Lat$contactKeyHex'; await prefs.setBool(key, enabled); } + + Future loadCyr2LatProfileId(String contactKeyHex) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot load contact settings.', + ); + return null; + } + final prefs = PrefsManager.instance; + final key = '${keyForCyr2Lat}profile_$contactKeyHex'; + return prefs.getString(key); + } + + Future saveCyr2LatProfileId( + String contactKeyHex, + String? profileId, + ) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot save contact settings.', + ); + return; + } + final prefs = PrefsManager.instance; + final key = '${keyForCyr2Lat}profile_$contactKeyHex'; + if (profileId == null) { + await prefs.remove(key); + } else { + await prefs.setString(key, profileId); + } + } } From 38f6e427964fc113a6729cdf82a358ea9e4a33d8 Mon Sep 17 00:00:00 2001 From: HDDen <62592944+HDDen@users.noreply.github.com> Date: Sat, 25 Apr 2026 02:15:18 +0300 Subject: [PATCH 16/51] just fixed conflict of cyr2lat with PR #405 --- 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 +-------- 34 files changed, 47 insertions(+), 188 deletions(-) diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 0a949eb..1ec2594 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1966,13 +1966,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", @@ -1985,7 +1978,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": { @@ -2117,5 +2109,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 e91fad8..3657eaa 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1938,13 +1938,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", @@ -1956,7 +1949,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.", @@ -2089,5 +2081,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 bd4a0bb..d1b1dc4 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -2028,13 +2028,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.", @@ -2046,7 +2039,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", @@ -2127,5 +2119,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 414ee2b..a4df437 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -1938,13 +1938,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.", @@ -1957,7 +1950,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": { @@ -2089,5 +2081,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 4faebd7..3a57c10 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -2028,13 +2028,6 @@ "radioStats_stripWaiting": "ラジオの統計情報を取得中…", "radioStats_settingsTile": "ラジオの統計", "radioStats_settingsSubtitle": "ノイズレベル、RSSI、SNR、および通信時間", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "settings_privacy": "プライバシー設定", "settings_privacySubtitle": "共有する情報の内容を管理する。", "settings_denyAll": "すべてを否定", @@ -2046,7 +2039,6 @@ "settings_telemetryEnvironmentMode": "テレメトリ環境モード", "settings_advertLocation": "広告掲載場所", "settings_advertLocationSubtitle": "広告に場所を記載してください。", - "settings_multiAck": "複数のACK:{value}", "settings_telemetryModeUpdated": "テレメトリモードが更新されました", "contact_info": "連絡先", "contact_settings": "連絡設定", @@ -2127,5 +2119,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 b2dc056..870420c 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -2028,13 +2028,6 @@ "radioStats_stripWaiting": "라디오 통계 가져오기…", "radioStats_settingsTile": "라디오 통계", "radioStats_settingsSubtitle": "잡음 수준, RSSI, 신호 대 잡음비, 통신 시간", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "settings_privacy": "개인 정보 설정", "settings_privacySubtitle": "어떤 정보를 공유할지 통제하세요.", "settings_privacySettingsDescription": "어떤 정보를 기기가 다른 사람들과 공유할지 선택하세요.", @@ -2046,7 +2039,6 @@ "settings_telemetryEnvironmentMode": "텔레메트리 환경 모드", "settings_advertLocation": "광고 위치", "settings_advertLocationSubtitle": "광고에 위치 정보를 포함하세요.", - "settings_multiAck": "다중 ACK: {value}", "settings_telemetryModeUpdated": "텔레메트리 모드 업데이트 완료", "contact_info": "연락처", "contact_settings": "연락처 설정", @@ -2127,5 +2119,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 e84ece6..3c20511 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 0c1cc08..858ee76 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 6fdbdd9..0164a03 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 36fbe87..c419c10 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 469b777..4c4c696 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 addba54..fd947e5 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 8285057..8da5f29 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 567e9dc..6cc619b 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 55e755e..313ec23 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 b8ec8a6..5173892 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 28e377c..45d21e1 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 e49f235..c8973fa 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 0d8d32f..464c30f 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 6ae1661..3f780e8 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 0e95654..a9fb036 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 b6264ea..9bf1cde 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 c540b99..af7e9fb 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 a5d63ff..e02237b 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 0a5e256..7ad23bb 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 f7fc5a4..54871bd 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -1938,13 +1938,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", @@ -1957,7 +1950,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": { @@ -2089,5 +2081,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 a59b394..50a4da5 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1976,13 +1976,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", @@ -1995,7 +1988,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": { @@ -2127,5 +2119,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 e49bc38..0fd70c7 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -1938,13 +1938,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.", @@ -1957,7 +1950,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 d3a0bcb..765f335 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -1178,13 +1178,6 @@ "contact_clearChat": "Очистить чат", "contact_lastSeen": "Последний раз видели", "contact_teleBaseSubtitle": "Разрешить обмен уровнем заряда батареи и базовой телеметрией", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_maxRouteWeight": "Максимальный допустимый вес маршрута", "appSettings_maxRouteWeightSubtitle": "Максимальный вес, который может быть перевезён по определённому маршруту при успешных доставках.", "appSettings_initialRouteWeightSubtitle": "Начальный вес для новых, только что открытых маршрутов", @@ -1197,7 +1190,6 @@ "appSettings_maxMessageRetriesSubtitle": "Количество попыток повторной отправки сообщения перед тем, как пометить его как неудачное.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрии обновлен", - "settings_multiAck": "Мульти-ACK: {value}", "map_showOverlaps": "Перекрытия ключа повтора", "map_runTraceWithReturnPath": "Вернуться обратно по тому же пути", "@radioStats_noiseFloor": { @@ -1329,5 +1321,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 c7a66a5..5213925 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -1938,13 +1938,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", @@ -1957,7 +1950,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 84f8068..7e2e3f4 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -1938,13 +1938,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", @@ -1956,7 +1949,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.", @@ -2089,5 +2081,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 f8da46f..08c3b33 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -1938,13 +1938,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.", @@ -1957,7 +1950,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": { @@ -2089,5 +2081,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 d324598..f376a1e 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1938,13 +1938,6 @@ "contact_lastSeen": "Останній раз бачили", "contact_teleEnv": "Середовище телеметрії", "contact_teleEnvSubtitle": "Дозволити спільний доступ до даних датчиків середовища", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_initialRouteWeight": "Початкова вартість маршруту", "appSettings_initialRouteWeightSubtitle": "Початкова вага для нових відкритих шляхів", "appSettings_maxRouteWeight": "Максимальна вага маршруту", @@ -1957,7 +1950,6 @@ "appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрії оновлено", - "settings_multiAck": "Багатократне підтвердження: {value}", "map_showOverlaps": "Перекриття ключа повторювача", "map_runTraceWithReturnPath": "Повернутися назад тим же шляхом", "@radioStats_noiseFloor": { @@ -2089,5 +2081,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 beb4d0c..1df214f 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1943,13 +1943,6 @@ "contact_settings": "联系人设置", "contact_teleLocSubtitle": "允许共享位置数据", "contact_telemetry": "遥测数据", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_maxRouteWeight": "最大路径重量", "appSettings_initialRouteWeightSubtitle": "新发现路径的初始重量", "appSettings_initialRouteWeight": "初始路线权重", @@ -1961,7 +1954,7 @@ "appSettings_maxMessageRetries": "最大消息重试次数", "appSettings_maxMessageRetriesSubtitle": "在将消息标记为失败之前,允许尝试的次数", "path_routeWeight": "{weight}/{max}", - "settings_multiAck": "多重ACK:{value}", + "settings_multiAck": "多重ACK", "settings_telemetryModeUpdated": "遥测模式已更新", "map_showOverlaps": "重复键重叠", "map_runTraceWithReturnPath": "沿着相同的路径返回", From 4e368d562d3868fda7cd539d2609377ed2748f97 Mon Sep 17 00:00:00 2001 From: just-stuff-tm Date: Sat, 25 Apr 2026 08:56:28 -0400 Subject: [PATCH 17/51] 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 0000000..412e2fb --- /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 7260c25..b37529b 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 bf8ad2e..86846e9 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 606410c..dfe8168 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 b058568..04497cb 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 fa375a4..59ca46a 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 ae6acfb..597faf6 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 35001e6..9db535d 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 7ac449d..4a45f4c 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 54073db..69fd99f 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 0907557..d090bc1 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 01973df..8ee18b6 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 60cd325..70d09b3 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 353e5bc..eeb9091 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 d656ac4..824daab 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 acdc945..1ad6939 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 24c9ac6..34c25bd 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 abb1427..21f9e52 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 6001826..d2d0511 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 8f74af8..b2bd913 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 d1ebea4..fadc7b1 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 918860e..cdfef43 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 5584da9..2b7d110 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 4b7e3d4..76cf8e3 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 f855652..1040797 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 2f3b30c..157dce8 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 00816f2..f3259c5 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 f43fd53..8f83263 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 6181e8a..4cef606 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 fa3eb37..fa8bc46 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 870be9e..2c971be 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 634cf1c..2b8efab 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 1ad0f1b..9ab27fe 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 9cd6072..daddb24 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 cace8c6..5969b91 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 aaa6ea7..5ed2561 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 489b8a2..1bf2b58 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 9d6a8bf..ee8285c 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 ec8a391..e88f4b9 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 7f056c8..e8d149a 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 267a70b..bd649f2 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 18/51] 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 412e2fb..0000000 --- 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 19/51] contextstream/ is added to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 88295e7..fa4d28d 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 20/51] 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 9e26dfe..6c2e251 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 21/51] 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 7260c25..c4af058 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 bf8ad2e..7d9edd5 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 606410c..9fd22e9 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 b058568..859b13b 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 fa375a4..9fc51c8 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 ae6acfb..de00ac4 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 35001e6..0ea9ab0 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 7ac449d..517ef5f 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 54073db..81b37f7 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 0907557..b8f35ea 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 01973df..1ac0074 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 60cd325..5954275 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 353e5bc..6611bd0 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 d656ac4..c5029a7 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 acdc945..91d6836 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 24c9ac6..609d4e1 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 abb1427..a906b7f 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 6001826..a222cd5 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 8f74af8..3a23f2c 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 d1ebea4..9470ad9 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 918860e..223a268 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 5584da9..18a6c69 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 4b7e3d4..ba4970f 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 f855652..f135fdd 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 2f3b30c..311946a 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 00816f2..bb1cc1b 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 f43fd53..c27b1d0 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 6181e8a..e415081 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 fa3eb37..932f999 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 870be9e..1509340 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 634cf1c..189f5b9 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 1ad0f1b..fc7830b 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 9cd6072..c6002ff 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 cace8c6..4ad9d9e 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 aaa6ea7..25a6dc2 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 489b8a2..1ac0b48 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 9d6a8bf..4eb539d 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 b203cbb..e89eb15 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 ffa8344..129f692 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 6a8acda..1f8bce2 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 22/51] 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 e89eb15..f7821a1 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 129f692..fe7b008 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 1f8bce2..fb48f83 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:,|