diff --git a/.gitignore b/.gitignore index 88295e7c..fa4d28d6 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ keystore.properties # IDE .vscode/launch.json .vscode/settings.json +.contextstream/ # Cloudflare Wrangler .wrangler diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 2de74a63..53f4d65a 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -677,6 +677,27 @@ class MeshCoreConnector extends ChangeNotifier { } } + void setContactUnreadCount(String contactKeyHex, int count) { + _contactUnreadCount[contactKeyHex] = count; + _unreadStore.saveContactUnreadCount( + Map.from(_contactUnreadCount), + ); + notifyListeners(); + } + + void setChannelUnreadCount(int channelIndex, int count) { + final channel = _findChannelByIndex(channelIndex); + if (channel != null) { + channel.unreadCount = count; + unawaited( + _channelStore.saveChannels( + _channels.isNotEmpty ? _channels : _cachedChannels, + ), + ); + notifyListeners(); + } + } + void markChannelRead(int channelIndex) { final channel = _findChannelByIndex(channelIndex); if (channel != null && channel.unreadCount > 0) { @@ -4043,7 +4064,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; @@ -4065,7 +4086,7 @@ class MeshCoreConnector extends ChangeNotifier { if (settings.notificationsEnabled && settings.notifyOnNewAdvert) { _notificationService.showAdvertNotification( contactName: contact.name, - contactType: contact.typeLabel, + contactType: contact.typeLabelRaw, contactId: contact.publicKeyHex, ); } @@ -4140,7 +4161,7 @@ class MeshCoreConnector extends ChangeNotifier { if (settings.notificationsEnabled && settings.notifyOnNewAdvert) { _notificationService.showAdvertNotification( contactName: contact.name, - contactType: contact.typeLabel, + contactType: contact.typeLabelRaw, contactId: contact.publicKeyHex, ); } @@ -6173,7 +6194,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/helpers/chat_scroll_controller.dart b/lib/helpers/chat_scroll_controller.dart index d2c73fbf..c0d19747 100644 --- a/lib/helpers/chat_scroll_controller.dart +++ b/lib/helpers/chat_scroll_controller.dart @@ -49,6 +49,25 @@ class ChatScrollController extends ScrollController { } } + /// Jumps toward an off-screen message so that lazy ListView.builder builds + /// items near it. Only visible + cacheExtent items have real heights, so we + /// use proportion of maxScrollExtent (itself an estimate from built items' + /// avg height). Call [onJumped] on the next frame to ensureVisible/scroll + /// to the exact target. + void jumpToEstimatedOffset({ + required int unreadCount, + required int totalMessages, + required VoidCallback onJumped, + }) { + if (!hasClients || totalMessages == 0) return; + final maxExtent = position.maxScrollExtent; + final jumpOffset = maxExtent * (unreadCount / totalMessages); + if (jumpOffset > 100) { + jumpTo(jumpOffset); + } + WidgetsBinding.instance.addPostFrameCallback((_) => onJumped()); + } + void scrollToBottomIfAtBottom() { // Only scroll if jump button is NOT showing (i.e., already at bottom) if (!showJumpToBottom.value && hasClients && position.maxScrollExtent > 0) { diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index c6ee97f1..82a598e5 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": "Реклама изпратена", @@ -2082,5 +2084,86 @@ "room_guest": "Информация за сървъра на стаята", "repeater_guest": "Информация за ретранслаторите", "repeater_guestTools": "Инструменти за гости", - "settings_multiAck": "Множество потвърждения" + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}", + "chat_score": "Score", + "settings_multiAck": "Множество потвърждения", + "map_sharedAt": "Споделено", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Избрано препятствие", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losBlockedSpotsHint": "Кликнете върху блокираната точка, за да я отбележите на картата.", + "losBlockedSpotsTitle": "Ограничени места", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index d0ea6129..3b05d235 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", @@ -2110,5 +2112,86 @@ "repeater_guestTools": "Gastwerkzeuge", "chat_sendMessage": "Nachricht senden", "room_guest": "Informationen zum Room Server", - "settings_multiAck": "Mehrere Bestätigungen" + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}", + "chat_score": "Score", + "settings_multiAck": "Mehrere Bestätigungen", + "map_sharedAt": "Geteilt", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsTitle": "Reservierte Plätze", + "losSelectedObstructionTitle": "Ausgewählte Behinderung", + "losBlockedSpotChip": "{distance} • {distanceUnit} • {obstruction} {heightUnit}", + "losBlockedSpotsHint": "Klicken Sie auf einen blockierten Bereich, um ihn auf der Karte hervorzuheben.", + "losSelectedObstructionDetails": "Blockiert durch {obstruction} in einer Höhe von {heightUnit}, {distanceFromA} von A und {distanceFromB} von B ({distanceUnit})." } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8d7addb6..22a7a1d4 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", @@ -181,6 +182,8 @@ "settings_multiAck": "Multi-ACKs", "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", @@ -516,6 +519,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", @@ -794,6 +805,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:", @@ -845,6 +857,8 @@ } } }, + "chat_markAsUnread": "Mark as Unread", + "chat_newMessages": "New messages", "chat_openLink": "Open Link?", "chat_openLinkConfirmation": "Do you want to open this link in your browser?", "chat_open": "Open", @@ -890,6 +904,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", @@ -924,6 +944,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.", @@ -1903,6 +1924,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", @@ -2142,5 +2203,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 1ec25943..8fb66c3b 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", @@ -2110,5 +2112,86 @@ "chat_sendMessage": "Enviar mensaje", "repeater_guestTools": "Herramientas para invitados", "room_guest": "Información del servidor", - "settings_multiAck": "Múltiples respuestas de confirmación" + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}", + "chat_score": "Score", + "settings_multiAck": "Múltiples respuestas de confirmación", + "map_sharedAt": "Compartido", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsTitle": "Espacios ocupados", + "losBlockedSpotsHint": "Seleccione un punto bloqueado para resaltarlo en el mapa.", + "losSelectedObstructionTitle": "Obstrucción seleccionada", + "losSelectedObstructionDetails": "Bloqueado por {obstruction} a una altura de {heightUnit}, a {distanceFromA} metros de A y a {distanceFromB} metros de B ({distanceUnit}).", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 3657eaa8..cd02a704 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", @@ -2082,5 +2084,86 @@ "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" + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}", + "chat_score": "Score", + "settings_multiAck": "Plusieurs accusés de réception", + "map_sharedAt": "Partagé", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Obstruction sélectionnée", + "losBlockedSpotsTitle": "Places occupés", + "losBlockedSpotsHint": "Sélectionnez un emplacement bloqué pour le mettre en évidence sur la carte.", + "losSelectedObstructionDetails": "Bloqué par {obstruction}, à une hauteur de {heightUnit}, à une distance de {distanceFromA} par rapport à A et à une distance de {distanceFromB} par rapport à B ({distanceUnit}).", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}" } diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index d1b1dc43..c243bc78 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", @@ -2120,5 +2122,86 @@ "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" + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}", + "chat_score": "Score", + "settings_multiAck": "Többszörös visszaigazolások", + "map_sharedAt": "Megosztva", + "@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 a4df4373..003d4749 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", @@ -2082,5 +2084,86 @@ "repeater_guestTools": "Strumenti per gli ospiti", "chat_sendMessage": "Invia messaggio", "room_guest": "Informazioni sul server", - "settings_multiAck": "ACK multipli" + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}", + "chat_score": "Score", + "settings_multiAck": "ACK multipli", + "map_sharedAt": "Condiviso", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Ostacolo selezionato", + "losBlockedSpotsHint": "Tocca un punto bloccato sulla mappa per evidenziarlo.", + "losBlockedSpotsTitle": "Posti occupati", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 3a57c10c..f5bf07b1 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": "広告が送信されました", @@ -2120,5 +2122,86 @@ "chat_sendMessage": "メッセージを送信する", "repeater_guest": "繰り返し送信に関する情報", "repeater_guestTools": "ゲスト向けツール", - "settings_multiAck": "複数のACK(応答)" + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}", + "chat_score": "Score", + "settings_multiAck": "複数のACK(応答)", + "map_sharedAt": "共有済み", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsHint": "地図上で、特定された場所を強調するために、該当する場所をタップしてください。", + "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 870420cc..1553d873 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": "광고 전송", @@ -2120,5 +2122,86 @@ "chat_sendMessage": "메시지를 보내기", "repeater_guest": "반복 장비 정보", "room_guest": "서버 정보", - "settings_multiAck": "다중 ACK" + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}", + "chat_score": "Score", + "settings_multiAck": "다중 ACK", + "map_sharedAt": "공유됨", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsHint": "지도에서 특정 위치를 강조하려면 해당 위치를 클릭하세요.", + "losBlockedSpotsTitle": "차단된 공간", + "losSelectedObstructionTitle": "선택된 장애물", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 3c205111..7cd8a756 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: @@ -2758,6 +2782,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: @@ -2920,6 +2950,18 @@ abstract class AppLocalizations { /// **'Unread: {count}'** String chat_unread(int count); + /// No description provided for @chat_markAsUnread. + /// + /// In en, this message translates to: + /// **'Mark as Unread'** + String get chat_markAsUnread; + + /// No description provided for @chat_newMessages. + /// + /// In en, this message translates to: + /// **'New messages'** + String get chat_newMessages; + /// No description provided for @chat_openLink. /// /// In en, this message translates to: @@ -3064,6 +3106,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: @@ -3226,6 +3304,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: @@ -5736,6 +5820,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: @@ -6484,6 +6609,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 858ee760..465c3c2c 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 => 'Редактирай'; @@ -445,6 +448,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 => 'Изпрати Реклама'; @@ -1095,6 +1105,11 @@ class AppLocalizationsBg extends AppLocalizations { @override String get channels_public => 'Публично'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => 'Личен'; @@ -1512,6 +1527,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get chat_successes => 'Успехи'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Премахни пътя'; @@ -1614,6 +1632,12 @@ class AppLocalizationsBg extends AppLocalizations { return 'Непрочетени: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => 'Отваряне на връзката?'; @@ -1695,6 +1719,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 => 'Споделете маркер тук'; @@ -1780,6 +1822,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get map_sharedPin => 'Споделено копие'; + @override + String get map_sharedAt => 'Споделено'; + @override String get map_joinRoom => 'Присъедини се към стаята'; @@ -3292,6 +3337,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 => 'Честота'; @@ -3756,4 +3832,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 0164a039..5f1f8e6a 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'; @@ -443,6 +446,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'; @@ -1090,6 +1100,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'; @@ -1511,6 +1526,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get chat_successes => 'Erfolgreich'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Pfad entfernen'; @@ -1611,6 +1629,12 @@ class AppLocalizationsDe extends AppLocalizations { return 'Ungelesen: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => 'Link öffnen?'; @@ -1692,6 +1716,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.'; @@ -1777,6 +1819,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'; @@ -3297,6 +3342,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'; @@ -3767,4 +3843,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 c419c10d..d435c09a 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'; @@ -435,6 +438,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'; @@ -1072,6 +1082,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'; @@ -1482,6 +1497,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get chat_successes => 'successes'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Remove path'; @@ -1579,6 +1597,12 @@ class AppLocalizationsEn extends AppLocalizations { return 'Unread: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => 'Open Link?'; @@ -1660,6 +1684,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'; @@ -1744,6 +1786,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'; @@ -3234,6 +3279,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'; @@ -3685,4 +3761,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 4c4c6964..c0c5ada5 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'; @@ -442,6 +445,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'; @@ -1092,6 +1102,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'; @@ -1509,6 +1524,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get chat_successes => 'Éxitos'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Eliminar ruta'; @@ -1610,6 +1628,12 @@ class AppLocalizationsEs extends AppLocalizations { return 'Sin leer: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => '¿Abrir enlace?'; @@ -1691,6 +1715,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í'; @@ -1776,6 +1818,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'; @@ -3291,6 +3336,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'; @@ -3760,4 +3836,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 fd947e5f..7096d90f 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'; @@ -447,6 +450,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'; @@ -1097,6 +1107,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é'; @@ -1516,6 +1531,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'; @@ -1619,6 +1637,12 @@ class AppLocalizationsFr extends AppLocalizations { return 'Non lu : $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => 'Ouvrir le lien ?'; @@ -1701,6 +1725,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'; @@ -1786,6 +1828,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'; @@ -3309,6 +3354,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'; @@ -3784,4 +3860,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 8da5f29c..3fff48bc 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'; @@ -445,6 +448,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'; @@ -1097,6 +1107,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'; @@ -1520,6 +1535,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'; @@ -1620,6 +1638,12 @@ class AppLocalizationsHu extends AppLocalizations { return 'Olvasatlan: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => 'Nyisd meg a linket?'; @@ -1702,6 +1726,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'; @@ -1788,6 +1830,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'; @@ -3304,6 +3349,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'; @@ -3775,4 +3851,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 6cc619bd..17623aa1 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'; @@ -445,6 +448,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'; @@ -1093,6 +1103,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'; @@ -1511,6 +1526,9 @@ class AppLocalizationsIt extends AppLocalizations { @override String get chat_successes => 'successi'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Rimuovi percorso'; @@ -1613,6 +1631,12 @@ class AppLocalizationsIt extends AppLocalizations { return 'Non letti: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => 'Aprire il link?'; @@ -1694,6 +1718,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'; @@ -1778,6 +1820,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'; @@ -3295,6 +3340,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'; @@ -3764,4 +3840,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 313ec234..766c9779 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 => '編集'; @@ -422,6 +425,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 => '広告を送信する'; @@ -1039,6 +1049,11 @@ class AppLocalizationsJa extends AppLocalizations { @override String get channels_public => '一般の人々'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => '個人の'; @@ -1444,6 +1459,9 @@ class AppLocalizationsJa extends AppLocalizations { @override String get chat_successes => '成功事例'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'パスを削除する'; @@ -1539,6 +1557,12 @@ class AppLocalizationsJa extends AppLocalizations { return '未読: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => 'リンクを開く?'; @@ -1617,6 +1641,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 => 'この場所でシェア'; @@ -1700,6 +1742,9 @@ class AppLocalizationsJa extends AppLocalizations { @override String get map_sharedPin => '共有パスワード'; + @override + String get map_sharedAt => '共有済み'; + @override String get map_joinRoom => '部屋に参加する'; @@ -3142,6 +3187,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 => '周波数'; @@ -3578,4 +3653,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 51738923..310fee04 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 => '수정'; @@ -422,6 +425,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 => '광고 전송'; @@ -1034,6 +1044,11 @@ class AppLocalizationsKo extends AppLocalizations { @override String get channels_public => '대중의'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => '사립'; @@ -1440,6 +1455,9 @@ class AppLocalizationsKo extends AppLocalizations { @override String get chat_successes => '성공 사례'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => '경로 제거'; @@ -1535,6 +1553,12 @@ class AppLocalizationsKo extends AppLocalizations { return '읽지 않음: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => '링크를 열기?'; @@ -1613,6 +1637,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 => '여기에서 마커 공유'; @@ -1696,6 +1738,9 @@ class AppLocalizationsKo extends AppLocalizations { @override String get map_sharedPin => '공유 비밀번호'; + @override + String get map_sharedAt => '공유됨'; + @override String get map_joinRoom => '방에 참여'; @@ -3142,6 +3187,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 => '빈도'; @@ -3577,4 +3652,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 45d21e10..d840d248 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'; @@ -440,6 +443,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'; @@ -1082,6 +1092,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é'; @@ -1497,6 +1512,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get chat_successes => 'Succesvol'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Pad verwijderen'; @@ -1598,6 +1616,12 @@ class AppLocalizationsNl extends AppLocalizations { return 'Nieuw: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => 'Link openen?'; @@ -1679,6 +1703,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'; @@ -1764,6 +1806,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'; @@ -3275,6 +3320,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'; @@ -3739,4 +3815,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 c8973fa1..0bb3d0a3 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'; @@ -448,6 +451,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'; @@ -1102,6 +1112,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'; @@ -1523,6 +1538,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ę'; @@ -1624,6 +1642,12 @@ class AppLocalizationsPl extends AppLocalizations { return 'Nieprzeczytane: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => 'Otworzyć link?'; @@ -1705,6 +1729,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'; @@ -1790,6 +1832,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'; @@ -3302,6 +3347,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ść'; @@ -3772,4 +3848,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 464c30f0..02e70676 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'; @@ -444,6 +447,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'; @@ -1093,6 +1103,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'; @@ -1508,6 +1523,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get chat_successes => 'Sucessos'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => 'Remover caminho'; @@ -1609,6 +1627,12 @@ class AppLocalizationsPt extends AppLocalizations { return 'Não lido: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => 'Abrir link?'; @@ -1691,6 +1715,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'; @@ -1776,6 +1818,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'; @@ -3289,6 +3334,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'; @@ -3753,4 +3829,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 3f780e89..870a2cdc 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 => 'Готово'; + @override String get common_edit => 'Изменить'; @@ -444,6 +447,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 => 'Отправить анонсирование'; @@ -1093,6 +1103,11 @@ class AppLocalizationsRu extends AppLocalizations { @override String get channels_public => 'Публичный'; + @override + String channels_via(String path) { + return 'через $path'; + } + @override String get channels_private => 'Приватный'; @@ -1511,6 +1526,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get chat_successes => 'успешно'; + @override + String get chat_score => 'Оценка'; + @override String get chat_removePath => 'Удалить маршрут'; @@ -1613,6 +1631,12 @@ class AppLocalizationsRu extends AppLocalizations { return 'Непрочитанных: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => 'Открыть ссылку?'; @@ -1694,6 +1718,24 @@ class AppLocalizationsRu extends AppLocalizations { @override String get map_flags => 'Флаги'; + @override + String get map_type => 'Тип'; + + @override + String get map_path => 'Путь'; + + @override + String get map_location => 'Местоположение'; + + @override + String get map_estLocation => 'Прибл. местоположение'; + + @override + String get map_publicKey => 'Публичный ключ'; + + @override + String get map_publicKeyPrefixHint => 'напр. ab12'; + @override String get map_shareMarkerHere => 'Поделиться меткой здесь'; @@ -1779,6 +1821,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get map_sharedPin => 'Общая метка'; + @override + String get map_sharedAt => 'Поделено'; + @override String get map_joinRoom => 'Присоединиться к комнате'; @@ -3294,6 +3339,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 => 'Частота'; @@ -3767,4 +3844,40 @@ class AppLocalizationsRu 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_sk.dart b/lib/l10n/app_localizations_sk.dart index a9fb036f..f4b11eed 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ť'; @@ -439,6 +442,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'; @@ -1082,6 +1092,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é'; @@ -1499,6 +1514,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'; @@ -1600,6 +1618,12 @@ class AppLocalizationsSk extends AppLocalizations { return 'Nezriadené: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => 'Otvoriť odkaz?'; @@ -1681,6 +1705,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'; @@ -1766,6 +1808,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ť'; @@ -3270,6 +3315,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'; @@ -3734,4 +3810,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 9bf1cde8..aaa48555 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'; @@ -438,6 +441,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'; @@ -1080,6 +1090,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'; @@ -1496,6 +1511,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'; @@ -1595,6 +1613,12 @@ class AppLocalizationsSl extends AppLocalizations { return 'Nerešeno: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => 'Odpreti povezavo?'; @@ -1677,6 +1701,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.'; @@ -1761,6 +1803,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'; @@ -3271,6 +3316,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'; @@ -3738,4 +3814,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 af7e9fbb..d0c26f6d 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'; @@ -436,6 +439,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'; @@ -1073,6 +1083,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'; @@ -1490,6 +1505,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'; @@ -1588,6 +1606,12 @@ class AppLocalizationsSv extends AppLocalizations { return 'Olästa: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => 'Öppna länk?'; @@ -1669,6 +1693,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'; @@ -1754,6 +1796,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'; @@ -3252,6 +3297,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'; @@ -3715,4 +3791,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 e02237bb..3c38411b 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 get settings_multiAck => 'Багато підтверджень'; @@ -440,6 +443,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 => 'Оголосити себе'; @@ -489,14 +499,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) { @@ -582,7 +592,7 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get appSettings_title => 'Налаштування програми'; + String get appSettings_title => 'Налаштування застосунку'; @override String get appSettings_appearance => 'Вигляд'; @@ -717,14 +727,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 => @@ -862,13 +872,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 => 'Область не вибрано'; @@ -882,19 +892,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 => 'Контакти'; @@ -1041,14 +1052,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 => @@ -1082,11 +1093,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 => 'Приватний'; @@ -1115,7 +1131,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String channels_channelDeleteFailed(String name) { - return 'Не вдалося видалити канал \"$name\"'; + return 'Не вдалось видалити канал \"$name\"'; } @override @@ -1149,7 +1165,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get channels_pskMustBe32Hex => - 'PSK має складатися з 32 шістнадцяткових символів.'; + 'PSK має складатись з 32 шістнадцяткових символів.'; @override String channels_channelAdded(String name) { @@ -1249,24 +1265,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-код'; @@ -1309,7 +1325,7 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get chat_location => 'Розташування'; + String get chat_location => 'Геопозиція'; @override String get chat_typeMessage => 'Введіть повідомлення...'; @@ -1370,7 +1386,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 не вдався'; @@ -1379,7 +1395,7 @@ class AppLocalizationsUk extends AppLocalizations { String get gifPicker_noInternet => 'Немає інтернет-з\'єднання'; @override - String get debugLog_appTitle => 'Журнал налагодження програми'; + String get debugLog_appTitle => 'Журнал налагодження застосунку'; @override String get debugLog_bleTitle => 'Журнал налагодження BLE'; @@ -1402,7 +1418,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get debugLog_enableInSettings => - 'Увімкніть налагодження програми в налаштуваннях'; + 'Увімкніть налагодження застосунку в налаштуваннях'; @override String get debugLog_frames => 'Кадри'; @@ -1473,31 +1489,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'; } @@ -1505,6 +1521,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get chat_successes => 'Успішно'; + @override + String get chat_score => 'Оцінка'; + @override String get chat_removePath => 'Видалити шлях'; @@ -1534,11 +1553,11 @@ class AppLocalizationsUk extends AppLocalizations { @override String get chat_floodModeSubtitle => - 'Використовувати перемикач маршрутизації в панелі програми'; + 'Використовувати перемикач маршрутизації в панелі застосунку'; @override String get chat_floodModeEnabled => - 'Увімкнено режим «на всю мережу». Перемикайте через іконку маршрутизації на панелі інструментів.'; + 'Увімкнено режим «через всю мережу». Перемикайте через іконку маршрутизації на панелі інструментів.'; @override String get chat_fullPath => 'Повний шлях'; @@ -1552,10 +1571,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'; } @@ -1583,30 +1602,36 @@ 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) { return 'Непрочитано: $count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => 'Відкрити посилання?'; @@ -1619,7 +1644,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String chat_couldNotOpenLink(String url) { - return 'Не вдалося відкрити посилання: $url'; + return 'Не вдалось відкрити посилання: $url'; } @override @@ -1635,12 +1660,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) { @@ -1678,7 +1702,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get map_disconnectConfirm => - 'Ви впевнені, що хочете відключитися від цього пристрою?'; + 'Ви впевнені, що хочете відключитись від цього пристрою?'; @override String get map_from => 'Від'; @@ -1690,10 +1714,28 @@ class AppLocalizationsUk extends AppLocalizations { String get map_flags => 'Прапорці'; @override - String get map_shareMarkerHere => 'Поділитися маркером тут'; + String get map_type => 'Тип'; @override - String get map_setAsMyLocation => 'Встановити моє місцезнаходження'; + String get map_path => 'Шлях'; + + @override + String get map_location => 'Геопозиція'; + + @override + String get map_estLocation => 'Орієнтовна геопозиція'; + + @override + String get map_publicKey => 'Публічний ключ'; + + @override + String get map_publicKeyPrefixHint => 'напр. ab12'; + + @override + String get map_shareMarkerHere => 'Поділитись маркером тут'; + + @override + String get map_setAsMyLocation => 'Встановити мою геопозицію'; @override String get map_pinLabel => 'Мітка піна'; @@ -1714,16 +1756,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 => 'Фільтрувати вузли'; @@ -1741,7 +1783,7 @@ class AppLocalizationsUk extends AppLocalizations { String get map_otherNodes => 'Інші вузли'; @override - String get map_showOverlaps => 'Перекриття ключа повторювача'; + String get map_showOverlaps => 'Перекриття ключів ретрансляторів'; @override String get map_keyPrefix => 'Префікс ключа'; @@ -1760,13 +1802,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 => 'Час останньої активності'; @@ -1775,7 +1817,10 @@ class AppLocalizationsUk extends AppLocalizations { String get map_sharedPin => 'Спільний пін'; @override - String get map_joinRoom => 'Приєднатися до кімнати'; + String get map_sharedAt => 'Поділено'; + + @override + String get map_joinRoom => 'Приєднатись до кімнати'; @override String get map_manageRepeater => 'Керувати ретранслятором'; @@ -1787,13 +1832,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 => 'Офлайн-кеш карти'; @@ -1933,7 +1978,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get dialog_disconnectConfirm => - 'Ви впевнені, що хочете відключитися від цього пристрою?'; + 'Ви впевнені, що хочете відключитись від цього пристрою?'; @override String get login_repeaterLogin => 'Вхід у ретранслятор'; @@ -1969,10 +2014,10 @@ class AppLocalizationsUk extends AppLocalizations { String get login_routingMode => 'Режим маршрутизації'; @override - String get login_autoUseSavedPath => 'Авто (використовувати збережений шлях)'; + String get login_autoUseSavedPath => 'Авто (збережений шлях)'; @override - String get login_forceFloodMode => 'Примусово на всю мережу'; + String get login_forceFloodMode => 'Примусово через всю мережу'; @override String get login_managePaths => 'Керувати шляхами'; @@ -2010,10 +2055,10 @@ class AppLocalizationsUk extends AppLocalizations { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'стрибками', - many: 'стрибками', - few: 'стрибками', - one: 'стрибком', + other: 'переходами', + many: 'переходами', + few: 'переходами', + one: 'переходом', ); return 'Використання шляху з $count $_temp0'; } @@ -2026,7 +2071,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get path_hexPrefixInstructions => - 'Введіть 2-символьні hex-префікси для кожного стрибка, розділені комами.'; + 'Введіть 2-символьні hex-префікси для кожного переходу, розділені комами.'; @override String get path_hexPrefixExample => @@ -2037,7 +2082,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get path_helperMaxHops => - 'Макс. 64 стрибки. Кожен префікс - 2 шістнадцяткові символи (1 байт)'; + 'Макс. 64 переходи. Кожен префікс — 2 шістнадцяткові символи (1 байт)'; @override String get path_selectFromContacts => 'Вибрати з контактів:'; @@ -2056,7 +2101,7 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get path_tooLong => 'Шлях занадто довгий. Максимум 64 стрибки.'; + String get path_tooLong => 'Шлях занадто довгий. Максимум 64 переходи.'; @override String get path_setPath => 'Встановити шлях'; @@ -2077,7 +2122,7 @@ class AppLocalizationsUk extends AppLocalizations { String get repeater_managementTools => 'Інструменти керування'; @override - String get repeater_guestTools => 'Інструменти для гостей'; + String get repeater_guestTools => 'Гостьові інструменти'; @override String get repeater_status => 'Статус'; @@ -2104,7 +2149,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_neighborsSubtitle => - 'Показати сусідів нульового стрибка.'; + 'Показати сусідів, доступних без ретрансляції.'; @override String get repeater_settings => 'Налаштування'; @@ -2130,7 +2175,7 @@ class AppLocalizationsUk extends AppLocalizations { 'Авто (використовувати збережений шлях)'; @override - String get repeater_forceFloodMode => 'Примусово на всю мережу'; + String get repeater_forceFloodMode => 'Примусово через всю мережу'; @override String get repeater_pathManagement => 'Керування шляхами'; @@ -2207,17 +2252,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 @@ -2276,7 +2321,7 @@ class AppLocalizationsUk extends AppLocalizations { String get repeater_codingRate => 'Швидкість кодування'; @override - String get repeater_locationSettings => 'Налаштування розташування'; + String get repeater_locationSettings => 'Налаштування геопозиції'; @override String get repeater_latitude => 'Широта'; @@ -2314,14 +2359,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) { @@ -2330,7 +2375,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_floodAdvertInterval => - 'Інтервал оголошень на всю мережу (flood)'; + 'Інтервал оголошень через всю мережу (flood)'; @override String repeater_floodAdvertIntervalHours(int hours) { @@ -2357,7 +2402,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_regenerateIdentityKey => - 'Перегенерувати ключ ідентичності'; + 'Перегенерувати ключ ідентифікації'; @override String get repeater_regenerateIdentityKeySubtitle => @@ -2365,7 +2410,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_regenerateIdentityKeyConfirm => - 'Це створить нову ідентичність для ретранслятора. Продовжити?'; + 'Це створить нову ідентифікацію для ретранслятора. Продовжити?'; @override String get repeater_eraseFileSystem => 'Очистити файлову систему'; @@ -2414,7 +2459,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_refreshLocationSettings => - 'Оновити налаштування розташування'; + 'Оновити налаштування геопозиції'; @override String get repeater_refreshPacketForwarding => 'Оновити пересилання пакетів'; @@ -2503,7 +2548,7 @@ class AppLocalizationsUk extends AppLocalizations { String get repeater_cliQuickClockSync => 'Синхронізація годинника'; @override - String get repeater_cliQuickDiscovery => 'Відкрити сусідів'; + String get repeater_cliQuickDiscovery => 'Виявити сусідів'; @override String get repeater_cliHelpAdvert => 'Надсилає пакет оголошення'; @@ -2545,7 +2590,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_cliHelpSetFloodMax => - 'Встановлює максимальну кількість стрибків для вхідних пакетів flood (якщо >= max, пакет не пересилається).'; + 'Встановлює максимальну кількість переходів для вхідних пакетів flood (якщо >= max, пакет не пересилається).'; @override String get repeater_cliHelpSetIntThresh => @@ -2565,7 +2610,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_cliHelpSetFloodAdvertInterval => - 'Встановлює інтервал таймера в годинах для надсилання пакету оголошення на всю мережу. Встановіть 0 для вимкнення.'; + 'Встановлює інтервал таймера в годинах для надсилання пакету оголошення через всю мережу. Встановіть 0 для вимкнення.'; @override String get repeater_cliHelpSetGuestPassword => @@ -2592,7 +2637,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_cliHelpSetTxDelay => - 'Встановлює множник для часу роботи в режимі «на всю мережу» (flood) для пакету та системи випадкових слотів, щоб затримати його відправку (для зменшення ймовірності колізій).'; + 'Встановлює множник для часу роботи в режимі «через всю мережу» (flood) для пакету та системи випадкових слотів, щоб затримати його відправку (для зменшення ймовірності колізій).'; @override String get repeater_cliHelpSetDirectTxDelay => @@ -2655,7 +2700,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get repeater_cliHelpRegion => - '(тільки серійний) Перелічує всі визначені регіони та поточні дозволи на оголошення «на всю мережу» (flood).'; + '(тільки серійний) Перелічує всі визначені регіони та поточні дозволи на оголошення «через всю мережу» (flood).'; @override String get repeater_cliHelpRegionLoad => @@ -2709,11 +2754,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 => 'Список команд'; @@ -2732,7 +2777,7 @@ class AppLocalizationsUk extends AppLocalizations { String get repeater_bridge => 'Міст'; @override - String get repeater_logging => 'Логування'; + String get repeater_logging => 'Журналювання'; @override String get repeater_neighborsRepeaterOnly => 'Сусіди (Тільки ретранслятор)'; @@ -2843,7 +2888,7 @@ class AppLocalizationsUk extends AppLocalizations { String get channelPath_otherObservedPaths => 'Інші спостережувані шляхи'; @override - String get channelPath_repeaterHops => 'Стрибки ретранслятора'; + String get channelPath_repeaterHops => 'Переходи через ретранслятори'; @override String get channelPath_noHopDetails => @@ -2875,7 +2920,7 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get channelPath_noLocationData => 'Немає даних про розташування'; + String get channelPath_noLocationData => 'Немає даних про геопозицію'; @override String channelPath_timeWithDate(int day, int month, String time) { @@ -2891,19 +2936,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 @@ -2931,7 +2976,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get channelPath_noHopDetailsAvailable => - 'Деталі стрибків недоступні для цього пакету.'; + 'Деталі переходів недоступні для цього пакету.'; @override String get channelPath_unknownRepeater => 'Невідомий ретранслятор'; @@ -2944,17 +2989,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 @@ -2990,16 +3035,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-код спільноти'; @@ -3056,11 +3101,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 @@ -3068,20 +3113,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 @@ -3089,7 +3134,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get community_addHashtagChannelDesc => - 'Додати канал хештегу для цієї спільноти'; + 'Додати хештег-канал для цієї спільноти'; @override String get community_selectCommunity => 'Вибрати спільноту'; @@ -3099,7 +3144,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get community_regularHashtagDesc => - 'Публічний хештег (будь-хто може приєднатися)'; + 'Публічний хештег (будь-хто може приєднатись)'; @override String get community_communityHashtag => 'Хештег спільноти'; @@ -3123,7 +3168,7 @@ class AppLocalizationsUk extends AppLocalizations { String get listFilter_latestMessages => 'Останні повідомлення'; @override - String get listFilter_heardRecently => 'Нещодавно чули'; + String get listFilter_heardRecently => 'Нещодавно почуті'; @override String get listFilter_az => 'А-Я'; @@ -3162,17 +3207,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 => 'Очистити шлях'; @@ -3208,7 +3253,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String losCustomPointLabel(int index) { - return 'Спеціальний $index'; + return 'Власна точка $index'; } @override @@ -3273,7 +3318,7 @@ class AppLocalizationsUk extends AppLocalizations { 'Недійсні дані про точки/висоту для розрахунку LOS.'; @override - String get losRenameCustomPoint => 'Перейменуйте спеціальну точку'; + String get losRenameCustomPoint => 'Перейменувати власну точку'; @override String get losPointName => 'Назва точки'; @@ -3297,6 +3342,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 => 'Частота'; @@ -3323,10 +3399,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 => 'Трасування шляху до серверу кімнати'; @@ -3352,28 +3428,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 => @@ -3381,15 +3455,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'; @@ -3427,7 +3501,7 @@ class AppLocalizationsUk extends AppLocalizations { locale: localeName, other: 'нових вузлів', many: 'нових вузлів', - few: 'нових вузли', + few: 'нові вузли', one: 'новий вузол', ); return '$count $_temp0'; @@ -3443,25 +3517,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.'; @@ -3474,14 +3548,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 => 'Усі місця контактів'; @@ -3495,7 +3569,7 @@ class AppLocalizationsUk extends AppLocalizations { 'експорт даних карти meshcore-open у форматі GPX'; @override - String get snrIndicator_nearByRepeaters => 'Ближні ретранслятори'; + String get snrIndicator_nearByRepeaters => 'Найближчі ретранслятори'; @override String get snrIndicator_lastSeen => 'Останній раз бачили'; @@ -3516,15 +3590,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 => @@ -3532,15 +3606,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 => 'Перезаписати найстаріше'; @@ -3590,33 +3664,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) { @@ -3635,12 +3709,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 @@ -3656,11 +3730,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 => 'Переклад'; @@ -3683,14 +3757,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 => @@ -3726,7 +3800,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String translation_downloadFailed(String error) { - return 'Не вдалося завантажити: $error'; + return 'Не вдалось завантажити: $error'; } @override @@ -3771,4 +3845,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 7ad23bb9..1f56d641 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 => '编辑'; @@ -416,6 +419,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 => '发送广播'; @@ -1021,6 +1031,11 @@ class AppLocalizationsZh extends AppLocalizations { @override String get channels_public => '公共'; + @override + String channels_via(String path) { + return 'via $path'; + } + @override String get channels_private => '私有'; @@ -1420,6 +1435,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get chat_successes => '成功'; + @override + String get chat_score => 'Score'; + @override String get chat_removePath => '移除路径'; @@ -1507,6 +1525,12 @@ class AppLocalizationsZh extends AppLocalizations { return '未读:$count'; } + @override + String get chat_markAsUnread => 'Mark as Unread'; + + @override + String get chat_newMessages => 'New messages'; + @override String get chat_openLink => '打开链接?'; @@ -1585,6 +1609,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 => '在此分享标记'; @@ -1668,6 +1710,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get map_sharedPin => '共享标记'; + @override + String get map_sharedAt => '已分享'; + @override String get map_joinRoom => '加入房间'; @@ -3066,6 +3111,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 => '频率'; @@ -3474,4 +3549,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 54871bda..08b05345 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", @@ -2082,5 +2084,86 @@ "room_guest": "Informatie over de server", "chat_sendMessage": "Verzend bericht", "repeater_guest": "Informatie over herhalingsapparatuur", - "settings_multiAck": "Meerdere bevestigingen" + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}", + "chat_score": "Score", + "settings_multiAck": "Meerdere bevestigingen", + "map_sharedAt": "Gedeeld", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Geselecteerde obstakel", + "losBlockedSpotsHint": "Tik op een geblokkeerd gebied om het op de kaart te markeren.", + "losBlockedSpotsTitle": "Geplande plaatsen", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 50a4da53..e96d056a 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", @@ -2120,5 +2122,86 @@ "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" + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}", + "chat_score": "Score", + "settings_multiAck": "Wielokrotne potwierdzenia odbioru", + "map_sharedAt": "Udostępnione", + "@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 0fd70c79..c2639ca7 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", @@ -2082,5 +2084,86 @@ "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}", + "chat_score": "Score", + "map_sharedAt": "Compartilhado", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsTitle": "Locais ocupados", + "losBlockedSpotsHint": "Toque em um ponto bloqueado para destacá-lo no mapa.", + "losSelectedObstructionTitle": "Obstrução selecionada", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 765f335e..80b4b9ff 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": "Анонсирование отправлено", @@ -1322,5 +1324,86 @@ "repeater_guest": "Информация о ретрансляторе", "room_guest": "Информация о сервере", "repeater_guestTools": "Инструменты для гостей", - "settings_multiAck": "Несколько подтверждений" + "common_done": "Готово", + "background_serviceTitle": "MeshCore работает", + "background_serviceText": "Поддерживает BLE-соединение", + "appSettings_translationModelDeleted": "Удалено {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Не удалось удалить: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Не удалось обновить канал: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Тип", + "map_path": "Путь", + "map_location": "Местоположение", + "map_estLocation": "Прибл. местоположение", + "map_publicKey": "Публичный ключ", + "map_publicKeyPrefixHint": "напр. ab12", + "contact_typeChat": "Чат", + "contact_typeRepeater": "Ретранслятор", + "contact_typeRoom": "Комната", + "contact_typeSensor": "Датчик", + "contact_typeUnknown": "Неизвестно", + "channels_via": "через {path}", + "chat_score": "Оценка", + "settings_multiAck": "Несколько подтверждений", + "map_sharedAt": "Поделено", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsHint": "Щелкните по заблокированной области, чтобы выделить ее на карте.", + "losBlockedSpotsTitle": "Зарезервированные места", + "losSelectedObstructionTitle": "Выбранный объект, препятствующий движению", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 52139257..0637b1b2 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á", @@ -2082,5 +2084,86 @@ "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}", + "chat_score": "Score", + "map_sharedAt": "Zdieľané", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsTitle": "Zablokované miesta", + "losBlockedSpotsHint": "Kliknite na zablokované miesto, aby ste ho zvýraznili na mape.", + "losSelectedObstructionTitle": "Vybraná prekážka", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index 7e2e3f4c..0f4a1669 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", @@ -2082,5 +2084,86 @@ "chat_sendMessage": "Pošlji sporočilo", "room_guest": "Informacije o strežniku", "repeater_guestTools": "Naložila za goste", - "settings_multiAck": "Več potrdil" + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}", + "chat_score": "Score", + "settings_multiAck": "Več potrdil", + "map_sharedAt": "Deljeno", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsHint": "Dotaknite blokirano točko, da jo označite na zemljeplati.", + "losSelectedObstructionTitle": "Izbrano ovire", + "losBlockedSpotsTitle": "Zasedena parkirišča", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index 08c3b33c..24d19874 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", @@ -2082,5 +2084,86 @@ "chat_sendMessage": "Skicka meddelande", "repeater_guestTools": "Gästverktyg", "room_guest": "Information om servern", - "settings_multiAck": "Flera bekräftelser" + "common_done": "Done", + "background_serviceTitle": "MeshCore running", + "background_serviceText": "Keeping BLE connected", + "appSettings_translationModelDeleted": "Deleted {name}", + "@appSettings_translationModelDeleted": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "appSettings_translationModelDeleteFailed": "Failed to delete: {error}", + "@appSettings_translationModelDeleteFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "channels_channelUpdateFailed": "Failed to update channel: {error}", + "@channels_channelUpdateFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "map_type": "Type", + "map_path": "Path", + "map_location": "Location", + "map_estLocation": "Est. Location", + "map_publicKey": "Public Key", + "map_publicKeyPrefixHint": "e.g. ab12", + "contact_typeChat": "Chat", + "contact_typeRepeater": "Repeater", + "contact_typeRoom": "Room", + "contact_typeSensor": "Sensor", + "contact_typeUnknown": "Unknown", + "channels_via": "via {path}", + "chat_score": "Score", + "settings_multiAck": "Flera bekräftelser", + "map_sharedAt": "Delad", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losBlockedSpotsTitle": "Reserverade platser", + "losSelectedObstructionTitle": "Vald hinder", + "losBlockedSpotsHint": "Klicka på en markerad plats för att framhäva den på kartan.", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index f376a1ed..866f1313 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": { @@ -438,7 +442,7 @@ } } }, - "chat_location": "Розташування", + "chat_location": "Геопозиція", "chat_sendMessageTo": "Надіслати повідомлення {contactName}", "@chat_sendMessageTo": { "placeholders": { @@ -482,17 +486,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.", @@ -562,12 +566,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": { @@ -576,6 +580,7 @@ } }, "chat_successes": "Успішно", + "chat_score": "Оцінка", "chat_removePath": "Видалити шлях", "chat_noPathHistoryYet": "Історія шляхів недоступна.\nНадішліть повідомлення, щоб виявити шляхи.", "chat_pathActions": "Дії зі шляхом:", @@ -584,11 +589,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": { @@ -606,9 +611,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": { @@ -616,9 +621,9 @@ } } }, - "chat_floodAuto": "На всю мережу (авто)", - "chat_direct": "Прямий", - "chat_poiShared": "Точкою інтересу поділилися", + "chat_floodAuto": "Через всю мережу (авто)", + "chat_direct": "Напряму", + "chat_poiShared": "Поділилися точкою інтересу", "chat_unread": "Непрочитано: {count}", "@chat_unread": { "placeholders": { @@ -630,7 +635,7 @@ "chat_openLink": "Відкрити посилання?", "chat_openLinkConfirmation": "Ви хочете відкрити це посилання у браузері?", "chat_open": "Відкрити", - "chat_couldNotOpenLink": "Не вдалося відкрити посилання: {url}", + "chat_couldNotOpenLink": "Не вдалось відкрити посилання: {url}", "@chat_couldNotOpenLink": { "placeholders": { "url": { @@ -640,8 +645,8 @@ }, "chat_invalidLink": "Невірний формат посилання", "map_title": "Карта вузлів", - "map_noNodesWithLocation": "Немає вузлів з даними про розташування", - "map_nodesNeedGps": "Вузли повинні надавати свої GPS координати,\nщоб з'явитися на карті.", + "map_noNodesWithLocation": "Немає вузлів з даними про геопозицію", + "map_nodesNeedGps": "Вузли мають надавати свої GPS координати,\nщоб з'явитись на карті.", "map_nodesCount": "Вузли: {count}", "@map_nodesCount": { "placeholders": { @@ -666,19 +671,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": { @@ -686,7 +697,7 @@ } } }, - "map_connectToShareMarkers": "Підключіться до пристрою, щоб поділитися маркерами", + "map_connectToShareMarkers": "Підключіться до пристрою, щоб поділитись маркерами", "map_filterNodes": "Фільтрувати вузли", "map_nodeTypes": "Типи вузлів", "map_chatNodes": "Вузли чату", @@ -699,7 +710,7 @@ "map_showSharedMarkers": "Показувати спільні маркери", "map_lastSeenTime": "Час останньої активності", "map_sharedPin": "Спільний пін", - "map_joinRoom": "Приєднатися до кімнати", + "map_joinRoom": "Приєднатись до кімнати", "map_manageRepeater": "Керувати ретранслятором", "mapCache_title": "Офлайн-кеш карти", "mapCache_selectAreaFirst": "Спершу виберіть область для кешування", @@ -822,7 +833,7 @@ "time_minutes": "хвилин", "time_allTime": "Весь час", "dialog_disconnect": "Відключити", - "dialog_disconnectConfirm": "Ви впевнені, що хочете відключитися від цього пристрою?", + "dialog_disconnectConfirm": "Ви впевнені, що хочете відключитись від цього пристрою?", "login_repeaterLogin": "Вхід у ретранслятор", "login_roomLogin": "Вхід у кімнату", "login_password": "Пароль", @@ -833,8 +844,8 @@ "login_roomDescription": "Введіть пароль кімнати для доступу до налаштувань та статусу.", "login_routing": "Маршрутизація", "login_routingMode": "Режим маршрутизації", - "login_autoUseSavedPath": "Авто (використовувати збережений шлях)", - "login_forceFloodMode": "Примусово на всю мережу", + "login_autoUseSavedPath": "Авто (збережений шлях)", + "login_forceFloodMode": "Примусово через всю мережу", "login_managePaths": "Керувати шляхами", "login_login": "Вхід", "login_attempt": "Спроба {current}/{max}", @@ -867,7 +878,7 @@ } } }, - "path_usingHopsPath": "Використання шляху з {count} {count, plural, =1{стрибком} few{стрибками} many{стрибками} other{стрибками}}", + "path_usingHopsPath": "Використання шляху з {count} {count, plural, =1{переходом} few{переходами} many{переходами} other{переходами}}", "@path_usingHopsPath": { "placeholders": { "count": { @@ -877,10 +888,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": "Власні шляхи вимагають проміжних вузлів, які можуть передавати повідомлення.", @@ -892,7 +903,7 @@ } } }, - "path_tooLong": "Шлях занадто довгий. Максимум 64 стрибки.", + "path_tooLong": "Шлях занадто довгий. Максимум 64 переходи.", "path_setPath": "Встановити шлях", "repeater_management": "Керування ретранслятором", "repeater_managementTools": "Інструменти керування", @@ -907,7 +918,7 @@ "repeater_statusTitle": "Статус ретранслятора", "repeater_routingMode": "Режим маршрутизації", "repeater_autoUseSavedPath": "Авто (використовувати збережений шлях)", - "repeater_forceFloodMode": "Примусово на всю мережу", + "repeater_forceFloodMode": "Примусово через всю мережу", "repeater_pathManagement": "Керування шляхами", "repeater_refresh": "Оновити", "repeater_statusRequestTimeout": "Час очікування запиту статусу вичерпано.", @@ -952,7 +963,7 @@ } } }, - "repeater_packetTxTotal": "Всього: {total}, На всю мережу: {flood}, Прямі: {direct}", + "repeater_packetTxTotal": "Всього: {total}, Через всю мережу: {flood}, Прямі: {direct}", "@repeater_packetTxTotal": { "placeholders": { "total": { @@ -966,7 +977,7 @@ } } }, - "repeater_packetRxTotal": "Всього: {total}, На всю мережу: {flood}, Прямі: {direct}", + "repeater_packetRxTotal": "Всього: {total}, Через всю мережу: {flood}, Прямі: {direct}", "@repeater_packetRxTotal": { "placeholders": { "total": { @@ -980,7 +991,7 @@ } } }, - "repeater_duplicatesFloodDirect": "На всю мережу: {flood}, Прямі: {direct}", + "repeater_duplicatesFloodDirect": "Через всю мережу: {flood}, Прямі: {direct}", "@repeater_duplicatesFloodDirect": { "placeholders": { "flood": { @@ -1015,7 +1026,7 @@ "repeater_bandwidth": "Смуга пропускання", "repeater_spreadingFactor": "Коефіцієнт розширення", "repeater_codingRate": "Швидкість кодування", - "repeater_locationSettings": "Налаштування розташування", + "repeater_locationSettings": "Налаштування геопозиції", "repeater_latitude": "Широта", "repeater_latitudeHelper": "Десяткові градуси (наприклад, 37.7749)", "repeater_longitude": "Довгота", @@ -1026,9 +1037,9 @@ "repeater_guestAccess": "Гостьовий доступ", "repeater_guestAccessSubtitle": "Дозволити гостьовий доступ лише для читання", "repeater_privacyMode": "Режим приватності", - "repeater_privacyModeSubtitle": "Приховати ім'я/розташування в оголошеннях", + "repeater_privacyModeSubtitle": "Приховати ім'я/геопозицію в оголошеннях", "repeater_advertisementSettings": "Налаштування оголошень", - "repeater_localAdvertInterval": "Інтервал локальних оголошень (0 стрибків)", + "repeater_localAdvertInterval": "Інтервал локальних оголошень (без ретрансляції)", "repeater_localAdvertIntervalMinutes": "{minutes} хвилин", "@repeater_localAdvertIntervalMinutes": { "placeholders": { @@ -1037,7 +1048,7 @@ } } }, - "repeater_floodAdvertInterval": "Інтервал оголошень на всю мережу (flood)", + "repeater_floodAdvertInterval": "Інтервал оголошень через всю мережу (flood)", "repeater_floodAdvertIntervalHours": "{hours} годин", "@repeater_floodAdvertIntervalHours": { "placeholders": { @@ -1051,9 +1062,9 @@ "repeater_rebootRepeater": "Перезавантажити ретранслятор", "repeater_rebootRepeaterSubtitle": "Скинути пристрій ретранслятора", "repeater_rebootRepeaterConfirm": "Ви впевнені, що хочете перезавантажити цей ретранслятор?", - "repeater_regenerateIdentityKey": "Перегенерувати ключ ідентичності", + "repeater_regenerateIdentityKey": "Перегенерувати ключ ідентифікації", "repeater_regenerateIdentityKeySubtitle": "Згенерувати нову пару ключів (публічний/приватний)", - "repeater_regenerateIdentityKeyConfirm": "Це створить нову ідентичність для ретранслятора. Продовжити?", + "repeater_regenerateIdentityKeyConfirm": "Це створить нову ідентифікацію для ретранслятора. Продовжити?", "repeater_eraseFileSystem": "Очистити файлову систему", "repeater_eraseFileSystemSubtitle": "Відформатувати файлову систему ретранслятора", "repeater_eraseFileSystemConfirm": "УВАГА: Це видалить всі дані з ретранслятора. Це не можна скасувати!", @@ -1087,7 +1098,7 @@ "repeater_refreshBasicSettings": "Оновити основні налаштування", "repeater_refreshRadioSettings": "Оновити налаштування радіо", "repeater_refreshTxPower": "Оновити потужність TX", - "repeater_refreshLocationSettings": "Оновити налаштування розташування", + "repeater_refreshLocationSettings": "Оновити налаштування геопозиції", "repeater_refreshPacketForwarding": "Оновити пересилання пакетів", "repeater_refreshGuestAccess": "Оновити гостьовий доступ", "repeater_refreshPrivacyMode": "Оновити режим приватності", @@ -1144,19 +1155,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": "Встановити затримку перед пересиланням пакетів.", @@ -1172,7 +1183,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": "Додає або оновлює визначення регіону з заданою назвою.", @@ -1186,14 +1197,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": "Команди регіонів були введені для керування визначеннями та дозволами регіонів.", @@ -1264,7 +1275,7 @@ "channelPath_title": "Шлях пакету", "channelPath_viewMap": "Показати карту", "channelPath_otherObservedPaths": "Інші спостережувані шляхи", - "channelPath_repeaterHops": "Стрибки ретранслятора", + "channelPath_repeaterHops": "Переходи через ретранслятори", "channelPath_noHopDetails": "Деталі відправки не надані для цього пакету.", "channelPath_messageDetails": "Деталі повідомлення", "channelPath_senderLabel": "Відправник", @@ -1283,7 +1294,7 @@ } } }, - "channelPath_noLocationData": "Немає даних про розташування", + "channelPath_noLocationData": "Немає даних про геопозицію", "channelPath_timeWithDate": "{day}/{month} {time}", "@channelPath_timeWithDate": { "placeholders": { @@ -1307,9 +1318,9 @@ } }, "channelPath_unknownPath": "Невідомий", - "channelPath_floodPath": "На всю мережу", - "channelPath_directPath": "Прямий", - "channelPath_observedZeroOf": "0 з {total} стрибків", + "channelPath_floodPath": "Через всю мережу", + "channelPath_directPath": "Напряму", + "channelPath_observedZeroOf": "0 з {total} переходів", "@channelPath_observedZeroOf": { "placeholders": { "total": { @@ -1317,7 +1328,7 @@ } } }, - "channelPath_observedSomeOf": "{observed} з {total} стрибків", + "channelPath_observedSomeOf": "{observed} з {total} переходів", "@channelPath_observedSomeOf": { "placeholders": { "observed": { @@ -1358,12 +1369,12 @@ } } }, - "channelPath_noHopDetailsAvailable": "Деталі стрибків недоступні для цього пакету.", + "channelPath_noHopDetailsAvailable": "Деталі переходів недоступні для цього пакету.", "channelPath_unknownRepeater": "Невідомий ретранслятор", "listFilter_tooltip": "Фільтр та сортування", "listFilter_sortBy": "Сортувати за", "listFilter_latestMessages": "Останні повідомлення", - "listFilter_heardRecently": "Нещодавно чули", + "listFilter_heardRecently": "Нещодавно почуті", "listFilter_az": "А-Я", "listFilter_filters": "Фільтри", "listFilter_all": "Все", @@ -1380,20 +1391,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": "Введіть хештег", @@ -1415,7 +1426,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": "Керувати сервером кімнати", @@ -1479,10 +1490,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-код", @@ -1492,9 +1503,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}».", @@ -1515,10 +1526,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}", @@ -1550,13 +1561,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": { @@ -1565,76 +1576,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.", @@ -1652,7 +1663,7 @@ "losMenuSubtitle": "Торкніться вузлів або утримуйте карту, щоб отримати власні точки", "losShowDisplayNodes": "Показати вузли відображення", "losCustomPoints": "Користувальницькі точки", - "losCustomPointLabel": "Спеціальний {index}", + "losCustomPointLabel": "Власна точка {index}", "@losCustomPointLabel": { "placeholders": { "index": { @@ -1741,7 +1752,7 @@ }, "losErrorElevationUnavailable": "Дані про висоту недоступні для одного чи кількох зразків.", "losErrorInvalidInput": "Недійсні дані про точки/висоту для розрахунку LOS.", - "losRenameCustomPoint": "Перейменуйте спеціальну точку", + "losRenameCustomPoint": "Перейменувати власну точку", "losPointName": "Назва точки", "losShowPanelTooltip": "Показати панель LOS", "losHidePanelTooltip": "Приховати панель LOS", @@ -1821,19 +1832,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": "Відповідних контактів не знайдено", @@ -1844,9 +1855,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", @@ -1858,8 +1869,8 @@ "usbErrorInvalidPort": "Виберіть дійсний USB-пристрій.", "usbErrorBusy": "Ще один запит на підключення через USB вже обробляється.", "usbErrorNotConnected": "Немає підключених пристроїв USB.", - "usbErrorOpenFailed": "Не вдалося відкрити вибране USB-пристрій.", - "usbErrorConnectFailed": "Не вдалося підключитися до вибраного USB-пристрою.", + "usbErrorOpenFailed": "Не вдалось відкрити вибране USB-пристрій.", + "usbErrorConnectFailed": "Не вдалось підключитись до вибраного USB-пристрою.", "usbErrorUnsupported": "Підтримка USB-серіального інтерфейсу не реалізована на цій платформі.", "usbErrorAlreadyActive": "USB-з'єднання вже встановлено.", "usbErrorNoDeviceSelected": "Не було вибрано жодного пристрою USB.", @@ -1874,9 +1885,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": { @@ -1894,18 +1905,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": { @@ -1916,12 +1927,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": "Відхилити все", @@ -1929,8 +1940,8 @@ "settings_telemetryEnvironmentMode": "Режим середовища телеметрії", "contact_info": "Контактна інформація", "contact_teleBaseSubtitle": "Дозволити спільний доступ до рівня заряду батареї та базової телеметрії", - "contact_teleLoc": "Розташування телеметрії", - "contact_teleBase": "Базовий телебачення", + "contact_teleLoc": "Геопозиція телеметрії", + "contact_teleBase": "Базова телеметрія", "contact_teleLocSubtitle": "Дозволити спільне використання даних про місцеположення", "contact_settings": "Налаштування контактів", "contact_telemetry": "Телеметрія", @@ -1950,8 +1961,8 @@ "appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрії оновлено", - "map_showOverlaps": "Перекриття ключа повторювача", - "map_runTraceWithReturnPath": "Повернутися назад тим же шляхом", + "map_showOverlaps": "Перекриття ключів ретрансляторів", + "map_runTraceWithReturnPath": "Повернутись назад тим же шляхом", "@radioStats_noiseFloor": { "placeholders": { "noiseDbm": { @@ -1995,26 +2006,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": { @@ -2028,9 +2039,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": "Завантаження...", @@ -2041,7 +2052,7 @@ "translation_deleteModel": "Видалити модель", "translation_modelDownloaded": "Модель перекладу завантажена.", "translation_downloadStopped": "Завантаження призупинено.", - "translation_downloadFailed": "Не вдалося завантажити: {error}", + "translation_downloadFailed": "Не вдалось завантажити: {error}", "translation_enterUrlFirst": "Спочатку введіть URL моделі.", "@scanner_linuxPairingPinPrompt": { "placeholders": { @@ -2069,7 +2080,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" }, @@ -2078,9 +2089,60 @@ }, "repeater_clockSyncAfterLoginSubtitle": "Автоматично надсилати повідомлення \"синхронізація годин\" після успішного входу.", "repeater_clockSyncAfterLogin": "Синхронізація годин після входу", - "repeater_guestTools": "Інструменти для гостей", + "repeater_guestTools": "Гостьові інструменти", "repeater_guest": "Інформація про ретранслятор", "room_guest": "Інформація про сервер кімнати", "chat_sendMessage": "Надіслати повідомлення", - "settings_multiAck": "Багато підтверджень" + "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": "Невідомо", + "settings_multiAck": "Багато підтверджень", + "map_sharedAt": "Поділено", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsHint": "Натисніть на заблоковане місце, щоб виділити його на карті.", + "losBlockedSpotsTitle": "Заблоковані місця", + "losSelectedObstructionTitle": "Вибраний об'єкт перешкоди", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 1df214f9..4dae5de8 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": "已发送广播", @@ -2087,5 +2089,86 @@ "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}", + "chat_score": "Score", + "map_sharedAt": "已分享", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsTitle": "被占用区域", + "losBlockedSpotsHint": "点击地图上的某个被遮盖的区域,以突出显示该区域。", + "losSelectedObstructionTitle": "选择性阻碍", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/contact_localization.dart b/lib/l10n/contact_localization.dart new file mode 100644 index 00000000..d8344a32 --- /dev/null +++ b/lib/l10n/contact_localization.dart @@ -0,0 +1,36 @@ +import '../connector/meshcore_protocol.dart'; +import '../models/contact.dart'; +import 'app_localizations.dart'; + +/// UI-level localization helpers for [Contact]. +/// +/// Kept out of the model layer so `Contact` does not depend on +/// `AppLocalizations`. Use these from widgets/screens; for logs and +/// non-UI export use `Contact.typeLabelRaw`. +extension ContactLocalization on Contact { + String typeLabel(AppLocalizations l10n) { + switch (type) { + case advTypeChat: + return l10n.contact_typeChat; + case advTypeRepeater: + return l10n.contact_typeRepeater; + case advTypeRoom: + return l10n.contact_typeRoom; + case advTypeSensor: + return l10n.contact_typeSensor; + default: + return l10n.contact_typeUnknown; + } + } + + String pathLabel(AppLocalizations l10n) { + if (pathOverride != null) { + if (pathOverride! < 0) return l10n.chat_floodForced; + if (pathOverride == 0) return l10n.chat_directForced; + return l10n.chat_hopsForced(pathOverride!); + } + if (pathLength < 0) return l10n.channelPath_floodPath; + if (pathLength == 0) return l10n.chat_direct; + return l10n.chat_hopsCount(pathLength); + } +} diff --git a/lib/main.dart b/lib/main.dart index 3e57eb10..cd622811 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -59,6 +59,9 @@ void main() async { final notificationService = NotificationService(); await notificationService.initialize(); await backgroundService.initialize(); + backgroundService.setLanguageOverrideProvider( + () => appSettingsService.settings.languageOverride, + ); _registerThirdPartyLicenses(); await chatTextScaleService.initialize(); diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 2699f939..b5df2af1 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -41,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'; @@ -56,17 +59,6 @@ class Contact { } } - String get pathLabel { - if (pathOverride != null) { - if (pathOverride! < 0) return 'Flood (forced)'; - if (pathOverride == 0) return 'Direct (forced)'; - return '$pathOverride hops (forced)'; - } - if (pathLength < 0) return 'Flood'; - if (pathLength == 0) return 'Direct'; - return '$pathLength hops'; - } - bool get hasLocation { const double epsilon = 1e-6; final lat = latitude ?? 0.0; diff --git a/lib/screens/app_settings_screen.dart b/lib/screens/app_settings_screen.dart index 051a6f7f..94b3efe8 100644 --- a/lib/screens/app_settings_screen.dart +++ b/lib/screens/app_settings_screen.dart @@ -1240,15 +1240,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 4f43e9c3..fcd50861 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -4,7 +4,7 @@ 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:intl/intl.dart'; import 'package:provider/provider.dart'; import '../connector/meshcore_connector.dart'; @@ -33,13 +33,19 @@ import '../widgets/message_translation_button.dart'; import '../widgets/message_status_icon.dart'; import '../widgets/radio_stats_entry.dart'; import '../widgets/translated_message_content.dart'; +import '../widgets/unread_divider.dart'; import 'channel_message_path_screen.dart'; import 'map_screen.dart'; class ChannelChatScreen extends StatefulWidget { final Channel channel; + final int initialUnreadCount; - const ChannelChatScreen({super.key, required this.channel}); + const ChannelChatScreen({ + super.key, + required this.channel, + this.initialUnreadCount = 0, + }); @override State createState() => _ChannelChatScreenState(); @@ -56,32 +62,46 @@ class _ChannelChatScreenState extends State { MeshCoreConnector? _connector; DateTime? _lastChannelSendAt; bool _channelSkipNextBottomSnap = false; + String? _unreadDividerMessageId; + + String? _cachedFormatLocale; + late DateFormat _hmFormat; + late DateFormat _mdFormat; @override void initState() { super.initState(); _textFieldFocusNode.addListener(_onTextFieldFocusChange); _scrollController.onScrollNearTop = _loadOlderMessages; + _scrollController.showJumpToBottom.addListener(_clearDividerAtBottom); SchedulerBinding.instance.addPostFrameCallback((_) { if (!mounted) return; final connector = context.read(); final settings = context.read().settings; final idx = widget.channel.index; - final unread = connector.getUnreadCountForChannelIndex(idx); + final unread = widget.initialUnreadCount; + final messages = connector.getChannelMessages(widget.channel); ChannelMessage? anchor; - if (settings.jumpToOldestUnread && unread > 0) { - anchor = _findOldestUnreadChannelAnchor( - connector.getChannelMessages(widget.channel), - unread, - ); + if (unread > 0) { + anchor = _findOldestUnreadChannelAnchor(messages, unread); } + setState(() { + if (anchor != null) _unreadDividerMessageId = anchor.messageId; + }); connector.setActiveChannel(idx); _connector = connector; - if (anchor != null) { + if (anchor != null && settings.jumpToOldestUnread) { _channelSkipNextBottomSnap = true; WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; - _scrollToMessage(anchor!.messageId); + _scrollController.jumpToEstimatedOffset( + unreadCount: unread, + totalMessages: messages.length, + onJumped: () { + if (!mounted) return; + _scrollToMessage(anchor!.messageId); + }, + ); }); } }); @@ -103,6 +123,13 @@ class _ChannelChatScreenState extends State { return oldest; } + void _clearDividerAtBottom() { + if (!_scrollController.showJumpToBottom.value && + _unreadDividerMessageId != null) { + setState(() => _unreadDividerMessageId = null); + } + } + void _onTextFieldFocusChange() { if (_textFieldFocusNode.hasFocus && mounted) { _scrollController.handleKeyboardOpen(); @@ -124,6 +151,7 @@ class _ChannelChatScreenState extends State { @override void dispose() { _connector?.setActiveChannel(null); + _scrollController.showJumpToBottom.removeListener(_clearDividerAtBottom); _textFieldFocusNode.removeListener(_onTextFieldFocusChange); _textFieldFocusNode.dispose(); _textController.dispose(); @@ -322,6 +350,9 @@ class _ChannelChatScreenState extends State { if (!_messageKeys.containsKey(message.messageId)) { _messageKeys[message.messageId] = GlobalKey(); } + final isUnreadAnchor = + _unreadDividerMessageId != null && + message.messageId == _unreadDividerMessageId; return Container( key: _messageKeys[message.messageId]!, child: Builder( @@ -330,10 +361,17 @@ class _ChannelChatScreenState extends State { .select( (service) => service.scale, ); - return _buildMessageBubble( + final bubble = _buildMessageBubble( message, textScale, ); + if (isUnreadAnchor) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [const UnreadDivider(), bubble], + ); + } + return bubble; }, ), ); @@ -353,12 +391,24 @@ class _ChannelChatScreenState extends State { ); } + void _markAsUnread(ChannelMessage message) { + final connector = context.read(); + final messages = connector.getChannelMessages(widget.channel); + var count = 0; + var found = false; + for (final m in messages) { + if (m.messageId == message.messageId) found = true; + if (found && !m.isOutgoing) count++; + } + connector.setChannelUnreadCount(widget.channel.index, count); + } + Widget _buildMessageBubble(ChannelMessage message, double textScale) { final settingsService = context.watch(); 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 @@ -446,6 +496,7 @@ class _ChannelChatScreenState extends State { poi, isOutgoing, textScale, + message.senderName, trailing: (!enableTracing && isOutgoing) ? Padding( padding: const EdgeInsets.only(bottom: 2), @@ -556,7 +607,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], @@ -577,7 +630,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], @@ -702,7 +755,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,24 +866,12 @@ 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) ?? ''; - return _PoiInfo(lat: lat, lon: lon, label: label); - } - Widget _buildPoiMessage( BuildContext context, - _PoiInfo poi, + MarkerPayload poi, bool isOutgoing, - double textScale, { + double textScale, + String senderName, { Widget? trailing, }) { final colorScheme = Theme.of(context).colorScheme; @@ -850,12 +891,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), + highlightPosition: poi.position, highlightLabel: poi.label, + highlightMarkerKey: key, ), ), ); @@ -1240,14 +1291,21 @@ 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(); + if (locale != _cachedFormatLocale) { + _cachedFormatLocale = locale; + _hmFormat = DateFormat.Hm(locale); + _mdFormat = DateFormat.Md(locale); + } + final hm = _hmFormat.format(time); if (diff.inDays > 0) { - return '${time.day}/${time.month} ${time.hour}:${time.minute.toString().padLeft(2, '0')}'; + return '${_mdFormat.format(time)} $hm'; } else { - return '${time.hour}:${time.minute.toString().padLeft(2, '0')}'; + return hm; } } @@ -1303,6 +1361,15 @@ class _ChannelChatScreenState extends State { _copyMessageText(message.text); }, ), + if (!message.isOutgoing) + ListTile( + leading: const Icon(Icons.mark_chat_unread_outlined), + title: Text(context.l10n.chat_markAsUnread), + onTap: () { + Navigator.pop(sheetContext); + _markAsUnread(message); + }, + ), ListTile( leading: const Icon(Icons.delete_outline), title: Text(context.l10n.common_delete), @@ -1522,11 +1589,3 @@ class _SwipeReplyBubbleState extends State<_SwipeReplyBubble> { ); } } - -class _PoiInfo { - final double lat; - final double lon; - final String label; - - const _PoiInfo({required this.lat, required this.lon, required this.label}); -} diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index f12018bd..0bd062f4 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -492,13 +492,18 @@ class _ChannelsScreenState extends State ], ), onTap: () async { + final unread = + connector.getUnreadCountForChannelIndex(channel.index); connector.markChannelRead(channel.index); await Future.delayed(const Duration(milliseconds: 50)); if (context.mounted) { Navigator.push( context, MaterialPageRoute( - builder: (context) => ChannelChatScreen(channel: channel), + builder: (context) => ChannelChatScreen( + channel: channel, + initialUnreadCount: unread, + ), ), ); } @@ -1552,7 +1557,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 9ec84416..46d89977 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'; @@ -21,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'; @@ -45,12 +45,18 @@ import '../widgets/translated_message_content.dart'; import '../utils/app_logger.dart'; import '../l10n/l10n.dart'; import '../helpers/snack_bar_builder.dart'; +import '../widgets/unread_divider.dart'; import 'telemetry_screen.dart'; class ChatScreen extends StatefulWidget { final Contact contact; + final int initialUnreadCount; - const ChatScreen({super.key, required this.contact}); + const ChatScreen({ + super.key, + required this.contact, + this.initialUnreadCount = 0, + }); @override State createState() => _ChatScreenState(); @@ -64,6 +70,7 @@ class _ChatScreenState extends State { bool _isLoadingOlder = false; MeshCoreConnector? _connector; Message? _pendingUnreadScrollTarget; + String? _unreadDividerMessageId; DateTime? _lastTextSendAt; @override @@ -71,34 +78,47 @@ class _ChatScreenState extends State { super.initState(); _textFieldFocusNode.addListener(_onTextFieldFocusChange); _scrollController.onScrollNearTop = _loadOlderMessages; + _scrollController.showJumpToBottom.addListener(_clearDividerAtBottom); SchedulerBinding.instance.addPostFrameCallback((_) { if (!mounted) return; final connector = context.read(); final settings = context.read().settings; final keyHex = widget.contact.publicKeyHex; - final unread = connector.getUnreadCountForContactKey(keyHex); + final unread = widget.initialUnreadCount; + final messages = connector.getMessages(widget.contact); Message? anchor; - if (settings.jumpToOldestUnread && unread > 0) { - anchor = _findOldestUnreadAnchor( - connector.getMessages(widget.contact), - unread, - ); + if (unread > 0) { + anchor = _findOldestUnreadAnchor(messages, unread); } + setState(() { + if (anchor != null) _unreadDividerMessageId = anchor.messageId; + if (anchor != null && settings.jumpToOldestUnread) { + _pendingUnreadScrollTarget = anchor; + } + }); connector.setActiveContact(keyHex); _connector = connector; - if (anchor != null) { - setState(() => _pendingUnreadScrollTarget = anchor); + if (anchor != null && settings.jumpToOldestUnread) { WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; - final ctx = _unreadScrollKey.currentContext; - if (ctx != null) { - Scrollable.ensureVisible( - ctx, - duration: const Duration(milliseconds: 350), - alignment: 0.15, - ); - } - setState(() => _pendingUnreadScrollTarget = null); + _scrollController.jumpToEstimatedOffset( + unreadCount: unread, + totalMessages: messages.length, + onJumped: () async { + if (!mounted) return; + final ctx = _unreadScrollKey.currentContext; + if (ctx != null) { + await Scrollable.ensureVisible( + ctx, + duration: const Duration(milliseconds: 350), + alignment: 0.15, + ); + } + if (mounted) { + setState(() => _pendingUnreadScrollTarget = null); + } + }, + ); }); } }); @@ -117,6 +137,13 @@ class _ChatScreenState extends State { return oldest; } + void _clearDividerAtBottom() { + if (!_scrollController.showJumpToBottom.value && + _unreadDividerMessageId != null) { + setState(() => _unreadDividerMessageId = null); + } + } + void _onTextFieldFocusChange() { if (_textFieldFocusNode.hasFocus && mounted) { _scrollController.handleKeyboardOpen(); @@ -138,6 +165,7 @@ class _ChatScreenState extends State { @override void dispose() { _connector?.setActiveContact(null); + _scrollController.showJumpToBottom.removeListener(_clearDividerAtBottom); _textFieldFocusNode.removeListener(_onTextFieldFocusChange); _textFieldFocusNode.dispose(); _textController.dispose(); @@ -480,6 +508,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), @@ -487,10 +516,19 @@ class _ChatScreenState extends State { onRetryReaction: (msg, emoji) => _sendReaction(msg, contact, emoji), ); + final isUnreadAnchor = + _unreadDividerMessageId != null && + message.messageId == _unreadDividerMessageId; + final child = isUnreadAnchor + ? Column( + mainAxisSize: MainAxisSize.min, + children: [const UnreadDivider(), bubble], + ) + : bubble; if (identical(message, _pendingUnreadScrollTarget)) { - return KeyedSubtree(key: _unreadScrollKey, child: bubble); + return KeyedSubtree(key: _unreadScrollKey, child: child); } - return bubble; + return child; }, ); }, @@ -498,6 +536,18 @@ class _ChatScreenState extends State { ); } + void _markAsUnread(Message message) { + final connector = context.read(); + final messages = connector.getMessages(widget.contact); + var count = 0; + var found = false; + for (final m in messages) { + if (m.messageId == message.messageId) found = true; + if (found && !m.isOutgoing && !m.isCli) count++; + } + connector.setContactUnreadCount(widget.contact.publicKeyHex, count); + } + Widget _buildInputBar(MeshCoreConnector connector) { final maxBytes = maxContactMessageBytes(); final colorScheme = Theme.of(context).colorScheme; @@ -1184,8 +1234,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.typeLabel(context.l10n)), + _buildInfoRow(context.l10n.chat_path, contact.pathLabel(context.l10n)), _buildInfoRow( context.l10n.contact_lastSeen, _formatContactLastMessage(contact.lastMessageAt), @@ -1409,11 +1459,15 @@ class _ChatScreenState extends State { } void _openChat(BuildContext context, Contact contact) { - // Check if this is a repeater - context.read().markContactRead(contact.publicKeyHex); + final connector = context.read(); + final unread = connector.getUnreadCountForContactKey(contact.publicKeyHex); + connector.markContactRead(contact.publicKeyHex); Navigator.push( context, - MaterialPageRoute(builder: (context) => ChatScreen(contact: contact)), + MaterialPageRoute( + builder: (context) => + ChatScreen(contact: contact, initialUnreadCount: unread), + ), ); } @@ -1550,6 +1604,15 @@ class _ChatScreenState extends State { _copyMessageText(message.text); }, ), + if (!message.isOutgoing) + ListTile( + leading: const Icon(Icons.mark_chat_unread_outlined), + title: Text(context.l10n.chat_markAsUnread), + onTap: () { + Navigator.pop(sheetContext); + _markAsUnread(message); + }, + ), ListTile( leading: const Icon(Icons.delete_outline), title: Text(context.l10n.common_delete), @@ -1657,10 +1720,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, @@ -1675,7 +1740,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 @@ -1767,6 +1832,7 @@ class _MessageBubble extends StatelessWidget { textColor, metaColor, textScale, + senderName, trailing: (!enableTracing && isOutgoing) ? Padding( padding: const EdgeInsets.only(bottom: 2), @@ -1948,25 +2014,13 @@ 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) ?? ''; - return _PoiInfo(lat: lat, lon: lon, label: label); - } - Widget _buildPoiMessage( BuildContext context, - _PoiInfo poi, + MarkerPayload poi, Color textColor, Color metaColor, - double textScale, { + double textScale, + String senderName, { Widget? trailing, }) { return Row( @@ -1976,13 +2030,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), + highlightPosition: poi.position, highlightLabel: poi.label, + highlightMarkerKey: key, ), ), ); @@ -2163,11 +2227,3 @@ class _MessageBubble extends StatelessWidget { return '$hour:$minute'; } } - -class _PoiInfo { - final double lat; - final double lon; - final String label; - - const _PoiInfo({required this.lat, required this.lon, required this.label}); -} diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index 54d32990..f2bd7374 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'; @@ -930,10 +931,18 @@ class _ContactsScreenState extends State } else if (contact.type == advTypeRoom) { _showRoomLogin(context, contact, RoomLoginDestination.chat); } else { - context.read().markContactRead(contact.publicKeyHex); + final connector = context.read(); + final unread = + connector.getUnreadCountForContactKey(contact.publicKeyHex); + connector.markContactRead(contact.publicKeyHex); Navigator.push( context, - MaterialPageRoute(builder: (context) => ChatScreen(contact: contact)), + MaterialPageRoute( + builder: (context) => ChatScreen( + contact: contact, + initialUnreadCount: unread, + ), + ), ); } } @@ -988,7 +997,10 @@ class _ContactsScreenState extends State builder: (context) => RoomLoginDialog( room: room, onLogin: (password, isAdmin) { - context.read().markContactRead(room.publicKeyHex); + final connector = context.read(); + final unread = + connector.getUnreadCountForContactKey(room.publicKeyHex); + connector.markContactRead(room.publicKeyHex); Navigator.push( context, MaterialPageRoute( @@ -999,7 +1011,10 @@ class _ContactsScreenState extends State password: password, isAdmin: isAdmin, ) - : ChatScreen(contact: room), + : ChatScreen( + contact: room, + initialUnreadCount: unread, + ), ), ); }, @@ -1122,7 +1137,9 @@ class _ContactsScreenState extends State return CheckboxListTile( value: isSelected, title: Text(contact.name), - subtitle: Text(contact.typeLabel), + subtitle: Text( + contact.typeLabel(context.l10n), + ), onChanged: (value) { setDialogState(() { if (value == true) { @@ -1464,7 +1481,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/line_of_sight_map_screen.dart b/lib/screens/line_of_sight_map_screen.dart index ec8a391f..e88f4b9e 100644 --- a/lib/screens/line_of_sight_map_screen.dart +++ b/lib/screens/line_of_sight_map_screen.dart @@ -62,6 +62,7 @@ class _LineOfSightMapScreenState extends State { bool _loading = false; String? _error; LineOfSightPathResult? _result; + LineOfSightObstruction? _selectedObstruction; LineOfSightEndpoint? _start; LineOfSightEndpoint? _end; final List _customEndpoints = []; @@ -111,6 +112,7 @@ class _LineOfSightMapScreenState extends State { if (start == null || end == null) { setState(() { _result = null; + _selectedObstruction = null; _error = _errorSelectStartEnd; }); return; @@ -142,6 +144,7 @@ class _LineOfSightMapScreenState extends State { } setState(() { _result = result; + _selectedObstruction = _defaultObstructionFor(result); }); } catch (e) { if (!mounted) return; @@ -156,6 +159,7 @@ class _LineOfSightMapScreenState extends State { } setState(() { _result = null; + _selectedObstruction = null; _error = context.l10n.losRunFailed(e.toString()); }); } finally { @@ -184,6 +188,7 @@ class _LineOfSightMapScreenState extends State { void _selectFromMap(LineOfSightEndpoint endpoint) { setState(() { _result = null; + _selectedObstruction = null; _error = null; if (_start == null || (_start != null && _end != null)) { _start = endpoint; @@ -241,6 +246,7 @@ class _LineOfSightMapScreenState extends State { _start = null; _end = null; _result = null; + _selectedObstruction = null; _error = _errorSelectStartEnd; }); } @@ -251,6 +257,7 @@ class _LineOfSightMapScreenState extends State { if (identical(_start, endpoint)) _start = null; if (identical(_end, endpoint)) _end = null; _result = null; + _selectedObstruction = null; }); } @@ -377,7 +384,9 @@ class _LineOfSightMapScreenState extends State { ), if (_result != null && _result!.segments.isNotEmpty) PolylineLayer(polylines: _buildSegmentPolylines(_result!)), - MarkerLayer(markers: _buildMarkers(endpoints)), + MarkerLayer( + markers: _buildMarkers(endpoints, _primaryObstructions()), + ), ], ), if (_showHud) @@ -445,6 +454,8 @@ class _LineOfSightMapScreenState extends State { ); final displayFrequencyMHz = segment?.frequencyMHz ?? reportedFrequencyMHz; final kFactorUsed = segment?.usedKFactor; + final obstructions = + segment?.obstructions ?? const []; final endpoints = _visibleEndpoints(); final distanceUnit = isImperial ? 'mi' : 'km'; final heightUnit = isImperial ? 'ft' : 'm'; @@ -463,31 +474,7 @@ class _LineOfSightMapScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (segment != null) - SizedBox( - height: 160, - width: double.infinity, - child: CustomPaint( - painter: _LosProfilePainter( - samples: segment.samples, - distanceUnit: distanceUnit, - heightUnit: heightUnit, - badgeTextStyle: - Theme.of(context).textTheme.labelSmall?.copyWith( - color: Colors.white70, - fontSize: 10, - fontWeight: FontWeight.w600, - ) ?? - const TextStyle( - color: Colors.white70, - fontSize: 10, - fontWeight: FontWeight.w600, - ), - terrainLabel: context.l10n.losLegendTerrain, - losBeamLabel: context.l10n.losLegendLosBeam, - radioHorizonLabel: context.l10n.losLegendRadioHorizon, - ), - ), - ) + _buildProfileView(segment, distanceUnit, heightUnit, isImperial) else SizedBox( height: 44, @@ -519,6 +506,96 @@ class _LineOfSightMapScreenState extends State { fontWeight: FontWeight.w600, ), ), + if (obstructions.isNotEmpty) ...[ + const SizedBox(height: 8), + Text( + context.l10n.losBlockedSpotsTitle, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 4), + Text( + context.l10n.losBlockedSpotsHint, + style: TextStyle(fontSize: 11, color: Colors.grey[700]), + ), + const SizedBox(height: 6), + Wrap( + spacing: 6, + runSpacing: 6, + children: [ + for (final obstruction in obstructions) + ChoiceChip( + label: Text( + _obstructionChipLabel(obstruction, isImperial), + style: const TextStyle(fontSize: 11), + ), + selected: + _selectedObstruction?.sampleIndex == + obstruction.sampleIndex, + onSelected: (_) => _selectObstruction(obstruction), + ), + ], + ), + if (_selectedObstruction != null) ...[ + const SizedBox(height: 8), + DecoratedBox( + decoration: BoxDecoration( + color: Colors.orange.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: Colors.deepOrangeAccent.withValues(alpha: 0.45), + ), + ), + child: Padding( + padding: const EdgeInsets.all(10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.l10n.losSelectedObstructionTitle, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 4), + Text( + context.l10n.losSelectedObstructionDetails( + _formatHeightValue( + _selectedObstruction!.obstructionMeters, + isImperial, + ), + heightUnit, + _formatDistanceValue( + _selectedObstruction!.distanceMeters, + isImperial, + ), + distanceUnit, + _formatDistanceValue( + segment!.totalDistanceMeters - + _selectedObstruction!.distanceMeters, + isImperial, + ), + ), + style: const TextStyle(fontSize: 11), + ), + const SizedBox(height: 4), + Text( + '${_selectedObstruction!.point.latitude.toStringAsFixed(5)}, ' + '${_selectedObstruction!.point.longitude.toStringAsFixed(5)}', + style: TextStyle( + fontSize: 11, + color: Colors.grey[700], + ), + ), + ], + ), + ), + ), + ], + ], const SizedBox(height: 4), if (displayFrequencyMHz != null) Padding( @@ -605,6 +682,7 @@ class _LineOfSightMapScreenState extends State { _showDisplayNodes = value; _sanitizeSelection(); _result = null; + _selectedObstruction = null; }); }, ), @@ -655,6 +733,7 @@ class _LineOfSightMapScreenState extends State { setState(() { _start = value; _result = null; + _selectedObstruction = null; }); if (_start != null && _end != null) { _runLos(); @@ -670,6 +749,7 @@ class _LineOfSightMapScreenState extends State { setState(() { _end = value; _result = null; + _selectedObstruction = null; }); if (_start != null && _end != null) { _runLos(); @@ -769,6 +849,179 @@ class _LineOfSightMapScreenState extends State { return _result!.segments.first.result; } + List _primaryObstructions() { + return _primarySegmentResult()?.obstructions ?? const []; + } + + LineOfSightObstruction? _defaultObstructionFor( + LineOfSightPathResult? result, + ) { + if (result == null || result.segments.isEmpty) return null; + final obstructions = result.segments.first.result.obstructions; + if (obstructions.isEmpty) return null; + return obstructions.reduce( + (current, next) => + next.obstructionMeters > current.obstructionMeters ? next : current, + ); + } + + void _selectObstruction(LineOfSightObstruction obstruction) { + setState(() { + _selectedObstruction = obstruction; + }); + } + + String _formatDistanceValue(double meters, bool isImperial) { + final value = isImperial ? (meters / 1000.0) * _kmToMiles : meters / 1000.0; + return value.toStringAsFixed(2); + } + + String _formatHeightValue(double meters, bool isImperial) { + final value = isImperial ? meters * _metersToFeet : meters; + return value.toStringAsFixed(1); + } + + String _obstructionChipLabel( + LineOfSightObstruction obstruction, + bool isImperial, + ) { + final distanceUnit = isImperial ? 'mi' : 'km'; + final heightUnit = isImperial ? 'ft' : 'm'; + return context.l10n.losBlockedSpotChip( + _formatDistanceValue(obstruction.distanceMeters, isImperial), + distanceUnit, + _formatHeightValue(obstruction.obstructionMeters, isImperial), + heightUnit, + ); + } + + Widget _buildProfileView( + LineOfSightResult segment, + String distanceUnit, + String heightUnit, + bool isImperial, + ) { + if (segment.samples.length < 2) { + return SizedBox( + height: 160, + width: double.infinity, + child: CustomPaint( + painter: _LosProfilePainter( + samples: segment.samples, + distanceUnit: distanceUnit, + heightUnit: heightUnit, + badgeTextStyle: + Theme.of(context).textTheme.labelSmall?.copyWith( + color: Colors.white70, + fontSize: 10, + fontWeight: FontWeight.w600, + ) ?? + const TextStyle( + color: Colors.white70, + fontSize: 10, + fontWeight: FontWeight.w600, + ), + terrainLabel: context.l10n.losLegendTerrain, + losBeamLabel: context.l10n.losLegendLosBeam, + radioHorizonLabel: context.l10n.losLegendRadioHorizon, + selectedSampleIndex: _selectedObstruction?.sampleIndex, + ), + ), + ); + } + return SizedBox( + height: 160, + width: double.infinity, + child: LayoutBuilder( + builder: (context, constraints) { + final size = Size(constraints.maxWidth, 160); + final geometry = _LosProfileGeometry( + samples: segment.samples, + size: size, + ); + return Stack( + clipBehavior: Clip.none, + children: [ + Positioned.fill( + child: CustomPaint( + painter: _LosProfilePainter( + samples: segment.samples, + distanceUnit: distanceUnit, + heightUnit: heightUnit, + badgeTextStyle: + Theme.of(context).textTheme.labelSmall?.copyWith( + color: Colors.white70, + fontSize: 10, + fontWeight: FontWeight.w600, + ) ?? + const TextStyle( + color: Colors.white70, + fontSize: 10, + fontWeight: FontWeight.w600, + ), + terrainLabel: context.l10n.losLegendTerrain, + losBeamLabel: context.l10n.losLegendLosBeam, + radioHorizonLabel: context.l10n.losLegendRadioHorizon, + selectedSampleIndex: _selectedObstruction?.sampleIndex, + ), + ), + ), + for (final obstruction in segment.obstructions) + Builder( + builder: (context) { + final sample = segment.samples[obstruction.sampleIndex]; + final position = geometry.mapPoint( + sample.distanceMeters, + sample.terrainMeters, + ); + final isSelected = + _selectedObstruction?.sampleIndex == + obstruction.sampleIndex; + final markerSize = isSelected ? 18.0 : 14.0; + final left = (position.dx - markerSize / 2) + .clamp(0.0, math.max(0.0, size.width - markerSize)) + .toDouble(); + final top = (position.dy - markerSize / 2) + .clamp(0.0, math.max(0.0, size.height - markerSize)) + .toDouble(); + return Positioned( + left: left, + top: top, + child: Tooltip( + message: _obstructionChipLabel(obstruction, isImperial), + child: GestureDetector( + onTap: () => _selectObstruction(obstruction), + child: Container( + width: markerSize, + height: markerSize, + decoration: BoxDecoration( + color: isSelected + ? Colors.amberAccent + : Colors.deepOrangeAccent, + shape: BoxShape.circle, + border: Border.all( + color: isSelected + ? Colors.white + : Colors.black87, + width: isSelected ? 2 : 1.5, + ), + boxShadow: const [ + BoxShadow(color: Colors.black45, blurRadius: 4), + ], + ), + ), + ), + ), + ); + }, + ), + ], + ); + }, + ), + ); + } + String _profileStats(LineOfSightResult result, bool isImperial) { final distance = isImperial ? (result.totalDistanceMeters / 1000.0) * _kmToMiles @@ -820,8 +1073,51 @@ class _LineOfSightMapScreenState extends State { return polylines; } - List _buildMarkers(List endpoints) { + List _buildMarkers( + List endpoints, + List obstructions, + ) { return [ + for (final obstruction in obstructions) + Marker( + point: obstruction.point, + width: 52, + height: 52, + child: GestureDetector( + onTap: () => _selectObstruction(obstruction), + child: Center( + child: Container( + width: + _selectedObstruction?.sampleIndex == obstruction.sampleIndex + ? 36 + : 24, + height: + _selectedObstruction?.sampleIndex == obstruction.sampleIndex + ? 36 + : 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.transparent, + border: Border.all( + color: + _selectedObstruction?.sampleIndex == + obstruction.sampleIndex + ? Colors.amberAccent + : Colors.deepOrangeAccent, + width: + _selectedObstruction?.sampleIndex == + obstruction.sampleIndex + ? 4 + : 3, + ), + boxShadow: const [ + BoxShadow(color: Colors.black26, blurRadius: 6), + ], + ), + ), + ), + ), + ), for (final endpoint in endpoints) Marker( point: endpoint.point, @@ -1010,6 +1306,51 @@ class _LineOfSightMapScreenState extends State { } } +class _LosProfileGeometry { + static const horizontalPadding = 12.0; + static const verticalPadding = 12.0; + + final List samples; + final Size size; + late final double minY = samples + .map( + (s) => math.min( + math.min(s.terrainMeters, s.lineHeightMeters), + s.refractedHeightMeters, + ), + ) + .reduce(math.min); + late final double maxY = samples + .map( + (s) => math.max( + math.max(s.terrainMeters, s.lineHeightMeters), + s.refractedHeightMeters, + ), + ) + .reduce(math.max); + late final double ySpan = math.max(1.0, maxY - minY); + late final double maxDist = math.max(1.0, samples.last.distanceMeters); + late final double chartWidth = math.max( + 1.0, + size.width - horizontalPadding * 2, + ); + late final double chartHeight = math.max( + 1.0, + size.height - verticalPadding * 2, + ); + + _LosProfileGeometry({required this.samples, required this.size}); + + Offset mapPoint(double distanceMeters, double elevationMeters) { + final px = horizontalPadding + (distanceMeters / maxDist) * chartWidth; + final py = + size.height - + verticalPadding - + ((elevationMeters - minY) / ySpan) * chartHeight; + return Offset(px, py); + } +} + class _LosProfilePainter extends CustomPainter { final List samples; final String distanceUnit; @@ -1018,6 +1359,7 @@ class _LosProfilePainter extends CustomPainter { final String terrainLabel; final String losBeamLabel; final String radioHorizonLabel; + final int? selectedSampleIndex; const _LosProfilePainter({ required this.samples, @@ -1027,6 +1369,7 @@ class _LosProfilePainter extends CustomPainter { required this.terrainLabel, required this.losBeamLabel, required this.radioHorizonLabel, + this.selectedSampleIndex, }); @override @@ -1212,6 +1555,32 @@ class _LosProfilePainter extends CustomPainter { ..color = horizonFillColor ..style = PaintingStyle.fill, ); + + if (selectedSampleIndex != null && + selectedSampleIndex! >= 0 && + selectedSampleIndex! < samples.length) { + final selectedSample = samples[selectedSampleIndex!]; + final selectedPoint = mapPoint( + selectedSample.distanceMeters, + selectedSample.terrainMeters, + ); + canvas.drawLine( + Offset(selectedPoint.dx, verticalPadding), + Offset(selectedPoint.dx, size.height - verticalPadding), + Paint() + ..color = Colors.amberAccent.withValues(alpha: 0.7) + ..strokeWidth = 1.5, + ); + canvas.drawCircle(selectedPoint, 7, Paint()..color = Colors.amberAccent); + canvas.drawCircle( + selectedPoint, + 8.5, + Paint() + ..color = Colors.white + ..style = PaintingStyle.stroke + ..strokeWidth = 1.5, + ); + } } @override @@ -1222,7 +1591,8 @@ class _LosProfilePainter extends CustomPainter { oldDelegate.badgeTextStyle != badgeTextStyle || oldDelegate.terrainLabel != terrainLabel || oldDelegate.losBeamLabel != losBeamLabel || - oldDelegate.radioHorizonLabel != radioHorizonLabel; + oldDelegate.radioHorizonLabel != radioHorizonLabel || + oldDelegate.selectedSampleIndex != selectedSampleIndex; } void _drawUnitBadge(Canvas canvas, Size size) { diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 6a8acda7..efc1656f 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'; @@ -37,6 +38,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 +46,7 @@ class MapScreen extends StatefulWidget { super.key, this.highlightPosition, this.highlightLabel, + this.highlightMarkerKey, this.highlightZoom = 15.0, this.hideBackButton = false, }); @@ -94,6 +97,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 +245,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 +509,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 +1275,39 @@ 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); + 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, + label: payload.label.isEmpty + ? context.l10n.map_sharedPin + : payload.label, flags: payload.flags, fromName: fromName, sourceLabel: contact.name, + timestamp: message.timestamp, isChannel: false, isPublicChannel: false, ), @@ -1272,23 +1319,28 @@ 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 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, + label: payload.label.isEmpty + ? context.l10n.map_sharedPin + : payload.label, flags: payload.flags, fromName: message.senderName, sourceLabel: channel.name.isEmpty ? 'Channel ${channel.index}' : channel.name, + timestamp: message.timestamp, isChannel: true, isPublicChannel: isPublic, ), @@ -1296,38 +1348,27 @@ 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; } - _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, - ); - } - - 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 +1378,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( @@ -1391,11 +1440,17 @@ class _MapScreenState extends State { room: room, // onLogin(password, isAdmin) isAdmin not used for room caht screen onLogin: (password, _) { - // Navigate to chat screen after successful login - context.read().markContactRead(room.publicKeyHex); + final connector = context.read(); + final unread = connector.getUnreadCountForContactKey( + room.publicKeyHex, + ); + connector.markContactRead(room.publicKeyHex); Navigator.push( context, - MaterialPageRoute(builder: (context) => ChatScreen(contact: room)), + MaterialPageRoute( + builder: (context) => + ChatScreen(contact: room, initialUnreadCount: unread), + ), ); }, ), @@ -1425,23 +1480,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.typeLabel(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: [ @@ -1456,11 +1511,17 @@ class _MapScreenState extends State { if (!contact.isActive) { connector.importDiscoveredContact(contact); } + final unread = connector.getUnreadCountForContactKey( + contact.publicKeyHex, + ); Navigator.pop(dialogContext); Navigator.push( context, MaterialPageRoute( - builder: (context) => ChatScreen(contact: contact), + builder: (context) => ChatScreen( + contact: contact, + initialUnreadCount: unread, + ), ), ); }, @@ -1542,7 +1603,9 @@ 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, @@ -1550,7 +1613,11 @@ class _MapScreenState extends State { _buildInfoRow(context.l10n.map_from, marker.fromName), _buildInfoRow(context.l10n.map_source, marker.sourceLabel), _buildInfoRow( - 'Location', + context.l10n.map_sharedAt, + _formatLastSeen(marker.timestamp), + ), + _buildInfoRow( + context.l10n.map_location, '${marker.position.latitude.toStringAsFixed(6)}, ${marker.position.longitude.toStringAsFixed(6)}', ), if (marker.flags.isNotEmpty) @@ -1715,6 +1782,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( @@ -2019,7 +2090,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, ), @@ -2310,18 +2381,50 @@ 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:,|