From 7a22223756519b64d30beccf4e1cb3f12b8ee7f0 Mon Sep 17 00:00:00 2001 From: spfmoby <40357319+spfmoby@users.noreply.github.com> Date: Mon, 12 Jan 2026 10:18:18 +0100 Subject: [PATCH 01/16] =?UTF-8?q?Replace=20Publicit=C3=A9=20by=20Annonce?= =?UTF-8?q?=20in=20the=20french=20translations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/l10n/app_fr.arb | 22 +++++++++++----------- lib/l10n/app_localizations_fr.dart | 24 ++++++++++++------------ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 4b8af01..b1d4e3c 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -91,12 +91,12 @@ "settings_latitude": "Latitude", "settings_longitude": "Longitude", "settings_privacyMode": "Mode de confidentialité", - "settings_privacyModeSubtitle": "Cacher le nom/l'emplacement dans les publicités", - "settings_privacyModeToggle": "Activer le mode confidentialité pour masquer votre nom et votre localisation dans les publicités.", + "settings_privacyModeSubtitle": "Cacher le nom/l'emplacement dans les annonces", + "settings_privacyModeToggle": "Activer le mode confidentialité pour masquer votre nom et votre localisation dans les annonces.", "settings_privacyModeEnabled": "Mode de confidentialité activé", "settings_privacyModeDisabled": "Mode de confidentialité désactivé", "settings_actions": "Actions", - "settings_sendAdvertisement": "Envoyer la publicité", + "settings_sendAdvertisement": "S'annoncer", "settings_sendAdvertisementSubtitle": "Présence diffusée maintenant", "settings_advertisementSent": "Annonce envoyée", "settings_syncTime": "Temps de synchronisation", @@ -176,7 +176,7 @@ "appSettings_languageBg": "Български", "appSettings_notifications": "Notifications", "appSettings_enableNotifications": "Activer les Notifications", - "appSettings_enableNotificationsSubtitle": "Recevoir des notifications pour les messages et les publicités", + "appSettings_enableNotificationsSubtitle": "Recevoir des notifications pour les messages et les annonces", "appSettings_notificationPermissionDenied": "Permission de notification refusée", "appSettings_notificationsEnabled": "Notifications activées", "appSettings_notificationsDisabled": "Notifications désactivées", @@ -990,9 +990,9 @@ "repeater_guestAccess": "Accès Invité", "repeater_guestAccessSubtitle": "Autoriser l'accès invité en lecture seule", "repeater_privacyMode": "Mode de confidentialité", - "repeater_privacyModeSubtitle": "Cacher le nom/l'emplacement dans les publicités", - "repeater_advertisementSettings": "Paramètres de Publicité", - "repeater_localAdvertInterval": "Intervalle Publicité Locale", + "repeater_privacyModeSubtitle": "Cacher le nom/l'emplacement dans les annonces", + "repeater_advertisementSettings": "Paramètres d'annonces", + "repeater_localAdvertInterval": "Intervalle des annonces Locale (0 saut)", "repeater_localAdvertIntervalMinutes": "{minutes} minutes", "@repeater_localAdvertIntervalMinutes": { "placeholders": { @@ -1001,7 +1001,7 @@ } } }, - "repeater_floodAdvertInterval": "Intervalle de Publicité Inondation", + "repeater_floodAdvertInterval": "Intervalle des annonces à tout le réseau (flood)", "repeater_floodAdvertIntervalHours": "{hours} heures", "@repeater_floodAdvertIntervalHours": { "placeholders": { @@ -1055,7 +1055,7 @@ "repeater_refreshPacketForwarding": "Rafraîchir le routage des paquets", "repeater_refreshGuestAccess": "Rafraîchir l'accès invité", "repeater_refreshPrivacyMode": "Rafraîchir le Mode Confidentialité", - "repeater_refreshAdvertisementSettings": "Rafraîchir les Paramètres de la Publicité", + "repeater_refreshAdvertisementSettings": "Rafraîchir les Paramètres des annonces", "repeater_refreshed": "{label} rafraîchi", "@repeater_refreshed": { "placeholders": { @@ -1115,7 +1115,7 @@ "repeater_cliHelpSetAdvertInterval": "Définit l'intervalle du minuteur pour envoyer un paquet d'annonce local (sans relais). Définir sur 0 pour désactiver.", "repeater_cliHelpSetFloodAdvertInterval": "Définit l'intervalle du minuteur en heures pour envoyer un paquet d'annonce massive. Définir sur 0 pour désactiver.", "repeater_cliHelpSetGuestPassword": "Définit/met à jour le mot de passe de l'invité. (pour les répéteurs, les connexions d'invités peuvent envoyer la requête \"Get Stats\")", - "repeater_cliHelpSetName": "Définit le nom de la publicité.", + "repeater_cliHelpSetName": "Définit le nom de l'annonce.", "repeater_cliHelpSetLat": "Définit la latitude de la carte des annonces. (degrés décimaux)", "repeater_cliHelpSetLon": "Définit la longitude de la carte de l'annonce. (degrés décimaux)", "repeater_cliHelpSetRadio": "Définit complètement de nouveaux paramètres de radio et les enregistre dans les préférences. Nécessite une commande \"redémarrage\" pour les appliquer.", @@ -1134,7 +1134,7 @@ "repeater_cliHelpLogStart": "Démarre l'enregistrement des paquets dans le système de fichiers.", "repeater_cliHelpLogStop": "Arrêter de journaliser les paquets vers le système de fichiers.", "repeater_cliHelpLogErase": "Supprime les journaux de paquets du système de fichiers.", - "repeater_cliHelpNeighbors": "Affiche une liste d'autres nœuds répétiteurs entendus via des publicités sans relais. Chaque ligne est id-préfixe-hexadécimal:timestamp:snr-fois-4", + "repeater_cliHelpNeighbors": "Affiche une liste d'autres nœuds répétiteurs entendus via des annonces sans relais. Chaque ligne est id-préfixe-hexadécimal:timestamp:snr-fois-4", "repeater_cliHelpNeighborRemove": "Supprime la première entrée correspondante (par préfixe de clé publique (hexadécimal)) de la liste des voisins.", "repeater_cliHelpRegion": "(série uniquement) Liste toutes les régions définies et les autorisations de débordement actuelles.", "repeater_cliHelpRegionLoad": "REMARQUE : il s'agit d'une invocation multi-commande spéciale. Chaque commande subséquente est un nom de région (indenté avec des espaces pour indiquer la hiérarchie parent, avec un minimum d'un espace). Terminé par l'envoi d'une ligne vide/commande.", diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index acdb5b0..fb30c12 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -211,11 +211,11 @@ class AppLocalizationsFr extends AppLocalizations { @override String get settings_privacyModeSubtitle => - 'Cacher le nom/l\'emplacement dans les publicités'; + 'Cacher le nom/l\'emplacement dans les annonces'; @override String get settings_privacyModeToggle => - 'Activer le mode confidentialité pour masquer votre nom et votre localisation dans les publicités.'; + 'Activer le mode confidentialité pour masquer votre nom et votre localisation dans les annonces.'; @override String get settings_privacyModeEnabled => 'Mode de confidentialité activé'; @@ -228,11 +228,11 @@ class AppLocalizationsFr extends AppLocalizations { String get settings_actions => 'Actions'; @override - String get settings_sendAdvertisement => 'Envoyer la publicité'; + String get settings_sendAdvertisement => 'Envoyer l\'annnonce'; @override String get settings_sendAdvertisementSubtitle => - 'Présence diffusée maintenant'; + 'Annonce diffusée maintenant'; @override String get settings_advertisementSent => 'Annonce envoyée'; @@ -438,7 +438,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get appSettings_enableNotificationsSubtitle => - 'Recevoir des notifications pour les messages et les publicités'; + 'Recevoir des notifications pour les messages et les annonces'; @override String get appSettings_notificationPermissionDenied => @@ -1771,13 +1771,13 @@ class AppLocalizationsFr extends AppLocalizations { @override String get repeater_privacyModeSubtitle => - 'Cacher le nom/l\'emplacement dans les publicités'; + 'Cacher le nom/l\'emplacement dans les annonces'; @override - String get repeater_advertisementSettings => 'Paramètres de Publicité'; + String get repeater_advertisementSettings => 'Paramètres d\'annonce'; @override - String get repeater_localAdvertInterval => 'Intervalle Publicité Locale'; + String get repeater_localAdvertInterval => 'Intervalle d\'annonce Locale'; @override String repeater_localAdvertIntervalMinutes(int minutes) { @@ -1786,7 +1786,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get repeater_floodAdvertInterval => - 'Intervalle de Publicité Inondation'; + 'Intervalle d\'annonce à tout le réseau'; @override String repeater_floodAdvertIntervalHours(int hours) { @@ -1886,7 +1886,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get repeater_refreshAdvertisementSettings => - 'Rafraîchir les Paramètres de la Publicité'; + 'Rafraîchir les Paramètres d\'annonce'; @override String repeater_refreshed(String label) { @@ -2026,7 +2026,7 @@ class AppLocalizationsFr extends AppLocalizations { 'Définit/met à jour le mot de passe de l\'invité. (pour les répéteurs, les connexions d\'invités peuvent envoyer la requête \"Get Stats\")'; @override - String get repeater_cliHelpSetName => 'Définit le nom de la publicité.'; + String get repeater_cliHelpSetName => 'Définit le nom de l\’annonce.'; @override String get repeater_cliHelpSetLat => @@ -2101,7 +2101,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get repeater_cliHelpNeighbors => - 'Affiche une liste d\'autres nœuds répétiteurs entendus via des publicités sans relais. Chaque ligne est id-préfixe-hexadécimal:timestamp:snr-fois-4'; + 'Affiche une liste d\'autres nœuds répétiteurs entendus via des annonces sans relais. Chaque ligne est id-préfixe-hexadécimal:timestamp:snr-fois-4'; @override String get repeater_cliHelpNeighborRemove => From 6ddb8f1a3d71d21df9f535e53607c5fee8ba7942 Mon Sep 17 00:00:00 2001 From: spfmoby <40357319+spfmoby@users.noreply.github.com> Date: Tue, 13 Jan 2026 08:27:01 +0100 Subject: [PATCH 02/16] more fr translations / .arb and .dart synced --- lib/l10n/app_fr.arb | 32 ++++++++++---------- lib/l10n/app_localizations_fr.dart | 47 +++++++++++++++--------------- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index b1d4e3c..6a09a33 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -184,7 +184,7 @@ "appSettings_messageNotificationsSubtitle": "Afficher une notification lors de la réception de nouveaux messages", "appSettings_channelMessageNotifications": "Notifications des Messages de Canal", "appSettings_channelMessageNotificationsSubtitle": "Afficher une notification lors de la réception des messages de canal", - "appSettings_advertisementNotifications": "Notifications publicitaires", + "appSettings_advertisementNotifications": "Notifications d'annonces", "appSettings_advertisementNotificationsSubtitle": "Afficher une notification lors de la découverte de nouveaux nœuds", "appSettings_messaging": "Messagerie", "appSettings_clearPathOnMaxRetry": "Effacer le chemin sur Max Retry", @@ -192,7 +192,7 @@ "appSettings_pathsWillBeCleared": "Les chemins seront effacés après 5 tentatives infructueuses.", "appSettings_pathsWillNotBeCleared": "Les chemins ne seront pas effacés automatiquement.", "appSettings_autoRouteRotation": "Rotation de l'itinéraire automatique", - "appSettings_autoRouteRotationSubtitle": "Alterner entre les meilleurs chemins et le mode inondation", + "appSettings_autoRouteRotationSubtitle": "Alterner entre les meilleurs chemins et le mode d'envoi sur tout le réseau (flood)", "appSettings_autoRouteRotationEnabled": "Rotation du routage automatique activée", "appSettings_autoRouteRotationDisabled": "Rotation de l'itinéraire automatique désactivée", "appSettings_battery": "Batterie", @@ -539,7 +539,7 @@ "chat_pathManagement": "Gestion des chemins", "chat_routingMode": "Mode de routage", "chat_autoUseSavedPath": "Auto (utiliser le chemin sauvegardé)", - "chat_forceFloodMode": "Mode Inondation Forcée", + "chat_forceFloodMode": "Mode tout le réseau forcé", "chat_recentAckPaths": "Chemins ACK récents (touchez pour utiliser) :", "chat_pathHistoryFull": "L'historique du chemin est plein. Supprimez les entrées pour en ajouter de nouvelles.", "chat_hopSingular": "Sautez", @@ -562,7 +562,7 @@ "chat_clearPathSubtitle": "Forcer la redécouverte lors de la prochaine envoi", "chat_pathCleared": "Le chemin est dégagé. Le prochain message redécouvrira le tracé.", "chat_floodModeSubtitle": "Utiliser le commutateur de routage dans la barre d'application", - "chat_floodModeEnabled": "Le mode inondation est activé. Réactiver via l'icône de routage dans la barre d'outils.", + "chat_floodModeEnabled": "Le mode envoi à tout le réseau est activé. Changer via l'icône de routage dans la barre d'outils.", "chat_fullPath": "Chemin complet", "chat_pathDetailsNotAvailable": "Les détails du chemin ne sont pas encore disponibles. Essayez d'envoyer un message pour rafraîchir.", "chat_pathSetHops": "Chemin défini : {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}", @@ -583,7 +583,7 @@ "chat_path": "Chemin", "chat_publicKey": "Clé Publique", "chat_compressOutgoingMessages": "Compresser les messages sortants", - "chat_floodForced": "Inondation (forcée)", + "chat_floodForced": "Tout le réseau (forcée)", "chat_directForced": "Direct (forcé)", "chat_hopsForced": "{count} sauts (forcés)", "@chat_hopsForced": { @@ -593,7 +593,7 @@ } } }, - "chat_floodAuto": "Inondation (auto)", + "chat_floodAuto": "Tout le réseau (auto)", "chat_direct": "Afficher", "chat_poiShared": "Point d'intérêt Partagé", "chat_unread": "Non lu : {count}", @@ -799,7 +799,7 @@ "login_routing": "Redirection", "login_routingMode": "Mode de routage", "login_autoUseSavedPath": "Auto (utiliser le chemin sauvegardé)", - "login_forceFloodMode": "Mode Inondation Forcée", + "login_forceFloodMode": "Mode tout le réseau forcé", "login_managePaths": "Gérer les chemins", "login_login": "Connexion", "login_attempt": "Essayer {current}/{max}", @@ -871,7 +871,7 @@ "repeater_statusTitle": "État du répétiteur", "repeater_routingMode": "Mode de routage", "repeater_autoUseSavedPath": "Auto (utiliser le chemin sauvegardé)", - "repeater_forceFloodMode": "Mode de submersion forcée", + "repeater_forceFloodMode": "Mode tout le réseau forcé", "repeater_pathManagement": "Gestion des chemins", "repeater_refresh": "Rafraîchir", "repeater_statusRequestTimeout": "Demande de statut délai dépassé.", @@ -916,7 +916,7 @@ } } }, - "repeater_packetTxTotal": "Total : {total}, Inondation : {flood}, Direct : {direct}", + "repeater_packetTxTotal": "Total : {total}, Tout le réseau : {flood}, Direct : {direct}", "@repeater_packetTxTotal": { "placeholders": { "total": { @@ -930,7 +930,7 @@ } } }, - "repeater_packetRxTotal": "Total : {total}, Inondation : {flood}, Direct : {direct}", + "repeater_packetRxTotal": "Total : {total}, Tout le réseau : {flood}, Direct : {direct}", "@repeater_packetRxTotal": { "placeholders": { "total": { @@ -944,7 +944,7 @@ } } }, - "repeater_duplicatesFloodDirect": "Inondation : {flood}, Direct : {direct}", + "repeater_duplicatesFloodDirect": "Tout le réseau : {flood}, Direct : {direct}", "@repeater_duplicatesFloodDirect": { "placeholders": { "flood": { @@ -1010,7 +1010,7 @@ } } }, - "repeater_encryptedAdvertInterval": "Intervalle publicitaire crypté", + "repeater_encryptedAdvertInterval": "Intervalle d'annonces cryptées", "repeater_dangerZone": "Zone d'alerte", "repeater_rebootRepeater": "Redémarrer Répéteur", "repeater_rebootRepeaterSubtitle": "Réinitialiser l'appareil répétiteur", @@ -1098,7 +1098,7 @@ "repeater_cliQuickVersion": "Version", "repeater_cliQuickAdvertise": "Publier", "repeater_cliQuickClock": "Horloge", - "repeater_cliHelpAdvert": "Envoie un paquet publicitaire", + "repeater_cliHelpAdvert": "Envoie un paquet d'annonce", "repeater_cliHelpReboot": "Redémarre l'appareil. (Note, vous risquez d'obtenir 'Timeout' ce qui est normal)", "repeater_cliHelpClock": "Affiche l'heure actuelle par l'horloge de chaque appareil.", "repeater_cliHelpPassword": "Définit un nouveau mot de passe administrateur pour l'appareil.", @@ -1120,7 +1120,7 @@ "repeater_cliHelpSetLon": "Définit la longitude de la carte de l'annonce. (degrés décimaux)", "repeater_cliHelpSetRadio": "Définit complètement de nouveaux paramètres de radio et les enregistre dans les préférences. Nécessite une commande \"redémarrage\" pour les appliquer.", "repeater_cliHelpSetRxDelay": "Paramètres (expérimental) de base pour appliquer un léger délai aux paquets reçus, en fonction de la force du signal/score. Définir sur 0 pour désactiver.", - "repeater_cliHelpSetTxDelay": "Définit un facteur multiplié par le temps de fonctionnement en mode inondation pour un paquet et avec un système de slot aléatoire, afin de retarder son envoi (pour diminuer la probabilité de collisions).", + "repeater_cliHelpSetTxDelay": "Définit un facteur multiplié par le temps de fonctionnement en mode vers tout le réseau (flood) pour un paquet et avec un système de slot aléatoire, afin de retarder son envoi (pour diminuer la probabilité de collisions).", "repeater_cliHelpSetDirectTxDelay": "Identique à txdelay, mais pour appliquer un délai aléatoire au transfert des paquets en mode direct.", "repeater_cliHelpSetBridgeEnabled": "Activer/Désactiver le pont.", "repeater_cliHelpSetBridgeDelay": "Définir le délai avant de renvoyer les paquets.", @@ -1136,7 +1136,7 @@ "repeater_cliHelpLogErase": "Supprime les journaux de paquets du système de fichiers.", "repeater_cliHelpNeighbors": "Affiche une liste d'autres nœuds répétiteurs entendus via des annonces sans relais. Chaque ligne est id-préfixe-hexadécimal:timestamp:snr-fois-4", "repeater_cliHelpNeighborRemove": "Supprime la première entrée correspondante (par préfixe de clé publique (hexadécimal)) de la liste des voisins.", - "repeater_cliHelpRegion": "(série uniquement) Liste toutes les régions définies et les autorisations de débordement actuelles.", + "repeater_cliHelpRegion": "(série uniquement) Liste toutes les régions définies et les autorisations actuelles d'annonces sur tout le réseau (flood).", "repeater_cliHelpRegionLoad": "REMARQUE : il s'agit d'une invocation multi-commande spéciale. Chaque commande subséquente est un nom de région (indenté avec des espaces pour indiquer la hiérarchie parent, avec un minimum d'un espace). Terminé par l'envoi d'une ligne vide/commande.", "repeater_cliHelpRegionGet": "Recherche la région avec le préfixe de nom donné (ou \"\" pour l'étendue globale). Répond avec \"-> nom-de-région (nom-parent) 'F'\"", "repeater_cliHelpRegionPut": "Ajoute ou met à jour une définition de région avec le nom donné.", @@ -1271,7 +1271,7 @@ } }, "channelPath_unknownPath": "Inconnu", - "channelPath_floodPath": "Inondation", + "channelPath_floodPath": "Tout le réseau", "channelPath_directPath": "Afficher", "channelPath_observedZeroOf": "0 de {total} sauts", "@channelPath_observedZeroOf": { diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index fb30c12..c7735bc 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -228,11 +228,11 @@ class AppLocalizationsFr extends AppLocalizations { String get settings_actions => 'Actions'; @override - String get settings_sendAdvertisement => 'Envoyer l\'annnonce'; + String get settings_sendAdvertisement => 'S\'annoncer'; @override String get settings_sendAdvertisementSubtitle => - 'Annonce diffusée maintenant'; + 'Présence diffusée maintenant'; @override String get settings_advertisementSent => 'Annonce envoyée'; @@ -467,7 +467,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get appSettings_advertisementNotifications => - 'Notifications publicitaires'; + 'Notifications d\'annonces'; @override String get appSettings_advertisementNotificationsSubtitle => @@ -498,7 +498,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get appSettings_autoRouteRotationSubtitle => - 'Alterner entre les meilleurs chemins et le mode inondation'; + 'Alterner entre les meilleurs chemins et le mode d\'envoi sur tout le réseau (flood)'; @override String get appSettings_autoRouteRotationEnabled => @@ -1016,7 +1016,7 @@ class AppLocalizationsFr extends AppLocalizations { String get chat_autoUseSavedPath => 'Auto (utiliser le chemin sauvegardé)'; @override - String get chat_forceFloodMode => 'Mode Inondation Forcée'; + String get chat_forceFloodMode => 'Mode tout le réseau forcé'; @override String get chat_recentAckPaths => @@ -1080,7 +1080,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get chat_floodModeEnabled => - 'Le mode inondation est activé. Réactiver via l\'icône de routage dans la barre d\'outils.'; + 'Le mode envoi à tout le réseau est activé. Changer via l\'icône de routage dans la barre d\'outils.'; @override String get chat_fullPath => 'Chemin complet'; @@ -1125,7 +1125,7 @@ class AppLocalizationsFr extends AppLocalizations { 'Compresser les messages sortants'; @override - String get chat_floodForced => 'Inondation (forcée)'; + String get chat_floodForced => 'Tout le réseau (forcée)'; @override String get chat_directForced => 'Direct (forcé)'; @@ -1136,7 +1136,7 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get chat_floodAuto => 'Inondation (auto)'; + String get chat_floodAuto => 'Tout le réseau (auto)'; @override String get chat_direct => 'Afficher'; @@ -1460,7 +1460,7 @@ class AppLocalizationsFr extends AppLocalizations { String get login_autoUseSavedPath => 'Auto (utiliser le chemin sauvegardé)'; @override - String get login_forceFloodMode => 'Mode Inondation Forcée'; + String get login_forceFloodMode => 'Mode tout le réseau forcé'; @override String get login_managePaths => 'Gérer les chemins'; @@ -1588,7 +1588,7 @@ class AppLocalizationsFr extends AppLocalizations { 'Auto (utiliser le chemin sauvegardé)'; @override - String get repeater_forceFloodMode => 'Mode de submersion forcée'; + String get repeater_forceFloodMode => 'Mode tout le réseau forcé'; @override String get repeater_pathManagement => 'Gestion des chemins'; @@ -1665,17 +1665,17 @@ class AppLocalizationsFr extends AppLocalizations { @override String repeater_packetTxTotal(int total, String flood, String direct) { - return 'Total : $total, Inondation : $flood, Direct : $direct'; + return 'Total : $total, Tout le réseau : $flood, Direct : $direct'; } @override String repeater_packetRxTotal(int total, String flood, String direct) { - return 'Total : $total, Inondation : $flood, Direct : $direct'; + return 'Total : $total, Tout le réseau : $flood, Direct : $direct'; } @override String repeater_duplicatesFloodDirect(String flood, String direct) { - return 'Inondation : $flood, Direct : $direct'; + return 'Tout le réseau : $flood, Direct : $direct'; } @override @@ -1774,10 +1774,11 @@ class AppLocalizationsFr extends AppLocalizations { 'Cacher le nom/l\'emplacement dans les annonces'; @override - String get repeater_advertisementSettings => 'Paramètres d\'annonce'; + String get repeater_advertisementSettings => 'Paramètres d\'annonces'; @override - String get repeater_localAdvertInterval => 'Intervalle d\'annonce Locale'; + String get repeater_localAdvertInterval => + 'Intervalle des annonces Locale (0 saut)'; @override String repeater_localAdvertIntervalMinutes(int minutes) { @@ -1786,7 +1787,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get repeater_floodAdvertInterval => - 'Intervalle d\'annonce à tout le réseau'; + 'Intervalle des annonces à tout le réseau (flood)'; @override String repeater_floodAdvertIntervalHours(int hours) { @@ -1795,7 +1796,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get repeater_encryptedAdvertInterval => - 'Intervalle publicitaire crypté'; + 'Intervalle d\'annonces cryptées'; @override String get repeater_dangerZone => 'Zone d\'alerte'; @@ -1886,7 +1887,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get repeater_refreshAdvertisementSettings => - 'Rafraîchir les Paramètres d\'annonce'; + 'Rafraîchir les Paramètres des annonces'; @override String repeater_refreshed(String label) { @@ -1960,7 +1961,7 @@ class AppLocalizationsFr extends AppLocalizations { String get repeater_cliQuickClock => 'Horloge'; @override - String get repeater_cliHelpAdvert => 'Envoie un paquet publicitaire'; + String get repeater_cliHelpAdvert => 'Envoie un paquet d\'annonce'; @override String get repeater_cliHelpReboot => @@ -2026,7 +2027,7 @@ class AppLocalizationsFr extends AppLocalizations { 'Définit/met à jour le mot de passe de l\'invité. (pour les répéteurs, les connexions d\'invités peuvent envoyer la requête \"Get Stats\")'; @override - String get repeater_cliHelpSetName => 'Définit le nom de l\’annonce.'; + String get repeater_cliHelpSetName => 'Définit le nom de l\'annonce.'; @override String get repeater_cliHelpSetLat => @@ -2046,7 +2047,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get repeater_cliHelpSetTxDelay => - 'Définit un facteur multiplié par le temps de fonctionnement en mode inondation pour un paquet et avec un système de slot aléatoire, afin de retarder son envoi (pour diminuer la probabilité de collisions).'; + 'Définit un facteur multiplié par le temps de fonctionnement en mode vers tout le réseau (flood) pour un paquet et avec un système de slot aléatoire, afin de retarder son envoi (pour diminuer la probabilité de collisions).'; @override String get repeater_cliHelpSetDirectTxDelay => @@ -2109,7 +2110,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get repeater_cliHelpRegion => - '(série uniquement) Liste toutes les régions définies et les autorisations de débordement actuelles.'; + '(série uniquement) Liste toutes les régions définies et les autorisations actuelles d\'annonces sur tout le réseau (flood).'; @override String get repeater_cliHelpRegionLoad => @@ -2319,7 +2320,7 @@ class AppLocalizationsFr extends AppLocalizations { String get channelPath_unknownPath => 'Inconnu'; @override - String get channelPath_floodPath => 'Inondation'; + String get channelPath_floodPath => 'Tout le réseau'; @override String get channelPath_directPath => 'Afficher'; From fcef82be632c659a2de6145da3287d348a62f546 Mon Sep 17 00:00:00 2001 From: Dennis ten Hoove Date: Mon, 12 Jan 2026 19:55:31 +0100 Subject: [PATCH 03/16] Update Dutch translations This solves many of the most obvious errors and inconsistensies in the initial translation. --- lib/l10n/app_localizations_nl.dart | 181 ++++++++++++++--------------- lib/l10n/app_nl.arb | 178 ++++++++++++++-------------- 2 files changed, 178 insertions(+), 181 deletions(-) diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 3484c0a..b9ae792 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -15,7 +15,7 @@ class AppLocalizationsNl extends AppLocalizations { String get nav_contacts => 'Contacten'; @override - String get nav_channels => 'Kanaal'; + String get nav_channels => 'Kanalen'; @override String get nav_map => 'Kaart'; @@ -39,7 +39,7 @@ class AppLocalizationsNl extends AppLocalizations { String get common_close => 'Sluiten'; @override - String get common_edit => 'Bewerk'; + String get common_edit => 'Bewerken'; @override String get common_add => 'Toevoegen'; @@ -48,13 +48,13 @@ class AppLocalizationsNl extends AppLocalizations { String get common_settings => 'Instellingen'; @override - String get common_disconnect => 'Verbinden verbreken'; + String get common_disconnect => 'Verbinding verbreken'; @override String get common_connected => 'Verbonden'; @override - String get common_disconnected => 'Ontkoppeld'; + String get common_disconnected => 'Verbinding verbroken'; @override String get common_create => 'Maak'; @@ -78,7 +78,7 @@ class AppLocalizationsNl extends AppLocalizations { String get common_remove => 'Verwijderen'; @override - String get common_enable => 'Aktivatie'; + String get common_enable => 'Activeren'; @override String get common_disable => 'Uitschakelen'; @@ -87,7 +87,7 @@ class AppLocalizationsNl extends AppLocalizations { String get common_reboot => 'Herstarten'; @override - String get common_loading => 'Laad...'; + String get common_loading => 'Laden...'; @override String get common_notAvailable => '—'; @@ -168,7 +168,7 @@ class AppLocalizationsNl extends AppLocalizations { String get settings_nodeNameNotSet => 'Niet ingesteld'; @override - String get settings_nodeNameHint => 'Voer knooppuntnaam in'; + String get settings_nodeNameHint => 'Voer nodenaam in'; @override String get settings_nodeNameUpdated => 'Naam bijgewerkt'; @@ -312,7 +312,7 @@ class AppLocalizationsNl extends AppLocalizations { String get settings_infoContactsCount => 'Aantal Contacten'; @override - String get settings_infoChannelCount => 'Kanaal Aantal'; + String get settings_infoChannelCount => 'Aantal Kanalen'; @override String get settings_presets => 'Presets'; @@ -354,10 +354,10 @@ class AppLocalizationsNl extends AppLocalizations { String get settings_txPowerInvalid => 'Ongeldige TX-vermogen (0-22 dBm)'; @override - String get settings_longRange => 'Lang Bereik'; + String get settings_longRange => 'Lange Afstand'; @override - String get settings_fastSpeed => 'Snelle Snelheid'; + String get settings_fastSpeed => 'Hoge Snelheid'; @override String settings_error(String message) { @@ -377,7 +377,7 @@ class AppLocalizationsNl extends AppLocalizations { String get appSettings_themeSystem => 'Standaardinstelling'; @override - String get appSettings_themeLight => 'Helder'; + String get appSettings_themeLight => 'Licht'; @override String get appSettings_themeDark => 'Donker'; @@ -469,13 +469,13 @@ class AppLocalizationsNl extends AppLocalizations { @override String get appSettings_advertisementNotificationsSubtitle => - 'Toon notificatie wanneer nieuwe knooppunten worden ontdekt'; + 'Toon notificatie wanneer nieuwe nodes worden ontdekt'; @override String get appSettings_messaging => 'Berichten'; @override - String get appSettings_clearPathOnMaxRetry => 'Duidelijke Pad op Max Retry'; + String get appSettings_clearPathOnMaxRetry => 'Wis Pad op Max Retry'; @override String get appSettings_clearPathOnMaxRetrySubtitle => @@ -490,19 +490,19 @@ class AppLocalizationsNl extends AppLocalizations { 'Padoms worden niet automatisch verwijderd'; @override - String get appSettings_autoRouteRotation => 'Automatische Route Rotatie'; + String get appSettings_autoRouteRotation => 'Route Automatisch Roteren'; @override String get appSettings_autoRouteRotationSubtitle => - 'Wissel tussen de beste paden en floodmodus over.'; + 'Verwissel tussen beste pad en floodmodus.'; @override String get appSettings_autoRouteRotationEnabled => - 'Automatische routeplanning rotatie ingeschakeld'; + 'Automatische route rotatie ingeschakeld'; @override String get appSettings_autoRouteRotationDisabled => - 'Automatische routeplanning rotatie is uitgeschakeld'; + 'Automatische route rotatie is uitgeschakeld'; @override String get appSettings_battery => 'Batterij'; @@ -532,11 +532,11 @@ class AppLocalizationsNl extends AppLocalizations { String get appSettings_mapDisplay => 'Kaartweergave'; @override - String get appSettings_showRepeaters => 'Toon Herhalingen'; + String get appSettings_showRepeaters => 'Toon Repeaters'; @override String get appSettings_showRepeatersSubtitle => - 'Toon herhalende knoopjes op de kaart'; + 'Toon repeaternodes op de kaart'; @override String get appSettings_showChatNodes => 'Chat Nodes tonen'; @@ -546,21 +546,21 @@ class AppLocalizationsNl extends AppLocalizations { 'Chatnodes weergeven op de kaart'; @override - String get appSettings_showOtherNodes => 'Toon Andere Knopen'; + String get appSettings_showOtherNodes => 'Toon Andere Nodes'; @override String get appSettings_showOtherNodesSubtitle => - 'Toon andere knooptypes op de kaart'; + 'Toon andere nodetypes op de kaart'; @override String get appSettings_timeFilter => 'Filter op tijd'; @override - String get appSettings_timeFilterShowAll => 'Alle knooppunten tonen'; + String get appSettings_timeFilterShowAll => 'Alle nodes tonen'; @override String appSettings_timeFilterShowLast(int hours) { - return 'Toon knopen van de laatste $hours uur'; + return 'Toon nodes van de laatste $hours uur'; } @override @@ -568,10 +568,10 @@ class AppLocalizationsNl extends AppLocalizations { @override String get appSettings_showNodesDiscoveredWithin => - 'Toon knooppunten ontdekt binnen:'; + 'Toon nodes ontdekt binnen:'; @override - String get appSettings_allTime => 'Alle tijd'; + String get appSettings_allTime => 'Altijd'; @override String get appSettings_lastHour => 'Laat uur'; @@ -642,7 +642,7 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get contacts_manageRepeater => 'Beheer Herhaling'; + String get contacts_manageRepeater => 'Beheer Repeater'; @override String get contacts_roomLogin => 'Ruimte Inloggen'; @@ -690,7 +690,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String contacts_lastSeenMinsAgo(int minutes) { - return 'Laast gezien $minutes minuten geleden'; + return 'Laatst gezien $minutes minuten geleden'; } @override @@ -1009,7 +1009,7 @@ class AppLocalizationsNl extends AppLocalizations { String get chat_autoUseSavedPath => 'Automatisch (gebruik opgeslagen pad)'; @override - String get chat_forceFloodMode => 'Dwing Overstromingsmodus'; + String get chat_forceFloodMode => 'Dwing Floodsmodus'; @override String get chat_recentAckPaths => 'Recente ACK Paden (tik om te gebruiken):'; @@ -1071,7 +1071,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get chat_floodModeEnabled => - 'Overstromingsmodus is ingeschakeld. Schakel dit uit via het route-icoon in de app-balk.'; + 'Floodmodus is ingeschakeld. Schakel dit uit via het route-icoon in de app-balk.'; @override String get chat_fullPath => 'Volledige Pad'; @@ -1115,18 +1115,18 @@ class AppLocalizationsNl extends AppLocalizations { 'Verzenden van uitgaande berichten comprimeren'; @override - String get chat_floodForced => 'Overstroming (gedwongen)'; + String get chat_floodForced => 'Flood (afgedwongen)'; @override - String get chat_directForced => 'Direct (gedwongen)'; + String get chat_directForced => 'Direct (afgedwongen)'; @override String chat_hopsForced(int count) { - return '$count sprongen (gedwongen)'; + return '$count hops (afgedwongen)'; } @override - String get chat_floodAuto => 'Overstroming (auto)'; + String get chat_floodAuto => 'Flood (auto)'; @override String get chat_direct => 'Direct'; @@ -1143,7 +1143,7 @@ class AppLocalizationsNl extends AppLocalizations { String get map_title => 'Node Map'; @override - String get map_noNodesWithLocation => 'Geen knopen met locatiegegevens'; + String get map_noNodesWithLocation => 'Geen nodes met locatiegegevens'; @override String get map_nodesNeedGps => @@ -1163,7 +1163,7 @@ class AppLocalizationsNl extends AppLocalizations { String get map_chat => 'Chat'; @override - String get map_repeater => 'Herhaling'; + String get map_repeater => 'Repeater'; @override String get map_room => 'Ruimte'; @@ -1230,19 +1230,19 @@ class AppLocalizationsNl extends AppLocalizations { 'Verbind met een apparaat om markers te delen'; @override - String get map_filterNodes => 'Filter Knopen'; + String get map_filterNodes => 'Filter Nodes'; @override - String get map_nodeTypes => 'Node Types'; + String get map_nodeTypes => 'Nodetypes'; @override - String get map_chatNodes => 'Chat Nodes'; + String get map_chatNodes => 'Chatnodes'; @override - String get map_repeaters => 'Herhalingen'; + String get map_repeaters => 'Repeaters'; @override - String get map_otherNodes => 'Andere knooppunten'; + String get map_otherNodes => 'Andere Nodes'; @override String get map_keyPrefix => 'Prefix sleutel'; @@ -1269,7 +1269,7 @@ class AppLocalizationsNl extends AppLocalizations { String get map_joinRoom => 'Sluit Kamer'; @override - String get map_manageRepeater => 'Beheer Herhaling'; + String get map_manageRepeater => 'Beheer Repeater'; @override String get mapCache_title => 'Offline Kaarten Cache'; @@ -1412,7 +1412,7 @@ class AppLocalizationsNl extends AppLocalizations { 'Ben je er zeker van dat je verbinding met dit apparaat wilt verbreken?'; @override - String get login_repeaterLogin => 'Herhaalders Inloggen'; + String get login_repeaterLogin => 'Inloggen Repeater'; @override String get login_roomLogin => 'Ruimte Inloggen'; @@ -1432,7 +1432,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get login_repeaterDescription => - 'Voer het wachtwoord van de herhaling in om instellingen en status te openen.'; + 'Voer het wachtwoord van de repeater in om instellingen en status te openen.'; @override String get login_roomDescription => @@ -1448,7 +1448,7 @@ class AppLocalizationsNl extends AppLocalizations { String get login_autoUseSavedPath => 'Automatisch (gebruik opgeslagen pad)'; @override - String get login_forceFloodMode => 'Dwing Overstromingsmodus'; + String get login_forceFloodMode => 'Dwing Floodmodus Af'; @override String get login_managePaths => 'Padbeheer'; @@ -1513,8 +1513,7 @@ class AppLocalizationsNl extends AppLocalizations { String get path_selectFromContacts => 'Of select contacten:'; @override - String get path_noRepeatersFound => - 'Geen herhalingen of zaalservers gevonden.'; + String get path_noRepeatersFound => 'Geen repeaters of roomservers gevonden.'; @override String get path_customPathsRequire => @@ -1533,7 +1532,7 @@ class AppLocalizationsNl extends AppLocalizations { String get path_setPath => 'Stel Pad in'; @override - String get repeater_management => 'Beheer Herhalingen'; + String get repeater_management => 'Beheer Repeaters'; @override String get repeater_managementTools => 'Beheerinstrumenten'; @@ -1556,7 +1555,7 @@ class AppLocalizationsNl extends AppLocalizations { String get repeater_cli => 'CLI'; @override - String get repeater_cliSubtitle => 'Verzend commando\'s naar de herhaaldere'; + String get repeater_cliSubtitle => 'Verzend commando\'s naar de repeater'; @override String get repeater_settings => 'Instellingen'; @@ -1565,7 +1564,7 @@ class AppLocalizationsNl extends AppLocalizations { String get repeater_settingsSubtitle => 'Configureer repeaterparameters'; @override - String get repeater_statusTitle => 'Status herhalen'; + String get repeater_statusTitle => 'Status repeater'; @override String get repeater_routingMode => 'Routeerwijze'; @@ -1575,7 +1574,7 @@ class AppLocalizationsNl extends AppLocalizations { 'Automatisch (gebruik opgeslagen pad)'; @override - String get repeater_forceFloodMode => 'Dwing Overloopmodus'; + String get repeater_forceFloodMode => 'Dwing Floodmodus Af'; @override String get repeater_pathManagement => 'Beheer van paden'; @@ -1592,7 +1591,7 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get repeater_systemInformation => 'Systeem Informatie'; + String get repeater_systemInformation => 'Systeeminformatie'; @override String get repeater_battery => 'Batterij'; @@ -1607,10 +1606,10 @@ class AppLocalizationsNl extends AppLocalizations { String get repeater_queueLength => 'Wachttijd'; @override - String get repeater_debugFlags => 'Debug Flags'; + String get repeater_debugFlags => 'Debugvlaggen'; @override - String get repeater_radioStatistics => 'Radio Statistieken'; + String get repeater_radioStatistics => 'Radiostatistieken'; @override String get repeater_lastRssi => 'Laatste RSSI'; @@ -1619,7 +1618,7 @@ class AppLocalizationsNl extends AppLocalizations { String get repeater_lastSnr => 'Laatste SNR'; @override - String get repeater_noiseFloor => 'Ruishoordniveau'; + String get repeater_noiseFloor => 'Ruisvloer'; @override String get repeater_txAirtime => 'TX Airtime'; @@ -1628,7 +1627,7 @@ class AppLocalizationsNl extends AppLocalizations { String get repeater_rxAirtime => 'RX Airtime'; @override - String get repeater_packetStatistics => 'Pakket Statistieken'; + String get repeater_packetStatistics => 'Pakketstatistieken'; @override String get repeater_sent => 'Verzonden'; @@ -1637,7 +1636,7 @@ class AppLocalizationsNl extends AppLocalizations { String get repeater_received => 'Ontvangen'; @override - String get repeater_duplicates => 'Dubbele'; + String get repeater_duplicates => 'Duplicaat'; @override String repeater_daysHoursMinsSecs( @@ -1651,17 +1650,17 @@ class AppLocalizationsNl extends AppLocalizations { @override String repeater_packetTxTotal(int total, String flood, String direct) { - return 'Totaal: $total, Overstroming: $flood, Direct: $direct'; + return 'Totaal: $total, Flood: $flood, Direct: $direct'; } @override String repeater_packetRxTotal(int total, String flood, String direct) { - return 'Totaal: $total, Overstroming: $flood, Direct: $direct'; + return 'Totaal: $total, Flood: $flood, Direct: $direct'; } @override String repeater_duplicatesFloodDirect(String flood, String direct) { - return 'Overstroming: $flood, Direct: $direct'; + return 'Flood: $flood, Direct: $direct'; } @override @@ -1670,16 +1669,16 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get repeater_settingsTitle => 'Herstelinstellingen'; + String get repeater_settingsTitle => 'Repeater Instellingen'; @override String get repeater_basicSettings => 'Basisinstellingen'; @override - String get repeater_repeaterName => 'Herhaalnaam'; + String get repeater_repeaterName => 'Repeaternaam'; @override - String get repeater_repeaterNameHelper => 'Weergave naam voor deze herhaling'; + String get repeater_repeaterNameHelper => 'Weergave naam voor deze repeater'; @override String get repeater_adminPassword => 'Admin wachtwoord'; @@ -1712,7 +1711,7 @@ class AppLocalizationsNl extends AppLocalizations { String get repeater_bandwidth => 'Bandbreedte'; @override - String get repeater_spreadingFactor => 'Spreadsnelheid'; + String get repeater_spreadingFactor => 'Spreidingsfactor'; @override String get repeater_codingRate => 'Codeertarief'; @@ -1736,11 +1735,11 @@ class AppLocalizationsNl extends AppLocalizations { String get repeater_features => 'Kenmerken'; @override - String get repeater_packetForwarding => 'Pakketdoorstrooming'; + String get repeater_packetForwarding => 'Pakketdoorvoering'; @override String get repeater_packetForwardingSubtitle => - 'Herstel activeren om pakketten door te sturen'; + 'Repeater instellen om pakketten door te sturen'; @override String get repeater_guestAccess => 'Toegang voor Gasten'; @@ -1750,7 +1749,7 @@ class AppLocalizationsNl extends AppLocalizations { 'Toegestane leesbeheer toegang voor gasten.'; @override - String get repeater_privacyMode => 'Privacy Mode'; + String get repeater_privacyMode => 'Privacy Modus'; @override String get repeater_privacyModeSubtitle => @@ -1768,8 +1767,7 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get repeater_floodAdvertInterval => - 'Advertentie Interval bij overstroming'; + String get repeater_floodAdvertInterval => 'Flood Advertentie Interval'; @override String repeater_floodAdvertIntervalHours(int hours) { @@ -1784,11 +1782,10 @@ class AppLocalizationsNl extends AppLocalizations { String get repeater_dangerZone => 'Gevaarzone'; @override - String get repeater_rebootRepeater => 'Herstart Herhaalder'; + String get repeater_rebootRepeater => 'Herstart Repeater'; @override - String get repeater_rebootRepeaterSubtitle => - 'De herstart van het herhalerapparaat'; + String get repeater_rebootRepeaterSubtitle => 'Herstart het Repeaterapparaat'; @override String get repeater_rebootRepeaterConfirm => @@ -1804,18 +1801,18 @@ class AppLocalizationsNl extends AppLocalizations { @override String get repeater_regenerateIdentityKeyConfirm => - 'Dit genereert een nieuwe identiteit voor de herhaling. Doorgaan?'; + 'Dit genereert een nieuwe identiteit voor de repeater. Doorgaan?'; @override String get repeater_eraseFileSystem => 'Verwijder Besturingssysteem'; @override String get repeater_eraseFileSystemSubtitle => - 'Formateer het herhalende bestandsysteem'; + 'Formateer het bestandsysteem van de repeater'; @override String get repeater_eraseFileSystemConfirm => - 'WAARSCHUWING: Dit zal alle gegevens op de herhaling wissen. Dit kan niet worden teruggedraaid!'; + 'WAARSCHUWING: Dit zal alle gegevens op de repeater wissen. Dit kan niet worden teruggedraaid!'; @override String get repeater_eraseSerialOnly => @@ -1847,7 +1844,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get repeater_refreshRadioSettings => - 'Radiozenders Instellingen Bijwerken'; + 'Radiozender Instellingen Verversen'; @override String get repeater_refreshTxPower => 'Nieuw laden TX-vermogen'; @@ -1881,7 +1878,7 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get repeater_cliTitle => 'Herhaling CLI'; + String get repeater_cliTitle => 'Repeater CLI'; @override String get repeater_debugNextCommand => 'Debug Volgende Commando'; @@ -1972,7 +1969,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get repeater_cliHelpSetRepeat => - 'Activeert of deactiveert de herhalerrol voor dit knoop.'; + 'Activeert of deactiveert de repeater rol van deze node.'; @override String get repeater_cliHelpSetAllowReadOnly => @@ -1980,7 +1977,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get repeater_cliHelpSetFloodMax => - 'Stelt het maximale aantal hops van een inkomend overlastpakket in (indien >= max, wordt het pakket niet doorgestuurd)'; + 'Stelt het maximale aantal hops van een inkomend floodpakket in (indien >= max, wordt het pakket niet doorgestuurd)'; @override String get repeater_cliHelpSetIntThresh => @@ -1992,7 +1989,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get repeater_cliHelpSetMultiAcks => - 'Activeert of deactiveert de functie \'dubbele ACKs\'.'; + 'Activeert of deactiveert de functie \'duplicate ACKs\'.'; @override String get repeater_cliHelpSetAdvertInterval => @@ -2000,7 +1997,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get repeater_cliHelpSetFloodAdvertInterval => - 'Stelt het timerinterval in uren in om een overstromingsadvertentiepakket te versturen. Stel in op 0 om dit uit te schakelen.'; + 'Stelt het timerinterval in uren in om een floodadvertentiepakket te versturen. Stel in op 0 om dit uit te schakelen.'; @override String get repeater_cliHelpSetGuestPassword => @@ -2091,7 +2088,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get repeater_cliHelpRegion => - '(reeks alleen) Lijst alle gedefinieerde regio\'s en huidige overstromingsrechten.'; + '(Alleen Serieel) Lijst alle gedefinieerde regio\'s en huidige floodrechten.'; @override String get repeater_cliHelpRegionLoad => @@ -2136,11 +2133,11 @@ class AppLocalizationsNl extends AppLocalizations { String get repeater_cliHelpGpsOnOff => 'Schakel de GPS-standby aan/uit.'; @override - String get repeater_cliHelpGpsSync => 'Synchroniseer knooptime met GPS-klok.'; + String get repeater_cliHelpGpsSync => 'Synchroniseer node met GPS-klok.'; @override String get repeater_cliHelpGpsSetLoc => - 'Stel de positie van het knoop vast naar GPS-coördinaten en sla de voorkeuren op.'; + 'Stel de positie van de node vast als GPS-coördinaten en sla de voorkeuren op.'; @override String get repeater_cliHelpGpsAdvert => @@ -2170,11 +2167,11 @@ class AppLocalizationsNl extends AppLocalizations { String get repeater_logging => 'Logging'; @override - String get repeater_neighborsRepeaterOnly => 'Buren (Alleen herhaald)'; + String get repeater_neighborsRepeaterOnly => 'Buren (Alleen repeaters)'; @override String get repeater_regionManagementRepeaterOnly => - 'Regiobeheer (Alleen voor Repeater)'; + 'Regiobeheer (Alleen Repeater)'; @override String get repeater_regionNote => @@ -2251,7 +2248,7 @@ class AppLocalizationsNl extends AppLocalizations { String get channelPath_otherObservedPaths => 'Overige Waargenomen Paden'; @override - String get channelPath_repeaterHops => 'Herhalingstapjes'; + String get channelPath_repeaterHops => 'Repeater Hops'; @override String get channelPath_noHopDetails => @@ -2267,7 +2264,7 @@ class AppLocalizationsNl extends AppLocalizations { String get channelPath_timeLabel => 'Tijd'; @override - String get channelPath_repeatsLabel => 'Herhalen'; + String get channelPath_repeatsLabel => 'Repeats'; @override String channelPath_pathLabel(int index) { @@ -2299,7 +2296,7 @@ class AppLocalizationsNl extends AppLocalizations { String get channelPath_unknownPath => 'Onbekend'; @override - String get channelPath_floodPath => 'Overstroming'; + String get channelPath_floodPath => 'Flood'; @override String get channelPath_directPath => 'Direct'; @@ -2319,7 +2316,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get channelPath_noRepeaterLocations => - 'Geen herhaler locaties beschikbaar voor deze route.'; + 'Geen repeaters beschikbaar voor deze route.'; @override String channelPath_primaryPath(int index) { @@ -2342,7 +2339,7 @@ class AppLocalizationsNl extends AppLocalizations { 'Geen details beschikbaar voor dit pakket.'; @override - String get channelPath_unknownRepeater => 'Onbekend Herhaalaar'; + String get channelPath_unknownRepeater => 'Onbekend Repeater'; @override String get listFilter_tooltip => 'Filteren en sorteren'; @@ -2369,10 +2366,10 @@ class AppLocalizationsNl extends AppLocalizations { String get listFilter_users => 'Gebruikers'; @override - String get listFilter_repeaters => 'Herhalingen'; + String get listFilter_repeaters => 'Repeaters'; @override - String get listFilter_roomServers => 'Kamervirtualisatie'; + String get listFilter_roomServers => 'Roomservers'; @override String get listFilter_unreadOnly => 'Alleen ongelezen'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index da33f11..79fc69d 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -2,7 +2,7 @@ "@@locale": "nl", "appTitle": "MeshCore Open", "nav_contacts": "Contacten", - "nav_channels": "Kanaal", + "nav_channels": "Kanalen", "nav_map": "Kaart", "common_cancel": "Annuleren", "common_connect": "Verbinden", @@ -10,12 +10,12 @@ "common_save": "Opslaan", "common_delete": "Verwijderen", "common_close": "Sluiten", - "common_edit": "Bewerk", + "common_edit": "Bewerken", "common_add": "Toevoegen", "common_settings": "Instellingen", - "common_disconnect": "Verbinden verbreken", + "common_disconnect": "Verbinding verbreken", "common_connected": "Verbonden", - "common_disconnected": "Ontkoppeld", + "common_disconnected": "Verbinding verbroken", "common_create": "Maak", "common_continue": "Doorgaan", "common_share": "Delen", @@ -23,10 +23,10 @@ "common_retry": "Nogmaals proberen", "common_hide": "Verbergen", "common_remove": "Verwijderen", - "common_enable": "Aktivatie", + "common_enable": "Activeren", "common_disable": "Uitschakelen", "common_reboot": "Herstarten", - "common_loading": "Laad...", + "common_loading": "Laden...", "common_notAvailable": "—", "common_voltageValue": "{volts} V", "@common_voltageValue": { @@ -78,7 +78,7 @@ "settings_nodeSettings": "Node Instellingen", "settings_nodeName": "Node Naam", "settings_nodeNameNotSet": "Niet ingesteld", - "settings_nodeNameHint": "Voer knooppuntnaam in", + "settings_nodeNameHint": "Voer nodenaam in", "settings_nodeNameUpdated": "Naam bijgewerkt", "settings_radioSettings": "Radio Instellingen", "settings_radioSettingsSubtitle": "Frequentie, vermogen, spredfactor", @@ -129,7 +129,7 @@ "settings_infoBattery": "Batterij", "settings_infoPublicKey": "Openbare Sleutel", "settings_infoContactsCount": "Aantal Contacten", - "settings_infoChannelCount": "Kanaal Aantal", + "settings_infoChannelCount": "Aantal Kanalen", "settings_presets": "Presets", "settings_preset915Mhz": "915 MHz", "settings_preset868Mhz": "868 MHz", @@ -143,8 +143,8 @@ "settings_txPower": "TX Vermogen (dBm)", "settings_txPowerHelper": "0 - 22", "settings_txPowerInvalid": "Ongeldige TX-vermogen (0-22 dBm)", - "settings_longRange": "Lang Bereik", - "settings_fastSpeed": "Snelle Snelheid", + "settings_longRange": "Lange Afstand", + "settings_fastSpeed": "Hoge Snelheid", "settings_error": "Fout: {message}", "@settings_error": { "placeholders": { @@ -157,7 +157,7 @@ "appSettings_appearance": "Uiterlijk", "appSettings_theme": "Thema", "appSettings_themeSystem": "Standaardinstelling", - "appSettings_themeLight": "Helder", + "appSettings_themeLight": "Licht", "appSettings_themeDark": "Donker", "appSettings_language": "Taal", "appSettings_languageSystem": "Standaardinstelling", @@ -185,16 +185,16 @@ "appSettings_channelMessageNotifications": "Kanaal Bericht Meldingen", "appSettings_channelMessageNotificationsSubtitle": "Toon notificatie bij het ontvangen van kanaalberichten", "appSettings_advertisementNotifications": "Advertentie-meldingen", - "appSettings_advertisementNotificationsSubtitle": "Toon notificatie wanneer nieuwe knooppunten worden ontdekt", + "appSettings_advertisementNotificationsSubtitle": "Toon notificatie wanneer nieuwe nodes worden ontdekt", "appSettings_messaging": "Berichten", - "appSettings_clearPathOnMaxRetry": "Duidelijke Pad op Max Retry", + "appSettings_clearPathOnMaxRetry": "Wis Pad op Max Retry", "appSettings_clearPathOnMaxRetrySubtitle": "Reset contactpad na 5 mislukte verzendpogingen", "appSettings_pathsWillBeCleared": "De paden worden na 5 mislukte pogingen leeggehaald.", "appSettings_pathsWillNotBeCleared": "Padoms worden niet automatisch verwijderd", - "appSettings_autoRouteRotation": "Automatische Route Rotatie", - "appSettings_autoRouteRotationSubtitle": "Wissel tussen de beste paden en floodmodus over.", - "appSettings_autoRouteRotationEnabled": "Automatische routeplanning rotatie ingeschakeld", - "appSettings_autoRouteRotationDisabled": "Automatische routeplanning rotatie is uitgeschakeld", + "appSettings_autoRouteRotation": "Route Automatisch Roteren", + "appSettings_autoRouteRotationSubtitle": "Verwissel tussen beste pad en floodmodus.", + "appSettings_autoRouteRotationEnabled": "Automatische route rotatie ingeschakeld", + "appSettings_autoRouteRotationDisabled": "Automatische route rotatie is uitgeschakeld", "appSettings_battery": "Batterij", "appSettings_batteryChemistry": "Batterijchemie", "appSettings_batteryChemistryPerDevice": "Instellen per apparaat ({deviceName})", @@ -210,15 +210,15 @@ "appSettings_batteryLifepo4": "LiFePO4 (2,6-3,65V)", "appSettings_batteryLipo": "LiPo (3,0-4,2V)", "appSettings_mapDisplay": "Kaartweergave", - "appSettings_showRepeaters": "Toon Herhalingen", - "appSettings_showRepeatersSubtitle": "Toon herhalende knoopjes op de kaart", + "appSettings_showRepeaters": "Toon Repeaters", + "appSettings_showRepeatersSubtitle": "Toon repeaternodes op de kaart", "appSettings_showChatNodes": "Chat Nodes tonen", "appSettings_showChatNodesSubtitle": "Chatnodes weergeven op de kaart", - "appSettings_showOtherNodes": "Toon Andere Knopen", - "appSettings_showOtherNodesSubtitle": "Toon andere knooptypes op de kaart", + "appSettings_showOtherNodes": "Toon Andere Nodes", + "appSettings_showOtherNodesSubtitle": "Toon andere nodetypes op de kaart", "appSettings_timeFilter": "Filter op tijd", - "appSettings_timeFilterShowAll": "Alle knooppunten tonen", - "appSettings_timeFilterShowLast": "Toon knopen van de laatste {hours} uur", + "appSettings_timeFilterShowAll": "Alle nodes tonen", + "appSettings_timeFilterShowLast": "Toon nodes van de laatste {hours} uur", "@appSettings_timeFilterShowLast": { "placeholders": { "hours": { @@ -227,8 +227,8 @@ } }, "appSettings_mapTimeFilter": "Filter tijd op kaart", - "appSettings_showNodesDiscoveredWithin": "Toon knooppunten ontdekt binnen:", - "appSettings_allTime": "Alle tijd", + "appSettings_showNodesDiscoveredWithin": "Toon nodes ontdekt binnen:", + "appSettings_allTime": "Altijd", "appSettings_lastHour": "Laat uur", "appSettings_last6Hours": "laatste 6 uur", "appSettings_last24Hours": "De laatste 24 uur", @@ -266,7 +266,7 @@ } } }, - "contacts_manageRepeater": "Beheer Herhaling", + "contacts_manageRepeater": "Beheer Repeater", "contacts_roomLogin": "Ruimte Inloggen", "contacts_openChat": "Open Chat", "contacts_editGroup": "Groep bewerken", @@ -294,7 +294,7 @@ "contacts_noContactsMatchFilter": "Geen contacten matchen met uw filter", "contacts_noMembers": "Geen leden", "contacts_lastSeenNow": "Laatste keer gezien nu", - "contacts_lastSeenMinsAgo": "Laast gezien {minutes} minuten geleden", + "contacts_lastSeenMinsAgo": "Laatst gezien {minutes} minuten geleden", "@contacts_lastSeenMinsAgo": { "placeholders": { "minutes": { @@ -539,7 +539,7 @@ "chat_pathManagement": "Beheer van Paden", "chat_routingMode": "Routeerwijze", "chat_autoUseSavedPath": "Automatisch (gebruik opgeslagen pad)", - "chat_forceFloodMode": "Dwing Overstromingsmodus", + "chat_forceFloodMode": "Dwing Floodsmodus", "chat_recentAckPaths": "Recente ACK Paden (tik om te gebruiken):", "chat_pathHistoryFull": "De voorgeschiedenis is vol. Verwijder vermeldingen om er nieuwe aan toe te voegen.", "chat_hopSingular": "Hop", @@ -562,7 +562,7 @@ "chat_clearPathSubtitle": "Dwing herontdekking bij volgende verzending", "chat_pathCleared": "Pad is vrijgegeven. Volgende bericht herontdekt route.", "chat_floodModeSubtitle": "Gebruik de route-schakelaar in de app-balk", - "chat_floodModeEnabled": "Overstromingsmodus is ingeschakeld. Schakel dit uit via het route-icoon in de app-balk.", + "chat_floodModeEnabled": "Floodmodus is ingeschakeld. Schakel dit uit via het route-icoon in de app-balk.", "chat_fullPath": "Volledige Pad", "chat_pathDetailsNotAvailable": "De paddetails zijn nog niet beschikbaar. Probeer een bericht te sturen om te vernieuwen.", "chat_pathSetHops": "Pad ingesteld: {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}", @@ -583,9 +583,9 @@ "chat_path": "Pad", "chat_publicKey": "Openbare Sleutel", "chat_compressOutgoingMessages": "Verzenden van uitgaande berichten comprimeren", - "chat_floodForced": "Overstroming (gedwongen)", - "chat_directForced": "Direct (gedwongen)", - "chat_hopsForced": "{count} sprongen (gedwongen)", + "chat_floodForced": "Flood (afgedwongen)", + "chat_directForced": "Direct (afgedwongen)", + "chat_hopsForced": "{count} hops (afgedwongen)", "@chat_hopsForced": { "placeholders": { "count": { @@ -593,7 +593,7 @@ } } }, - "chat_floodAuto": "Overstroming (auto)", + "chat_floodAuto": "Flood (auto)", "chat_direct": "Direct", "chat_poiShared": "Gedeelde POI", "chat_unread": "Nieuw: {count}", @@ -605,7 +605,7 @@ } }, "map_title": "Node Map", - "map_noNodesWithLocation": "Geen knopen met locatiegegevens", + "map_noNodesWithLocation": "Geen nodes met locatiegegevens", "map_nodesNeedGps": "Nodes moeten hun GPS-coördinaten delen\nom op de kaart te verschijnen", "map_nodesCount": "Nodes: {count}", "@map_nodesCount": { @@ -624,7 +624,7 @@ } }, "map_chat": "Chat", - "map_repeater": "Herhaling", + "map_repeater": "Repeater", "map_room": "Ruimte", "map_sensor": "Sensor", "map_pinDm": "Verzenden als bericht (DM)", @@ -652,11 +652,11 @@ } }, "map_connectToShareMarkers": "Verbind met een apparaat om markers te delen", - "map_filterNodes": "Filter Knopen", - "map_nodeTypes": "Node Types", - "map_chatNodes": "Chat Nodes", - "map_repeaters": "Herhalingen", - "map_otherNodes": "Andere knooppunten", + "map_filterNodes": "Filter Nodes", + "map_nodeTypes": "Nodetypes", + "map_chatNodes": "Chatnodes", + "map_repeaters": "Repeaters", + "map_otherNodes": "Andere Nodes", "map_keyPrefix": "Prefix sleutel", "map_filterByKeyPrefix": "Filteren op sleutelvoorgemeld", "map_publicKeyPrefix": "Openbare sleutelvoorgemeld", @@ -665,7 +665,7 @@ "map_lastSeenTime": "Laatste Bekeken Tijd", "map_sharedPin": "Gedeelde pin", "map_joinRoom": "Sluit Kamer", - "map_manageRepeater": "Beheer Herhaling", + "map_manageRepeater": "Beheer Repeater", "mapCache_title": "Offline Kaarten Cache", "mapCache_selectAreaFirst": "Select een gebied om eerst in de cache op te slaan", "mapCache_noTilesToDownload": "Geen tiles te downloaden voor dit gebied.", @@ -788,18 +788,18 @@ "time_allTime": "Alle tijd", "dialog_disconnect": "Verbinden verbreken", "dialog_disconnectConfirm": "Ben je er zeker van dat je verbinding met dit apparaat wilt verbreken?", - "login_repeaterLogin": "Herhaalders Inloggen", + "login_repeaterLogin": "Inloggen Repeater", "login_roomLogin": "Ruimte Inloggen", "login_password": "Wachtwoord", "login_enterPassword": "Wachtwoord invoeren", "login_savePassword": "Wachtwoord opslaan", "login_savePasswordSubtitle": "Het wachtwoord wordt veilig op dit apparaat opgeslagen.", - "login_repeaterDescription": "Voer het wachtwoord van de herhaling in om instellingen en status te openen.", + "login_repeaterDescription": "Voer het wachtwoord van de repeater in om instellingen en status te openen.", "login_roomDescription": "Voer het wachtwoord van de kamer in om toegang te krijgen tot instellingen en status.", "login_routing": "Routing", "login_routingMode": "Routeerwijze", "login_autoUseSavedPath": "Automatisch (gebruik opgeslagen pad)", - "login_forceFloodMode": "Dwing Overstromingsmodus", + "login_forceFloodMode": "Dwing Floodmodus Af", "login_managePaths": "Padbeheer", "login_login": "Inloggen", "login_attempt": "Poging {current}/{max}", @@ -846,7 +846,7 @@ "path_labelHexPrefixes": "Pad (hex-voorkeursletters)", "path_helperMaxHops": "Maximaal 64 sprongen. Elke prefix is 2 hexadecimale tekens (1 byte)", "path_selectFromContacts": "Of select contacten:", - "path_noRepeatersFound": "Geen herhalingen of zaalservers gevonden.", + "path_noRepeatersFound": "Geen repeaters of roomservers gevonden.", "path_customPathsRequire": "Aangepaste paden vereisen tussentse overstappen die berichten kunnen doorgeven.", "path_invalidHexPrefixes": "Ongeldige hex-voorkeursletters: {prefixes}", "@path_invalidHexPrefixes": { @@ -858,20 +858,20 @@ }, "path_tooLong": "Pad is te lang. Maximaal 64 sprongen zijn toegestaan.", "path_setPath": "Stel Pad in", - "repeater_management": "Beheer Herhalingen", + "repeater_management": "Beheer Repeaters", "repeater_managementTools": "Beheerinstrumenten", "repeater_status": "Status", "repeater_statusSubtitle": "Status, statistieken en buren bekijken", "repeater_telemetry": "Telemetry", "repeater_telemetrySubtitle": "Bekijk telemetrie van sensoren en systeemgegevens", "repeater_cli": "CLI", - "repeater_cliSubtitle": "Verzend commando's naar de herhaaldere", + "repeater_cliSubtitle": "Verzend commando's naar de repeater", "repeater_settings": "Instellingen", "repeater_settingsSubtitle": "Configureer repeaterparameters", - "repeater_statusTitle": "Status herhalen", + "repeater_statusTitle": "Status repeater", "repeater_routingMode": "Routeerwijze", "repeater_autoUseSavedPath": "Automatisch (gebruik opgeslagen pad)", - "repeater_forceFloodMode": "Dwing Overloopmodus", + "repeater_forceFloodMode": "Dwing Floodmodus Af", "repeater_pathManagement": "Beheer van paden", "repeater_refresh": "Vernieuwen", "repeater_statusRequestTimeout": "Statusverzoek is uitgevallen.", @@ -883,22 +883,22 @@ } } }, - "repeater_systemInformation": "Systeem Informatie", + "repeater_systemInformation": "Systeeminformatie", "repeater_battery": "Batterij", "repeater_clockAtLogin": "Tijd (bij aanmelden)", "repeater_uptime": "Beschikbaarheid", "repeater_queueLength": "Wachttijd", - "repeater_debugFlags": "Debug Flags", - "repeater_radioStatistics": "Radio Statistieken", + "repeater_debugFlags": "Debugvlaggen", + "repeater_radioStatistics": "Radiostatistieken", "repeater_lastRssi": "Laatste RSSI", "repeater_lastSnr": "Laatste SNR", - "repeater_noiseFloor": "Ruishoordniveau", + "repeater_noiseFloor": "Ruisvloer", "repeater_txAirtime": "TX Airtime", "repeater_rxAirtime": "RX Airtime", - "repeater_packetStatistics": "Pakket Statistieken", + "repeater_packetStatistics": "Pakketstatistieken", "repeater_sent": "Verzonden", "repeater_received": "Ontvangen", - "repeater_duplicates": "Dubbele", + "repeater_duplicates": "Duplicaat", "repeater_daysHoursMinsSecs": "{days} dagen {hours} uur {minutes} minuten {seconds} seconden", "@repeater_daysHoursMinsSecs": { "placeholders": { @@ -916,7 +916,7 @@ } } }, - "repeater_packetTxTotal": "Totaal: {total}, Overstroming: {flood}, Direct: {direct}", + "repeater_packetTxTotal": "Totaal: {total}, Flood: {flood}, Direct: {direct}", "@repeater_packetTxTotal": { "placeholders": { "total": { @@ -930,7 +930,7 @@ } } }, - "repeater_packetRxTotal": "Totaal: {total}, Overstroming: {flood}, Direct: {direct}", + "repeater_packetRxTotal": "Totaal: {total}, Flood: {flood}, Direct: {direct}", "@repeater_packetRxTotal": { "placeholders": { "total": { @@ -944,7 +944,7 @@ } } }, - "repeater_duplicatesFloodDirect": "Overstroming: {flood}, Direct: {direct}", + "repeater_duplicatesFloodDirect": "Flood: {flood}, Direct: {direct}", "@repeater_duplicatesFloodDirect": { "placeholders": { "flood": { @@ -963,10 +963,10 @@ } } }, - "repeater_settingsTitle": "Herstelinstellingen", + "repeater_settingsTitle": "Repeater Instellingen", "repeater_basicSettings": "Basisinstellingen", - "repeater_repeaterName": "Herhaalnaam", - "repeater_repeaterNameHelper": "Weergave naam voor deze herhaling", + "repeater_repeaterName": "Repeaternaam", + "repeater_repeaterNameHelper": "Weergave naam voor deze repeater", "repeater_adminPassword": "Admin wachtwoord", "repeater_adminPasswordHelper": "Volledige toegangspaswoord", "repeater_guestPassword": "Wachtwoord Gast", @@ -977,7 +977,7 @@ "repeater_txPower": "TX Power", "repeater_txPowerHelper": "1-30 dBm", "repeater_bandwidth": "Bandbreedte", - "repeater_spreadingFactor": "Spreadsnelheid", + "repeater_spreadingFactor": "Spreidingsfactor", "repeater_codingRate": "Codeertarief", "repeater_locationSettings": "Locatie Instellingen", "repeater_latitude": "Breedtegraad", @@ -985,11 +985,11 @@ "repeater_longitude": "Lengtegraad", "repeater_longitudeHelper": "Graadseconden (bijv. -122.4194)", "repeater_features": "Kenmerken", - "repeater_packetForwarding": "Pakketdoorstrooming", - "repeater_packetForwardingSubtitle": "Herstel activeren om pakketten door te sturen", + "repeater_packetForwarding": "Pakketdoorvoering", + "repeater_packetForwardingSubtitle": "Repeater instellen om pakketten door te sturen", "repeater_guestAccess": "Toegang voor Gasten", "repeater_guestAccessSubtitle": "Toegestane leesbeheer toegang voor gasten.", - "repeater_privacyMode": "Privacy Mode", + "repeater_privacyMode": "Privacy Modus", "repeater_privacyModeSubtitle": "Naam/locatie verbergen in advertenties", "repeater_advertisementSettings": "Advertentie Instellingen", "repeater_localAdvertInterval": "Lokale Advertentie Interval", @@ -1001,7 +1001,7 @@ } } }, - "repeater_floodAdvertInterval": "Advertentie Interval bij overstroming", + "repeater_floodAdvertInterval": "Flood Advertentie Interval", "repeater_floodAdvertIntervalHours": "{hours} uur", "@repeater_floodAdvertIntervalHours": { "placeholders": { @@ -1012,15 +1012,15 @@ }, "repeater_encryptedAdvertInterval": "Versleutelde Advertentie Interval", "repeater_dangerZone": "Gevaarzone", - "repeater_rebootRepeater": "Herstart Herhaalder", - "repeater_rebootRepeaterSubtitle": "De herstart van het herhalerapparaat", + "repeater_rebootRepeater": "Herstart Repeater", + "repeater_rebootRepeaterSubtitle": "Herstart het Repeaterapparaat", "repeater_rebootRepeaterConfirm": "Ben je er zeker van dat je deze repeater opnieuw wilt opstarten?", "repeater_regenerateIdentityKey": "Identiteit sleutel opnieuw genereren", "repeater_regenerateIdentityKeySubtitle": "Nieuwe publieke/private sleutelpaar genereren", - "repeater_regenerateIdentityKeyConfirm": "Dit genereert een nieuwe identiteit voor de herhaling. Doorgaan?", + "repeater_regenerateIdentityKeyConfirm": "Dit genereert een nieuwe identiteit voor de repeater. Doorgaan?", "repeater_eraseFileSystem": "Verwijder Besturingssysteem", - "repeater_eraseFileSystemSubtitle": "Formateer het herhalende bestandsysteem", - "repeater_eraseFileSystemConfirm": "WAARSCHUWING: Dit zal alle gegevens op de herhaling wissen. Dit kan niet worden teruggedraaid!", + "repeater_eraseFileSystemSubtitle": "Formateer het bestandsysteem van de repeater", + "repeater_eraseFileSystemConfirm": "WAARSCHUWING: Dit zal alle gegevens op de repeater wissen. Dit kan niet worden teruggedraaid!", "repeater_eraseSerialOnly": "Verwijderen is alleen beschikbaar via de seriële console.", "repeater_commandSent": "Commando verzonden: {command}", "@repeater_commandSent": { @@ -1049,7 +1049,7 @@ } }, "repeater_refreshBasicSettings": "Basisinstellingen vernieuwen", - "repeater_refreshRadioSettings": "Radiozenders Instellingen Bijwerken", + "repeater_refreshRadioSettings": "Radiozender Instellingen Verversen", "repeater_refreshTxPower": "Nieuw laden TX-vermogen", "repeater_refreshLocationSettings": "Instellingen Locatie Vernieuwen", "repeater_refreshPacketForwarding": "Vernieuwen Pakket Doorversturing", @@ -1072,7 +1072,7 @@ } } }, - "repeater_cliTitle": "Herhaling CLI", + "repeater_cliTitle": "Repeater CLI", "repeater_debugNextCommand": "Debug Volgende Commando", "repeater_commandHelp": "Help", "repeater_clearHistory": "Verwijder Geschiedenis", @@ -1106,14 +1106,14 @@ "repeater_cliHelpClearStats": "Reset verschillende statistiek-tellers naar nul.", "repeater_cliHelpSetAf": "Stelt de luchtvaartfactor in.", "repeater_cliHelpSetTx": "Stelt LoRa zendvermogen in dBm. (om te wijzigen)", - "repeater_cliHelpSetRepeat": "Activeert of deactiveert de herhalerrol voor dit knoop.", + "repeater_cliHelpSetRepeat": "Activeert of deactiveert de repeater rol van deze node.", "repeater_cliHelpSetAllowReadOnly": "(Kamervisie) Als 'aan', dan wordt inloggen met een blanco wachtwoord toegestaan, maar kan niet naar de kamervisie Posten. (alleen lezen mogelijk).", - "repeater_cliHelpSetFloodMax": "Stelt het maximale aantal hops van een inkomend overlastpakket in (indien >= max, wordt het pakket niet doorgestuurd)", + "repeater_cliHelpSetFloodMax": "Stelt het maximale aantal hops van een inkomend floodpakket in (indien >= max, wordt het pakket niet doorgestuurd)", "repeater_cliHelpSetIntThresh": "Stelt de Interferentiewaarde (in dB) in. Standaardwaarde is 14. Stel in op 0 om het detecteren van kanaalinterferentie uit te schakelen.", "repeater_cliHelpSetAgcResetInterval": "Stelt het interval in om de Auto Gain Controller te resetten. Stel in op 0 om dit uit te schakelen.", - "repeater_cliHelpSetMultiAcks": "Activeert of deactiveert de functie 'dubbele ACKs'.", + "repeater_cliHelpSetMultiAcks": "Activeert of deactiveert de functie 'duplicate ACKs'.", "repeater_cliHelpSetAdvertInterval": "Stelt het timerinterval in minuten in om een lokale (zero-hop) advertentiepakket te versturen. Stel in op 0 om uit te schakelen.", - "repeater_cliHelpSetFloodAdvertInterval": "Stelt het timerinterval in uren in om een overstromingsadvertentiepakket te versturen. Stel in op 0 om dit uit te schakelen.", + "repeater_cliHelpSetFloodAdvertInterval": "Stelt het timerinterval in uren in om een floodadvertentiepakket te versturen. Stel in op 0 om dit uit te schakelen.", "repeater_cliHelpSetGuestPassword": "Stelt/past de gastenwacht aan of wijzigt deze. (voor herstelcontacten kunnen gastelogins de \"Get Stats\" verzoek verzenden)", "repeater_cliHelpSetName": "Stelt de advertentietitel in.", "repeater_cliHelpSetLat": "Stelt de breedtegraad van de advertentiekaart in. (graadrijssysteem)", @@ -1136,7 +1136,7 @@ "repeater_cliHelpLogErase": "Verwijdert de pakketlogs uit het bestandssysteem.", "repeater_cliHelpNeighbors": "Toont een lijst met andere repeater nodes die via nul-hop advertenties zijn gehoord. Elke regel is id-prefix-hex:timestamp:snr-times-4", "repeater_cliHelpNeighborRemove": "Verwijdert de eerste overeenkomende vermelding (via pubkey prefix (hex)) uit de lijst van buren.", - "repeater_cliHelpRegion": "(reeks alleen) Lijst alle gedefinieerde regio's en huidige overstromingsrechten.", + "repeater_cliHelpRegion": "(Alleen Serieel) Lijst alle gedefinieerde regio's en huidige floodrechten.", "repeater_cliHelpRegionLoad": "LET OP: dit is een speciale multi-command aanroep. Elke volgende opdracht is een regiortaak (uitgelijnd met spaties om de ouderhiërarchie aan te duiden, met minimaal één spatie). Beëindigd door een lege regel/opdracht te sturen.", "repeater_cliHelpRegionGet": "Zoekt naar regio met gegeven naam voorvoegsel (of \"\" voor de globale scope). Antwoordt met \"-> regio-naam (ouder-naam) 'F'\"", "repeater_cliHelpRegionPut": "Voegt of wijzigt een regio-definitie met de gegeven naam.", @@ -1148,8 +1148,8 @@ "repeater_cliHelpRegionSave": "Bewaar de lijst/kaart van de regio's naar de opslag.", "repeater_cliHelpGps": "Geeft de status van de GPS. Wanneer de GPS uit staat, antwoordt het alleen met \"uit\", als het aan staat, antwoordt het met \"aan\", status, fix, sat count.", "repeater_cliHelpGpsOnOff": "Schakel de GPS-standby aan/uit.", - "repeater_cliHelpGpsSync": "Synchroniseer knooptime met GPS-klok.", - "repeater_cliHelpGpsSetLoc": "Stel de positie van het knoop vast naar GPS-coördinaten en sla de voorkeuren op.", + "repeater_cliHelpGpsSync": "Synchroniseer node met GPS-klok.", + "repeater_cliHelpGpsSetLoc": "Stel de positie van de node vast als GPS-coördinaten en sla de voorkeuren op.", "repeater_cliHelpGpsAdvert": "Geeft de locatie advertentieconfiguratie van de node:\n- none: locatie niet in advertenties opnemen\n- share: gps locatie delen (van SensorManager)\n- prefs: locatie adverteren die in de voorkeuren is opgeslagen", "repeater_cliHelpGpsAdvertSet": "Stelt advertentie locatie configuratie in.", "repeater_commandsListTitle": "Commandenlijst", @@ -1158,8 +1158,8 @@ "repeater_settingsCategory": "Instellingen", "repeater_bridge": "Bruggen", "repeater_logging": "Logging", - "repeater_neighborsRepeaterOnly": "Buren (Alleen herhaald)", - "repeater_regionManagementRepeaterOnly": "Regiobeheer (Alleen voor Repeater)", + "repeater_neighborsRepeaterOnly": "Buren (Alleen repeaters)", + "repeater_regionManagementRepeaterOnly": "Regiobeheer (Alleen Repeater)", "repeater_regionNote": "Regio-commando's zijn geïntroduceerd om regio-definities en permissies te beheren.", "repeater_gpsManagement": "Beheer GPS", "repeater_gpsNote": "De GPS-commando is geïntroduceerd om locatiegerelateerde onderwerpen te beheren.", @@ -1228,12 +1228,12 @@ "channelPath_title": "Pakketpad", "channelPath_viewMap": "Kaart bekijken", "channelPath_otherObservedPaths": "Overige Waargenomen Paden", - "channelPath_repeaterHops": "Herhalingstapjes", + "channelPath_repeaterHops": "Repeater Hops", "channelPath_noHopDetails": "De details van de pakket zijn niet verstrekt.", "channelPath_messageDetails": "Details Bericht", "channelPath_senderLabel": "Afzender", "channelPath_timeLabel": "Tijd", - "channelPath_repeatsLabel": "Herhalen", + "channelPath_repeatsLabel": "Repeats", "channelPath_pathLabel": "Pad {index}", "channelPath_observedLabel": "Waargenomen", "channelPath_observedPathTitle": "Waargenomen pad {index} • {hops}", @@ -1271,7 +1271,7 @@ } }, "channelPath_unknownPath": "Onbekend", - "channelPath_floodPath": "Overstroming", + "channelPath_floodPath": "Flood", "channelPath_directPath": "Direct", "channelPath_observedZeroOf": "0 van {total} sprongen", "@channelPath_observedZeroOf": { @@ -1293,7 +1293,7 @@ } }, "channelPath_mapTitle": "Padkaart", - "channelPath_noRepeaterLocations": "Geen herhaler locaties beschikbaar voor deze route.", + "channelPath_noRepeaterLocations": "Geen repeaters beschikbaar voor deze route.", "channelPath_primaryPath": "Pad {index} (Hoofdtype)", "@channelPath_primaryPath": { "placeholders": { @@ -1323,7 +1323,7 @@ } }, "channelPath_noHopDetailsAvailable": "Geen details beschikbaar voor dit pakket.", - "channelPath_unknownRepeater": "Onbekend Herhaalaar", + "channelPath_unknownRepeater": "Onbekend Repeater", "listFilter_tooltip": "Filteren en sorteren", "listFilter_sortBy": "Sorteren door", "listFilter_latestMessages": "Recente berichten", @@ -1332,8 +1332,8 @@ "listFilter_filters": "Filters", "listFilter_all": "Alles", "listFilter_users": "Gebruikers", - "listFilter_repeaters": "Herhalingen", - "listFilter_roomServers": "Kamervirtualisatie", + "listFilter_repeaters": "Repeaters", + "listFilter_roomServers": "Roomservers", "listFilter_unreadOnly": "Alleen ongelezen", "listFilter_newGroup": "Nieuwe groep" } From e36f6b7eb9b4e91e61e526bca576e161abb565a4 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Wed, 14 Jan 2026 19:33:07 -0800 Subject: [PATCH 04/16] changed contects list to show public keys of contect --- lib/screens/contacts_screen.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index 3bae3dd..b9ceb84 100644 --- a/lib/screens/contacts_screen.dart +++ b/lib/screens/contacts_screen.dart @@ -759,13 +759,14 @@ class _ContactTile extends StatelessWidget { @override Widget build(BuildContext context) { + final shotPublicKey = "<${contact.publicKeyHex.substring(0, 8)}...${contact.publicKeyHex.substring(contact.publicKeyHex.length - 8)}>"; return ListTile( leading: CircleAvatar( backgroundColor: _getTypeColor(contact.type), child: _buildContactAvatar(contact), ), title: Text(contact.name), - subtitle: Text('${contact.typeLabel} • ${contact.pathLabel}'), + subtitle: Text('${contact.typeLabel} • ${contact.pathLabel} $shotPublicKey'), trailing: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, From dd1a73c247fbb6784d27817978d78887fea8a8f3 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Wed, 14 Jan 2026 19:34:41 -0800 Subject: [PATCH 05/16] Repeater hub now show public key at the top --- lib/screens/repeater_hub_screen.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/screens/repeater_hub_screen.dart b/lib/screens/repeater_hub_screen.dart index 88a58fa..b6e6d3f 100644 --- a/lib/screens/repeater_hub_screen.dart +++ b/lib/screens/repeater_hub_screen.dart @@ -56,6 +56,11 @@ class RepeaterHubScreen extends StatelessWidget { style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), + Text( + '<${repeater.publicKeyHex.substring(0, 8)}...${repeater.publicKeyHex.substring(repeater.publicKeyHex.length - 8)}>', + style: TextStyle(fontSize: 14, color: Colors.grey[600]), + ), + const SizedBox(height: 8), Text( repeater.pathLabel, style: TextStyle(fontSize: 14, color: Colors.grey[600]), From a8f387b0da71f4da2ca12c71b77cf0cb0fbe7074 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Wed, 14 Jan 2026 19:28:08 -0800 Subject: [PATCH 06/16] Fix map centering weirdly When nodes or markers are outside of the main area of interest. --- lib/screens/map_screen.dart | 66 +++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index bdc96c8..1a6b527 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; +import 'dart:math'; import '../connector/meshcore_connector.dart'; import '../l10n/l10n.dart'; @@ -70,6 +71,25 @@ class _MapScreenState extends State { }); } + double _standardDeviation(List values) { + if (values.length <= 1) { + return 0.0; + } + + final mean = values.reduce((a, b) => a + b) / values.length; + + double sumSquaredDiff = 0.0; + for (final value in values) { + final diff = value - mean; + sumSquaredDiff += diff * diff; + } + + // Sample standard deviation (n-1) — most appropriate here + final variance = sumSquaredDiff / (values.length - 1); + + return sqrt(variance); + } + @override Widget build(BuildContext context) { return Consumer2( @@ -116,19 +136,37 @@ class _MapScreenState extends State { _isSelectingPoi || highlightPosition != null; if (contactsWithLocation.isNotEmpty || sharedMarkers.isNotEmpty) { - double avgLat = contactsWithLocation - .map((c) => c.latitude!) - .fold(0, (sum, lat) => sum + lat); - double avgLon = contactsWithLocation - .map((c) => c.longitude!) - .fold(0, (sum, lon) => sum + lon); - for (final marker in sharedMarkers) { - avgLat += marker.position.latitude; - avgLon += marker.position.longitude; - } - final total = contactsWithLocation.length + sharedMarkers.length; - if (total > 0) { - center = LatLng(avgLat / total, avgLon / total); + double avgLat = 0.0; + double avgLon = 0.0; + final allPoints = [ + ...contactsWithLocation.map((c) => LatLng(c.latitude!, c.longitude!)), + ...sharedMarkers.map((m) => m.position), + ]; + if (allPoints.length >= 3) { + final latValues = allPoints.map((p) => p.latitude).toList(); + final lonValues = allPoints.map((p) => p.longitude).toList(); + + final latStdDev = _standardDeviation(latValues); + final lonStdDev = _standardDeviation(lonValues); + final filteredLatValues = latValues + .where((lat) => (lat - (latValues.reduce((a, b) => a + b) / latValues.length)).abs() <= latStdDev * 2) + .toList(); + final filteredLonValues = lonValues + .where((lon) => (lon - (lonValues.reduce((a, b) => a + b) / lonValues.length)).abs() <= lonStdDev * 2) + .toList(); + center = LatLng( + filteredLatValues.reduce((a, b) => a + b) / filteredLatValues.length, + filteredLonValues.reduce((a, b) => a + b) / filteredLonValues.length, + ); + } else { + for (final point in allPoints) { + avgLat += point.latitude; + avgLon += point.longitude; + } + center = LatLng( + avgLat / allPoints.length, + avgLon / allPoints.length, + ); } } if (highlightPosition != null) { @@ -169,7 +207,7 @@ class _MapScreenState extends State { mapController: _mapController, options: MapOptions( initialCenter: center, - initialZoom: 13.0, + initialZoom: 10.0, minZoom: 2.0, maxZoom: 18.0, onTap: (_, latLng) { From 7c33647119f6b19629185ce5a5f753e27ff9fbcd Mon Sep 17 00:00:00 2001 From: zjs81 Date: Thu, 15 Jan 2026 18:39:48 -0700 Subject: [PATCH 07/16] Add key.properties support for signing configuration in build.gradle.kts --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 97cafdb..b918113 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,7 @@ secrets.dart **/android/local.properties **/android/.externalNativeBuild/ *.jks +key.properties keystore.properties # Generated files From 614f3d460155e544895eda7c9d1552f154c175f8 Mon Sep 17 00:00:00 2001 From: zjs81 Date: Thu, 15 Jan 2026 18:41:06 -0700 Subject: [PATCH 08/16] Add signing configuration support in build.gradle.kts --- android/app/build.gradle.kts | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 7c6e251..740451b 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.Properties + plugins { id("com.android.application") id("kotlin-android") @@ -5,6 +7,12 @@ plugins { id("dev.flutter.flutter-gradle-plugin") } +val keystoreProperties = Properties() +val keystorePropertiesFile = rootProject.file("key.properties") +if (keystorePropertiesFile.exists()) { + keystorePropertiesFile.inputStream().use { keystoreProperties.load(it) } +} + android { namespace = "com.meshcore.meshcore_open" compileSdk = flutter.compileSdkVersion @@ -40,11 +48,25 @@ android { // } } + signingConfigs { + create("release") { + val storeFilePath = keystoreProperties["storeFile"] as String? + if (storeFilePath != null) { + storeFile = file(storeFilePath) + storePassword = keystoreProperties["storePassword"] as String? + keyAlias = keystoreProperties["keyAlias"] as String? + keyPassword = keystoreProperties["keyPassword"] as String? + } + } + } + buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("debug") + signingConfig = if (keystorePropertiesFile.exists()) { + signingConfigs.getByName("release") + } else { + signingConfigs.getByName("debug") + } } } From a6b2756d0df9e83bb47f20cf331735f99b07edbe Mon Sep 17 00:00:00 2001 From: zjs81 Date: Thu, 15 Jan 2026 19:11:13 -0700 Subject: [PATCH 09/16] Ran flutter format on the file --- lib/screens/map_screen.dart | 323 ++++++++++++++++++++++-------------- 1 file changed, 202 insertions(+), 121 deletions(-) diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 1a6b527..42ecc76 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -100,10 +100,12 @@ class _MapScreenState extends State { final highlightPosition = widget.highlightPosition; final sharedMarkers = settings.mapShowMarkers ? _collectSharedMarkers(connector) - .where((marker) => - !_hiddenMarkerIds.contains(marker.id) && - !_removedMarkerIds.contains(marker.id)) - .toList() + .where( + (marker) => + !_hiddenMarkerIds.contains(marker.id) && + !_removedMarkerIds.contains(marker.id), + ) + .toList() : <_SharedMarker>[]; // Filter by time @@ -111,16 +113,18 @@ class _MapScreenState extends State { final filteredByTime = settings.mapTimeFilterHours == 0 ? contacts : contacts.where((c) { - final hoursSinceLastSeen = - now.difference(c.lastSeen).inHours; + final hoursSinceLastSeen = now.difference(c.lastSeen).inHours; return hoursSinceLastSeen <= settings.mapTimeFilterHours; }).toList(); // Filter by key prefix final keyPrefix = settings.mapKeyPrefix.trim(); - final filteredByKeyPrefix = (settings.mapKeyPrefixEnabled && keyPrefix.isNotEmpty) + final filteredByKeyPrefix = + (settings.mapKeyPrefixEnabled && keyPrefix.isNotEmpty) ? filteredByTime.where((c) { - return c.publicKeyHex.toLowerCase().startsWith(keyPrefix.toLowerCase()); + return c.publicKeyHex.toLowerCase().startsWith( + keyPrefix.toLowerCase(), + ); }).toList() : filteredByTime; @@ -131,7 +135,8 @@ class _MapScreenState extends State { // Calculate center of all nodes, or default to (0, 0) LatLng center = const LatLng(0, 0); - final hasMapContent = contactsWithLocation.isNotEmpty || + final hasMapContent = + contactsWithLocation.isNotEmpty || sharedMarkers.isNotEmpty || _isSelectingPoi || highlightPosition != null; @@ -139,24 +144,42 @@ class _MapScreenState extends State { double avgLat = 0.0; double avgLon = 0.0; final allPoints = [ - ...contactsWithLocation.map((c) => LatLng(c.latitude!, c.longitude!)), + ...contactsWithLocation.map( + (c) => LatLng(c.latitude!, c.longitude!), + ), ...sharedMarkers.map((m) => m.position), ]; if (allPoints.length >= 3) { final latValues = allPoints.map((p) => p.latitude).toList(); final lonValues = allPoints.map((p) => p.longitude).toList(); - + final latStdDev = _standardDeviation(latValues); final lonStdDev = _standardDeviation(lonValues); final filteredLatValues = latValues - .where((lat) => (lat - (latValues.reduce((a, b) => a + b) / latValues.length)).abs() <= latStdDev * 2) + .where( + (lat) => + (lat - + (latValues.reduce((a, b) => a + b) / + latValues.length)) + .abs() <= + latStdDev * 2, + ) .toList(); final filteredLonValues = lonValues - .where((lon) => (lon - (lonValues.reduce((a, b) => a + b) / lonValues.length)).abs() <= lonStdDev * 2) + .where( + (lon) => + (lon - + (lonValues.reduce((a, b) => a + b) / + lonValues.length)) + .abs() <= + lonStdDev * 2, + ) .toList(); center = LatLng( - filteredLatValues.reduce((a, b) => a + b) / filteredLatValues.length, - filteredLonValues.reduce((a, b) => a + b) / filteredLonValues.length, + filteredLatValues.reduce((a, b) => a + b) / + filteredLatValues.length, + filteredLonValues.reduce((a, b) => a + b) / + filteredLonValues.length, ); } else { for (final point in allPoints) { @@ -194,7 +217,9 @@ class _MapScreenState extends State { tooltip: context.l10n.common_settings, onPressed: () => Navigator.push( context, - MaterialPageRoute(builder: (context) => const SettingsScreen()), + MaterialPageRoute( + builder: (context) => const SettingsScreen(), + ), ), ), ], @@ -272,14 +297,18 @@ class _MapScreenState extends State { ), ], ), - _buildLegend(contactsWithLocation.length, sharedMarkers.length), + _buildLegend( + contactsWithLocation.length, + sharedMarkers.length, + ), ], ), bottomNavigationBar: SafeArea( top: false, child: QuickSwitchBar( selectedIndex: 2, - onDestinationSelected: (index) => _handleQuickSwitch(index, context), + onDestinationSelected: (index) => + _handleQuickSwitch(index, context), ), ), floatingActionButton: FloatingActionButton( @@ -297,27 +326,17 @@ class _MapScreenState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - Icons.location_off, - size: 64, - color: Colors.grey[400], - ), + Icon(Icons.location_off, size: 64, color: Colors.grey[400]), const SizedBox(height: 16), Text( context.l10n.map_noNodesWithLocation, - style: TextStyle( - fontSize: 18, - color: Colors.grey[600], - ), + style: TextStyle(fontSize: 18, color: Colors.grey[600]), ), const SizedBox(height: 8), Text( context.l10n.map_nodesNeedGps, textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - color: Colors.grey[500], - ), + style: TextStyle(fontSize: 14, color: Colors.grey[500]), ), ], ), @@ -331,7 +350,8 @@ class _MapScreenState extends State { if (!contact.hasLocation) continue; // Apply node type filters - if (contact.type == advTypeRepeater && !settings.mapShowRepeaters) continue; + if (contact.type == advTypeRepeater && !settings.mapShowRepeaters) + continue; if (contact.type == advTypeChat && !settings.mapShowChatNodes) continue; if (contact.type != advTypeChat && contact.type != advTypeRepeater && @@ -434,13 +454,37 @@ class _MapScreenState extends State { ), ), const SizedBox(height: 8), - _buildLegendItem(Icons.person, context.l10n.map_chat, Colors.blue), - _buildLegendItem(Icons.router, context.l10n.map_repeater, Colors.green), - _buildLegendItem(Icons.meeting_room, context.l10n.map_room, Colors.purple), - _buildLegendItem(Icons.sensors, context.l10n.map_sensor, Colors.orange), + _buildLegendItem( + Icons.person, + context.l10n.map_chat, + Colors.blue, + ), + _buildLegendItem( + Icons.router, + context.l10n.map_repeater, + Colors.green, + ), + _buildLegendItem( + Icons.meeting_room, + context.l10n.map_room, + Colors.purple, + ), + _buildLegendItem( + Icons.sensors, + context.l10n.map_sensor, + Colors.orange, + ), _buildLegendItem(Icons.flag, context.l10n.map_pinDm, Colors.blue), - _buildLegendItem(Icons.flag, context.l10n.map_pinPrivate, Colors.purple), - _buildLegendItem(Icons.flag, context.l10n.map_pinPublic, Colors.orange), + _buildLegendItem( + Icons.flag, + context.l10n.map_pinPrivate, + Colors.purple, + ), + _buildLegendItem( + Icons.flag, + context.l10n.map_pinPublic, + Colors.orange, + ), ], ), ), @@ -456,10 +500,7 @@ class _MapScreenState extends State { children: [ Icon(icon, size: 16, color: color), const SizedBox(width: 8), - Text( - label, - style: const TextStyle(fontSize: 12), - ), + Text(label, style: const TextStyle(fontSize: 12)), ], ), ); @@ -513,7 +554,9 @@ class _MapScreenState extends State { label: payload.label, flags: payload.flags, fromName: message.senderName, - sourceLabel: channel.name.isEmpty ? 'Channel ${channel.index}' : channel.name, + sourceLabel: channel.name.isEmpty + ? 'Channel ${channel.index}' + : channel.name, isChannel: true, isPublicChannel: isPublic, ), @@ -579,11 +622,7 @@ class _MapScreenState extends State { ), ], ), - child: const Icon( - Icons.flag, - color: Colors.white, - size: 20, - ), + child: const Icon(Icons.flag, color: Colors.white, size: 20), ), ], ), @@ -601,10 +640,8 @@ class _MapScreenState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => RepeaterHubScreen( - repeater: repeater, - password: password, - ), + builder: (context) => + RepeaterHubScreen(repeater: repeater, password: password), ), ); }, @@ -622,9 +659,7 @@ class _MapScreenState extends State { context.read().markContactRead(room.publicKeyHex); Navigator.push( context, - MaterialPageRoute( - builder: (context) => ChatScreen(contact: room), - ), + MaterialPageRoute(builder: (context) => ChatScreen(contact: room)), ); }, ), @@ -651,9 +686,14 @@ class _MapScreenState extends State { children: [ _buildInfoRow('Type', contact.typeLabel), _buildInfoRow('Path', contact.pathLabel), - _buildInfoRow('Location', - '${contact.latitude!.toStringAsFixed(6)}, ${contact.longitude!.toStringAsFixed(6)}'), - _buildInfoRow(context.l10n.map_lastSeen, _formatLastSeen(contact.lastSeen)), + _buildInfoRow( + 'Location', + '${contact.latitude!.toStringAsFixed(6)}, ${contact.longitude!.toStringAsFixed(6)}', + ), + _buildInfoRow( + context.l10n.map_lastSeen, + _formatLastSeen(contact.lastSeen), + ), _buildInfoRow('Public Key', contact.publicKeyHex), ], ), @@ -662,7 +702,8 @@ class _MapScreenState extends State { onPressed: () => Navigator.pop(dialogContext), child: Text(context.l10n.common_close), ), - if (contact.type == advTypeChat) // Only show chat button for chat nodes + if (contact.type == + advTypeChat) // Only show chat button for chat nodes TextButton( onPressed: () { Navigator.pop(dialogContext); @@ -675,22 +716,22 @@ class _MapScreenState extends State { }, child: Text(context.l10n.contacts_openChat), ), - if (contact.type == advTypeRepeater) - TextButton( - onPressed: () { - Navigator.pop(dialogContext); - _showRepeaterLogin(context, contact); - }, - child: Text(context.l10n.map_manageRepeater), - ), - if (contact.type == advTypeRoom) - TextButton( - onPressed: () { - Navigator.pop(dialogContext); - _showRoomLogin(context, contact); - }, - child: Text(context.l10n.map_joinRoom), - ), + if (contact.type == advTypeRepeater) + TextButton( + onPressed: () { + Navigator.pop(dialogContext); + _showRepeaterLogin(context, contact); + }, + child: Text(context.l10n.map_manageRepeater), + ), + if (contact.type == advTypeRoom) + TextButton( + onPressed: () { + Navigator.pop(dialogContext); + _showRoomLogin(context, contact); + }, + child: Text(context.l10n.map_joinRoom), + ), ], ), ); @@ -702,17 +743,13 @@ class _MapScreenState extends State { case 0: Navigator.pushReplacement( context, - buildQuickSwitchRoute( - const ContactsScreen(hideBackButton: true), - ), + buildQuickSwitchRoute(const ContactsScreen(hideBackButton: true)), ); break; case 1: Navigator.pushReplacement( context, - buildQuickSwitchRoute( - const ChannelsScreen(hideBackButton: true), - ), + buildQuickSwitchRoute(const ChannelsScreen(hideBackButton: true)), ); break; } @@ -760,7 +797,8 @@ class _MapScreenState extends State { 'Location', '${marker.position.latitude.toStringAsFixed(6)}, ${marker.position.longitude.toStringAsFixed(6)}', ), - if (marker.flags.isNotEmpty) _buildInfoRow(context.l10n.map_flags, marker.flags), + if (marker.flags.isNotEmpty) + _buildInfoRow(context.l10n.map_flags, marker.flags), ], ), actions: [ @@ -810,10 +848,7 @@ class _MapScreenState extends State { ), ), const SizedBox(height: 2), - Text( - value, - style: const TextStyle(fontSize: 14), - ), + Text(value, style: const TextStyle(fontSize: 14)), ], ), ); @@ -898,7 +933,10 @@ class _MapScreenState extends State { ); } - Future _promptForLabel(BuildContext context, String defaultLabel) async { + Future _promptForLabel( + BuildContext context, + String defaultLabel, + ) async { final controller = TextEditingController(text: defaultLabel); return showDialog( context: context, @@ -919,7 +957,10 @@ class _MapScreenState extends State { TextButton( onPressed: () { final label = controller.text.trim().replaceAll('|', '/'); - Navigator.pop(dialogContext, label.isEmpty ? defaultLabel : label); + Navigator.pop( + dialogContext, + label.isEmpty ? defaultLabel : label, + ); }, child: Text(context.l10n.common_continue), ), @@ -951,8 +992,11 @@ class _MapScreenState extends State { return Consumer( builder: (consumerContext, liveConnector, child) { final allContacts = liveConnector.contacts - .where((contact) => - contact.type != advTypeRepeater && contact.type != advTypeRoom) + .where( + (contact) => + contact.type != advTypeRepeater && + contact.type != advTypeRoom, + ) .toList(); return SafeArea( child: SingleChildScrollView( @@ -962,7 +1006,10 @@ class _MapScreenState extends State { children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), - child: Text(context.l10n.map_sendToContact, style: const TextStyle(fontWeight: FontWeight.bold)), + child: Text( + context.l10n.map_sendToContact, + style: const TextStyle(fontWeight: FontWeight.bold), + ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 4, 16, 8), @@ -973,7 +1020,10 @@ class _MapScreenState extends State { border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), ), onChanged: (value) { setSheetState(() { @@ -983,50 +1033,73 @@ class _MapScreenState extends State { ), ), ...allContacts - .where((contact) => - query.isEmpty || matchesContactQuery(contact, query)) + .where( + (contact) => + query.isEmpty || + matchesContactQuery(contact, query), + ) .map((contact) { - return ListTile( - leading: const Icon(Icons.person), - title: Text(contact.name), - onTap: () { - Navigator.pop(sheetContext); - liveConnector.sendMessage(contact, markerText); - }, - ); - }), + return ListTile( + leading: const Icon(Icons.person), + title: Text(contact.name), + onTap: () { + Navigator.pop(sheetContext); + liveConnector.sendMessage(contact, markerText); + }, + ); + }), Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), - child: Text(context.l10n.map_sendToChannel, style: const TextStyle(fontWeight: FontWeight.bold)), + child: Text( + context.l10n.map_sendToChannel, + style: const TextStyle(fontWeight: FontWeight.bold), + ), ), if (liveConnector.isLoadingChannels) const Padding( - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), child: LinearProgressIndicator(), ) - else if (liveConnector.channels.where((c) => !c.isEmpty).isEmpty) + else if (liveConnector.channels + .where((c) => !c.isEmpty) + .isEmpty) Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), child: Text(context.l10n.map_noChannelsAvailable), ) else - ...liveConnector.channels.where((c) => !c.isEmpty).map((channel) { + ...liveConnector.channels.where((c) => !c.isEmpty).map(( + channel, + ) { final isPublic = _isPublicChannel(channel); - final label = channel.name.isEmpty ? 'Channel ${channel.index}' : channel.name; + final label = channel.name.isEmpty + ? 'Channel ${channel.index}' + : channel.name; return ListTile( leading: Icon( isPublic ? Icons.public : Icons.tag, color: isPublic ? Colors.orange : Colors.blue, ), title: Text(label), - subtitle: isPublic ? Text(context.l10n.channels_publicChannel) : null, + subtitle: isPublic + ? Text(context.l10n.channels_publicChannel) + : null, onTap: () async { Navigator.pop(sheetContext); final canSend = isPublic ? await _confirmPublicShare(context, label) : true; if (canSend) { - liveConnector.sendChannelMessage(channel, markerText); + liveConnector.sendChannelMessage( + channel, + markerText, + ); } }, ); @@ -1046,12 +1119,17 @@ class _MapScreenState extends State { return channel.isPublicChannel; } - Future _confirmPublicShare(BuildContext context, String channelLabel) async { + Future _confirmPublicShare( + BuildContext context, + String channelLabel, + ) async { final result = await showDialog( context: context, builder: (dialogContext) => AlertDialog( title: Text(context.l10n.map_publicLocationShare), - content: Text(context.l10n.map_publicLocationShareConfirm(channelLabel)), + content: Text( + context.l10n.map_publicLocationShareConfirm(channelLabel), + ), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext, false), @@ -1067,7 +1145,10 @@ class _MapScreenState extends State { return result ?? false; } - void _showFilterDialog(BuildContext context, AppSettingsService settingsService) { + void _showFilterDialog( + BuildContext context, + AppSettingsService settingsService, + ) { showDialog( context: context, builder: (dialogContext) => AlertDialog( @@ -1171,10 +1252,7 @@ class _MapScreenState extends State { const SizedBox(height: 8), Text( _getTimeFilterLabel(settings.mapTimeFilterHours), - style: TextStyle( - fontSize: 14, - color: Colors.grey[700], - ), + style: TextStyle(fontSize: 14, color: Colors.grey[700]), ), Slider( value: _hoursToSliderValue(settings.mapTimeFilterHours), @@ -1214,11 +1292,14 @@ class _MapScreenState extends State { if (hours <= 24) { return (hours / 24) * 40; - } else if (hours <= 168) { // 7 days + } else if (hours <= 168) { + // 7 days return 40 + ((hours - 24) / (168 - 24)) * 20; - } else if (hours <= 720) { // 30 days + } else if (hours <= 720) { + // 30 days return 60 + ((hours - 168) / (720 - 168)) * 20; - } else if (hours <= 4380) { // 6 months + } else if (hours <= 4380) { + // 6 months return 80 + ((hours - 720) / (4380 - 720)) * 19; } else { return 100; From 7cc7183e0cee1d4747719ef98297c566964f3eea Mon Sep 17 00:00:00 2001 From: zjs81 Date: Thu, 15 Jan 2026 19:15:42 -0700 Subject: [PATCH 10/16] Refactor map initialization and zoom calculation logic in MapScreen --- lib/screens/map_screen.dart | 94 ++++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 28 deletions(-) diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 42ecc76..12e4470 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -1,8 +1,9 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; -import 'dart:math'; import '../connector/meshcore_connector.dart'; import '../l10n/l10n.dart'; @@ -48,6 +49,8 @@ class _MapScreenState extends State { final Set _hiddenMarkerIds = {}; Set _removedMarkerIds = {}; bool _isSelectingPoi = false; + bool _hasInitializedMap = false; + bool _removedMarkersLoaded = false; @override void initState() { @@ -68,6 +71,7 @@ class _MapScreenState extends State { if (!mounted) return; setState(() { _removedMarkerIds = ids; + _removedMarkersLoaded = true; }); } @@ -90,6 +94,16 @@ class _MapScreenState extends State { return sqrt(variance); } + // Calculate zoom level based on the spread of points (std deviation in degrees) + double _zoomFromStdDev(double latStdDev, double lonStdDev) { + final maxSpread = max(latStdDev, lonStdDev); + if (maxSpread <= 0) return 13.0; + // Approzimate: each zoom level halves the visible area + // ~0.01 degrees spread -> zoom 13, ~0.1 -> zoom 10, ~1.0 -> zoom 7 + final zoom = 10.0 - log(maxSpread * 10 + 1) / ln10 * 3; + return zoom.clamp(4.0, 15.0); + } + @override Widget build(BuildContext context) { return Consumer2( @@ -133,16 +147,15 @@ class _MapScreenState extends State { .where((c) => c.hasLocation) .toList(); - // Calculate center of all nodes, or default to (0, 0) + // Calculate center and zoom of all nodes, or default to (0, 0) LatLng center = const LatLng(0, 0); + double initialZoom = 10.0; final hasMapContent = contactsWithLocation.isNotEmpty || sharedMarkers.isNotEmpty || _isSelectingPoi || highlightPosition != null; if (contactsWithLocation.isNotEmpty || sharedMarkers.isNotEmpty) { - double avgLat = 0.0; - double avgLon = 0.0; final allPoints = [ ...contactsWithLocation.map( (c) => LatLng(c.latitude!, c.longitude!), @@ -153,35 +166,48 @@ class _MapScreenState extends State { final latValues = allPoints.map((p) => p.latitude).toList(); final lonValues = allPoints.map((p) => p.longitude).toList(); + final meanLat = + latValues.reduce((a, b) => a + b) / latValues.length; + final meanLon = + lonValues.reduce((a, b) => a + b) / lonValues.length; final latStdDev = _standardDeviation(latValues); final lonStdDev = _standardDeviation(lonValues); - final filteredLatValues = latValues + + final filteredPoints = allPoints .where( - (lat) => - (lat - - (latValues.reduce((a, b) => a + b) / - latValues.length)) - .abs() <= - latStdDev * 2, + (p) => + (p.latitude - meanLat).abs() <= latStdDev * 2 && + (p.longitude - meanLon).abs() <= lonStdDev * 2, ) .toList(); - final filteredLonValues = lonValues - .where( - (lon) => - (lon - - (lonValues.reduce((a, b) => a + b) / - lonValues.length)) - .abs() <= - lonStdDev * 2, - ) - .toList(); - center = LatLng( - filteredLatValues.reduce((a, b) => a + b) / - filteredLatValues.length, - filteredLonValues.reduce((a, b) => a + b) / - filteredLonValues.length, - ); + + if (filteredPoints.isNotEmpty) { + final filteredLatValues = filteredPoints + .map((p) => p.latitude) + .toList(); + final filteredLonValues = filteredPoints + .map((p) => p.longitude) + .toList(); + final avgLat = filteredLatValues.reduce((a, b) => a + b); + final avgLon = filteredLonValues.reduce((a, b) => a + b); + center = LatLng( + avgLat / filteredPoints.length, + avgLon / filteredPoints.length, + ); + // Use std deviation of filtered points for zoom + final filteredLatStdDev = _standardDeviation(filteredLatValues); + final filteredLonStdDev = _standardDeviation(filteredLonValues); + initialZoom = _zoomFromStdDev( + filteredLatStdDev, + filteredLonStdDev, + ); + } else { + center = LatLng(meanLat, meanLon); + initialZoom = _zoomFromStdDev(latStdDev, lonStdDev); + } } else { + double avgLat = 0.0; + double avgLon = 0.0; for (final point in allPoints) { avgLat += point.latitude; avgLon += point.longitude; @@ -190,10 +216,22 @@ class _MapScreenState extends State { avgLat / allPoints.length, avgLon / allPoints.length, ); + initialZoom = 12.0; } } if (highlightPosition != null) { center = highlightPosition; + initialZoom = widget.highlightZoom; + } + + // Re center map after removed markers have loaded + if (!_hasInitializedMap && _removedMarkersLoaded && hasMapContent) { + _hasInitializedMap = true; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _mapController.move(center, initialZoom); + } + }); } final allowBack = !connector.isConnected; @@ -232,7 +270,7 @@ class _MapScreenState extends State { mapController: _mapController, options: MapOptions( initialCenter: center, - initialZoom: 10.0, + initialZoom: initialZoom, minZoom: 2.0, maxZoom: 18.0, onTap: (_, latLng) { From a14462978d958748cc98a04c49a1480bc8ff166c Mon Sep 17 00:00:00 2001 From: zjs81 Date: Thu, 15 Jan 2026 21:48:10 -0700 Subject: [PATCH 11/16] Replace Column with SingleChildScrollView in RepeaterLoginDialog for better layout handling --- lib/widgets/repeater_login_dialog.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/widgets/repeater_login_dialog.dart b/lib/widgets/repeater_login_dialog.dart index 2865bce..f4129cd 100644 --- a/lib/widgets/repeater_login_dialog.dart +++ b/lib/widgets/repeater_login_dialog.dart @@ -261,10 +261,11 @@ class _RepeaterLoginDialogState extends State { child: CircularProgressIndicator(), ), ) - : Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + : SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Text( l10n.login_repeaterDescription, style: const TextStyle(fontSize: 14), @@ -382,6 +383,7 @@ class _RepeaterLoginDialogState extends State { ), ], ), + ), actions: [ TextButton( onPressed: () => Navigator.pop(context), From 2a04ebb8b613842c9ec7b75157da699fab82549f Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Fri, 16 Jan 2026 09:35:02 -0500 Subject: [PATCH 12/16] Show repeater login error in login dialog --- lib/l10n/app_bg.arb | 2 ++ lib/l10n/app_de.arb | 2 ++ lib/l10n/app_en.arb | 2 ++ lib/l10n/app_es.arb | 2 ++ lib/l10n/app_fr.arb | 2 ++ lib/l10n/app_it.arb | 2 ++ lib/l10n/app_localizations.dart | 6 ++++ lib/l10n/app_localizations_bg.dart | 4 +++ lib/l10n/app_localizations_de.dart | 4 +++ lib/l10n/app_localizations_en.dart | 4 +++ lib/l10n/app_localizations_es.dart | 4 +++ lib/l10n/app_localizations_fr.dart | 4 +++ lib/l10n/app_localizations_it.dart | 4 +++ lib/l10n/app_localizations_nl.dart | 4 +++ lib/l10n/app_localizations_pl.dart | 4 +++ lib/l10n/app_localizations_pt.dart | 4 +++ lib/l10n/app_localizations_sk.dart | 4 +++ lib/l10n/app_localizations_sl.dart | 4 +++ lib/l10n/app_localizations_sv.dart | 4 +++ lib/l10n/app_localizations_zh.dart | 3 ++ lib/l10n/app_nl.arb | 2 ++ lib/l10n/app_pl.arb | 2 ++ lib/l10n/app_pt.arb | 2 ++ lib/l10n/app_sk.arb | 2 ++ lib/l10n/app_sl.arb | 2 ++ lib/l10n/app_sv.arb | 2 ++ lib/l10n/app_zh.arb | 2 ++ lib/widgets/repeater_login_dialog.dart | 45 +++++++++++++++++++++----- 28 files changed, 120 insertions(+), 8 deletions(-) diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 4c747da..92bbb99 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -821,6 +821,8 @@ } } }, + "login_failedMessage": "Входът не беше успешен. Или паролата е грешна, или повторителят е недостъпен.", + "common_reload": "Презареди", "common_clear": "Изчисти", "path_currentPath": "Текущ път: {path}", diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 9044962..d3a2dd1 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -821,6 +821,8 @@ } } }, + "login_failedMessage": "Anmeldung fehlgeschlagen. Entweder ist das Passwort falsch oder der Repeater ist nicht erreichbar.", + "common_reload": "Neu laden", "common_clear": "Löschen", "path_currentPath": "Aktiger Pfad: {path}", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index dc53f73..00c59a1 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -711,6 +711,8 @@ "error": {"type": "String"} } }, + "login_failedMessage": "Login failed. Either the password is incorrect or the repeater is unreachable.", + "common_reload": "Reload", "common_clear": "Clear", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 1515eb6..ee56cb7 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -821,6 +821,8 @@ } } }, + "login_failedMessage": "Inicio fallido. La contraseña es incorrecta o el repetidor no está disponible.", + "common_reload": "Recargar", "common_clear": "Borrar", "path_currentPath": "Ruta actual: {path}", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 6a09a33..a83a8ae 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -821,6 +821,8 @@ } } }, + "login_failedMessage": "Connexion échouée. Soit le mot de passe est incorrect, soit le relais est injoignable.", + "common_reload": "Recharger", "common_clear": "Effacer", "path_currentPath": "Chemin actuel : {path}", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 72be091..e44115b 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -821,6 +821,8 @@ } } }, + "login_failedMessage": "Accesso fallito. La password non è corretta oppure il ripetitore non è raggiungibile.", + "common_reload": "Ricaricare", "common_clear": "Cancella", "path_currentPath": "Percorso corrente: {path}", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index a2c7ddb..4b2fcfb 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2687,6 +2687,12 @@ abstract class AppLocalizations { /// **'Login failed: {error}'** String login_failed(String error); + /// No description provided for @login_failedMessage. + /// + /// In en, this message translates to: + /// **'Login failed. Either the password is incorrect or the repeater is unreachable.'** + String get login_failedMessage; + /// No description provided for @common_reload. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 5def822..b5a1b49 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -1475,6 +1475,10 @@ class AppLocalizationsBg extends AppLocalizations { return 'Входът не беше успешен: $error'; } + @override + String get login_failedMessage => + 'Входът не беше успешен. Или паролата е грешна, или повторителят е недостъпен.'; + @override String get common_reload => 'Презареди'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 591de39..6366b80 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1474,6 +1474,10 @@ class AppLocalizationsDe extends AppLocalizations { return 'Anmeldung fehlgeschlagen: $error'; } + @override + String get login_failedMessage => + 'Anmeldung fehlgeschlagen. Entweder ist das Passwort falsch oder der Repeater ist nicht erreichbar.'; + @override String get common_reload => 'Neu laden'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 9e04923..37f103e 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1453,6 +1453,10 @@ class AppLocalizationsEn extends AppLocalizations { return 'Login failed: $error'; } + @override + String get login_failedMessage => + 'Login failed. Either the password is incorrect or the repeater is unreachable.'; + @override String get common_reload => 'Reload'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 82259cf..f939e3b 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1472,6 +1472,10 @@ class AppLocalizationsEs extends AppLocalizations { return 'Inicio fallido: $error'; } + @override + String get login_failedMessage => + 'Inicio fallido. La contraseña es incorrecta o el repetidor no está disponible.'; + @override String get common_reload => 'Recargar'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index c7735bc..6b0a987 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1478,6 +1478,10 @@ class AppLocalizationsFr extends AppLocalizations { return 'Connexion échouée : $error'; } + @override + String get login_failedMessage => + 'Connexion échouée. Soit le mot de passe est incorrect, soit le relais est injoignable.'; + @override String get common_reload => 'Recharger'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index cd2d022..117f4c1 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1470,6 +1470,10 @@ class AppLocalizationsIt extends AppLocalizations { return 'Accesso fallito: $error'; } + @override + String get login_failedMessage => + 'Accesso fallito. La password non è corretta oppure il ripetitore non è raggiungibile.'; + @override String get common_reload => 'Ricaricare'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index b9ae792..5f3e436 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1466,6 +1466,10 @@ class AppLocalizationsNl extends AppLocalizations { return 'Inloggen mislukt: $error'; } + @override + String get login_failedMessage => + 'Inloggen mislukt. Het wachtwoord is onjuist of de repeater is niet bereikbaar.'; + @override String get common_reload => 'Opnieuw laden'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 180d8e2..73adc0d 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1474,6 +1474,10 @@ class AppLocalizationsPl extends AppLocalizations { return 'Zalogowanie się nie powiodło: $error'; } + @override + String get login_failedMessage => + 'Logowanie nie powiodło się. Hasło jest nieprawidłowe albo repeater jest nieosiągalny.'; + @override String get common_reload => 'Ponownie załadować'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 403c4d6..3a995b5 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1472,6 +1472,10 @@ class AppLocalizationsPt extends AppLocalizations { return 'Login falhou: $error'; } + @override + String get login_failedMessage => + 'Falha no login. A senha está incorreta ou o repetidor está inacessível.'; + @override String get common_reload => 'Recarregar'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index e9eb10c..52178a2 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -1468,6 +1468,10 @@ class AppLocalizationsSk extends AppLocalizations { return 'Prihlásenie zlyhalo: $error'; } + @override + String get login_failedMessage => + 'Prihlásenie zlyhalo. Heslo je nesprávne alebo je opakovač nedostupný.'; + @override String get common_reload => 'Načítať'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index f95797b..35f9aee 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -1469,6 +1469,10 @@ class AppLocalizationsSl extends AppLocalizations { return 'Prijava je bila neuspešna: $error'; } + @override + String get login_failedMessage => + 'Prijava je bila neuspešna. Geslo je napačno ali pa je repetitor nedosegljiv.'; + @override String get common_reload => 'Ponovno naloži'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 6f97659..eb6d693 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -1457,6 +1457,10 @@ class AppLocalizationsSv extends AppLocalizations { return 'Inloggning misslyckades: $error'; } + @override + String get login_failedMessage => + 'Inloggning misslyckades. Antingen är lösenordet fel eller så går det inte att nå repeatern.'; + @override String get common_reload => 'Ladda om'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index e5b5a9f..767a380 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1411,6 +1411,9 @@ class AppLocalizationsZh extends AppLocalizations { return '登录失败:$error'; } + @override + String get login_failedMessage => '登录失败。密码不正确或中继器不可达。'; + @override String get common_reload => '重新加载'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 79fc69d..88cce67 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -821,6 +821,8 @@ } } }, + "login_failedMessage": "Inloggen mislukt. Het wachtwoord is onjuist of de repeater is niet bereikbaar.", + "common_reload": "Opnieuw laden", "common_clear": "Schoonmaken", "path_currentPath": "Huidige pad: {path}", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index bab67b5..d1ce3d0 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -821,6 +821,8 @@ } } }, + "login_failedMessage": "Logowanie nie powiodło się. Hasło jest nieprawidłowe albo repeater jest nieosiągalny.", + "common_reload": "Ponownie załadować", "common_clear": "Wyczyść", "path_currentPath": "Aktualny ścieżka: {path}", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 59f4a47..a98663c 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -821,6 +821,8 @@ } } }, + "login_failedMessage": "Falha no login. A senha está incorreta ou o repetidor está inacessível.", + "common_reload": "Recarregar", "common_clear": "Limpar", "path_currentPath": "Caminho atual: {path}", diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 67043a7..c1e5fcb 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -821,6 +821,8 @@ } } }, + "login_failedMessage": "Prihlásenie zlyhalo. Heslo je nesprávne alebo je opakovač nedostupný.", + "common_reload": "Načítať", "common_clear": "Zmazať", "path_currentPath": "Aktívna cesta: {path}", diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index 8a8dc59..b551934 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -821,6 +821,8 @@ } } }, + "login_failedMessage": "Prijava je bila neuspešna. Geslo je napačno ali pa je repetitor nedosegljiv.", + "common_reload": "Ponovno naloži", "common_clear": "Ponoviti", "path_currentPath": "Trenutna pot: {path}", diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index 866e438..c0c299b 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -821,6 +821,8 @@ } } }, + "login_failedMessage": "Inloggning misslyckades. Antingen är lösenordet fel eller så går det inte att nå repeatern.", + "common_reload": "Ladda om", "common_clear": "Rensa", "path_currentPath": "Nuvarande sökväg: {path}", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index f1349e8..e3b5633 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -821,6 +821,8 @@ } } }, + "login_failedMessage": "登录失败。密码不正确或中继器不可达。", + "common_reload": "重新加载", "common_clear": "清除", "path_currentPath": "当前路径:{path}", diff --git a/lib/widgets/repeater_login_dialog.dart b/lib/widgets/repeater_login_dialog.dart index f4129cd..54c0150 100644 --- a/lib/widgets/repeater_login_dialog.dart +++ b/lib/widgets/repeater_login_dialog.dart @@ -31,6 +31,7 @@ class _RepeaterLoginDialogState extends State { bool _savePassword = false; bool _isLoading = true; bool _obscurePassword = true; + String? _loginError; late MeshCoreConnector _connector; int _currentAttempt = 0; static const int _maxAttempts = 5; @@ -79,6 +80,7 @@ class _RepeaterLoginDialogState extends State { setState(() { _isLoggingIn = true; _currentAttempt = 0; + _loginError = null; }); try { @@ -134,7 +136,7 @@ class _RepeaterLoginDialogState extends State { 'Login failed for ${repeater.name}', tag: 'RepeaterLogin', ); - throw Exception('Wrong password or node is unreachable'); + break; } appLogger.warn( 'Login attempt ${attempt + 1} timed out after ${timeoutSeconds}s', @@ -156,7 +158,13 @@ class _RepeaterLoginDialogState extends State { } if (loginResult != true) { - throw Exception('Wrong password or node is unreachable'); + if (mounted) { + setState(() { + _isLoggingIn = false; + _loginError = context.l10n.login_failedMessage; + }); + } + return; } // If we got a response, login succeeded @@ -182,13 +190,8 @@ class _RepeaterLoginDialogState extends State { if (mounted) { setState(() { _isLoggingIn = false; + _loginError = context.l10n.login_failedMessage; }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.login_failed(e.toString())), - backgroundColor: Colors.red, - ), - ); } } } @@ -271,6 +274,25 @@ class _RepeaterLoginDialogState extends State { style: const TextStyle(fontSize: 14), ), const SizedBox(height: 16), + if (_loginError != null) ...[ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.error, size: 18, color: Theme.of(context).colorScheme.error), + const SizedBox(width: 8), + Expanded( + child: Text( + _loginError!, + style: TextStyle( + color: Theme.of(context).colorScheme.error, + fontSize: 13, + ), + ), + ), + ], + ), + const SizedBox(height: 12), + ], TextField( controller: _passwordController, obscureText: _obscurePassword, @@ -292,6 +314,13 @@ class _RepeaterLoginDialogState extends State { }, ), ), + onChanged: (_) { + if (_loginError != null && mounted) { + setState(() { + _loginError = null; + }); + } + }, onSubmitted: (_) => _handleLogin(), autofocus: _passwordController.text.isEmpty, ), From 14ff8250c09c53b39f616a7e4ba2addf7769ce11 Mon Sep 17 00:00:00 2001 From: zjs81 Date: Fri, 16 Jan 2026 19:06:39 -0700 Subject: [PATCH 13/16] Add support for private and hashtag channels in localization and channel management - Updated Polish, Portuguese, Slovak, Slovenian, Swedish, and Chinese localization files to include new strings for creating and joining private channels, as well as joining hashtag channels. - Enhanced the channel management UI to allow users to create and join private channels, join public channels, and join channels via hashtags. - Implemented PSK derivation from hashtags using SHA256 in the Channel model. - Improved the translation script to handle missing keys and translate all locales efficiently. --- lib/l10n/app_bg.arb | 14 +- lib/l10n/app_de.arb | 14 +- lib/l10n/app_en.arb | 12 + lib/l10n/app_es.arb | 14 +- lib/l10n/app_fr.arb | 14 +- lib/l10n/app_it.arb | 14 +- lib/l10n/app_localizations.dart | 72 +++++ lib/l10n/app_localizations_bg.dart | 39 +++ lib/l10n/app_localizations_de.dart | 42 +++ lib/l10n/app_localizations_en.dart | 37 +++ lib/l10n/app_localizations_es.dart | 40 +++ lib/l10n/app_localizations_fr.dart | 40 +++ lib/l10n/app_localizations_it.dart | 40 +++ lib/l10n/app_localizations_nl.dart | 40 +++ lib/l10n/app_localizations_pl.dart | 40 +++ lib/l10n/app_localizations_pt.dart | 40 +++ lib/l10n/app_localizations_sk.dart | 39 +++ lib/l10n/app_localizations_sl.dart | 39 +++ lib/l10n/app_localizations_sv.dart | 40 +++ lib/l10n/app_localizations_zh.dart | 36 +++ lib/l10n/app_nl.arb | 14 +- lib/l10n/app_pl.arb | 14 +- lib/l10n/app_pt.arb | 14 +- lib/l10n/app_sk.arb | 14 +- lib/l10n/app_sl.arb | 14 +- lib/l10n/app_sv.arb | 14 +- lib/l10n/app_zh.arb | 14 +- lib/models/channel.dart | 12 + lib/screens/channels_screen.dart | 408 +++++++++++++++++++++-------- tools/translate.py | 207 +++++++++++++-- 30 files changed, 1250 insertions(+), 141 deletions(-) diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 4c747da..c17f117 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -1335,5 +1335,17 @@ "listFilter_repeaters": "Повторители", "listFilter_roomServers": "Сървъри на стая", "listFilter_unreadOnly": "Само непрочетените", - "listFilter_newGroup": "Нова група" + "listFilter_newGroup": "Нова група", + "channels_createPrivateChannel": "Създай Частен Канал", + "channels_joinPrivateChannel": "Присъедини се към Частен Канал", + "channels_createPrivateChannelDesc": "Защитено с таен ключ.", + "channels_joinPrivateChannelDesc": "Ръчно въведете таен ключ.", + "channels_joinPublicChannel": "Присъединете се към Публичния канал", + "channels_joinPublicChannelDesc": "Всеки може да се присъедини към този канал.", + "channels_joinHashtagChannel": "Присъедини се към Хаштаг Канал", + "channels_joinHashtagChannelDesc": "Всеки може да се присъедини към хаштаговите канали.", + "channels_scanQrCode": "Сканирайте QR код", + "channels_scanQrCodeComingSoon": "Ще излезе скоро", + "channels_enterHashtag": "Въведете хаштаг", + "channels_hashtagHint": "напр. #отбор" } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 9044962..6c79f2d 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1335,5 +1335,17 @@ "listFilter_repeaters": "Wiederholer", "listFilter_roomServers": "Raumserver", "listFilter_unreadOnly": "Nur nicht gelesen", - "listFilter_newGroup": "Neue Gruppe" + "listFilter_newGroup": "Neue Gruppe", + "channels_joinPrivateChannel": "Treten Sie einem privaten Kanal bei", + "channels_joinPrivateChannelDesc": "Manuelle Eingabe eines geheimen Schlüssels.", + "channels_createPrivateChannel": "Erstelle einen privaten Kanal", + "channels_createPrivateChannelDesc": "Verschlüsselt mit einem geheimen Schlüssel.", + "channels_joinPublicChannel": "Tritt dem öffentlichen Kanal bei", + "channels_joinPublicChannelDesc": "Jeder kann diesem Kanal beitreten.", + "channels_joinHashtagChannel": "Treten Sie einem Hashtag-Kanal bei", + "channels_joinHashtagChannelDesc": "Jeder kann sich bei Hashtag-Kanälen beteiligen.", + "channels_scanQrCode": "Scannen Sie einen QR-Code", + "channels_scanQrCodeComingSoon": "Bald verfügbar", + "channels_enterHashtag": "Gib Hashtag ein", + "channels_hashtagHint": "z.B. #team" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index dc53f73..8a647b4 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -361,6 +361,18 @@ "channels_sortAZ": "A-Z", "channels_sortLatestMessages": "Latest messages", "channels_sortUnread": "Unread", + "channels_createPrivateChannel": "Create a Private Channel", + "channels_createPrivateChannelDesc": "Secured with a secret key.", + "channels_joinPrivateChannel": "Join a Private Channel", + "channels_joinPrivateChannelDesc": "Manually enter a secret key.", + "channels_joinPublicChannel": "Join the Public Channel", + "channels_joinPublicChannelDesc": "Anyone can join this channel.", + "channels_joinHashtagChannel": "Join a Hashtag Channel", + "channels_joinHashtagChannelDesc": "Anyone can join hashtag channels.", + "channels_scanQrCode": "Scan a QR Code", + "channels_scanQrCodeComingSoon": "Coming soon", + "channels_enterHashtag": "Enter hashtag", + "channels_hashtagHint": "e.g. #team", "chat_noMessages": "No messages yet", "chat_sendMessageToStart": "Send a message to get started", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 1515eb6..5a2672e 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1335,5 +1335,17 @@ "listFilter_repeaters": "Repetidores", "listFilter_roomServers": "Servidores de la sala", "listFilter_unreadOnly": "Solo sin leer", - "listFilter_newGroup": "Nuevo grupo" + "listFilter_newGroup": "Nuevo grupo", + "channels_joinPrivateChannel": "Únete a un Canal Privado", + "channels_createPrivateChannel": "Crear un Canal Privado", + "channels_createPrivateChannelDesc": "Cifrado con una clave secreta.", + "channels_joinPrivateChannelDesc": "Introducir manualmente una clave secreta.", + "channels_joinPublicChannel": "Únete al Canal Público", + "channels_joinPublicChannelDesc": "Cualquiera puede unirse a este canal.", + "channels_joinHashtagChannel": "Únete a un Canal con Hashtag", + "channels_joinHashtagChannelDesc": "Cualquiera puede unirse a los canales de hashtag.", + "channels_scanQrCode": "Escanear un Código QR", + "channels_scanQrCodeComingSoon": "Próximamente", + "channels_enterHashtag": "Introducir hashtag", + "channels_hashtagHint": "ej. #equipo" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 6a09a33..60ac827 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1335,5 +1335,17 @@ "listFilter_repeaters": "Répéteurs", "listFilter_roomServers": "Serveurs de pièce", "listFilter_unreadOnly": "Messages non lus seulement", - "listFilter_newGroup": "Nouvelle groupe" + "listFilter_newGroup": "Nouvelle groupe", + "channels_createPrivateChannelDesc": "Sécurisé avec une clé secrète.", + "channels_joinPrivateChannel": "Rejoindre un Canal Privé", + "channels_createPrivateChannel": "Créer un Canal Privé", + "channels_joinPrivateChannelDesc": "Entrer manuellement une clé secrète.", + "channels_joinPublicChannel": "Rejoindre le canal public", + "channels_joinPublicChannelDesc": "Tout le monde peut rejoindre ce canal.", + "channels_joinHashtagChannel": "Rejoindre un Canal Hashtag", + "channels_joinHashtagChannelDesc": "N'importe qui peut rejoindre les canaux #hashtag.", + "channels_scanQrCode": "Scanner un code QR", + "channels_scanQrCodeComingSoon": "Bientôt disponible", + "channels_enterHashtag": "Entrez le hashtag", + "channels_hashtagHint": "ex. #équipe" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 72be091..08163c6 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -1335,5 +1335,17 @@ "listFilter_repeaters": "Ripetitori", "listFilter_roomServers": "Server della stanza", "listFilter_unreadOnly": "Solo non letto", - "listFilter_newGroup": "Nuovo gruppo" + "listFilter_newGroup": "Nuovo gruppo", + "channels_createPrivateChannel": "Crea un Canale Privato", + "channels_createPrivateChannelDesc": "Protetta con una chiave segreta.", + "channels_joinPrivateChannel": "Unisciti a un Canale Privato", + "channels_joinPrivateChannelDesc": "Inserire manualmente una chiave segreta.", + "channels_joinPublicChannel": "Unisciti al Canale Pubblico", + "channels_joinPublicChannelDesc": "Chiunque può unirsi a questo canale.", + "channels_joinHashtagChannel": "Unisciti a un Canale con Hashtag", + "channels_joinHashtagChannelDesc": "Chiunque può unirsi ai canali hashtag.", + "channels_scanQrCode": "Scansiona un codice QR", + "channels_scanQrCodeComingSoon": "Arriverà presto", + "channels_enterHashtag": "Inserisci hashtag", + "channels_hashtagHint": "es. #team" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index a2c7ddb..e0c1f8e 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1596,6 +1596,78 @@ abstract class AppLocalizations { /// **'Unread'** String get channels_sortUnread; + /// No description provided for @channels_createPrivateChannel. + /// + /// In en, this message translates to: + /// **'Create a Private Channel'** + String get channels_createPrivateChannel; + + /// No description provided for @channels_createPrivateChannelDesc. + /// + /// In en, this message translates to: + /// **'Secured with a secret key.'** + String get channels_createPrivateChannelDesc; + + /// No description provided for @channels_joinPrivateChannel. + /// + /// In en, this message translates to: + /// **'Join a Private Channel'** + String get channels_joinPrivateChannel; + + /// No description provided for @channels_joinPrivateChannelDesc. + /// + /// In en, this message translates to: + /// **'Manually enter a secret key.'** + String get channels_joinPrivateChannelDesc; + + /// No description provided for @channels_joinPublicChannel. + /// + /// In en, this message translates to: + /// **'Join the Public Channel'** + String get channels_joinPublicChannel; + + /// No description provided for @channels_joinPublicChannelDesc. + /// + /// In en, this message translates to: + /// **'Anyone can join this channel.'** + String get channels_joinPublicChannelDesc; + + /// No description provided for @channels_joinHashtagChannel. + /// + /// In en, this message translates to: + /// **'Join a Hashtag Channel'** + String get channels_joinHashtagChannel; + + /// No description provided for @channels_joinHashtagChannelDesc. + /// + /// In en, this message translates to: + /// **'Anyone can join hashtag channels.'** + String get channels_joinHashtagChannelDesc; + + /// No description provided for @channels_scanQrCode. + /// + /// In en, this message translates to: + /// **'Scan a QR Code'** + String get channels_scanQrCode; + + /// No description provided for @channels_scanQrCodeComingSoon. + /// + /// In en, this message translates to: + /// **'Coming soon'** + String get channels_scanQrCodeComingSoon; + + /// No description provided for @channels_enterHashtag. + /// + /// In en, this message translates to: + /// **'Enter hashtag'** + String get channels_enterHashtag; + + /// No description provided for @channels_hashtagHint. + /// + /// In en, this message translates to: + /// **'e.g. #team'** + String get channels_hashtagHint; + /// No description provided for @chat_noMessages. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 5def822..019566a 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -830,6 +830,45 @@ class AppLocalizationsBg extends AppLocalizations { @override String get channels_sortUnread => 'Непрочетено'; + @override + String get channels_createPrivateChannel => 'Създай Частен Канал'; + + @override + String get channels_createPrivateChannelDesc => 'Защитено с таен ключ.'; + + @override + String get channels_joinPrivateChannel => 'Присъедини се към Частен Канал'; + + @override + String get channels_joinPrivateChannelDesc => 'Ръчно въведете таен ключ.'; + + @override + String get channels_joinPublicChannel => + 'Присъединете се към Публичния канал'; + + @override + String get channels_joinPublicChannelDesc => + 'Всеки може да се присъедини към този канал.'; + + @override + String get channels_joinHashtagChannel => 'Присъедини се към Хаштаг Канал'; + + @override + String get channels_joinHashtagChannelDesc => + 'Всеки може да се присъедини към хаштаговите канали.'; + + @override + String get channels_scanQrCode => 'Сканирайте QR код'; + + @override + String get channels_scanQrCodeComingSoon => 'Ще излезе скоро'; + + @override + String get channels_enterHashtag => 'Въведете хаштаг'; + + @override + String get channels_hashtagHint => 'напр. #отбор'; + @override String get chat_noMessages => 'Няма съобщения.'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 591de39..1ae2fb4 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -828,6 +828,48 @@ class AppLocalizationsDe extends AppLocalizations { @override String get channels_sortUnread => 'Unlescht'; + @override + String get channels_createPrivateChannel => 'Erstelle einen privaten Kanal'; + + @override + String get channels_createPrivateChannelDesc => + 'Verschlüsselt mit einem geheimen Schlüssel.'; + + @override + String get channels_joinPrivateChannel => + 'Treten Sie einem privaten Kanal bei'; + + @override + String get channels_joinPrivateChannelDesc => + 'Manuelle Eingabe eines geheimen Schlüssels.'; + + @override + String get channels_joinPublicChannel => 'Tritt dem öffentlichen Kanal bei'; + + @override + String get channels_joinPublicChannelDesc => + 'Jeder kann diesem Kanal beitreten.'; + + @override + String get channels_joinHashtagChannel => + 'Treten Sie einem Hashtag-Kanal bei'; + + @override + String get channels_joinHashtagChannelDesc => + 'Jeder kann sich bei Hashtag-Kanälen beteiligen.'; + + @override + String get channels_scanQrCode => 'Scannen Sie einen QR-Code'; + + @override + String get channels_scanQrCodeComingSoon => 'Bald verfügbar'; + + @override + String get channels_enterHashtag => 'Gib Hashtag ein'; + + @override + String get channels_hashtagHint => 'z.B. #team'; + @override String get chat_noMessages => 'Noch keine Nachrichten.'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 9e04923..a98546e 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -818,6 +818,43 @@ class AppLocalizationsEn extends AppLocalizations { @override String get channels_sortUnread => 'Unread'; + @override + String get channels_createPrivateChannel => 'Create a Private Channel'; + + @override + String get channels_createPrivateChannelDesc => 'Secured with a secret key.'; + + @override + String get channels_joinPrivateChannel => 'Join a Private Channel'; + + @override + String get channels_joinPrivateChannelDesc => 'Manually enter a secret key.'; + + @override + String get channels_joinPublicChannel => 'Join the Public Channel'; + + @override + String get channels_joinPublicChannelDesc => 'Anyone can join this channel.'; + + @override + String get channels_joinHashtagChannel => 'Join a Hashtag Channel'; + + @override + String get channels_joinHashtagChannelDesc => + 'Anyone can join hashtag channels.'; + + @override + String get channels_scanQrCode => 'Scan a QR Code'; + + @override + String get channels_scanQrCodeComingSoon => 'Coming soon'; + + @override + String get channels_enterHashtag => 'Enter hashtag'; + + @override + String get channels_hashtagHint => 'e.g. #team'; + @override String get chat_noMessages => 'No messages yet'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 82259cf..aa3926e 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -829,6 +829,46 @@ class AppLocalizationsEs extends AppLocalizations { @override String get channels_sortUnread => 'Sin leer'; + @override + String get channels_createPrivateChannel => 'Crear un Canal Privado'; + + @override + String get channels_createPrivateChannelDesc => + 'Cifrado con una clave secreta.'; + + @override + String get channels_joinPrivateChannel => 'Únete a un Canal Privado'; + + @override + String get channels_joinPrivateChannelDesc => + 'Introducir manualmente una clave secreta.'; + + @override + String get channels_joinPublicChannel => 'Únete al Canal Público'; + + @override + String get channels_joinPublicChannelDesc => + 'Cualquiera puede unirse a este canal.'; + + @override + String get channels_joinHashtagChannel => 'Únete a un Canal con Hashtag'; + + @override + String get channels_joinHashtagChannelDesc => + 'Cualquiera puede unirse a los canales de hashtag.'; + + @override + String get channels_scanQrCode => 'Escanear un Código QR'; + + @override + String get channels_scanQrCodeComingSoon => 'Próximamente'; + + @override + String get channels_enterHashtag => 'Introducir hashtag'; + + @override + String get channels_hashtagHint => 'ej. #equipo'; + @override String get chat_noMessages => 'Aún no hay mensajes'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index c7735bc..f8e89cc 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -830,6 +830,46 @@ class AppLocalizationsFr extends AppLocalizations { @override String get channels_sortUnread => 'Non lu'; + @override + String get channels_createPrivateChannel => 'Créer un Canal Privé'; + + @override + String get channels_createPrivateChannelDesc => + 'Sécurisé avec une clé secrète.'; + + @override + String get channels_joinPrivateChannel => 'Rejoindre un Canal Privé'; + + @override + String get channels_joinPrivateChannelDesc => + 'Entrer manuellement une clé secrète.'; + + @override + String get channels_joinPublicChannel => 'Rejoindre le canal public'; + + @override + String get channels_joinPublicChannelDesc => + 'Tout le monde peut rejoindre ce canal.'; + + @override + String get channels_joinHashtagChannel => 'Rejoindre un Canal Hashtag'; + + @override + String get channels_joinHashtagChannelDesc => + 'N\'importe qui peut rejoindre les canaux #hashtag.'; + + @override + String get channels_scanQrCode => 'Scanner un code QR'; + + @override + String get channels_scanQrCodeComingSoon => 'Bientôt disponible'; + + @override + String get channels_enterHashtag => 'Entrez le hashtag'; + + @override + String get channels_hashtagHint => 'ex. #équipe'; + @override String get chat_noMessages => 'Aucun message pour le moment.'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index cd2d022..1b15148 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -827,6 +827,46 @@ class AppLocalizationsIt extends AppLocalizations { @override String get channels_sortUnread => 'Non letto'; + @override + String get channels_createPrivateChannel => 'Crea un Canale Privato'; + + @override + String get channels_createPrivateChannelDesc => + 'Protetta con una chiave segreta.'; + + @override + String get channels_joinPrivateChannel => 'Unisciti a un Canale Privato'; + + @override + String get channels_joinPrivateChannelDesc => + 'Inserire manualmente una chiave segreta.'; + + @override + String get channels_joinPublicChannel => 'Unisciti al Canale Pubblico'; + + @override + String get channels_joinPublicChannelDesc => + 'Chiunque può unirsi a questo canale.'; + + @override + String get channels_joinHashtagChannel => 'Unisciti a un Canale con Hashtag'; + + @override + String get channels_joinHashtagChannelDesc => + 'Chiunque può unirsi ai canali hashtag.'; + + @override + String get channels_scanQrCode => 'Scansiona un codice QR'; + + @override + String get channels_scanQrCodeComingSoon => 'Arriverà presto'; + + @override + String get channels_enterHashtag => 'Inserisci hashtag'; + + @override + String get channels_hashtagHint => 'es. #team'; + @override String get chat_noMessages => 'Nessun messaggio ancora'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index b9ae792..0d9f807 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -824,6 +824,46 @@ class AppLocalizationsNl extends AppLocalizations { @override String get channels_sortUnread => 'Ongelezen'; + @override + String get channels_createPrivateChannel => 'Maak een Privé Kanaal'; + + @override + String get channels_createPrivateChannelDesc => + 'Beveiligd met een geheime sleutel.'; + + @override + String get channels_joinPrivateChannel => 'Sluit een Privé Kanaal aan'; + + @override + String get channels_joinPrivateChannelDesc => + 'Handmatig een geheime sleutel invoeren.'; + + @override + String get channels_joinPublicChannel => 'Sluit het Open Kanaal'; + + @override + String get channels_joinPublicChannelDesc => + 'Iedereen kan dit kanaal aanmelden.'; + + @override + String get channels_joinHashtagChannel => 'Sluit een Hashtag Kanaal'; + + @override + String get channels_joinHashtagChannelDesc => + 'Iedereen kan lid worden van hashtag-kanalen.'; + + @override + String get channels_scanQrCode => 'Scan een QR-code'; + + @override + String get channels_scanQrCodeComingSoon => 'Komt later'; + + @override + String get channels_enterHashtag => 'Voer hashtag in'; + + @override + String get channels_hashtagHint => 'bijv. #team'; + @override String get chat_noMessages => 'Nog geen berichten.'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 180d8e2..6626308 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -828,6 +828,46 @@ class AppLocalizationsPl extends AppLocalizations { @override String get channels_sortUnread => 'Niezgłoszone'; + @override + String get channels_createPrivateChannel => 'Utwórz Prywatny Kanał'; + + @override + String get channels_createPrivateChannelDesc => + 'Zabezpieczone kluczem szyfrowym.'; + + @override + String get channels_joinPrivateChannel => 'Dołącz do Prywatnego Kanału'; + + @override + String get channels_joinPrivateChannelDesc => 'Ręcznie wprowadź klucz tajny.'; + + @override + String get channels_joinPublicChannel => 'Dołącz do kanału publicznego.'; + + @override + String get channels_joinPublicChannelDesc => + 'Każdy może dołączyć do tego kanału.'; + + @override + String get channels_joinHashtagChannel => + 'Dołącz do kanału oznaczanego hashtagiem'; + + @override + String get channels_joinHashtagChannelDesc => + 'Każdy może dołączyć do kanałów z hashtagami.'; + + @override + String get channels_scanQrCode => 'Skanuj kod QR'; + + @override + String get channels_scanQrCodeComingSoon => 'Wkrótce'; + + @override + String get channels_enterHashtag => 'Wprowadź hashtag'; + + @override + String get channels_hashtagHint => 'np. #zespół'; + @override String get chat_noMessages => 'Brak jeszcze wiadomości'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 403c4d6..614ff0d 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -829,6 +829,46 @@ class AppLocalizationsPt extends AppLocalizations { @override String get channels_sortUnread => 'Não lido'; + @override + String get channels_createPrivateChannel => 'Criar um Canal Privado'; + + @override + String get channels_createPrivateChannelDesc => + 'Protegido com uma chave secreta.'; + + @override + String get channels_joinPrivateChannel => 'Junte-se a um Canal Privado'; + + @override + String get channels_joinPrivateChannelDesc => + 'Inserir uma chave secreta manualmente.'; + + @override + String get channels_joinPublicChannel => 'Junte-se ao Canal Público'; + + @override + String get channels_joinPublicChannelDesc => + 'Qualquer pessoa pode entrar neste canal.'; + + @override + String get channels_joinHashtagChannel => 'Junte-se a um Canal com Hashtag'; + + @override + String get channels_joinHashtagChannelDesc => + 'Qualquer pessoa pode participar de canais com hashtag.'; + + @override + String get channels_scanQrCode => 'Digitalizar um Código QR'; + + @override + String get channels_scanQrCodeComingSoon => 'Em breve'; + + @override + String get channels_enterHashtag => 'Insira hashtag'; + + @override + String get channels_hashtagHint => 'ex. #equipe'; + @override String get chat_noMessages => 'Ainda não existem mensagens.'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index e9eb10c..e3747f2 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -824,6 +824,45 @@ class AppLocalizationsSk extends AppLocalizations { @override String get channels_sortUnread => 'Nezriadené'; + @override + String get channels_createPrivateChannel => 'Vytvorte súkromný kanál'; + + @override + String get channels_createPrivateChannelDesc => + 'Zabezpečené pomocou tajného kľúča.'; + + @override + String get channels_joinPrivateChannel => 'Pripojiť sa k súkromnému kanálu'; + + @override + String get channels_joinPrivateChannelDesc => 'Ručne zadajte tajný kľúč.'; + + @override + String get channels_joinPublicChannel => 'Pripojte sa k verejnému kanálu'; + + @override + String get channels_joinPublicChannelDesc => + 'Któvek sátó na tutó kanalizovát.'; + + @override + String get channels_joinHashtagChannel => 'Pripojte sa k Hashtag Kanálu'; + + @override + String get channels_joinHashtagChannelDesc => + 'Ktoekolikoľvek sa môže pridať do hashtag kanálov.'; + + @override + String get channels_scanQrCode => 'Skenujte QR kód'; + + @override + String get channels_scanQrCodeComingSoon => 'Čoskoro'; + + @override + String get channels_enterHashtag => 'Zadajte hashtag'; + + @override + String get channels_hashtagHint => 'napr. #tím'; + @override String get chat_noMessages => 'Zatiaľ žiadne správy.'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index f95797b..4d2ad7f 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -824,6 +824,45 @@ class AppLocalizationsSl extends AppLocalizations { @override String get channels_sortUnread => 'Nerešeno'; + @override + String get channels_createPrivateChannel => 'Ustvari zasebno kanal.'; + + @override + String get channels_createPrivateChannelDesc => + 'Varno zaklenjeno s skrivnim ključem.'; + + @override + String get channels_joinPrivateChannel => 'Pridružite se zasebni skupini'; + + @override + String get channels_joinPrivateChannelDesc => 'Ročno vnesite zaporni ključ.'; + + @override + String get channels_joinPublicChannel => 'Pridružite se javnemu kanalu'; + + @override + String get channels_joinPublicChannelDesc => + 'Kdor karkoli je, lahko se pridruži tej skupini.'; + + @override + String get channels_joinHashtagChannel => 'Pridružite se Kanalu z Hashtagom'; + + @override + String get channels_joinHashtagChannelDesc => + 'Kdor karkoli, lahko se pridruži hashtag kanalom.'; + + @override + String get channels_scanQrCode => 'Skeniraj QR kodo'; + + @override + String get channels_scanQrCodeComingSoon => 'Prihajajoča'; + + @override + String get channels_enterHashtag => 'Vnesite hashtag'; + + @override + String get channels_hashtagHint => 'npr. #ekipa'; + @override String get chat_noMessages => 'Še ni sporočil.'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 6f97659..d170b4a 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -817,6 +817,46 @@ class AppLocalizationsSv extends AppLocalizations { @override String get channels_sortUnread => 'Oläst'; + @override + String get channels_createPrivateChannel => 'Skapa en privat kanal'; + + @override + String get channels_createPrivateChannelDesc => + 'Skyddat med en hemlig nyckel.'; + + @override + String get channels_joinPrivateChannel => 'Gå med i en Privat Kanal'; + + @override + String get channels_joinPrivateChannelDesc => + 'Ange en hemlig nyckel manuellt.'; + + @override + String get channels_joinPublicChannel => 'Gå med i den Offentliga Kanalen'; + + @override + String get channels_joinPublicChannelDesc => + 'Vem som helst kan gå med i denna kanal.'; + + @override + String get channels_joinHashtagChannel => 'Gå med i en Hashtagkanal'; + + @override + String get channels_joinHashtagChannelDesc => + 'Väldigt enkelt att gå med i hashtag-kanaler.'; + + @override + String get channels_scanQrCode => 'Skanna en QR-kod'; + + @override + String get channels_scanQrCodeComingSoon => 'Kommer snart'; + + @override + String get channels_enterHashtag => 'Ange hashtag'; + + @override + String get channels_hashtagHint => 't.ex. #team'; + @override String get chat_noMessages => 'Inga meddelanden ännu'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index e5b5a9f..7d02bf5 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -789,6 +789,42 @@ class AppLocalizationsZh extends AppLocalizations { @override String get channels_sortUnread => '未读'; + @override + String get channels_createPrivateChannel => '创建私聊频道'; + + @override + String get channels_createPrivateChannelDesc => '使用密钥保护。'; + + @override + String get channels_joinPrivateChannel => '加入私密频道'; + + @override + String get channels_joinPrivateChannelDesc => '手动输入密钥。'; + + @override + String get channels_joinPublicChannel => '加入公共频道'; + + @override + String get channels_joinPublicChannelDesc => '任何人都可以加入这个频道。'; + + @override + String get channels_joinHashtagChannel => '加入标签频道'; + + @override + String get channels_joinHashtagChannelDesc => '任何人都可以加入话题频道。'; + + @override + String get channels_scanQrCode => '扫描二维码'; + + @override + String get channels_scanQrCodeComingSoon => '即将到来'; + + @override + String get channels_enterHashtag => '输入标签'; + + @override + String get channels_hashtagHint => '例如 #团队'; + @override String get chat_noMessages => '目前还没有消息'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 79fc69d..f78fc68 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -1335,5 +1335,17 @@ "listFilter_repeaters": "Repeaters", "listFilter_roomServers": "Roomservers", "listFilter_unreadOnly": "Alleen ongelezen", - "listFilter_newGroup": "Nieuwe groep" + "listFilter_newGroup": "Nieuwe groep", + "channels_createPrivateChannelDesc": "Beveiligd met een geheime sleutel.", + "channels_createPrivateChannel": "Maak een Privé Kanaal", + "channels_joinPrivateChannel": "Sluit een Privé Kanaal aan", + "channels_joinPrivateChannelDesc": "Handmatig een geheime sleutel invoeren.", + "channels_joinPublicChannel": "Sluit het Open Kanaal", + "channels_joinPublicChannelDesc": "Iedereen kan dit kanaal aanmelden.", + "channels_joinHashtagChannel": "Sluit een Hashtag Kanaal", + "channels_joinHashtagChannelDesc": "Iedereen kan lid worden van hashtag-kanalen.", + "channels_scanQrCode": "Scan een QR-code", + "channels_scanQrCodeComingSoon": "Komt later", + "channels_enterHashtag": "Voer hashtag in", + "channels_hashtagHint": "bijv. #team" } diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index bab67b5..24edcaf 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1335,5 +1335,17 @@ "listFilter_repeaters": "Powtarzacze", "listFilter_roomServers": "Serwery pokoju", "listFilter_unreadOnly": "Tylko nieprzeczytane", - "listFilter_newGroup": "Nowa grupa" + "listFilter_newGroup": "Nowa grupa", + "channels_joinPrivateChannelDesc": "Ręcznie wprowadź klucz tajny.", + "channels_createPrivateChannel": "Utwórz Prywatny Kanał", + "channels_createPrivateChannelDesc": "Zabezpieczone kluczem szyfrowym.", + "channels_joinPrivateChannel": "Dołącz do Prywatnego Kanału", + "channels_joinPublicChannel": "Dołącz do kanału publicznego.", + "channels_joinPublicChannelDesc": "Każdy może dołączyć do tego kanału.", + "channels_joinHashtagChannel": "Dołącz do kanału oznaczanego hashtagiem", + "channels_joinHashtagChannelDesc": "Każdy może dołączyć do kanałów z hashtagami.", + "channels_scanQrCode": "Skanuj kod QR", + "channels_scanQrCodeComingSoon": "Wkrótce", + "channels_enterHashtag": "Wprowadź hashtag", + "channels_hashtagHint": "np. #zespół" } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 59f4a47..47f9cef 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -1335,5 +1335,17 @@ "listFilter_repeaters": "Repetidores", "listFilter_roomServers": "Servidores de sala", "listFilter_unreadOnly": "Apenas não lido", - "listFilter_newGroup": "Novo grupo" + "listFilter_newGroup": "Novo grupo", + "channels_createPrivateChannelDesc": "Protegido com uma chave secreta.", + "channels_joinPrivateChannelDesc": "Inserir uma chave secreta manualmente.", + "channels_createPrivateChannel": "Criar um Canal Privado", + "channels_joinPrivateChannel": "Junte-se a um Canal Privado", + "channels_joinPublicChannel": "Junte-se ao Canal Público", + "channels_joinPublicChannelDesc": "Qualquer pessoa pode entrar neste canal.", + "channels_joinHashtagChannel": "Junte-se a um Canal com Hashtag", + "channels_joinHashtagChannelDesc": "Qualquer pessoa pode participar de canais com hashtag.", + "channels_scanQrCode": "Digitalizar um Código QR", + "channels_scanQrCodeComingSoon": "Em breve", + "channels_enterHashtag": "Insira hashtag", + "channels_hashtagHint": "ex. #equipe" } diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 67043a7..752c8d4 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -1335,5 +1335,17 @@ "listFilter_repeaters": "Opakovadlá", "listFilter_roomServers": "Servéry miestnosti", "listFilter_unreadOnly": "Nezaregistrované len", - "listFilter_newGroup": "Nová skupina" + "listFilter_newGroup": "Nová skupina", + "channels_createPrivateChannel": "Vytvorte súkromný kanál", + "channels_joinPrivateChannel": "Pripojiť sa k súkromnému kanálu", + "channels_joinPrivateChannelDesc": "Ručne zadajte tajný kľúč.", + "channels_createPrivateChannelDesc": "Zabezpečené pomocou tajného kľúča.", + "channels_joinPublicChannel": "Pripojte sa k verejnému kanálu", + "channels_joinPublicChannelDesc": "Któvek sátó na tutó kanalizovát.", + "channels_joinHashtagChannel": "Pripojte sa k Hashtag Kanálu", + "channels_joinHashtagChannelDesc": "Ktoekolikoľvek sa môže pridať do hashtag kanálov.", + "channels_scanQrCode": "Skenujte QR kód", + "channels_scanQrCodeComingSoon": "Čoskoro", + "channels_enterHashtag": "Zadajte hashtag", + "channels_hashtagHint": "napr. #tím" } diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index 8a8dc59..3e40f2d 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -1335,5 +1335,17 @@ "listFilter_repeaters": "Ponovitve", "listFilter_roomServers": "Smeti za prostore", "listFilter_unreadOnly": "Nezbrani samo", - "listFilter_newGroup": "Nova skupina" + "listFilter_newGroup": "Nova skupina", + "channels_joinPrivateChannel": "Pridružite se zasebni skupini", + "channels_createPrivateChannelDesc": "Varno zaklenjeno s skrivnim ključem.", + "channels_joinPrivateChannelDesc": "Ročno vnesite zaporni ključ.", + "channels_createPrivateChannel": "Ustvari zasebno kanal.", + "channels_joinPublicChannel": "Pridružite se javnemu kanalu", + "channels_joinPublicChannelDesc": "Kdor karkoli je, lahko se pridruži tej skupini.", + "channels_joinHashtagChannel": "Pridružite se Kanalu z Hashtagom", + "channels_joinHashtagChannelDesc": "Kdor karkoli, lahko se pridruži hashtag kanalom.", + "channels_scanQrCode": "Skeniraj QR kodo", + "channels_scanQrCodeComingSoon": "Prihajajoča", + "channels_enterHashtag": "Vnesite hashtag", + "channels_hashtagHint": "npr. #ekipa" } diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index 866e438..35ec92b 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -1335,5 +1335,17 @@ "listFilter_repeaters": "Upprepare", "listFilter_roomServers": "Rumservrar", "listFilter_unreadOnly": "Endast oinlästa", - "listFilter_newGroup": "Ny grupp" + "listFilter_newGroup": "Ny grupp", + "channels_createPrivateChannel": "Skapa en privat kanal", + "channels_joinPrivateChannel": "Gå med i en Privat Kanal", + "channels_joinPrivateChannelDesc": "Ange en hemlig nyckel manuellt.", + "channels_createPrivateChannelDesc": "Skyddat med en hemlig nyckel.", + "channels_joinPublicChannel": "Gå med i den Offentliga Kanalen", + "channels_joinPublicChannelDesc": "Vem som helst kan gå med i denna kanal.", + "channels_joinHashtagChannel": "Gå med i en Hashtagkanal", + "channels_joinHashtagChannelDesc": "Väldigt enkelt att gå med i hashtag-kanaler.", + "channels_scanQrCode": "Skanna en QR-kod", + "channels_scanQrCodeComingSoon": "Kommer snart", + "channels_enterHashtag": "Ange hashtag", + "channels_hashtagHint": "t.ex. #team" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index f1349e8..33cd517 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1335,5 +1335,17 @@ "listFilter_repeaters": "重复器", "listFilter_roomServers": "房间服务器", "listFilter_unreadOnly": "未读消息", - "listFilter_newGroup": "新组" + "listFilter_newGroup": "新组", + "channels_joinPrivateChannel": "加入私密频道", + "channels_createPrivateChannelDesc": "使用密钥保护。", + "channels_joinPrivateChannelDesc": "手动输入密钥。", + "channels_createPrivateChannel": "创建私聊频道", + "channels_joinPublicChannel": "加入公共频道", + "channels_joinPublicChannelDesc": "任何人都可以加入这个频道。", + "channels_joinHashtagChannel": "加入标签频道", + "channels_joinHashtagChannelDesc": "任何人都可以加入话题频道。", + "channels_scanQrCode": "扫描二维码", + "channels_scanQrCodeComingSoon": "即将到来", + "channels_enterHashtag": "输入标签", + "channels_hashtagHint": "例如 #团队" } diff --git a/lib/models/channel.dart b/lib/models/channel.dart index 79df62d..3325280 100644 --- a/lib/models/channel.dart +++ b/lib/models/channel.dart @@ -1,5 +1,8 @@ +import 'dart:convert'; import 'dart:typed_data'; +import 'package:crypto/crypto.dart' as crypto; + import '../connector/meshcore_protocol.dart'; class Channel { @@ -61,6 +64,15 @@ class Channel { return bytes; } + /// Derive PSK from hashtag name using SHA256. + /// The hashtag is normalized to include '#' prefix. + /// Returns first 16 bytes of SHA256 hash as PSK. + static Uint8List derivePskFromHashtag(String hashtag) { + final name = hashtag.startsWith('#') ? hashtag : '#$hashtag'; + final hash = crypto.sha256.convert(utf8.encode(name)).bytes; + return Uint8List.fromList(hash.sublist(0, 16)); + } + static String formatPskHex(Uint8List psk) { return _bytesToHex(psk); } diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index a652d00..bd40e1f 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -515,132 +515,318 @@ class _ChannelsScreenState extends State void _showAddChannelDialog(BuildContext context) { final connector = context.read(); + final nextIndex = _findNextAvailableIndex(connector.channels, connector.maxChannels); + final hasPublicChannel = connector.channels.any((c) => c.isPublicChannel); + int? selectedOption; final nameController = TextEditingController(); final pskController = TextEditingController(); - final maxChannels = connector.maxChannels; - int selectedIndex = _findNextAvailableIndex(connector.channels, maxChannels); - bool usePublicPsk = false; + final hashtagController = TextEditingController(); showDialog( context: context, builder: (dialogContext) => StatefulBuilder( - builder: (dialogContext, setDialogState) => AlertDialog( - title: Text(dialogContext.l10n.channels_addChannel), - content: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - DropdownButtonFormField( - initialValue: selectedIndex, - decoration: InputDecoration( - labelText: dialogContext.l10n.channels_channelIndexLabel, - border: const OutlineInputBorder(), - ), - items: List.generate(maxChannels, (i) => i) - .map((i) => DropdownMenuItem( - value: i, - child: Text(dialogContext.l10n.channels_channelIndex(i)), - )) - .toList(), - onChanged: (value) { - if (value != null) { - setDialogState(() => selectedIndex = value); - } - }, + builder: (dialogContext, setDialogState) { + Widget buildOptionTile({ + required int optionIndex, + required IconData icon, + required String title, + required String subtitle, + bool enabled = true, + }) { + final isSelected = selectedOption == optionIndex; + return ListTile( + leading: CircleAvatar( + backgroundColor: enabled + ? (isSelected ? Theme.of(dialogContext).colorScheme.primaryContainer : null) + : Colors.grey.withValues(alpha: 0.2), + child: Icon( + icon, + color: enabled + ? (isSelected ? Theme.of(dialogContext).colorScheme.primary : null) + : Colors.grey, ), - const SizedBox(height: 16), - TextField( - controller: nameController, - decoration: InputDecoration( - labelText: dialogContext.l10n.channels_channelName, - border: const OutlineInputBorder(), - ), - maxLength: 31, - ), - const SizedBox(height: 8), - CheckboxListTile( - title: Text(dialogContext.l10n.channels_usePublicChannel), - subtitle: Text(dialogContext.l10n.channels_standardPublicPsk), - value: usePublicPsk, - onChanged: (value) { - setDialogState(() { - usePublicPsk = value ?? false; - if (usePublicPsk) { - nameController.text = 'Public'; - pskController.text = Channel.publicChannelPsk; - } else { + ), + title: Text( + title, + style: TextStyle(color: enabled ? null : Colors.grey), + ), + subtitle: Text( + subtitle, + style: TextStyle(color: enabled ? null : Colors.grey), + ), + trailing: enabled ? const Icon(Icons.chevron_right) : null, + selected: isSelected, + onTap: enabled + ? () { + setDialogState(() { + selectedOption = optionIndex; + nameController.clear(); pskController.clear(); - } - }); - }, - ), - if (!usePublicPsk) ...[ - const SizedBox(height: 8), - TextField( - controller: pskController, - decoration: InputDecoration( - labelText: dialogContext.l10n.channels_pskHex, - border: const OutlineInputBorder(), - suffixIcon: IconButton( - icon: const Icon(Icons.casino), - tooltip: dialogContext.l10n.channels_generateRandomPsk, - onPressed: () { - final random = Random.secure(); - final bytes = Uint8List(16); - for (int i = 0; i < 16; i++) { - bytes[i] = random.nextInt(256); - } - pskController.text = Channel.formatPskHex(bytes); - }, + hashtagController.clear(); + }); + } + : null, + ); + } + + Widget? buildExpandedContent() { + switch (selectedOption) { + case 0: // Create Private Channel + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: TextField( + controller: nameController, + decoration: InputDecoration( + labelText: dialogContext.l10n.channels_channelName, + border: const OutlineInputBorder(), + ), + maxLength: 31, ), ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Expanded( + child: FilledButton( + onPressed: () { + final name = nameController.text.trim(); + if (name.isEmpty) { + ScaffoldMessenger.of(dialogContext).showSnackBar( + SnackBar(content: Text(dialogContext.l10n.channels_enterChannelName)), + ); + return; + } + final random = Random.secure(); + final psk = Uint8List(16); + for (int i = 0; i < 16; i++) { + psk[i] = random.nextInt(256); + } + Navigator.pop(dialogContext); + connector.setChannel(nextIndex, name, psk); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.channels_channelAdded(name))), + ); + } + }, + child: Text(dialogContext.l10n.common_create), + ), + ), + ], + ), + ), + ], + ); + + case 1: // Join Private Channel + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: TextField( + controller: nameController, + decoration: InputDecoration( + labelText: dialogContext.l10n.channels_channelName, + border: const OutlineInputBorder(), + ), + maxLength: 31, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: TextField( + controller: pskController, + decoration: InputDecoration( + labelText: dialogContext.l10n.channels_pskHex, + border: const OutlineInputBorder(), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Expanded( + child: FilledButton( + onPressed: () { + final name = nameController.text.trim(); + final pskHex = pskController.text.trim(); + if (name.isEmpty) { + ScaffoldMessenger.of(dialogContext).showSnackBar( + SnackBar(content: Text(dialogContext.l10n.channels_enterChannelName)), + ); + return; + } + Uint8List psk; + try { + psk = Channel.parsePskHex(pskHex); + } on FormatException { + ScaffoldMessenger.of(dialogContext).showSnackBar( + SnackBar(content: Text(dialogContext.l10n.channels_pskMustBe32Hex)), + ); + return; + } + Navigator.pop(dialogContext); + connector.setChannel(nextIndex, name, psk); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.channels_channelAdded(name))), + ); + } + }, + child: Text(dialogContext.l10n.common_add), + ), + ), + ], + ), + ), + ], + ); + + case 2: // Join Public Channel + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: [ + Expanded( + child: FilledButton( + onPressed: () { + final psk = Channel.parsePskHex(Channel.publicChannelPsk); + Navigator.pop(dialogContext); + connector.setChannel(nextIndex, 'Public', psk); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.channels_publicChannelAdded)), + ); + } + }, + child: Text(dialogContext.l10n.common_add), + ), + ), + ], ), - ], - ], - ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(dialogContext), - child: Text(dialogContext.l10n.common_cancel), - ), - FilledButton( - onPressed: () { - final name = nameController.text.trim(); - final pskHex = usePublicPsk - ? Channel.publicChannelPsk - : pskController.text.trim(); + ); - if (name.isEmpty) { - ScaffoldMessenger.of(dialogContext).showSnackBar( - SnackBar(content: Text(dialogContext.l10n.channels_enterChannelName)), - ); - return; - } + case 3: // Join Hashtag Channel + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: TextField( + controller: hashtagController, + decoration: InputDecoration( + labelText: dialogContext.l10n.channels_enterHashtag, + hintText: dialogContext.l10n.channels_hashtagHint, + border: const OutlineInputBorder(), + prefixIcon: const Icon(Icons.tag), + ), + maxLength: 31, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Expanded( + child: FilledButton( + onPressed: () { + var hashtag = hashtagController.text.trim(); + if (hashtag.isEmpty) { + ScaffoldMessenger.of(dialogContext).showSnackBar( + SnackBar(content: Text(dialogContext.l10n.channels_enterChannelName)), + ); + return; + } + // Normalize hashtag name + final name = hashtag.startsWith('#') ? hashtag : '#$hashtag'; + final psk = Channel.derivePskFromHashtag(hashtag); + Navigator.pop(dialogContext); + connector.setChannel(nextIndex, name, psk); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.channels_channelAdded(name))), + ); + } + }, + child: Text(dialogContext.l10n.common_add), + ), + ), + ], + ), + ), + ], + ); - Uint8List psk; - try { - psk = Channel.parsePskHex(pskHex); - } on FormatException { - ScaffoldMessenger.of(dialogContext).showSnackBar( - SnackBar(content: Text(dialogContext.l10n.channels_pskMustBe32Hex)), - ); - return; - } + default: + return null; + } + } - Navigator.pop(dialogContext); - connector.setChannel(selectedIndex, name, psk); - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.channels_channelAdded(name))), - ); - } - }, - child: Text(dialogContext.l10n.common_add), + return AlertDialog( + title: Text(dialogContext.l10n.channels_addChannel), + contentPadding: const EdgeInsets.symmetric(vertical: 16), + content: SizedBox( + width: double.maxFinite, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + buildOptionTile( + optionIndex: 0, + icon: Icons.add, + title: dialogContext.l10n.channels_createPrivateChannel, + subtitle: dialogContext.l10n.channels_createPrivateChannelDesc, + ), + if (selectedOption == 0) buildExpandedContent()!, + const Divider(height: 1), + buildOptionTile( + optionIndex: 1, + icon: Icons.lock, + title: dialogContext.l10n.channels_joinPrivateChannel, + subtitle: dialogContext.l10n.channels_joinPrivateChannelDesc, + ), + if (selectedOption == 1) buildExpandedContent()!, + if (!hasPublicChannel) ...[ + const Divider(height: 1), + buildOptionTile( + optionIndex: 2, + icon: Icons.public, + title: dialogContext.l10n.channels_joinPublicChannel, + subtitle: dialogContext.l10n.channels_joinPublicChannelDesc, + ), + if (selectedOption == 2) buildExpandedContent()!, + ], + const Divider(height: 1), + buildOptionTile( + optionIndex: 3, + icon: Icons.tag, + title: dialogContext.l10n.channels_joinHashtagChannel, + subtitle: dialogContext.l10n.channels_joinHashtagChannelDesc, + ), + if (selectedOption == 3) buildExpandedContent()!, + const Divider(height: 1), + buildOptionTile( + optionIndex: 4, + icon: Icons.qr_code, + title: dialogContext.l10n.channels_scanQrCode, + subtitle: dialogContext.l10n.channels_scanQrCodeComingSoon, + enabled: false, + ), + ], + ), + ), ), - ], - ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(dialogContext), + child: Text(dialogContext.l10n.common_close), + ), + ], + ); + }, ), ); } diff --git a/tools/translate.py b/tools/translate.py index 54dd3bc..8a82100 100644 --- a/tools/translate.py +++ b/tools/translate.py @@ -10,6 +10,7 @@ Translates ARB/JSON localization values using a local Ollama model, while: - printing progress as it runs Usage: + # Translate all strings: python translate_arb_with_ollama.py \ --in /home/zjs81/Desktop/meshcore-open/lib/l10n/app_en.arb \ --out /home/zjs81/Desktop/meshcore-open/lib/l10n/app_es.arb \ @@ -17,12 +18,28 @@ Usage: --model ministral-3:latest \ --temperature 0 \ --concurrency 4 + + # Translate only missing/untranslated strings: + python translate_arb_with_ollama.py \ + --in /home/zjs81/Desktop/meshcore-open/lib/l10n/app_en.arb \ + --out /home/zjs81/Desktop/meshcore-open/lib/l10n/app_es.arb \ + --to-locale es \ + --missing-only \ + --model ministral-3:latest + + # Translate all locales (missing strings only): + python translate_arb_with_ollama.py \ + --in /home/zjs81/Desktop/meshcore-open/lib/l10n/app_en.arb \ + --l10n-dir /home/zjs81/Desktop/meshcore-open/lib/l10n \ + --missing-only \ + --model ministral-3:latest """ from __future__ import annotations import argparse import json +import os import re import sys import time @@ -448,11 +465,48 @@ def fmt_duration(seconds: float) -> str: return f"{h}h {m2}m" +def find_missing_keys(source_data: Dict[str, Any], target_data: Dict[str, Any]) -> List[str]: + """Find keys that are in source but not in target (excluding metadata keys).""" + missing = [] + for key in source_data: + if key == "@@locale": + continue + if key.startswith("@"): + continue + if key not in target_data: + missing.append(key) + return missing + + +def get_all_locale_files(l10n_dir: str, template_file: str) -> List[Tuple[str, str]]: + """Find all locale .arb files in the directory, excluding the template. + + Returns list of (locale_code, file_path) tuples. + """ + locales = [] + template_basename = os.path.basename(template_file) + + for filename in os.listdir(l10n_dir): + if not filename.endswith('.arb'): + continue + if filename == template_basename: + continue + # Extract locale from filename like app_es.arb -> es + if filename.startswith('app_') and filename.endswith('.arb'): + locale = filename[4:-4] # Remove 'app_' prefix and '.arb' suffix + filepath = os.path.join(l10n_dir, filename) + locales.append((locale, filepath)) + + return sorted(locales) + + def main() -> int: ap = argparse.ArgumentParser() - ap.add_argument("--in", dest="in_path", required=True, help="Input .arb/.json file path") - ap.add_argument("--out", dest="out_path", required=True, help="Output .arb/.json file path") - ap.add_argument("--to-locale", required=True, help="Target locale code, e.g. es, fr, de") + ap.add_argument("--in", dest="in_path", required=True, help="Input .arb/.json file path (source/template)") + ap.add_argument("--out", dest="out_path", default=None, help="Output .arb/.json file path (required unless using --l10n-dir)") + ap.add_argument("--to-locale", default=None, help="Target locale code, e.g. es, fr, de (required unless using --l10n-dir)") + ap.add_argument("--l10n-dir", default=None, help="Directory containing locale .arb files. When set, translates all locales.") + ap.add_argument("--missing-only", action="store_true", help="Only translate keys missing from target file") ap.add_argument("--target-lang", default=None, help="Target language name for the model, e.g. Spanish (defaults from locale)") ap.add_argument("--model", default="gemma3:4b", help="Ollama model name") ap.add_argument("--fallback-model", default=None, help="Larger model to use for low-confidence translations") @@ -504,19 +558,119 @@ def main() -> int: "vi": "Vietnamese", "id": "Indonesian", } - target_lang = args.target_lang or locale_map.get(args.to_locale, args.to_locale) + # Read source/template file try: with open(args.in_path, "r", encoding="utf-8") as f: - data = json.load(f) + source_data = json.load(f) except Exception as e: print(f"Failed to read input: {e}", file=sys.stderr) return 2 - if not isinstance(data, dict): + if not isinstance(source_data, dict): print("Input JSON must be an object at top-level.", file=sys.stderr) return 2 + # If --l10n-dir is provided, process all locale files + if args.l10n_dir: + locales = get_all_locale_files(args.l10n_dir, args.in_path) + if not locales: + print(f"No locale files found in {args.l10n_dir}", file=sys.stderr) + return 1 + + print(f"Found {len(locales)} locale file(s) to process") + + total_translated = 0 + for locale_code, locale_path in locales: + target_lang = locale_map.get(locale_code, locale_code) + + # Read existing target file + try: + with open(locale_path, "r", encoding="utf-8") as f: + target_data = json.load(f) + except Exception as e: + print(f" [{locale_code}] Failed to read {locale_path}: {e}") + continue + + if args.missing_only: + missing_keys = find_missing_keys(source_data, target_data) + if not missing_keys: + print(f" [{locale_code}] No missing keys") + continue + print(f" [{locale_code}] {len(missing_keys)} missing key(s): {', '.join(missing_keys[:5])}{'...' if len(missing_keys) > 5 else ''}") + else: + missing_keys = None + + # Run translation for this locale + result = translate_locale( + source_data=source_data, + target_data=target_data, + target_locale=locale_code, + target_lang=target_lang, + out_path=locale_path, + args=args, + locale_map=locale_map, + missing_keys=missing_keys, + ) + total_translated += result + + print(f"\nTotal: {total_translated} string(s) translated across {len(locales)} locale(s)") + return 0 + + # Single locale mode - validate required args + if not args.out_path: + print("--out is required when not using --l10n-dir", file=sys.stderr) + return 1 + if not args.to_locale: + print("--to-locale is required when not using --l10n-dir", file=sys.stderr) + return 1 + + target_lang = args.target_lang or locale_map.get(args.to_locale, args.to_locale) + + # Read existing target file if --missing-only and file exists + target_data: Dict[str, Any] = {} + missing_keys: Optional[List[str]] = None + if args.missing_only: + if os.path.exists(args.out_path): + try: + with open(args.out_path, "r", encoding="utf-8") as f: + target_data = json.load(f) + missing_keys = find_missing_keys(source_data, target_data) + if not missing_keys: + print(f"No missing keys in {args.out_path}") + return 0 + print(f"Found {len(missing_keys)} missing key(s) to translate") + except Exception as e: + print(f"Failed to read target file: {e}", file=sys.stderr) + return 2 + else: + print(f"Target file {args.out_path} does not exist. Will translate all strings.") + + result = translate_locale( + source_data=source_data, + target_data=target_data, + target_locale=args.to_locale, + target_lang=target_lang, + out_path=args.out_path, + args=args, + locale_map=locale_map, + missing_keys=missing_keys, + ) + return 0 if result >= 0 else 1 + + +def translate_locale( + source_data: Dict[str, Any], + target_data: Dict[str, Any], + target_locale: str, + target_lang: str, + out_path: str, + args, + locale_map: Dict[str, str], + missing_keys: Optional[List[str]] = None, +) -> int: + """Translate a single locale. Returns number of strings translated.""" + cfg = OllamaConfig( host=args.host, model=args.model, @@ -540,17 +694,34 @@ def main() -> int: top_p=args.top_p, ) - out_data: Dict[str, Any] = dict(data) - out_data["@@locale"] = args.to_locale + # Start with target data (preserves existing translations) or source data + if target_data: + out_data: Dict[str, Any] = dict(target_data) + else: + out_data: Dict[str, Any] = dict(source_data) + out_data["@@locale"] = target_locale - items: List[Tuple[str, str]] = [(k, v) for k, v in data.items() if is_translatable_entry(k, v)] + # Build list of items to translate + if missing_keys is not None: + # Only translate missing keys + items: List[Tuple[str, str]] = [ + (k, source_data[k]) for k in missing_keys + if is_translatable_entry(k, source_data.get(k)) + ] + # Also copy over any metadata keys for missing items + for key in missing_keys: + meta_key = f"@{key}" + if meta_key in source_data: + out_data[meta_key] = source_data[meta_key] + else: + items: List[Tuple[str, str]] = [(k, v) for k, v in source_data.items() if is_translatable_entry(k, v)] # Apply manual translations first manual_count = 0 items_to_translate: List[Tuple[str, str]] = [] for k, v in items: - if k in MANUAL_TRANSLATIONS and args.to_locale in MANUAL_TRANSLATIONS[k]: - out_data[k] = MANUAL_TRANSLATIONS[k][args.to_locale] + if k in MANUAL_TRANSLATIONS and target_locale in MANUAL_TRANSLATIONS[k]: + out_data[k] = MANUAL_TRANSLATIONS[k][target_locale] manual_count += 1 else: items_to_translate.append((k, v)) @@ -560,8 +731,8 @@ def main() -> int: total = len(items_to_translate) if total == 0 and manual_count == 0: - print("No translatable string entries found (excluding @@locale and @metadata).", file=sys.stderr) - return 1 + print("No translatable string entries found (excluding @@locale and @metadata).") + return 0 if total == 0: print("All strings handled by manual translations.") @@ -705,18 +876,18 @@ def main() -> int: if args.dry_run: print("Dry run: not writing output file.") - return 0 + return translated_ok try: - with open(args.out_path, "w", encoding="utf-8") as f: + with open(out_path, "w", encoding="utf-8") as f: json.dump(out_data, f, ensure_ascii=False, indent=2) f.write("\n") except Exception as e: print(f"Failed to write output: {e}", file=sys.stderr) - return 2 + return -1 - print(f"Wrote: {args.out_path}") - return 0 + print(f"Wrote: {out_path}") + return translated_ok if __name__ == "__main__": From 1726119c3eded3d63cca8b1a0a48976c4b0dbfba Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Sat, 17 Jan 2026 10:48:46 -0500 Subject: [PATCH 14/16] Add a Github Action to build code in CI This adds a CI workflow in Github Actions to verify that the flutter builds compile for all supported platforms. I tried adding Windows, but it currently fails, so I excluded it from this initial set. --- .github/workflows/build.yml | 76 +++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..8826c55 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,76 @@ +name: Build + +on: + push: + pull_request: + +jobs: + android: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + - uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('android/gradle/wrapper/gradle-wrapper.properties', 'android/build.gradle', 'android/settings.gradle', 'android/app/build.gradle', 'pubspec.lock') }} + restore-keys: | + ${{ runner.os }}-gradle- + - run: flutter pub get + - run: flutter build apk --release --no-pub + + ios: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true + - run: flutter pub get + - run: flutter build ios --release --no-codesign --no-pub + + linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true + - name: Install Linux build deps + run: sudo apt-get update && sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev + - run: flutter pub get + - run: flutter build linux --release --no-pub + + macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true + - run: flutter pub get + - run: flutter build macos --release --no-pub + + web: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true + - run: flutter pub get + - run: flutter build web --release --no-pub From 6782347cf4cb32623856eb86c70cda5cdc1c67a1 Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Sat, 17 Jan 2026 11:00:34 -0500 Subject: [PATCH 15/16] Fix issues flagged by flutter analyze This fixes code quality issues that flutter analyze catches and adds a CI step to Github Actions to flag on any future issues. --- .github/workflows/flutter_analyze.yml | 23 ++++++++++++++++ lib/connector/meshcore_connector.dart | 6 ++--- lib/main.dart | 2 +- lib/screens/map_screen.dart | 3 ++- lib/screens/repeater_settings_screen.dart | 6 ++--- lib/screens/telemetry_screen.dart | 11 +++----- lib/services/ble_debug_log_service.dart | 32 ++++++++++++++++++++--- lib/services/message_retry_service.dart | 4 +-- 8 files changed, 65 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/flutter_analyze.yml diff --git a/.github/workflows/flutter_analyze.yml b/.github/workflows/flutter_analyze.yml new file mode 100644 index 0000000..af4a3b7 --- /dev/null +++ b/.github/workflows/flutter_analyze.yml @@ -0,0 +1,23 @@ +name: Flutter Analyze + +on: + pull_request: + push: + +jobs: + analyze: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + + - name: Install dependencies + run: flutter pub get + + - name: Analyze + run: flutter analyze --fatal-infos --fatal-warnings diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 584fbdc..8b32870 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -1766,7 +1766,7 @@ class MeshCoreConnector extends ChangeNotifier { _batteryMillivolts = readUint16LE(frame, 1); final volts = (_batteryMillivolts! / 1000.0).toStringAsFixed(2); _appDebugLogService?.info( - 'Pulled battery: $volts V (${_batteryMillivolts} mV)', + 'Pulled battery: $volts V ($_batteryMillivolts mV)', tag: 'Battery', ); notifyListeners(); @@ -2272,7 +2272,6 @@ class MeshCoreConnector extends ChangeNotifier { // [6-9] = estimated_timeout_ms (uint32) if (frame.length >= 10) { - final isFlood = frame[1] != 0; final ackHash = Uint8List.fromList(frame.sublist(2, 6)); final timeoutMs = readUint32LE(frame, 6); @@ -2618,7 +2617,7 @@ class MeshCoreConnector extends ChangeNotifier { final keyLen = psk.length < 16 ? psk.length : 16; key16.setRange(0, keyLen, psk); - final cipher = ECBBlockCipher(AESFastEngine()); + final cipher = ECBBlockCipher(AESEngine()); cipher.init(false, KeyParameter(key16)); final out = Uint8List(cipherText.length); for (var i = 0; i < cipherText.length; i += 16) { @@ -2992,7 +2991,6 @@ const int _phVerMask = 0x03; const int _routeTransportFlood = 0x00; const int _routeFlood = 0x01; -const int _routeDirect = 0x02; const int _routeTransportDirect = 0x03; const int _payloadTypeGroupText = 0x05; diff --git a/lib/main.dart b/lib/main.dart index 4b59d8b..6dac19b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,7 +27,7 @@ void main() async { final storage = StorageService(); final connector = MeshCoreConnector(); final pathHistoryService = PathHistoryService(storage); - final retryService = MessageRetryService(storage); + final retryService = MessageRetryService(); final appSettingsService = AppSettingsService(); final bleDebugLogService = BleDebugLogService(); final appDebugLogService = AppDebugLogService(); diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 12e4470..2aa3daa 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -388,8 +388,9 @@ class _MapScreenState extends State { if (!contact.hasLocation) continue; // Apply node type filters - if (contact.type == advTypeRepeater && !settings.mapShowRepeaters) + if (contact.type == advTypeRepeater && !settings.mapShowRepeaters) { continue; + } if (contact.type == advTypeChat && !settings.mapShowChatNodes) continue; if (contact.type != advTypeChat && contact.type != advTypeRepeater && diff --git a/lib/screens/repeater_settings_screen.dart b/lib/screens/repeater_settings_screen.dart index 79da459..c757404 100644 --- a/lib/screens/repeater_settings_screen.dart +++ b/lib/screens/repeater_settings_screen.dart @@ -895,7 +895,7 @@ class _RepeaterSettingsScreenState extends State { ), const SizedBox(height: 16), DropdownButtonFormField( - value: _bandwidth, + initialValue: _bandwidth, decoration: InputDecoration( labelText: l10n.repeater_bandwidth, border: const OutlineInputBorder(), @@ -917,7 +917,7 @@ class _RepeaterSettingsScreenState extends State { ), const SizedBox(height: 16), DropdownButtonFormField( - value: _spreadingFactor, + initialValue: _spreadingFactor, decoration: InputDecoration( labelText: l10n.repeater_spreadingFactor, border: const OutlineInputBorder(), @@ -939,7 +939,7 @@ class _RepeaterSettingsScreenState extends State { ), const SizedBox(height: 16), DropdownButtonFormField( - value: _codingRate, + initialValue: _codingRate, decoration: InputDecoration( labelText: l10n.repeater_codingRate, border: const OutlineInputBorder(), diff --git a/lib/screens/telemetry_screen.dart b/lib/screens/telemetry_screen.dart index 9210e3d..8770938 100644 --- a/lib/screens/telemetry_screen.dart +++ b/lib/screens/telemetry_screen.dart @@ -1,7 +1,4 @@ import 'dart:async'; -import 'dart:convert'; -import 'dart:math'; -import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -34,7 +31,6 @@ class _TelemetryScreenState extends State { static const int _statusResponseBytes = _statusPayloadOffset + _statusStatsSize; Uint8List _tagData = Uint8List(4); - int _timeEstment = 0; bool _isLoading = false; bool _isLoaded = false; @@ -64,18 +60,19 @@ class _TelemetryScreenState extends State { if (frame[0] == respCodeSent) { _tagData = frame.sublist(2, 6); - _timeEstment = frame.buffer.asByteData().getUint32(6, Endian.little); } // Check if it's a binary response if (frame[0] == pushCodeBinaryResponse && listEquals(frame.sublist(2, 6), _tagData)) { - _handleStatusResponse(context, frame.sublist(6)); + if (!mounted) return; + _handleStatusResponse(frame.sublist(6)); } }); } - void _handleStatusResponse(BuildContext context, Uint8List frame) { + void _handleStatusResponse(Uint8List frame) { + if (!mounted) return; setState(() { _parsedTelemetry = CayenneLpp.parseByChannel(frame); }); diff --git a/lib/services/ble_debug_log_service.dart b/lib/services/ble_debug_log_service.dart index 6313971..002161b 100644 --- a/lib/services/ble_debug_log_service.dart +++ b/lib/services/ble_debug_log_service.dart @@ -86,14 +86,26 @@ class BleDebugLogService extends ChangeNotifier { } String _describeFrame(int code, Uint8List frame, bool outgoing, String? note) { - final label = _codeLabel(code); + final label = _codeLabel(code, outgoing: outgoing); final prefix = outgoing ? 'TX' : 'RX'; final extra = _frameDetail(code, frame); final noteText = note != null ? ' • $note' : ''; return '$prefix $label$extra$noteText'; } - String _codeLabel(int code) { + String _codeLabel(int code, {required bool outgoing}) { + if (outgoing) { + return _commandLabel(code) ?? 'CODE_$code'; + } + + final pushLabel = _pushLabel(code); + if (pushLabel != null) return pushLabel; + final responseLabel = _responseLabel(code); + if (responseLabel != null) return responseLabel; + return 'CODE_$code'; + } + + String? _commandLabel(int code) { switch (code) { case cmdAppStart: return 'CMD_APP_START'; @@ -135,6 +147,13 @@ class BleDebugLogService extends ChangeNotifier { return 'CMD_SET_CHANNEL'; case cmdGetRadioSettings: return 'CMD_GET_RADIO_SETTINGS'; + default: + return null; + } + } + + String? _responseLabel(int code) { + switch (code) { case respCodeOk: return 'RESP_CODE_OK'; case respCodeErr: @@ -167,6 +186,13 @@ class BleDebugLogService extends ChangeNotifier { return 'RESP_CODE_CHANNEL_INFO'; case respCodeRadioSettings: return 'RESP_CODE_RADIO_SETTINGS'; + default: + return null; + } + } + + String? _pushLabel(int code) { + switch (code) { case pushCodeAdvert: return 'PUSH_CODE_ADVERT'; case pushCodePathUpdated: @@ -184,7 +210,7 @@ class BleDebugLogService extends ChangeNotifier { case pushCodeNewAdvert: return 'PUSH_CODE_NEW_ADVERT'; default: - return 'CODE_$code'; + return null; } } diff --git a/lib/services/message_retry_service.dart b/lib/services/message_retry_service.dart index 0b96ba4..403cd93 100644 --- a/lib/services/message_retry_service.dart +++ b/lib/services/message_retry_service.dart @@ -6,7 +6,6 @@ import 'package:crypto/crypto.dart'; import '../models/contact.dart'; import '../models/message.dart'; import '../models/path_selection.dart'; -import 'storage_service.dart'; import 'app_settings_service.dart'; import 'app_debug_log_service.dart'; @@ -36,7 +35,6 @@ class MessageRetryService extends ChangeNotifier { static const int maxRetries = 5; static const int maxAckHistorySize = 100; - final StorageService _storage; final Map _timeoutTimers = {}; final Map _pendingMessages = {}; final Map _pendingContacts = {}; @@ -59,7 +57,7 @@ class MessageRetryService extends ChangeNotifier { AppDebugLogService? _debugLogService; Function(String, PathSelection, bool, int?)? _recordPathResultCallback; - MessageRetryService(this._storage); + MessageRetryService(); void initialize({ required Function(Contact, String, int, int) sendMessageCallback, From 09e60cebd9d0599c56e6175fb32b9a65841c555b Mon Sep 17 00:00:00 2001 From: ericz Date: Sat, 17 Jan 2026 17:03:39 +0100 Subject: [PATCH 16/16] German translation V2 --- lib/l10n/app_de.arb | 262 ++++++++++++++-------------- lib/l10n/app_localizations_de.dart | 270 ++++++++++++++--------------- 2 files changed, 265 insertions(+), 267 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index b6de02a..9d2701f 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -69,7 +69,7 @@ }, "scanner_stop": "Stopp", "scanner_scan": "Scannen", - "device_quickSwitch": "Schneller Umschalten", + "device_quickSwitch": "Schnelles Umschalten", "device_meshcore": "MeshCore", "settings_title": "Einstellungen", "settings_deviceInfo": "Geräteinformationen", @@ -78,7 +78,7 @@ "settings_nodeSettings": "Knoten-Einstellungen", "settings_nodeName": "Knotenname", "settings_nodeNameNotSet": "Nicht festgelegt", - "settings_nodeNameHint": "Gib den Knotenamen ein", + "settings_nodeNameHint": "Gebe den Knotenamen ein", "settings_nodeNameUpdated": "Name aktualisiert", "settings_radioSettings": "Funk Einstellungen", "settings_radioSettingsSubtitle": "Frequenz, Leistung, Verbreitungsfaktor", @@ -90,17 +90,17 @@ "settings_locationInvalid": "Ungültige Breiten- oder Längengrade.", "settings_latitude": "Breitengrad", "settings_longitude": "Längengrad", - "settings_privacyMode": "Privatschutzzustand", - "settings_privacyModeSubtitle": "Verstecken Sie Name/Ort in Anzeigen", - "settings_privacyModeToggle": "Aktivieren Sie den Datenschutzzustand, um Ihren Namen und Ihre Standortdaten in Anzeigen zu verbergen.", - "settings_privacyModeEnabled": "Privatschutzzustand aktiviert", + "settings_privacyMode": "Privatsphäreeinstellung", + "settings_privacyModeSubtitle": "Verstecken Sie Name/Ort in Ankündigungen", + "settings_privacyModeToggle": "Aktivieren Sie die Privatsphäreeinstellung, um Ihren Namen und Ihre Standortdaten in Ankündigungen zu verbergen.", + "settings_privacyModeEnabled": "Datenschutzmodus aktiviert", "settings_privacyModeDisabled": "Datenschutzmodus deaktiviert", "settings_actions": "Aktionen", - "settings_sendAdvertisement": "Senden Sie Anzeige", - "settings_sendAdvertisementSubtitle": "Sendungsstatus jetzt", - "settings_advertisementSent": "Anzeige gesendet", - "settings_syncTime": "Synchronisierungszeit", - "settings_syncTimeSubtitle": "Stelle die Gerätewielfalt auf die Uhrzeit des Telefons ein", + "settings_sendAdvertisement": "Sende eine Ankündigung", + "settings_sendAdvertisementSubtitle": "Sende Ankündigung", + "settings_advertisementSent": "Ankündigung gesendet", + "settings_syncTime": "Zeitsynchronisierung", + "settings_syncTimeSubtitle": "Stelle die Gerätezeit auf die Uhrzeit des Telefons ein", "settings_timeSynchronized": "Zeit synchronisiert", "settings_refreshContacts": "Kontakte aktualisieren", "settings_refreshContactsSubtitle": "Kontakte-Liste vom Gerät neu laden", @@ -128,8 +128,8 @@ "settings_infoStatus": "Status", "settings_infoBattery": "Akku", "settings_infoPublicKey": "Öffentlicher Schlüssel", - "settings_infoContactsCount": "Kontakte Anzahl", - "settings_infoChannelCount": "Kanalanzahl", + "settings_infoContactsCount": "Anzahl Kontakte", + "settings_infoChannelCount": "Anzahl Kanäle", "settings_presets": "Voreinstellungen", "settings_preset915Mhz": "915 MHz", "settings_preset868Mhz": "868 MHz", @@ -139,11 +139,11 @@ "settings_frequencyInvalid": "Ungültige Frequenz (300-2500 MHz)", "settings_bandwidth": "Bandbreite", "settings_spreadingFactor": "Verteilungsfaktor", - "settings_codingRate": "Programmierpauschale", + "settings_codingRate": "Kodierungsrate", "settings_txPower": "TX-Leistung (dBm)", "settings_txPowerHelper": "0 - 22", "settings_txPowerInvalid": "Ungültige TX-Leistung (0-22 dBm)", - "settings_longRange": "Langreich", + "settings_longRange": "Grosse Reichweite", "settings_fastSpeed": "Schnelle Geschwindigkeit", "settings_error": "Fehler: {message}", "@settings_error": { @@ -157,7 +157,7 @@ "appSettings_appearance": "Aussehen", "appSettings_theme": "Theme", "appSettings_themeSystem": "Systemstandard", - "appSettings_themeLight": "Helligkeit", + "appSettings_themeLight": "Hell", "appSettings_themeDark": "Dunkel", "appSettings_language": "Sprache", "appSettings_languageSystem": "Systemstandard", @@ -176,19 +176,19 @@ "appSettings_languageBg": "Български", "appSettings_notifications": "Benachrichtigungen", "appSettings_enableNotifications": "Benachrichtigungen aktivieren", - "appSettings_enableNotificationsSubtitle": "Erhalte Benachrichtigungen für Nachrichten und Anzeigen", + "appSettings_enableNotificationsSubtitle": "Erhalte Benachrichtigungen für Nachrichten und Ankündigungen", "appSettings_notificationPermissionDenied": "Erlaubnis zur Benachrichtigung verweigert", "appSettings_notificationsEnabled": "Benachrichtigungen aktiviert", "appSettings_notificationsDisabled": "Benachrichtigungen deaktiviert", - "appSettings_messageNotifications": "Nachrichtenbenachrichtigungen", - "appSettings_messageNotificationsSubtitle": "Zeige Benachrichtigung beim Empfang neuer Nachrichten", - "appSettings_channelMessageNotifications": "Kanal-Nachrichten-Benachrichtigungen", + "appSettings_messageNotifications": "Direktnachrichten Benachrichtigungen", + "appSettings_messageNotificationsSubtitle": "Zeige Benachrichtigung beim Empfang neuer Direktnachrichten", + "appSettings_channelMessageNotifications": "Kanalnachrichten Benachrichtigungen", "appSettings_channelMessageNotificationsSubtitle": "Zeige Benachrichtigung beim Empfangen von Kanalnachrichten", - "appSettings_advertisementNotifications": "Werbeanzeigenbenachrichtigungen", + "appSettings_advertisementNotifications": "Ankündigungsbenachrichtigungen", "appSettings_advertisementNotificationsSubtitle": "Zeige Benachrichtigung, wenn neue Knoten entdeckt werden.", "appSettings_messaging": "Nachrichten", - "appSettings_clearPathOnMaxRetry": "Klares Pfad bei Max Wiederholungsversuch", - "appSettings_clearPathOnMaxRetrySubtitle": "Zurücksetzen des Kontaktpfads nach 5 fehlgeschlagenen Sendeverboten", + "appSettings_clearPathOnMaxRetry": "Lösche Pfade bei Max Wiederholungsversuchen", + "appSettings_clearPathOnMaxRetrySubtitle": "Zurücksetzen der Kontaktpfade nach 5 fehlgeschlagenen Sendeabbrüchen", "appSettings_pathsWillBeCleared": "Die Pfade werden nach 5 fehlgeschlagenen Versuchen gelöscht.", "appSettings_pathsWillNotBeCleared": "Die Pfade werden nicht automatisch gelöscht.", "appSettings_autoRouteRotation": "Automatische Routenrotation", @@ -226,10 +226,10 @@ } } }, - "appSettings_mapTimeFilter": "Kartent Zeitfilter", + "appSettings_mapTimeFilter": "Karten Zeitfilter", "appSettings_showNodesDiscoveredWithin": "Zeige Knoten, die innerhalb von:", - "appSettings_allTime": "Alle Zeit", - "appSettings_lastHour": "Letzter Stunde", + "appSettings_allTime": "Ganzer Zeitverlauf", + "appSettings_lastHour": "Letzte Stunde", "appSettings_last6Hours": "Letzte 6 Stunden", "appSettings_last24Hours": "Letzte 24 Stunden", "appSettings_lastWeek": "Letzte Woche", @@ -252,13 +252,13 @@ "appSettings_appDebugLoggingEnabled": "App-Debug-Protokollierung aktiviert", "appSettings_appDebugLoggingDisabled": "App-Debug-Protokollierung deaktiviert", "contacts_title": "Kontakte", - "contacts_noContacts": "No Contacts noch", - "contacts_contactsWillAppear": "Kontakte werden angezeigt, wenn Geräte Werbung machen.", + "contacts_noContacts": "Noch keine Kontakte vorhanden.", + "contacts_contactsWillAppear": "Kontakte werden angezeigt, wenn Geräte eine Ankündigung machen.", "contacts_searchContacts": "Suche Kontakte...", - "contacts_noUnreadContacts": "Keine ungeklärten Kontakte", + "contacts_noUnreadContacts": "Keine ungesehene Kontakte", "contacts_noContactsFound": "Keine Kontakte oder Gruppen gefunden.", - "contacts_deleteContact": "Löschen Sie Kontakt", - "contacts_removeConfirm": "Entfernen {contactName} aus den Kontakten?", + "contacts_deleteContact": "Lösche den Kontakt", + "contacts_removeConfirm": "{contactName} aus den Kontakten entfernen?", "@contacts_removeConfirm": { "placeholders": { "contactName": { @@ -266,12 +266,12 @@ } } }, - "contacts_manageRepeater": "Wiederholung verwalten", + "contacts_manageRepeater": "Wiederholungen verwalten", "contacts_roomLogin": "Raum-Login", - "contacts_openChat": "Öffnen Sie Chat", - "contacts_editGroup": "Gruppen bearbeiten", + "contacts_openChat": "Öffne Chat", + "contacts_editGroup": "Gruppe bearbeiten", "contacts_deleteGroup": "Löschen Gruppe", - "contacts_deleteGroupConfirm": "Löschen Sie \"{groupName}\"?", + "contacts_deleteGroupConfirm": "Löschen von \"{groupName}\"?", "@contacts_deleteGroupConfirm": { "placeholders": { "groupName": { @@ -293,8 +293,8 @@ "contacts_filterContacts": "Filtert Kontakte...", "contacts_noContactsMatchFilter": "Keine Kontakte passen zu Ihrem Filter", "contacts_noMembers": "Keine Mitglieder", - "contacts_lastSeenNow": "Letztes Ansehen jetzt", - "contacts_lastSeenMinsAgo": "Letzte Sichtung {minutes} Minuten her.", + "contacts_lastSeenNow": "gerade gesehen", + "contacts_lastSeenMinsAgo": "Letzte Sichtung vor {minutes} Minuten.", "@contacts_lastSeenMinsAgo": { "placeholders": { "minutes": { @@ -303,7 +303,7 @@ } }, "contacts_lastSeenHourAgo": "Letzte Sichtung vor 1 Stunde.", - "contacts_lastSeenHoursAgo": "Letzte Aktivität vor {hours} Stunden.", + "contacts_lastSeenHoursAgo": "Letzte Sichtung vor {hours} Stunden.", "@contacts_lastSeenHoursAgo": { "placeholders": { "hours": { @@ -339,8 +339,8 @@ "channels_publicChannel": "Öffentlicher Kanal", "channels_privateChannel": "Privater Kanal", "channels_editChannel": "Kanal bearbeiten", - "channels_deleteChannel": "Löschen Sie Kanal", - "channels_deleteChannelConfirm": "Löschen \"{name}\"? Dies kann nicht rückgängig gemacht werden.", + "channels_deleteChannel": "Lösche den Kanal", + "channels_deleteChannelConfirm": "Löschen von \"{name}\"? Dies kann nicht rückgängig gemacht werden.", "@channels_deleteChannelConfirm": { "placeholders": { "name": { @@ -373,7 +373,7 @@ } } }, - "channels_editChannelTitle": "Bearbeiteten Kanal {index}", + "channels_editChannelTitle": "Bearbeiteter Kanal {index}", "@channels_editChannelTitle": { "placeholders": { "index": { @@ -392,10 +392,10 @@ }, "channels_publicChannelAdded": "Öffentlicher Kanal hinzugefügt", "channels_sortBy": "Sortiere nach", - "channels_sortManual": "Manuelle", + "channels_sortManual": "Manuell", "channels_sortAZ": "A bis Z", "channels_sortLatestMessages": "Letzte Nachrichten", - "channels_sortUnread": "Unlescht", + "channels_sortUnread": "Ungelesen", "chat_noMessages": "Noch keine Nachrichten.", "chat_sendMessageToStart": "Eine Nachricht senden, um anzufangen.", "chat_originalMessageNotFound": "Originalmeldung nicht gefunden", @@ -407,7 +407,7 @@ } } }, - "chat_replyTo": "Antworten Sie {name}", + "chat_replyTo": "Antwort an {name}", "@chat_replyTo": { "placeholders": { "name": { @@ -436,7 +436,7 @@ "chat_messageCopied": "Nachricht kopiert", "chat_messageDeleted": "Nachricht gelöscht", "chat_retryingMessage": "Versuche es erneut.", - "chat_retryCount": "Versuchen {current}/{max}", + "chat_retryCount": "Versuche {current}/{max}", "@chat_retryCount": { "placeholders": { "current": { @@ -457,22 +457,22 @@ "emojiCategoryObjects": "Objekte", "gifPicker_title": "Wähle ein GIF", "gifPicker_searchHint": "Suche nach GIFs...", - "gifPicker_poweredBy": "Angetrieben von GIPHY", + "gifPicker_poweredBy": "Bereitgestellt von GIPHY", "gifPicker_noGifsFound": "Keine GIFs gefunden", - "gifPicker_failedLoad": "GIF-Dateien konnten nicht geladen werden.", + "gifPicker_failedLoad": "GIF-Datei konnten nicht geladen werden.", "gifPicker_failedSearch": "Suche nach GIFs fehlgeschlagen", "gifPicker_noInternet": "Keine Internetverbindung", "debugLog_appTitle": "App-Debug-Protokoll", "debugLog_bleTitle": "BLE-Debug-Protokoll", - "debugLog_copyLog": "Kopieren Sie Protokoll", - "debugLog_clearLog": "Log löschen", + "debugLog_copyLog": "Kopieren des Protokolls", + "debugLog_clearLog": "Protokoll löschen", "debugLog_copied": "Debug-Protokoll kopiert", "debugLog_bleCopied": "BLE-Protokoll kopiert", "debugLog_noEntries": "No Debug-Protokolle noch verfügbar", "debugLog_enableInSettings": "Aktivieren Sie das App-Debug-Logging in den Einstellungen", "debugLog_frames": "Rahmen", "debugLog_rawLogRx": "Roh-Log-RX", - "debugLog_noBleActivity": "No BLE-Aktivität bisher", + "debugLog_noBleActivity": "Bisher keine BLE-Aktivität", "debugFrame_length": "Rahmenlänge: {count} Bytes", "@debugFrame_length": { "placeholders": { @@ -539,12 +539,12 @@ "chat_pathManagement": "Pfadverwaltung", "chat_routingMode": "Routenmodus", "chat_autoUseSavedPath": "Automatisch (gespeicherten Pfad verwenden)", - "chat_forceFloodMode": "Zwangsgelände-Modus erzwingen", + "chat_forceFloodMode": "Flut-Modus erzwingen", "chat_recentAckPaths": "Aktuelle ACK-Pfade (tasten, um zu verwenden):", "chat_pathHistoryFull": "Die Pfadhistorie ist voll. Entferne Einträge, um neue hinzuzufügen.", - "chat_hopSingular": "Springe", - "chat_hopPlural": "Hops", - "chat_hopsCount": "{count} {count, plural, =1{Hop} other{Hops}}", + "chat_hopSingular": "Sprung", + "chat_hopPlural": "Sprünge", + "chat_hopsCount": "{count} {count, plural, =1{Sprung} other{Sprünge}}", "@chat_hopsCount": { "placeholders": { "count": { @@ -552,17 +552,17 @@ } } }, - "chat_successes": "Erfolgreiche", + "chat_successes": "Erfolgreich", "chat_removePath": "Pfad entfernen", - "chat_noPathHistoryYet": "Noe eine Pfadhistorie vorhanden.\nSende eine Nachricht, um Pfade zu entdecken.", + "chat_noPathHistoryYet": "Keine eine Pfadhistorie vorhanden.\nSende eine Nachricht, um Pfade zu entdecken.", "chat_pathActions": "Pfadaktionen:", "chat_setCustomPath": "Lege benutzerdefinierten Pfad fest", - "chat_setCustomPathSubtitle": "Manuelle Routenpfad festlegen", - "chat_clearPath": "Klares Pfad", - "chat_clearPathSubtitle": "Zwinge bei nächster Sendung eine erneute Entdeckung durch.", - "chat_pathCleared": "Pfad freigelegt. Nächste Nachricht wird Route neu entdecken.", + "chat_setCustomPathSubtitle": "Manuellen Routenpfad festlegen", + "chat_clearPath": "Pfad zurücksetzen", + "chat_clearPathSubtitle": "Setze Pfad zurück, erkenne neuen Pfad bei nächster Sendung.", + "chat_pathCleared": "Pfad zurückgesetzt. Nächste Nachricht wird Route neu entdecken.", "chat_floodModeSubtitle": "Verwende den Routingschalter in der App-Leiste", - "chat_floodModeEnabled": "Flutmodus aktiviert. Über den Routing-Icon in der App-Leiste wieder aktivieren.", + "chat_floodModeEnabled": "Flutmodus aktiviert.", "chat_fullPath": "Vollständiger Pfad", "chat_pathDetailsNotAvailable": "Die Pfaddetails sind noch nicht verfügbar. Versuchen Sie, eine Nachricht zu senden, um zu aktualisieren.", "chat_pathSetHops": "Pfad gesetzt: {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}", @@ -576,15 +576,15 @@ } } }, - "chat_pathSavedLocally": "Gespeichert lokal. Mit Verbinden zum Synchronisieren.", + "chat_pathSavedLocally": "Lokal Gespeichert. Bitte Verbinden zum Synchronisieren.", "chat_pathDeviceConfirmed": "Gerät bestätigt.", "chat_pathDeviceNotConfirmed": "Gerät noch nicht bestätigt.", - "chat_type": "Gib ein", + "chat_type": "Gebe ein", "chat_path": "Pfad", "chat_publicKey": "Öffentlicher Schlüssel", - "chat_compressOutgoingMessages": "Komprimieren ausgehende Nachrichten", - "chat_floodForced": "Überschwemmung (erzwungen)", - "chat_directForced": "Direkt (gezwungen)", + "chat_compressOutgoingMessages": "Komprimieren ausgehender Nachrichten", + "chat_floodForced": "Geflutet (erzwungen)", + "chat_directForced": "Direkt (erzwungen)", "chat_hopsForced": "{count} Sprünge (erzwungen)", "@chat_hopsForced": { "placeholders": { @@ -593,10 +593,10 @@ } } }, - "chat_floodAuto": "Überschwemmung (automatisch)", + "chat_floodAuto": "Geflutet (automatisch)", "chat_direct": "Direkt", - "chat_poiShared": "Gemeinsamer POI", - "chat_unread": "Unlescht: {count}", + "chat_poiShared": "Geteilter POI", + "chat_unread": "Ungelesen: {count}", "@chat_unread": { "placeholders": { "count": { @@ -604,9 +604,9 @@ } } }, - "map_title": "Knotenkarte", + "map_title": "Karte", "map_noNodesWithLocation": "Keine Knoten mit Standortdaten", - "map_nodesNeedGps": "Knoten müssen ihre GPS-Koordinaten\nteilen,\num auf der Karte\nerscheinen.", + "map_nodesNeedGps": "Knoten müssen ihre GPS-Koordinaten teilen,\num auf der Karte zu erscheinen.", "map_nodesCount": "Knoten: {count}", "@map_nodesCount": { "placeholders": { @@ -623,24 +623,24 @@ } } }, - "map_chat": "Chat", - "map_repeater": "Wiederholung", + "map_chat": "Benutzer", + "map_repeater": "Repeater", "map_room": "Raum", "map_sensor": "Sensor", - "map_pinDm": "Sperren (DM)", - "map_pinPrivate": "Privat-Pin", - "map_pinPublic": "Öffentliche Taste (PIN)", + "map_pinDm": "Pin (Kontakt)", + "map_pinPrivate": "Pin (Channel)", + "map_pinPublic": "Pin (Public)", "map_lastSeen": "Letzte Sichtung", "map_disconnectConfirm": "Sind Sie sicher, dass Sie sich von diesem Gerät trennen möchten?", "map_from": "Von", "map_source": "Quelle", - "map_flags": "Flaggen", - "map_shareMarkerHere": "Teilen Sie hier das Marker.", - "map_pinLabel": "Kennzeichnungslabel", + "map_flags": "Flags", + "map_shareMarkerHere": "Teilen Sie den Marker hier.", + "map_pinLabel": "Pin Name", "map_label": "Label", "map_pointOfInterest": "Punkt von Interesse", "map_sendToContact": "Senden an Kontakt", - "map_sendToChannel": "Senden Sie Kanal", + "map_sendToChannel": "Senden an Kanal", "map_noChannelsAvailable": "Keine Kanäle verfügbar", "map_publicLocationShare": "Öffentliche Standortfreigabe", "map_publicLocationShareConfirm": "Sie werden kurz darauf einen Ort in {channelLabel} teilen. Dieser Kanal ist öffentlich und jeder mit dem PSK kann ihn sehen.", @@ -652,25 +652,25 @@ } }, "map_connectToShareMarkers": "Verbinde ein Gerät, um Marker zu teilen", - "map_filterNodes": "Filter Knoten", + "map_filterNodes": "Knotenfilter", "map_nodeTypes": "Knotentypen", "map_chatNodes": "Chat-Knoten", - "map_repeaters": "Wiederholer", + "map_repeaters": "Repeater", "map_otherNodes": "Andere Knoten", "map_keyPrefix": "Schlüsselpräfix", "map_filterByKeyPrefix": "Filter nach Schlüsselpräfix", - "map_publicKeyPrefix": "Öffentlicher Schlüsselpräfix", + "map_publicKeyPrefix": "Schlüsselpräfix", "map_markers": "Marker", "map_showSharedMarkers": "Zeige gemeinsam genutzte Marker", "map_lastSeenTime": "Letzte Sichtung", "map_sharedPin": "Gemeinsames Passwort", "map_joinRoom": "Beitreten Sie dem Raum", - "map_manageRepeater": "Wiederholung verwalten", + "map_manageRepeater": "Repeater verwalten", "mapCache_title": "Offline-Karten-Cache", "mapCache_selectAreaFirst": "Wählen Sie zuerst einen Bereich zum Zwischenspeichern aus.", - "mapCache_noTilesToDownload": "Keine Tiles für diese Region zum Herunterladen verfügbar.", - "mapCache_downloadTilesTitle": "Herunterladen von Tiles", - "mapCache_downloadTilesPrompt": "Laden {count} Tiles für den Offline-Bereich herunter?", + "mapCache_noTilesToDownload": "Keine Kacheln für diese Region zum Herunterladen verfügbar.", + "mapCache_downloadTilesTitle": "Herunterladen von Kacheln", + "mapCache_downloadTilesPrompt": "Laden {count} Kacheln für den Offline-Bereich herunter?", "@mapCache_downloadTilesPrompt": { "placeholders": { "count": { @@ -679,7 +679,7 @@ } }, "mapCache_downloadAction": "Herunterladen", - "mapCache_cachedTiles": "Zwischengespeicherte {count} Fliesen", + "mapCache_cachedTiles": "Zwischengespeicherte {count} Kacheln", "@mapCache_cachedTiles": { "placeholders": { "count": { @@ -687,7 +687,7 @@ } } }, - "mapCache_cachedTilesWithFailed": "Zwischengespeicherte {downloaded} Tiles ({failed} fehlgeschlagen)", + "mapCache_cachedTilesWithFailed": "Zwischengespeicherte {downloaded} Kacheln ({failed} fehlgeschlagen)", "@mapCache_cachedTilesWithFailed": { "placeholders": { "downloaded": { @@ -698,7 +698,7 @@ } } }, - "mapCache_clearOfflineCacheTitle": "Leeren Offline-Cache", + "mapCache_clearOfflineCacheTitle": "Leere Offline-Cache", "mapCache_clearOfflineCachePrompt": "Alle zwischengespeicherten Kartenraster entfernen?", "mapCache_offlineCacheCleared": "Offline-Cache gelöscht", "mapCache_noAreaSelected": "Kein Bereich ausgewählt", @@ -724,7 +724,7 @@ } } }, - "mapCache_downloadTilesButton": "Herunterladen von Tiles", + "mapCache_downloadTilesButton": "Herunterladen von Kacheln", "mapCache_clearCacheButton": "Cache leeren", "mapCache_failedDownloads": "Fehlgeschlagene Downloads: {count}", "@mapCache_failedDownloads": { @@ -785,10 +785,10 @@ "time_month": "Monat", "time_months": "Monate", "time_minutes": "Minuten", - "time_allTime": "Alle Zeit", + "time_allTime": "Ganzer Zeitraum", "dialog_disconnect": "Trennen", "dialog_disconnectConfirm": "Sind Sie sicher, dass Sie sich von diesem Gerät trennen möchten?", - "login_repeaterLogin": "Wiederholungseingang anmelden", + "login_repeaterLogin": "Beim Repeater anmelden", "login_roomLogin": "Raum-Login", "login_password": "Passwort", "login_enterPassword": "Passwort eingeben", @@ -799,7 +799,7 @@ "login_routing": "Routen", "login_routingMode": "Routenmodus", "login_autoUseSavedPath": "Automatisch (gespeicherten Pfad verwenden)", - "login_forceFloodMode": "Zwangsgelände-Modus erzwingen", + "login_forceFloodMode": "Flut-Modus erzwingen", "login_managePaths": "Pfadverwaltung", "login_login": "Anmelden", "login_attempt": "Versuche {current}/{max}", @@ -825,7 +825,7 @@ "common_reload": "Neu laden", "common_clear": "Löschen", - "path_currentPath": "Aktiger Pfad: {path}", + "path_currentPath": "Aktiver Pfad: {path}", "@path_currentPath": { "placeholders": { "path": { @@ -841,9 +841,9 @@ } } }, - "path_enterCustomPath": "Gib Pfad an", + "path_enterCustomPath": "Gebe Pfad ein", "path_currentPathLabel": "Aktueller Pfad", - "path_hexPrefixInstructions": "Gib für jeden Hopfen 2-stellige Hex-Präfixe ein, getrennt durch Kommas.", + "path_hexPrefixInstructions": "Gebe für jeden Hopfen 2-stellige Hex-Präfixe ein, getrennt durch Kommas.", "path_hexPrefixExample": "Beispiel: A1,F2,3C (jeder Knoten verwendet den ersten Byte seines öffentlichen Schlüssels)", "path_labelHexPrefixes": "Pfad (Hex-Präfixe)", "path_helperMaxHops": "Max 64 Sprünge. Jede Präfixe ist 2 Hexadezimalzeichen (1 Byte)", @@ -860,7 +860,7 @@ }, "path_tooLong": "Pfad zu lang. Maximal 64 Hops erlaubt.", "path_setPath": "Pfad festlegen", - "repeater_management": "Wiederholungselement-Verwaltung", + "repeater_management": "Repeater-Verwaltung", "repeater_managementTools": "Verwaltungs-Tools", "repeater_status": "Status", "repeater_statusSubtitle": "Status, Statistiken und Nachbarn anzeigen", @@ -869,11 +869,11 @@ "repeater_cli": "CLI", "repeater_cliSubtitle": "Sende Befehle an den Repeater", "repeater_settings": "Einstellungen", - "repeater_settingsSubtitle": "Wiederholungsparameter konfigurieren", - "repeater_statusTitle": "Wiederholungszustand", + "repeater_settingsSubtitle": "Repeater-parameter konfigurieren", + "repeater_statusTitle": "Repeaterstatus", "repeater_routingMode": "Routenmodus", "repeater_autoUseSavedPath": "Automatisch (gespeicherten Pfad verwenden)", - "repeater_forceFloodMode": "Zwangsgelände-Modus erzwingen", + "repeater_forceFloodMode": "Flut-Modus erzwingen", "repeater_pathManagement": "Pfadverwaltung", "repeater_refresh": "Aktualisieren", "repeater_statusRequestTimeout": "Statusanfrage zeitweise fehlgeschlagen.", @@ -965,9 +965,9 @@ } } }, - "repeater_settingsTitle": "Wiederholungseinstellungen", + "repeater_settingsTitle": "Repeater Einstellungen", "repeater_basicSettings": "Grundlegende Einstellungen", - "repeater_repeaterName": "Wiederholungseintrag", + "repeater_repeaterName": "Repeater Name", "repeater_repeaterNameHelper": "Anzeigename für diesen Repeater", "repeater_adminPassword": "Admin-Passwort", "repeater_adminPasswordHelper": "Vollzugriffspasswort", @@ -980,7 +980,7 @@ "repeater_txPowerHelper": "1-30 dBm", "repeater_bandwidth": "Bandbreite", "repeater_spreadingFactor": "Verteilungsfaktor", - "repeater_codingRate": "Programmierpauschale", + "repeater_codingRate": "Kodierungsrate", "repeater_locationSettings": "Standort Einstellungen", "repeater_latitude": "Breitengrad", "repeater_latitudeHelper": "Dezimalgrad (z.B. 37,7749)", @@ -991,10 +991,10 @@ "repeater_packetForwardingSubtitle": "Aktivieren Sie den Repeater, um Pakete weiterzuleiten.", "repeater_guestAccess": "Gastzugriff", "repeater_guestAccessSubtitle": "Gast-Zugriff mit beschränkten Rechten zulassen", - "repeater_privacyMode": "Privatschutzzustand", - "repeater_privacyModeSubtitle": "Verstecken Sie Name/Ort in Anzeigen", - "repeater_advertisementSettings": "Werbe Einstellungen", - "repeater_localAdvertInterval": "Lokaler Werbeintervall", + "repeater_privacyMode": "Privatsphäreeinstellung", + "repeater_privacyModeSubtitle": "Verstecken Sie Name/Ort in Ankündigungen", + "repeater_advertisementSettings": "Ankündigungseinstellungen", + "repeater_localAdvertInterval": "Intervall der lokalen Ankündigungen", "repeater_localAdvertIntervalMinutes": "{minutes} Minuten", "@repeater_localAdvertIntervalMinutes": { "placeholders": { @@ -1003,7 +1003,7 @@ } } }, - "repeater_floodAdvertInterval": "Überschwemmungsanzeige-Intervall", + "repeater_floodAdvertInterval": "Intervall der gefluteten Ankündigungen", "repeater_floodAdvertIntervalHours": "{hours} Stunden", "@repeater_floodAdvertIntervalHours": { "placeholders": { @@ -1012,7 +1012,7 @@ } } }, - "repeater_encryptedAdvertInterval": "Verschlüsselte Werbeintervall", + "repeater_encryptedAdvertInterval": "Intervall der verschlüsselten Ankündigung", "repeater_dangerZone": "Gefahrenzone", "repeater_rebootRepeater": "Neustart Repeater", "repeater_rebootRepeaterSubtitle": "Wiederholen Sie das Repeater-Gerät.", @@ -1052,12 +1052,12 @@ }, "repeater_refreshBasicSettings": "Grundlegende Einstellungen aktualisieren", "repeater_refreshRadioSettings": "Radio-Einstellungen aktualisieren", - "repeater_refreshTxPower": "Batterie-Strom aktualisieren", + "repeater_refreshTxPower": "Sendeleistung aktualisieren", "repeater_refreshLocationSettings": "Aktualisieren Sie die Standort Einstellungen", "repeater_refreshPacketForwarding": "Aktualisieren Paketweiterleitung", "repeater_refreshGuestAccess": "Aktualisieren Sie den Gastzugriff", "repeater_refreshPrivacyMode": "Wiederherstellen des Datenschutzzustands", - "repeater_refreshAdvertisementSettings": "Aktualisieren Sie die Werbe Einstellungen", + "repeater_refreshAdvertisementSettings": "Aktualisieren Sie die Ankündigungseinstellungen", "repeater_refreshed": "{label} wurde aktualisiert", "@repeater_refreshed": { "placeholders": { @@ -1074,10 +1074,10 @@ } } }, - "repeater_cliTitle": "Wiederholung CLI", + "repeater_cliTitle": "Repeater CLI", "repeater_debugNextCommand": "Fehlersuche Nächster Befehl", "repeater_commandHelp": "Hilfe", - "repeater_clearHistory": "Löschung der Historie", + "repeater_clearHistory": "Löschen der Historie", "repeater_noCommandsSent": "Noch keine Befehle gesendet.", "repeater_typeCommandOrUseQuick": "Geben Sie einen Befehl unten ein oder verwenden Sie Schnellbefehle", "repeater_enterCommandHint": "Geben Sie den Befehl ein...", @@ -1098,37 +1098,37 @@ "repeater_cliQuickGetTx": "Erhalte TX", "repeater_cliQuickNeighbors": "Nachbarn", "repeater_cliQuickVersion": "Version", - "repeater_cliQuickAdvertise": "Werben", + "repeater_cliQuickAdvertise": "Ankündigungen", "repeater_cliQuickClock": "Uhr", - "repeater_cliHelpAdvert": "Sendet ein Werbepaket", + "repeater_cliHelpAdvert": "Sendet eine Ankündigung", "repeater_cliHelpReboot": "Startet das Gerät neu. (Beachten Sie, dass es möglicherweise zu einer 'Timeout'-Situation kommt, was normal ist.)", "repeater_cliHelpClock": "Zeigt die aktuelle Uhrzeit pro Gerät an.", "repeater_cliHelpPassword": "Legt ein neues Administrator-Passwort für das Gerät fest.", "repeater_cliHelpVersion": "Zeigt die Geräteversion und das Datum des Firmware-Builds an.", - "repeater_cliHelpClearStats": "Setzt verschiedene Statistikkalkulate auf Null zurück.", + "repeater_cliHelpClearStats": "Setzt verschiedene Statistikberechnungen auf Null zurück.", "repeater_cliHelpSetAf": "Legt den Luftzeitfaktor fest.", "repeater_cliHelpSetTx": "Legt die LoRa-Übertragungspower in dBm (bezogen auf 1 Watt) fest. (Neustart erforderlich, um die Änderungen anzuwenden)", "repeater_cliHelpSetRepeat": "Aktiviert oder deaktiviert die Repeater-Rolle für diesen Knoten.", - "repeater_cliHelpSetAllowReadOnly": "(Raumspeicher) Wenn 'an', dann wird die Anmeldung mit einem leeren Passwort erlaubt sein, aber kann nicht in den Raum geschickt werden. (nur lesen möglich).", + "repeater_cliHelpSetAllowReadOnly": "(Raumspeicher) Wenn 'an', dann wird die Anmeldung mit einem leeren Passwort erlaubt sein, aber es kann nicht in den Raum gesendet werden. (nur lesen möglich).", "repeater_cliHelpSetFloodMax": "Legt die maximale Anzahl an Hops für Pakete der eingehenden Flut (wenn >= max, wird das Paket nicht weitergeleitet)", "repeater_cliHelpSetIntThresh": "Legt den Interferenzeniveau (in dB) fest. Der Standardwert ist 14. Auf 0 setzen, um die Erkennung von Kanalinterferenzen zu deaktivieren.", "repeater_cliHelpSetAgcResetInterval": "Legt das Intervall für das Zurücksetzen des Auto Gain Controllers fest. Auf 0 setzen, um die Funktion zu deaktivieren.", "repeater_cliHelpSetMultiAcks": "Aktiviert oder deaktiviert die Funktion 'Doppel-ACKs'.", - "repeater_cliHelpSetAdvertInterval": "Legt das Timer-Intervall in Minuten fest, um ein lokales (ohne-Weiterleitung) Werbe-Paket zu senden. Auf 0 setzen, um die Funktion zu deaktivieren.", - "repeater_cliHelpSetFloodAdvertInterval": "Legt das Timer-Intervall in Stunden für den Versand eines Flut-Werbungspakets fest. Auf 0 setzen, um es zu deaktivieren.", + "repeater_cliHelpSetAdvertInterval": "Legt das Timer-Intervall in Minuten fest, um ein lokales (ohne-Weiterleitung) Ankündigungspaket zu senden. Auf 0 setzen, um die Funktion zu deaktivieren.", + "repeater_cliHelpSetFloodAdvertInterval": "Legt das Timer-Intervall in Stunden für den Versand eines Flut-Ankündigungspacket fest. Auf 0 setzen, um es zu deaktivieren.", "repeater_cliHelpSetGuestPassword": "Legt/aktualisiert das Gastpasswort fest. (für Repeater können Gast-Logins die \"Get Stats\"-Anfrage senden)", "repeater_cliHelpSetName": "Legt den Anzeigenamen fest.", - "repeater_cliHelpSetLat": "Legt die Breitengrad-Angabe der Werbekarte fest. (dezimale Grad)", - "repeater_cliHelpSetLon": "Legt die Längengrade der Werbe-Map fest. (dezimale Grad)", + "repeater_cliHelpSetLat": "Legt die Breitengrad der Ankündigung fest. (dezimale Grad)", + "repeater_cliHelpSetLon": "Legt die Längengrade der Ankündigung fest. (dezimale Grad)", "repeater_cliHelpSetRadio": "Legt komplett neue Radio-Parameter fest und speichert diese als Präferenzen. Benötigt einen \"Reboot\"-Befehl, um sie anzuwenden.", "repeater_cliHelpSetRxDelay": "Sets (experimentell) als Basis (muss > 1 sein für den Effekt) zur Anwendung einer leichten Verzögerung bei empfangenen Paketen, basierend auf Signalstärke/Punktzahl. Auf 0 setzen, um die Funktion zu deaktivieren.", "repeater_cliHelpSetTxDelay": "Legt einen Faktor fest, der mit der Zeit bei voller Zuluft für ein Flood-Mode-Paket und mit einem zufälligen Slot-System multipliziert wird, um dessen Weiterleitung zu verzögern (um Kollisionen zu vermeiden).", "repeater_cliHelpSetDirectTxDelay": "Ähnlich wie txdelay, aber zum Anwenden einer zufälligen Verzögerung bei der Weiterleitung von Direktmodus-Paketen.", "repeater_cliHelpSetBridgeEnabled": "Brücke aktivieren/deaktivieren.", "repeater_cliHelpSetBridgeDelay": "Setze Verzögerung vor erneuter Übertragung von Paketen.", - "repeater_cliHelpSetBridgeSource": "Wählen Sie, ob die Brücke empfangene oder gesendete Pakete erneut übertragen soll.", + "repeater_cliHelpSetBridgeSource": "Wählen Sie, ob über die Brücke empfangene oder gesendete Pakete erneut übertragen soll.", "repeater_cliHelpSetBridgeBaud": "Setze die serielle Link-Baudrate für RS232-Brücken.", - "repeater_cliHelpSetBridgeSecret": "Richte das Espnow-Brücken-Geheimnis ein.", + "repeater_cliHelpSetBridgeSecret": "Richte das Brückenpassword ein.", "repeater_cliHelpSetAdcMultiplier": "Legt einen benutzerdefinierten Faktor zur Anpassung der gemeldeten Batteriewirkspannung fest (nur auf ausgewählten Boards unterstützt).", "repeater_cliHelpTempRadio": "Legt vorübergehende Funkparameter für die angegebene Anzahl von Minuten fest und kehrt anschließend zu den ursprünglichen Funkparametern zurück (wird nicht in den Einstellungen gespeichert).", "repeater_cliHelpSetPerm": "Ändert die ACL. Entfernt das passende Eintragen (durch Pubkey-Präfix), wenn \"permissions\" auf 0 steht. Fügt ein neues Eintragen hinzu, wenn die Pubkey-Hex-Länge vollständig ist und nicht bereits in der ACL vorhanden ist. Aktualisiert das Eintragen anhand des übereinstimmenden Pubkey-Präfix. Berechtigungsbits variieren je nach Firmware-Rolle, aber die unteren 2 Bits sind: 0 (Gast), 1 (Nur Lesen), 2 (Lesen/Schreiben), 3 (Admin)", @@ -1136,9 +1136,9 @@ "repeater_cliHelpLogStart": "Beginnt die Paketprotokollierung in das Dateisystem.", "repeater_cliHelpLogStop": "Stoppt das Paketprotokollieren in das Dateisystem.", "repeater_cliHelpLogErase": "Löscht die Paketprotokolle aus dem Dateisystem.", - "repeater_cliHelpNeighbors": "Zeigt eine Liste anderer Repeater-Knoten an, die über Zero-Hop-Werbung gehört wurden. Jede Zeile ist id-prefix-hex:timestamp:snr-times-4", + "repeater_cliHelpNeighbors": "Zeigt eine Liste anderer Repeater-Knoten an, die über Zero-Hop-Ankündigung gehört wurden. Jede Zeile ist id-prefix-hex:timestamp:snr-times-4", "repeater_cliHelpNeighborRemove": "Entfernt das erste übereinstimmende Element (über Pubkey-Präfix (hex)) aus der Liste der Nachbarn.", - "repeater_cliHelpRegion": "(Serien nur) Listet alle definierten Regionen und aktuelle Hochwassermissungen auf.", + "repeater_cliHelpRegion": "Listet alle definierten Regionen auf.", "repeater_cliHelpRegionLoad": "Hinweis: Dies ist ein spezieller Mehrbefehl-Aufruf. Jeder nachfolgende Befehl ist ein Regionsname (eingedruckt mit Leerzeichen zur Angabe der übergeordneten Hierarchie, mit mindestens einem Leerzeichen). Beendet durch das Senden einer Leerzeile/des Befehls.", "repeater_cliHelpRegionGet": "Sucht die Region mit dem gegebenen Namenspräfix (oder \"\\\" für den globalen Scope) und antwortet mit \"-> region-name (parent-name) 'F'\".", "repeater_cliHelpRegionPut": "Fügt eine Region-Definition mit dem angegebenen Namen hinzu oder aktualisiert diese.", @@ -1230,12 +1230,12 @@ "channelPath_title": "Paketpfad", "channelPath_viewMap": "Karte anzeigen", "channelPath_otherObservedPaths": "Sonstige beobachtete Pfade", - "channelPath_repeaterHops": "Wiederholungs-Sprünge", + "channelPath_repeaterHops": "Repeater-Sprünge", "channelPath_noHopDetails": "Die Detailangaben für dieses Paket sind nicht verfügbar.", "channelPath_messageDetails": "Nachrichtsdetails", "channelPath_senderLabel": "Sender", "channelPath_timeLabel": "Zeit", - "channelPath_repeatsLabel": "Wiederholung", + "channelPath_repeatsLabel": "Wiederholungen", "channelPath_pathLabel": "Pfad {index}", "channelPath_observedLabel": "Beobachtet", "channelPath_observedPathTitle": "Beobachteter Pfad {index} • {hops}", @@ -1273,7 +1273,7 @@ } }, "channelPath_unknownPath": "Unbekannt", - "channelPath_floodPath": "Überschwemmung", + "channelPath_floodPath": "Geflutet", "channelPath_directPath": "Direkt", "channelPath_observedZeroOf": "0 von {total} Sprüngen", "@channelPath_observedZeroOf": { @@ -1329,12 +1329,12 @@ "listFilter_tooltip": "Filteren und sortieren", "listFilter_sortBy": "Sortiere nach", "listFilter_latestMessages": "Letzte Nachrichten", - "listFilter_heardRecently": "Hörte kürzlich", + "listFilter_heardRecently": "Kürzlich gehört", "listFilter_az": "A-Z", "listFilter_filters": "Filtere", "listFilter_all": "Alle", "listFilter_users": "Benutzer", - "listFilter_repeaters": "Wiederholer", + "listFilter_repeaters": "Repeater", "listFilter_roomServers": "Raumserver", "listFilter_unreadOnly": "Nur nicht gelesen", "listFilter_newGroup": "Neue Gruppe", diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 7c3fc93..3d0cbcd 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -141,7 +141,7 @@ class AppLocalizationsDe extends AppLocalizations { String get scanner_scan => 'Scannen'; @override - String get device_quickSwitch => 'Schneller Umschalten'; + String get device_quickSwitch => 'Schnelles Umschalten'; @override String get device_meshcore => 'MeshCore'; @@ -169,7 +169,7 @@ class AppLocalizationsDe extends AppLocalizations { String get settings_nodeNameNotSet => 'Nicht festgelegt'; @override - String get settings_nodeNameHint => 'Gib den Knotenamen ein'; + String get settings_nodeNameHint => 'Gebe den Knotenamen ein'; @override String get settings_nodeNameUpdated => 'Name aktualisiert'; @@ -207,18 +207,18 @@ class AppLocalizationsDe extends AppLocalizations { String get settings_longitude => 'Längengrad'; @override - String get settings_privacyMode => 'Privatschutzzustand'; + String get settings_privacyMode => 'Privatsphäreeinstellung'; @override String get settings_privacyModeSubtitle => - 'Verstecken Sie Name/Ort in Anzeigen'; + 'Verstecken Sie Name/Ort in Ankündigungen'; @override String get settings_privacyModeToggle => - 'Aktivieren Sie den Datenschutzzustand, um Ihren Namen und Ihre Standortdaten in Anzeigen zu verbergen.'; + 'Aktivieren Sie die Privatsphäreeinstellung, um Ihren Namen und Ihre Standortdaten in Ankündigungen zu verbergen.'; @override - String get settings_privacyModeEnabled => 'Privatschutzzustand aktiviert'; + String get settings_privacyModeEnabled => 'Datenschutzmodus aktiviert'; @override String get settings_privacyModeDisabled => 'Datenschutzmodus deaktiviert'; @@ -227,20 +227,20 @@ class AppLocalizationsDe extends AppLocalizations { String get settings_actions => 'Aktionen'; @override - String get settings_sendAdvertisement => 'Senden Sie Anzeige'; + String get settings_sendAdvertisement => 'Sende eine Ankündigung'; @override - String get settings_sendAdvertisementSubtitle => 'Sendungsstatus jetzt'; + String get settings_sendAdvertisementSubtitle => 'Sende Ankündigung'; @override - String get settings_advertisementSent => 'Anzeige gesendet'; + String get settings_advertisementSent => 'Ankündigung gesendet'; @override - String get settings_syncTime => 'Synchronisierungszeit'; + String get settings_syncTime => 'Zeitsynchronisierung'; @override String get settings_syncTimeSubtitle => - 'Stelle die Gerätewielfalt auf die Uhrzeit des Telefons ein'; + 'Stelle die Gerätezeit auf die Uhrzeit des Telefons ein'; @override String get settings_timeSynchronized => 'Zeit synchronisiert'; @@ -309,10 +309,10 @@ class AppLocalizationsDe extends AppLocalizations { String get settings_infoPublicKey => 'Öffentlicher Schlüssel'; @override - String get settings_infoContactsCount => 'Kontakte Anzahl'; + String get settings_infoContactsCount => 'Anzahl Kontakte'; @override - String get settings_infoChannelCount => 'Kanalanzahl'; + String get settings_infoChannelCount => 'Anzahl Kanäle'; @override String get settings_presets => 'Voreinstellungen'; @@ -342,7 +342,7 @@ class AppLocalizationsDe extends AppLocalizations { String get settings_spreadingFactor => 'Verteilungsfaktor'; @override - String get settings_codingRate => 'Programmierpauschale'; + String get settings_codingRate => 'Kodierungsrate'; @override String get settings_txPower => 'TX-Leistung (dBm)'; @@ -354,7 +354,7 @@ class AppLocalizationsDe extends AppLocalizations { String get settings_txPowerInvalid => 'Ungültige TX-Leistung (0-22 dBm)'; @override - String get settings_longRange => 'Langreich'; + String get settings_longRange => 'Grosse Reichweite'; @override String get settings_fastSpeed => 'Schnelle Geschwindigkeit'; @@ -377,7 +377,7 @@ class AppLocalizationsDe extends AppLocalizations { String get appSettings_themeSystem => 'Systemstandard'; @override - String get appSettings_themeLight => 'Helligkeit'; + String get appSettings_themeLight => 'Hell'; @override String get appSettings_themeDark => 'Dunkel'; @@ -435,7 +435,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get appSettings_enableNotificationsSubtitle => - 'Erhalte Benachrichtigungen für Nachrichten und Anzeigen'; + 'Erhalte Benachrichtigungen für Nachrichten und Ankündigungen'; @override String get appSettings_notificationPermissionDenied => @@ -450,15 +450,15 @@ class AppLocalizationsDe extends AppLocalizations { @override String get appSettings_messageNotifications => - 'Nachrichtenbenachrichtigungen'; + 'Direktnachrichten Benachrichtigungen'; @override String get appSettings_messageNotificationsSubtitle => - 'Zeige Benachrichtigung beim Empfang neuer Nachrichten'; + 'Zeige Benachrichtigung beim Empfang neuer Direktnachrichten'; @override String get appSettings_channelMessageNotifications => - 'Kanal-Nachrichten-Benachrichtigungen'; + 'Kanalnachrichten Benachrichtigungen'; @override String get appSettings_channelMessageNotificationsSubtitle => @@ -466,7 +466,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get appSettings_advertisementNotifications => - 'Werbeanzeigenbenachrichtigungen'; + 'Ankündigungsbenachrichtigungen'; @override String get appSettings_advertisementNotificationsSubtitle => @@ -477,11 +477,11 @@ class AppLocalizationsDe extends AppLocalizations { @override String get appSettings_clearPathOnMaxRetry => - 'Klares Pfad bei Max Wiederholungsversuch'; + 'Lösche Pfade bei Max Wiederholungsversuchen'; @override String get appSettings_clearPathOnMaxRetrySubtitle => - 'Zurücksetzen des Kontaktpfads nach 5 fehlgeschlagenen Sendeverboten'; + 'Zurücksetzen der Kontaktpfade nach 5 fehlgeschlagenen Sendeabbrüchen'; @override String get appSettings_pathsWillBeCleared => @@ -566,17 +566,17 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get appSettings_mapTimeFilter => 'Kartent Zeitfilter'; + String get appSettings_mapTimeFilter => 'Karten Zeitfilter'; @override String get appSettings_showNodesDiscoveredWithin => 'Zeige Knoten, die innerhalb von:'; @override - String get appSettings_allTime => 'Alle Zeit'; + String get appSettings_allTime => 'Ganzer Zeitverlauf'; @override - String get appSettings_lastHour => 'Letzter Stunde'; + String get appSettings_lastHour => 'Letzte Stunde'; @override String get appSettings_last6Hours => 'Letzte 6 Stunden'; @@ -620,48 +620,48 @@ class AppLocalizationsDe extends AppLocalizations { String get contacts_title => 'Kontakte'; @override - String get contacts_noContacts => 'No Contacts noch'; + String get contacts_noContacts => 'Noch keine Kontakte vorhanden.'; @override String get contacts_contactsWillAppear => - 'Kontakte werden angezeigt, wenn Geräte Werbung machen.'; + 'Kontakte werden angezeigt, wenn Geräte eine Ankündigung machen.'; @override String get contacts_searchContacts => 'Suche Kontakte...'; @override - String get contacts_noUnreadContacts => 'Keine ungeklärten Kontakte'; + String get contacts_noUnreadContacts => 'Keine ungesehene Kontakte'; @override String get contacts_noContactsFound => 'Keine Kontakte oder Gruppen gefunden.'; @override - String get contacts_deleteContact => 'Löschen Sie Kontakt'; + String get contacts_deleteContact => 'Lösche den Kontakt'; @override String contacts_removeConfirm(String contactName) { - return 'Entfernen $contactName aus den Kontakten?'; + return '$contactName aus den Kontakten entfernen?'; } @override - String get contacts_manageRepeater => 'Wiederholung verwalten'; + String get contacts_manageRepeater => 'Wiederholungen verwalten'; @override String get contacts_roomLogin => 'Raum-Login'; @override - String get contacts_openChat => 'Öffnen Sie Chat'; + String get contacts_openChat => 'Öffne Chat'; @override - String get contacts_editGroup => 'Gruppen bearbeiten'; + String get contacts_editGroup => 'Gruppe bearbeiten'; @override String get contacts_deleteGroup => 'Löschen Gruppe'; @override String contacts_deleteGroupConfirm(String groupName) { - return 'Löschen Sie \"$groupName\"?'; + return 'Löschen von \"$groupName\"?'; } @override @@ -689,11 +689,11 @@ class AppLocalizationsDe extends AppLocalizations { String get contacts_noMembers => 'Keine Mitglieder'; @override - String get contacts_lastSeenNow => 'Letztes Ansehen jetzt'; + String get contacts_lastSeenNow => 'gerade gesehen'; @override String contacts_lastSeenMinsAgo(int minutes) { - return 'Letzte Sichtung $minutes Minuten her.'; + return 'Letzte Sichtung vor $minutes Minuten.'; } @override @@ -701,7 +701,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String contacts_lastSeenHoursAgo(int hours) { - return 'Letzte Aktivität vor $hours Stunden.'; + return 'Letzte Sichtung vor $hours Stunden.'; } @override @@ -751,11 +751,11 @@ class AppLocalizationsDe extends AppLocalizations { String get channels_editChannel => 'Kanal bearbeiten'; @override - String get channels_deleteChannel => 'Löschen Sie Kanal'; + String get channels_deleteChannel => 'Lösche den Kanal'; @override String channels_deleteChannelConfirm(String name) { - return 'Löschen \"$name\"? Dies kann nicht rückgängig gemacht werden.'; + return 'Löschen von \"$name\"? Dies kann nicht rückgängig gemacht werden.'; } @override @@ -799,7 +799,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String channels_editChannelTitle(int index) { - return 'Bearbeiteten Kanal $index'; + return 'Bearbeiteter Kanal $index'; } @override @@ -817,7 +817,7 @@ class AppLocalizationsDe extends AppLocalizations { String get channels_sortBy => 'Sortiere nach'; @override - String get channels_sortManual => 'Manuelle'; + String get channels_sortManual => 'Manuell'; @override String get channels_sortAZ => 'A bis Z'; @@ -826,7 +826,7 @@ class AppLocalizationsDe extends AppLocalizations { String get channels_sortLatestMessages => 'Letzte Nachrichten'; @override - String get channels_sortUnread => 'Unlescht'; + String get channels_sortUnread => 'Ungelesen'; @override String get channels_createPrivateChannel => 'Erstelle einen privaten Kanal'; @@ -886,7 +886,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String chat_replyTo(String name) { - return 'Antworten Sie $name'; + return 'Antwort an $name'; } @override @@ -916,7 +916,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String chat_retryCount(int current, int max) { - return 'Versuchen $current/$max'; + return 'Versuche $current/$max'; } @override @@ -950,14 +950,13 @@ class AppLocalizationsDe extends AppLocalizations { String get gifPicker_searchHint => 'Suche nach GIFs...'; @override - String get gifPicker_poweredBy => 'Angetrieben von GIPHY'; + String get gifPicker_poweredBy => 'Bereitgestellt von GIPHY'; @override String get gifPicker_noGifsFound => 'Keine GIFs gefunden'; @override - String get gifPicker_failedLoad => - 'GIF-Dateien konnten nicht geladen werden.'; + String get gifPicker_failedLoad => 'GIF-Datei konnten nicht geladen werden.'; @override String get gifPicker_failedSearch => 'Suche nach GIFs fehlgeschlagen'; @@ -972,10 +971,10 @@ class AppLocalizationsDe extends AppLocalizations { String get debugLog_bleTitle => 'BLE-Debug-Protokoll'; @override - String get debugLog_copyLog => 'Kopieren Sie Protokoll'; + String get debugLog_copyLog => 'Kopieren des Protokolls'; @override - String get debugLog_clearLog => 'Log löschen'; + String get debugLog_clearLog => 'Protokoll löschen'; @override String get debugLog_copied => 'Debug-Protokoll kopiert'; @@ -997,7 +996,7 @@ class AppLocalizationsDe extends AppLocalizations { String get debugLog_rawLogRx => 'Roh-Log-RX'; @override - String get debugLog_noBleActivity => 'No BLE-Aktivität bisher'; + String get debugLog_noBleActivity => 'Bisher keine BLE-Aktivität'; @override String debugFrame_length(int count) { @@ -1057,7 +1056,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Automatisch (gespeicherten Pfad verwenden)'; @override - String get chat_forceFloodMode => 'Zwangsgelände-Modus erzwingen'; + String get chat_forceFloodMode => 'Flut-Modus erzwingen'; @override String get chat_recentAckPaths => @@ -1068,31 +1067,31 @@ class AppLocalizationsDe extends AppLocalizations { 'Die Pfadhistorie ist voll. Entferne Einträge, um neue hinzuzufügen.'; @override - String get chat_hopSingular => 'Springe'; + String get chat_hopSingular => 'Sprung'; @override - String get chat_hopPlural => 'Hops'; + String get chat_hopPlural => 'Sprünge'; @override String chat_hopsCount(int count) { String _temp0 = intl.Intl.pluralLogic( count, locale: localeName, - other: 'Hops', - one: 'Hop', + other: 'Sprünge', + one: 'Sprung', ); return '$count $_temp0'; } @override - String get chat_successes => 'Erfolgreiche'; + String get chat_successes => 'Erfolgreich'; @override String get chat_removePath => 'Pfad entfernen'; @override String get chat_noPathHistoryYet => - 'Noe eine Pfadhistorie vorhanden.\nSende eine Nachricht, um Pfade zu entdecken.'; + 'Keine eine Pfadhistorie vorhanden.\nSende eine Nachricht, um Pfade zu entdecken.'; @override String get chat_pathActions => 'Pfadaktionen:'; @@ -1101,26 +1100,25 @@ class AppLocalizationsDe extends AppLocalizations { String get chat_setCustomPath => 'Lege benutzerdefinierten Pfad fest'; @override - String get chat_setCustomPathSubtitle => 'Manuelle Routenpfad festlegen'; + String get chat_setCustomPathSubtitle => 'Manuellen Routenpfad festlegen'; @override - String get chat_clearPath => 'Klares Pfad'; + String get chat_clearPath => 'Pfad zurücksetzen'; @override String get chat_clearPathSubtitle => - 'Zwinge bei nächster Sendung eine erneute Entdeckung durch.'; + 'Setze Pfad zurück, erkenne neuen Pfad bei nächster Sendung.'; @override String get chat_pathCleared => - 'Pfad freigelegt. Nächste Nachricht wird Route neu entdecken.'; + 'Pfad zurückgesetzt. Nächste Nachricht wird Route neu entdecken.'; @override String get chat_floodModeSubtitle => 'Verwende den Routingschalter in der App-Leiste'; @override - String get chat_floodModeEnabled => - 'Flutmodus aktiviert. Über den Routing-Icon in der App-Leiste wieder aktivieren.'; + String get chat_floodModeEnabled => 'Flutmodus aktiviert.'; @override String get chat_fullPath => 'Vollständiger Pfad'; @@ -1142,7 +1140,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get chat_pathSavedLocally => - 'Gespeichert lokal. Mit Verbinden zum Synchronisieren.'; + 'Lokal Gespeichert. Bitte Verbinden zum Synchronisieren.'; @override String get chat_pathDeviceConfirmed => 'Gerät bestätigt.'; @@ -1151,7 +1149,7 @@ class AppLocalizationsDe extends AppLocalizations { String get chat_pathDeviceNotConfirmed => 'Gerät noch nicht bestätigt.'; @override - String get chat_type => 'Gib ein'; + String get chat_type => 'Gebe ein'; @override String get chat_path => 'Pfad'; @@ -1161,13 +1159,13 @@ class AppLocalizationsDe extends AppLocalizations { @override String get chat_compressOutgoingMessages => - 'Komprimieren ausgehende Nachrichten'; + 'Komprimieren ausgehender Nachrichten'; @override - String get chat_floodForced => 'Überschwemmung (erzwungen)'; + String get chat_floodForced => 'Geflutet (erzwungen)'; @override - String get chat_directForced => 'Direkt (gezwungen)'; + String get chat_directForced => 'Direkt (erzwungen)'; @override String chat_hopsForced(int count) { @@ -1175,28 +1173,28 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get chat_floodAuto => 'Überschwemmung (automatisch)'; + String get chat_floodAuto => 'Geflutet (automatisch)'; @override String get chat_direct => 'Direkt'; @override - String get chat_poiShared => 'Gemeinsamer POI'; + String get chat_poiShared => 'Geteilter POI'; @override String chat_unread(int count) { - return 'Unlescht: $count'; + return 'Ungelesen: $count'; } @override - String get map_title => 'Knotenkarte'; + String get map_title => 'Karte'; @override String get map_noNodesWithLocation => 'Keine Knoten mit Standortdaten'; @override String get map_nodesNeedGps => - 'Knoten müssen ihre GPS-Koordinaten\nteilen,\num auf der Karte\nerscheinen.'; + 'Knoten müssen ihre GPS-Koordinaten teilen,\num auf der Karte zu erscheinen.'; @override String map_nodesCount(int count) { @@ -1209,10 +1207,10 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get map_chat => 'Chat'; + String get map_chat => 'Benutzer'; @override - String get map_repeater => 'Wiederholung'; + String get map_repeater => 'Repeater'; @override String get map_room => 'Raum'; @@ -1221,13 +1219,13 @@ class AppLocalizationsDe extends AppLocalizations { String get map_sensor => 'Sensor'; @override - String get map_pinDm => 'Sperren (DM)'; + String get map_pinDm => 'Pin (Kontakt)'; @override - String get map_pinPrivate => 'Privat-Pin'; + String get map_pinPrivate => 'Pin (Channel)'; @override - String get map_pinPublic => 'Öffentliche Taste (PIN)'; + String get map_pinPublic => 'Pin (Public)'; @override String get map_lastSeen => 'Letzte Sichtung'; @@ -1243,13 +1241,13 @@ class AppLocalizationsDe extends AppLocalizations { String get map_source => 'Quelle'; @override - String get map_flags => 'Flaggen'; + String get map_flags => 'Flags'; @override - String get map_shareMarkerHere => 'Teilen Sie hier das Marker.'; + String get map_shareMarkerHere => 'Teilen Sie den Marker hier.'; @override - String get map_pinLabel => 'Kennzeichnungslabel'; + String get map_pinLabel => 'Pin Name'; @override String get map_label => 'Label'; @@ -1261,7 +1259,7 @@ class AppLocalizationsDe extends AppLocalizations { String get map_sendToContact => 'Senden an Kontakt'; @override - String get map_sendToChannel => 'Senden Sie Kanal'; + String get map_sendToChannel => 'Senden an Kanal'; @override String get map_noChannelsAvailable => 'Keine Kanäle verfügbar'; @@ -1279,7 +1277,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Verbinde ein Gerät, um Marker zu teilen'; @override - String get map_filterNodes => 'Filter Knoten'; + String get map_filterNodes => 'Knotenfilter'; @override String get map_nodeTypes => 'Knotentypen'; @@ -1288,7 +1286,7 @@ class AppLocalizationsDe extends AppLocalizations { String get map_chatNodes => 'Chat-Knoten'; @override - String get map_repeaters => 'Wiederholer'; + String get map_repeaters => 'Repeater'; @override String get map_otherNodes => 'Andere Knoten'; @@ -1300,7 +1298,7 @@ class AppLocalizationsDe extends AppLocalizations { String get map_filterByKeyPrefix => 'Filter nach Schlüsselpräfix'; @override - String get map_publicKeyPrefix => 'Öffentlicher Schlüsselpräfix'; + String get map_publicKeyPrefix => 'Schlüsselpräfix'; @override String get map_markers => 'Marker'; @@ -1318,7 +1316,7 @@ class AppLocalizationsDe extends AppLocalizations { String get map_joinRoom => 'Beitreten Sie dem Raum'; @override - String get map_manageRepeater => 'Wiederholung verwalten'; + String get map_manageRepeater => 'Repeater verwalten'; @override String get mapCache_title => 'Offline-Karten-Cache'; @@ -1329,14 +1327,14 @@ class AppLocalizationsDe extends AppLocalizations { @override String get mapCache_noTilesToDownload => - 'Keine Tiles für diese Region zum Herunterladen verfügbar.'; + 'Keine Kacheln für diese Region zum Herunterladen verfügbar.'; @override - String get mapCache_downloadTilesTitle => 'Herunterladen von Tiles'; + String get mapCache_downloadTilesTitle => 'Herunterladen von Kacheln'; @override String mapCache_downloadTilesPrompt(int count) { - return 'Laden $count Tiles für den Offline-Bereich herunter?'; + return 'Laden $count Kacheln für den Offline-Bereich herunter?'; } @override @@ -1344,16 +1342,16 @@ class AppLocalizationsDe extends AppLocalizations { @override String mapCache_cachedTiles(int count) { - return 'Zwischengespeicherte $count Fliesen'; + return 'Zwischengespeicherte $count Kacheln'; } @override String mapCache_cachedTilesWithFailed(int downloaded, int failed) { - return 'Zwischengespeicherte $downloaded Tiles ($failed fehlgeschlagen)'; + return 'Zwischengespeicherte $downloaded Kacheln ($failed fehlgeschlagen)'; } @override - String get mapCache_clearOfflineCacheTitle => 'Leeren Offline-Cache'; + String get mapCache_clearOfflineCacheTitle => 'Leere Offline-Cache'; @override String get mapCache_clearOfflineCachePrompt => @@ -1385,7 +1383,7 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get mapCache_downloadTilesButton => 'Herunterladen von Tiles'; + String get mapCache_downloadTilesButton => 'Herunterladen von Kacheln'; @override String get mapCache_clearCacheButton => 'Cache leeren'; @@ -1451,7 +1449,7 @@ class AppLocalizationsDe extends AppLocalizations { String get time_minutes => 'Minuten'; @override - String get time_allTime => 'Alle Zeit'; + String get time_allTime => 'Ganzer Zeitraum'; @override String get dialog_disconnect => 'Trennen'; @@ -1461,7 +1459,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Sind Sie sicher, dass Sie sich von diesem Gerät trennen möchten?'; @override - String get login_repeaterLogin => 'Wiederholungseingang anmelden'; + String get login_repeaterLogin => 'Beim Repeater anmelden'; @override String get login_roomLogin => 'Raum-Login'; @@ -1498,7 +1496,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Automatisch (gespeicherten Pfad verwenden)'; @override - String get login_forceFloodMode => 'Zwangsgelände-Modus erzwingen'; + String get login_forceFloodMode => 'Flut-Modus erzwingen'; @override String get login_managePaths => 'Pfadverwaltung'; @@ -1528,7 +1526,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String path_currentPath(String path) { - return 'Aktiger Pfad: $path'; + return 'Aktiver Pfad: $path'; } @override @@ -1543,14 +1541,14 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get path_enterCustomPath => 'Gib Pfad an'; + String get path_enterCustomPath => 'Gebe Pfad ein'; @override String get path_currentPathLabel => 'Aktueller Pfad'; @override String get path_hexPrefixInstructions => - 'Gib für jeden Hopfen 2-stellige Hex-Präfixe ein, getrennt durch Kommas.'; + 'Gebe für jeden Hopfen 2-stellige Hex-Präfixe ein, getrennt durch Kommas.'; @override String get path_hexPrefixExample => @@ -1586,7 +1584,7 @@ class AppLocalizationsDe extends AppLocalizations { String get path_setPath => 'Pfad festlegen'; @override - String get repeater_management => 'Wiederholungselement-Verwaltung'; + String get repeater_management => 'Repeater-Verwaltung'; @override String get repeater_managementTools => 'Verwaltungs-Tools'; @@ -1615,11 +1613,10 @@ class AppLocalizationsDe extends AppLocalizations { String get repeater_settings => 'Einstellungen'; @override - String get repeater_settingsSubtitle => - 'Wiederholungsparameter konfigurieren'; + String get repeater_settingsSubtitle => 'Repeater-parameter konfigurieren'; @override - String get repeater_statusTitle => 'Wiederholungszustand'; + String get repeater_statusTitle => 'Repeaterstatus'; @override String get repeater_routingMode => 'Routenmodus'; @@ -1629,7 +1626,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Automatisch (gespeicherten Pfad verwenden)'; @override - String get repeater_forceFloodMode => 'Zwangsgelände-Modus erzwingen'; + String get repeater_forceFloodMode => 'Flut-Modus erzwingen'; @override String get repeater_pathManagement => 'Pfadverwaltung'; @@ -1725,13 +1722,13 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get repeater_settingsTitle => 'Wiederholungseinstellungen'; + String get repeater_settingsTitle => 'Repeater Einstellungen'; @override String get repeater_basicSettings => 'Grundlegende Einstellungen'; @override - String get repeater_repeaterName => 'Wiederholungseintrag'; + String get repeater_repeaterName => 'Repeater Name'; @override String get repeater_repeaterNameHelper => 'Anzeigename für diesen Repeater'; @@ -1771,7 +1768,7 @@ class AppLocalizationsDe extends AppLocalizations { String get repeater_spreadingFactor => 'Verteilungsfaktor'; @override - String get repeater_codingRate => 'Programmierpauschale'; + String get repeater_codingRate => 'Kodierungsrate'; @override String get repeater_locationSettings => 'Standort Einstellungen'; @@ -1806,17 +1803,18 @@ class AppLocalizationsDe extends AppLocalizations { 'Gast-Zugriff mit beschränkten Rechten zulassen'; @override - String get repeater_privacyMode => 'Privatschutzzustand'; + String get repeater_privacyMode => 'Privatsphäreeinstellung'; @override String get repeater_privacyModeSubtitle => - 'Verstecken Sie Name/Ort in Anzeigen'; + 'Verstecken Sie Name/Ort in Ankündigungen'; @override - String get repeater_advertisementSettings => 'Werbe Einstellungen'; + String get repeater_advertisementSettings => 'Ankündigungseinstellungen'; @override - String get repeater_localAdvertInterval => 'Lokaler Werbeintervall'; + String get repeater_localAdvertInterval => + 'Intervall der lokalen Ankündigungen'; @override String repeater_localAdvertIntervalMinutes(int minutes) { @@ -1824,7 +1822,8 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get repeater_floodAdvertInterval => 'Überschwemmungsanzeige-Intervall'; + String get repeater_floodAdvertInterval => + 'Intervall der gefluteten Ankündigungen'; @override String repeater_floodAdvertIntervalHours(int hours) { @@ -1833,7 +1832,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get repeater_encryptedAdvertInterval => - 'Verschlüsselte Werbeintervall'; + 'Intervall der verschlüsselten Ankündigung'; @override String get repeater_dangerZone => 'Gefahrenzone'; @@ -1906,7 +1905,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Radio-Einstellungen aktualisieren'; @override - String get repeater_refreshTxPower => 'Batterie-Strom aktualisieren'; + String get repeater_refreshTxPower => 'Sendeleistung aktualisieren'; @override String get repeater_refreshLocationSettings => @@ -1925,7 +1924,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get repeater_refreshAdvertisementSettings => - 'Aktualisieren Sie die Werbe Einstellungen'; + 'Aktualisieren Sie die Ankündigungseinstellungen'; @override String repeater_refreshed(String label) { @@ -1938,7 +1937,7 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get repeater_cliTitle => 'Wiederholung CLI'; + String get repeater_cliTitle => 'Repeater CLI'; @override String get repeater_debugNextCommand => 'Fehlersuche Nächster Befehl'; @@ -1947,7 +1946,7 @@ class AppLocalizationsDe extends AppLocalizations { String get repeater_commandHelp => 'Hilfe'; @override - String get repeater_clearHistory => 'Löschung der Historie'; + String get repeater_clearHistory => 'Löschen der Historie'; @override String get repeater_noCommandsSent => 'Noch keine Befehle gesendet.'; @@ -1992,13 +1991,13 @@ class AppLocalizationsDe extends AppLocalizations { String get repeater_cliQuickVersion => 'Version'; @override - String get repeater_cliQuickAdvertise => 'Werben'; + String get repeater_cliQuickAdvertise => 'Ankündigungen'; @override String get repeater_cliQuickClock => 'Uhr'; @override - String get repeater_cliHelpAdvert => 'Sendet ein Werbepaket'; + String get repeater_cliHelpAdvert => 'Sendet eine Ankündigung'; @override String get repeater_cliHelpReboot => @@ -2018,7 +2017,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get repeater_cliHelpClearStats => - 'Setzt verschiedene Statistikkalkulate auf Null zurück.'; + 'Setzt verschiedene Statistikberechnungen auf Null zurück.'; @override String get repeater_cliHelpSetAf => 'Legt den Luftzeitfaktor fest.'; @@ -2033,7 +2032,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get repeater_cliHelpSetAllowReadOnly => - '(Raumspeicher) Wenn \'an\', dann wird die Anmeldung mit einem leeren Passwort erlaubt sein, aber kann nicht in den Raum geschickt werden. (nur lesen möglich).'; + '(Raumspeicher) Wenn \'an\', dann wird die Anmeldung mit einem leeren Passwort erlaubt sein, aber es kann nicht in den Raum gesendet werden. (nur lesen möglich).'; @override String get repeater_cliHelpSetFloodMax => @@ -2053,11 +2052,11 @@ class AppLocalizationsDe extends AppLocalizations { @override String get repeater_cliHelpSetAdvertInterval => - 'Legt das Timer-Intervall in Minuten fest, um ein lokales (ohne-Weiterleitung) Werbe-Paket zu senden. Auf 0 setzen, um die Funktion zu deaktivieren.'; + 'Legt das Timer-Intervall in Minuten fest, um ein lokales (ohne-Weiterleitung) Ankündigungspaket zu senden. Auf 0 setzen, um die Funktion zu deaktivieren.'; @override String get repeater_cliHelpSetFloodAdvertInterval => - 'Legt das Timer-Intervall in Stunden für den Versand eines Flut-Werbungspakets fest. Auf 0 setzen, um es zu deaktivieren.'; + 'Legt das Timer-Intervall in Stunden für den Versand eines Flut-Ankündigungspacket fest. Auf 0 setzen, um es zu deaktivieren.'; @override String get repeater_cliHelpSetGuestPassword => @@ -2068,11 +2067,11 @@ class AppLocalizationsDe extends AppLocalizations { @override String get repeater_cliHelpSetLat => - 'Legt die Breitengrad-Angabe der Werbekarte fest. (dezimale Grad)'; + 'Legt die Breitengrad der Ankündigung fest. (dezimale Grad)'; @override String get repeater_cliHelpSetLon => - 'Legt die Längengrade der Werbe-Map fest. (dezimale Grad)'; + 'Legt die Längengrade der Ankündigung fest. (dezimale Grad)'; @override String get repeater_cliHelpSetRadio => @@ -2100,7 +2099,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get repeater_cliHelpSetBridgeSource => - 'Wählen Sie, ob die Brücke empfangene oder gesendete Pakete erneut übertragen soll.'; + 'Wählen Sie, ob über die Brücke empfangene oder gesendete Pakete erneut übertragen soll.'; @override String get repeater_cliHelpSetBridgeBaud => @@ -2108,7 +2107,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get repeater_cliHelpSetBridgeSecret => - 'Richte das Espnow-Brücken-Geheimnis ein.'; + 'Richte das Brückenpassword ein.'; @override String get repeater_cliHelpSetAdcMultiplier => @@ -2140,15 +2139,14 @@ class AppLocalizationsDe extends AppLocalizations { @override String get repeater_cliHelpNeighbors => - 'Zeigt eine Liste anderer Repeater-Knoten an, die über Zero-Hop-Werbung gehört wurden. Jede Zeile ist id-prefix-hex:timestamp:snr-times-4'; + 'Zeigt eine Liste anderer Repeater-Knoten an, die über Zero-Hop-Ankündigung gehört wurden. Jede Zeile ist id-prefix-hex:timestamp:snr-times-4'; @override String get repeater_cliHelpNeighborRemove => 'Entfernt das erste übereinstimmende Element (über Pubkey-Präfix (hex)) aus der Liste der Nachbarn.'; @override - String get repeater_cliHelpRegion => - '(Serien nur) Listet alle definierten Regionen und aktuelle Hochwassermissungen auf.'; + String get repeater_cliHelpRegion => 'Listet alle definierten Regionen auf.'; @override String get repeater_cliHelpRegionLoad => @@ -2310,7 +2308,7 @@ class AppLocalizationsDe extends AppLocalizations { String get channelPath_otherObservedPaths => 'Sonstige beobachtete Pfade'; @override - String get channelPath_repeaterHops => 'Wiederholungs-Sprünge'; + String get channelPath_repeaterHops => 'Repeater-Sprünge'; @override String get channelPath_noHopDetails => @@ -2326,7 +2324,7 @@ class AppLocalizationsDe extends AppLocalizations { String get channelPath_timeLabel => 'Zeit'; @override - String get channelPath_repeatsLabel => 'Wiederholung'; + String get channelPath_repeatsLabel => 'Wiederholungen'; @override String channelPath_pathLabel(int index) { @@ -2358,7 +2356,7 @@ class AppLocalizationsDe extends AppLocalizations { String get channelPath_unknownPath => 'Unbekannt'; @override - String get channelPath_floodPath => 'Überschwemmung'; + String get channelPath_floodPath => 'Geflutet'; @override String get channelPath_directPath => 'Direkt'; @@ -2413,7 +2411,7 @@ class AppLocalizationsDe extends AppLocalizations { String get listFilter_latestMessages => 'Letzte Nachrichten'; @override - String get listFilter_heardRecently => 'Hörte kürzlich'; + String get listFilter_heardRecently => 'Kürzlich gehört'; @override String get listFilter_az => 'A-Z'; @@ -2428,7 +2426,7 @@ class AppLocalizationsDe extends AppLocalizations { String get listFilter_users => 'Benutzer'; @override - String get listFilter_repeaters => 'Wiederholer'; + String get listFilter_repeaters => 'Repeater'; @override String get listFilter_roomServers => 'Raumserver';