From 609d0c8dbce3356d0b5164946119a098720eecf5 Mon Sep 17 00:00:00 2001 From: HDDen <62592944+HDDen@users.noreply.github.com> Date: Wed, 22 Apr 2026 03:59:43 +0300 Subject: [PATCH 1/6] Added Cyr2Lat compression by replacing 2-byte cyrillic chars by 1-byte latin --- lib/connector/meshcore_connector.dart | 67 +++++++++++++- lib/helpers/cyr2lat.dart | 63 +++++++++++++ lib/l10n/app_bg.arb | 10 +++ lib/l10n/app_de.arb | 10 +++ lib/l10n/app_en.arb | 15 ++++ lib/l10n/app_es.arb | 10 +++ lib/l10n/app_fr.arb | 10 +++ lib/l10n/app_hu.arb | 10 +++ lib/l10n/app_it.arb | 10 +++ lib/l10n/app_ja.arb | 10 +++ lib/l10n/app_ko.arb | 10 +++ lib/l10n/app_localizations.dart | 60 +++++++++++++ lib/l10n/app_localizations_bg.dart | 37 ++++++++ lib/l10n/app_localizations_de.dart | 36 ++++++++ lib/l10n/app_localizations_en.dart | 36 ++++++++ lib/l10n/app_localizations_es.dart | 37 ++++++++ lib/l10n/app_localizations_fr.dart | 38 ++++++++ lib/l10n/app_localizations_hu.dart | 36 ++++++++ lib/l10n/app_localizations_it.dart | 38 ++++++++ lib/l10n/app_localizations_ja.dart | 33 +++++++ lib/l10n/app_localizations_ko.dart | 33 +++++++ lib/l10n/app_localizations_nl.dart | 37 ++++++++ lib/l10n/app_localizations_pl.dart | 37 ++++++++ lib/l10n/app_localizations_pt.dart | 37 ++++++++ lib/l10n/app_localizations_ru.dart | 35 ++++++++ lib/l10n/app_localizations_sk.dart | 37 ++++++++ lib/l10n/app_localizations_sl.dart | 36 ++++++++ lib/l10n/app_localizations_sv.dart | 36 ++++++++ lib/l10n/app_localizations_uk.dart | 35 ++++++++ lib/l10n/app_localizations_zh.dart | 32 +++++++ lib/l10n/app_nl.arb | 10 +++ lib/l10n/app_pl.arb | 10 +++ lib/l10n/app_pt.arb | 10 +++ lib/l10n/app_ru.arb | 10 +++ lib/l10n/app_sk.arb | 10 +++ lib/l10n/app_sl.arb | 10 +++ lib/l10n/app_sv.arb | 10 +++ lib/l10n/app_uk.arb | 10 +++ lib/l10n/app_zh.arb | 10 +++ lib/models/app_settings.dart | 38 +++++++- lib/screens/app_settings_screen.dart | 113 ++++++++++++++++++++++++ lib/screens/channel_chat_screen.dart | 17 +++- lib/screens/channels_screen.dart | 26 +++++- lib/screens/chat_screen.dart | 60 ++++++++++++- lib/services/app_settings_service.dart | 7 ++ lib/storage/channel_settings_store.dart | 41 ++++++++- lib/storage/contact_settings_store.dart | 39 ++++++++ 47 files changed, 1350 insertions(+), 12 deletions(-) create mode 100644 lib/helpers/cyr2lat.dart diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index fceee150..6e3a1b9f 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -16,6 +16,7 @@ import '../models/message.dart'; import '../models/path_selection.dart'; import '../models/translation_support.dart'; import '../helpers/reaction_helper.dart'; +import '../helpers/cyr2lat.dart'; import '../helpers/smaz.dart'; import '../services/app_debug_log_service.dart'; import '../services/ble_debug_log_service.dart'; @@ -283,9 +284,11 @@ class MeshCoreConnector extends ChangeNotifier { final UnreadStore _unreadStore = UnreadStore(); List _cachedChannels = []; final Map _channelSmazEnabled = {}; + final Map _channelCyr2LatEnabled = {}; bool _lastSentWasCliCommand = false; // Track if last sent message was a CLI command final Map _contactSmazEnabled = {}; + final Map _contactCyr2LatEnabled = {}; final Set _knownContactKeys = {}; final Map _contactUnreadCount = {}; final Map _repeaterBatterySnapshots = {}; @@ -607,6 +610,18 @@ class MeshCoreConnector extends ChangeNotifier { _ensureContactSmazSettingLoaded(contactKeyHex); } + bool isChannelCyr2LatEnabled(int channelIndex) { + return _channelCyr2LatEnabled[channelIndex] ?? false; + } + + bool isContactCyr2LatEnabled(String contactKeyHex) { + return _contactCyr2LatEnabled[contactKeyHex] ?? false; + } + + void ensureContactCyr2LatSettingLoaded(String contactKeyHex) { + _ensureContactCyr2LatSettingLoaded(contactKeyHex); + } + Future loadUnreadState() async { _contactUnreadCount ..clear() @@ -682,6 +697,10 @@ class MeshCoreConnector extends ChangeNotifier { Future setChannelSmazEnabled(int channelIndex, bool enabled) async { _channelSmazEnabled[channelIndex] = enabled; + if (enabled) { + _channelCyr2LatEnabled[channelIndex] = false; + await _channelSettingsStore.saveCyr2LatEnabled(channelIndex, false); + } await _channelSettingsStore.saveSmazEnabled(channelIndex, enabled); notifyListeners(); } @@ -692,6 +711,25 @@ class MeshCoreConnector extends ChangeNotifier { notifyListeners(); } + Future setChannelCyr2LatEnabled(int channelIndex, bool enabled) async { + _channelCyr2LatEnabled[channelIndex] = enabled; + if (enabled) { + _channelSmazEnabled[channelIndex] = false; + await _channelSettingsStore.saveSmazEnabled(channelIndex, false); + } + await _channelSettingsStore.saveCyr2LatEnabled(channelIndex, enabled); + notifyListeners(); + } + + Future setContactCyr2LatEnabled( + String contactKeyHex, + bool enabled, + ) async { + _contactCyr2LatEnabled[contactKeyHex] = enabled; + await _contactSettingsStore.saveCyr2LatEnabled(contactKeyHex, enabled); + notifyListeners(); + } + Future _loadChannelOrder() async { _channelOrder = await _channelOrderStore.loadChannelOrder(); _applyChannelOrder(); @@ -828,6 +866,7 @@ class MeshCoreConnector extends ChangeNotifier { ..addAll(cached); for (final contact in cached) { _ensureContactSmazSettingLoaded(contact.publicKeyHex); + _ensureContactCyr2LatSettingLoaded(contact.publicKeyHex); } } @@ -840,9 +879,12 @@ class MeshCoreConnector extends ChangeNotifier { Future loadChannelSettings({int? maxChannels}) async { _channelSmazEnabled.clear(); + _channelCyr2LatEnabled.clear(); final channelCount = maxChannels ?? _maxChannels; for (int i = 0; i < channelCount; i++) { _channelSmazEnabled[i] = await _channelSettingsStore.loadSmazEnabled(i); + _channelCyr2LatEnabled[i] = await _channelSettingsStore + .loadCyr2LatEnabled(i); } } @@ -4432,6 +4474,15 @@ class MeshCoreConnector extends ChangeNotifier { }); } + void _ensureContactCyr2LatSettingLoaded(String contactKeyHex) { + if (_contactCyr2LatEnabled.containsKey(contactKeyHex)) return; + _contactSettingsStore.loadCyr2LatEnabled(contactKeyHex).then((enabled) { + if (_contactCyr2LatEnabled[contactKeyHex] == enabled) return; + _contactCyr2LatEnabled[contactKeyHex] = enabled; + notifyListeners(); + }); + } + /// Prepares contact outbound text by applying SMAZ encoding if enabled. /// This should be used to transform text before computing ACK hashes. String prepareContactOutboundText(Contact contact, String text) { @@ -4440,8 +4491,12 @@ class MeshCoreConnector extends ChangeNotifier { trimmed.startsWith('g:') || trimmed.startsWith('m:') || trimmed.startsWith('V1|'); - if (!isStructuredPayload && isContactSmazEnabled(contact.publicKeyHex)) { - return Smaz.encodeIfSmaller(text); + if (!isStructuredPayload) { + if (isContactSmazEnabled(contact.publicKeyHex)) { + return Smaz.encodeIfSmaller(text); + } else if (isContactCyr2LatEnabled(contact.publicKeyHex)) { + return Cyr2Lat.encode(text); + } } return text; } @@ -4450,8 +4505,12 @@ class MeshCoreConnector extends ChangeNotifier { final trimmed = text.trim(); final isStructuredPayload = trimmed.startsWith('g:') || trimmed.startsWith('m:'); - if (!isStructuredPayload && isChannelSmazEnabled(channelIndex)) { - return Smaz.encodeIfSmaller(text); + if (!isStructuredPayload) { + if (isChannelSmazEnabled(channelIndex)) { + return Smaz.encodeIfSmaller(text); + } else if (isChannelCyr2LatEnabled(channelIndex)) { + return Cyr2Lat.encode(text); + } } return text; } diff --git a/lib/helpers/cyr2lat.dart b/lib/helpers/cyr2lat.dart new file mode 100644 index 00000000..a7ec5dde --- /dev/null +++ b/lib/helpers/cyr2lat.dart @@ -0,0 +1,63 @@ +class Cyr2Lat { + static Map _charMap = { + 'А': 'A', + 'В': 'B', + 'Е': 'E', + 'Ё': 'E', + 'З': '3', + 'К': 'K', + 'М': 'M', + 'Н': 'H', + 'О': 'O', + 'Р': 'P', + 'С': 'C', + 'Т': 'T', + 'Х': 'X', + 'Ь': 'b', + 'а': 'a', + 'е': 'e', + 'ё': 'e', + 'о': 'o', + 'р': 'p', + 'с': 'c', + 'у': 'y', + 'х': 'x', + }; + + static final RegExp _prefixRegExp = RegExp(r'\@\[[\S\s]+\] '); + + static void setCharMap(Map charMap) { + _charMap = Map.from(charMap); + } + + static String encode(String text) { + if (text.isEmpty) return text; + final buffer = StringBuffer(); + + final senderName = extractSenderName(text); + final msgText = removeSenderName(text); + + for (final rune in msgText.runes) { + final char = String.fromCharCode(rune); + buffer.write(_charMap[char] ?? char); + } + + return senderName + buffer.toString(); + } + + static String removeSenderName(String text) { + final match = _prefixRegExp.matchAsPrefix(text); + if (match != null) { + return text.substring(match.end); + } + return text; + } + + static String extractSenderName(String text) { + final match = _prefixRegExp.matchAsPrefix(text); + if (match != null) { + return match.group(0) ?? ''; + } + return ''; + } +} diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 7ac54177..8f59d981 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "Компресия SMAZ", + "channels_cyr2latCompression": "Компресия Cyr2Lat", + "channels_cyr2latCompressionDscr": "Заменя някои кирилични символи с латиница при изпращане.", + "channels_cyr2latSettingsHeading": "Настройки на Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Списък със замествания", + "channels_cyr2latSettingsDscr": "Редактиране на JSON конфигурацията за заместване на символи", + "channels_cyr2latSettingsDialogHint": "JSON карта за замествания", + "channels_cyr2latSettingsDialogSuccess": "Списъкът със замествания е актуализиран", + "channels_cyr2latSettingsDialogWrongJSON": "Неправилен JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Възстановяване на първоначалните настройки", + "channels_cyr2latSettingsDialogResetted": "Настройките за заместване на Cyr2Lat са възстановени към първоначалните", "channels_channelUpdated": "Каналът \"{name}\" е актуализиран", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 46955052..e0cc58b5 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "SMAZ-Komprimierung", + "channels_cyr2latCompression": "Cyr2Lat-Komprimierung", + "channels_cyr2latCompressionDscr": "Ersetzt einige kyrillische Zeichen durch lateinische Zeichen, wenn sie gesendet werden.", + "channels_cyr2latSettingsHeading": "Cyr2Lat-Einstellungen", + "channels_cyr2latSettingsSubheading": "Ersetzungsliste", + "channels_cyr2latSettingsDscr": "JSON-Konfiguration für die Zeichenersetzung bearbeiten", + "channels_cyr2latSettingsDialogHint": "JSON-Ersetzungstabelle", + "channels_cyr2latSettingsDialogSuccess": "Ersetzungsliste aktualisiert", + "channels_cyr2latSettingsDialogWrongJSON": "Ungültiges JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Auf Standard zurücksetzen", + "channels_cyr2latSettingsDialogResetted": "Die Cyr2Lat-Ersetzungseinstellungen wurden auf die Standardeinstellungen zurückgesetzt", "channels_channelUpdated": "Kanal \"{name}\" aktualisiert", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8ad6bf37..03612b8d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -580,6 +580,21 @@ } }, "channels_smazCompression": "SMAZ compression", + "channels_cyr2latCompression": "Cyr2Lat compression", + "channels_cyr2latCompressionDscr": "Replaces some Cyrillic characters with Latin characters when sending.", + "channels_cyr2latSettingsHeading": "Cyr2Lat Setup", + "channels_cyr2latSettingsSubheading": "List of replacements", + "channels_cyr2latSettingsDscr": "Edit the JSON configuration of character replacement", + "channels_cyr2latSettingsDialogHint": "JSON replacement map", + "channels_cyr2latSettingsDialogSuccess": "The list of replacements has been updated", + "channels_cyr2latSettingsDialogWrongJSON": "Invalid JSON: {error}", + "@channels_cyr2latSettingsDialogWrongJSON": { + "placeholders": { + "error": {} + } + }, + "channels_cyr2latSettingsDialogReset": "Reset to default", + "channels_cyr2latSettingsDialogResetted": "Cyr2Lat replacement settings reset to default", "channels_channelUpdated": "Channel \"{name}\" updated", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index ac9527bd..bcb1b708 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "Compresión SMAZ", + "channels_cyr2latCompression": "Compresión Cyr2Lat", + "channels_cyr2latCompressionDscr": "Reemplaza algunos caracteres cirílicos con caracteres latinos al enviar.", + "channels_cyr2latSettingsHeading": "Configuración de Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Lista de sustituciones", + "channels_cyr2latSettingsDscr": "Editar la configuración JSON de sustitución de caracteres", + "channels_cyr2latSettingsDialogHint": "Mapa JSON de sustituciones", + "channels_cyr2latSettingsDialogSuccess": "Lista de sustituciones actualizada", + "channels_cyr2latSettingsDialogWrongJSON": "JSON incorrecto: {error}", + "channels_cyr2latSettingsDialogReset": "Restablecer valores predeterminados", + "channels_cyr2latSettingsDialogResetted": "La configuración de sustituciones de Cyr2Lat se ha restablecido a los valores predeterminados", "channels_channelUpdated": "Canal \"{name}\" actualizado", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index a942aa2b..d0813a23 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "Compression SMAZ", + "channels_cyr2latCompression": "Compression Cyr2Lat", + "channels_cyr2latCompressionDscr": "Remplace certains caractères cyrilliques par des caractères latins lors de l'envoi.", + "channels_cyr2latSettingsHeading" : "Paramètres Cyr2Lat", + "channels_cyr2latSettingsSubheading" : "Liste des remplacements", + "channels_cyr2latSettingsDscr" : "Modifier la configuration JSON des remplacements de caractères", + "channels_cyr2latSettingsDialogHint": "Tableau de remplacement JSON", + "channels_cyr2latSettingsDialogSuccess": "Liste de remplacement mise à jour", + "channels_cyr2latSettingsDialogWrongJSON": "JSON incorrect : {error}", + "channels_cyr2latSettingsDialogReset": "Réinitialiser les paramètres par défaut", + "channels_cyr2latSettingsDialogResetted": "Les paramètres de remplacement Cyr2Lat ont été réinitialisés aux valeurs par défaut", "channels_channelUpdated": "Le canal \"{name}\" a été mis à jour", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index 6f43463c..45a40a5f 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -548,6 +548,16 @@ } }, "channels_smazCompression": "SMAZ kompresszió", + "channels_cyr2latCompression": "Cyr2Lat kompresszió", + "channels_cyr2latCompressionDscr": "Néhány Cirill betűt Latin betűkkel helyettesít küldéskor.", + "channels_cyr2latSettingsHeading": "Cyr2Lat beállítások", + "channels_cyr2latSettingsSubheading": "Helyettesítési lista", + "channels_cyr2latSettingsDscr": "A karakterhelyettesítési JSON-konfiguráció szerkesztése", + "channels_cyr2latSettingsDialogHint": "JSON-csere táblázat", + "channels_cyr2latSettingsDialogSuccess": "A csere lista frissítve", + "channels_cyr2latSettingsDialogWrongJSON": "Hibás JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Alapértelmezett értékekre állítás", + "channels_cyr2latSettingsDialogResetted": "A Cyr2Lat helyettesítési beállítások alapértelmezett értékekre lettek állítva", "channels_channelUpdated": "A {name} csatorna frissítve", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 387c8cff..5f78fdcf 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "Compressione SMAZ", + "channels_cyr2latCompression": "Compressione Cyr2Lat", + "channels_cyr2latCompressionDscr": "Sostituisce alcuni caratteri cirillici con caratteri latini durante l'invio.", + "channels_cyr2latSettingsHeading": "Impostazioni Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Elenco delle sostituzioni", + "channels_cyr2latSettingsDscr": "Modifica la configurazione JSON delle sostituzioni dei caratteri", + "channels_cyr2latSettingsDialogHint": "Mappa JSON delle sostituzioni", + "channels_cyr2latSettingsDialogSuccess": "Elenco delle sostituzioni aggiornato", + "channels_cyr2latSettingsDialogWrongJSON": "JSON non corretto: {error}", + "channels_cyr2latSettingsDialogReset": "Ripristina impostazioni predefinite", + "channels_cyr2latSettingsDialogResetted": "Le impostazioni di sostituzione Cyr2Lat sono state ripristinate alle impostazioni predefinite", "channels_channelUpdated": "Canale \"{name}\" aggiornato", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index b63f146d..ae3d6898 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -548,6 +548,16 @@ } }, "channels_smazCompression": "SMAZ 圧縮", + "channels_cyr2latCompression": "Cyr2Lat 圧縮", + "channels_cyr2latCompressionDscr": "送信時に一部のキリル文字をラテン文字に置き換えます。", + "channels_cyr2latSettingsHeading": "cyr2latの設定", + "channels_cyr2latSettingsSubheading": "置換リスト", + "channels_cyr2latSettingsDscr": "文字置換のJSON設定を編集する", + "channels_cyr2latSettingsDialogHint": "JSON置換マップ", + "channels_cyr2latSettingsDialogSuccess": "置換リストが更新されました", + "channels_cyr2latSettingsDialogWrongJSON": "不正なJSON: {error}", + "channels_cyr2latSettingsDialogReset": "初期設定に戻す", + "channels_cyr2latSettingsDialogResetted": "cyr2latの置換設定が初期設定に戻されました", "channels_channelUpdated": "チャンネル「{name}」が更新されました", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 40721fe1..982bd421 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -548,6 +548,16 @@ } }, "channels_smazCompression": "SMAZ 압축", + "channels_cyr2latCompression": "Cyr2Lat 압축", + "channels_cyr2latCompressionDscr": "보낼 때 일부 키릴 문자를 라틴 문자로 바꿉니다.", + "channels_cyr2latSettingsHeading": "Cyr2Lat 설정", + "channels_cyr2latSettingsSubheading": "변환 목록", + "channels_cyr2latSettingsDscr": "문자 변환 JSON 구성 편집", + "channels_cyr2latSettingsDialogHint": "JSON 변환 맵", + "channels_cyr2latSettingsDialogSuccess": "변환 목록이 업데이트되었습니다", + "channels_cyr2latSettingsDialogWrongJSON": "잘못된 JSON: {error}", + "channels_cyr2latSettingsDialogReset": "초기값으로 초기화", + "channels_cyr2latSettingsDialogResetted": "Cyr2Lat 변환 설정이 초기값으로 초기화되었습니다", "channels_channelUpdated": "채널 \"{name}\"이 업데이트되었습니다.", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 2c1342d0..419d8c98 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2176,6 +2176,66 @@ abstract class AppLocalizations { /// **'SMAZ compression'** String get channels_smazCompression; + /// No description provided for @channels_cyr2latCompression. + /// + /// In en, this message translates to: + /// **'Cyr2Lat compression'** + String get channels_cyr2latCompression; + + /// No description provided for @channels_cyr2latCompressionDscr. + /// + /// In en, this message translates to: + /// **'Replaces some Cyrillic characters with Latin characters when sending.'** + String get channels_cyr2latCompressionDscr; + + /// No description provided for @channels_cyr2latSettingsHeading. + /// + /// In en, this message translates to: + /// **'Cyr2Lat Setup'** + String get channels_cyr2latSettingsHeading; + + /// No description provided for @channels_cyr2latSettingsSubheading. + /// + /// In en, this message translates to: + /// **'List of replacements'** + String get channels_cyr2latSettingsSubheading; + + /// No description provided for @channels_cyr2latSettingsDscr. + /// + /// In en, this message translates to: + /// **'Edit the JSON configuration of character replacement'** + String get channels_cyr2latSettingsDscr; + + /// No description provided for @channels_cyr2latSettingsDialogHint. + /// + /// In en, this message translates to: + /// **'JSON replacement map'** + String get channels_cyr2latSettingsDialogHint; + + /// No description provided for @channels_cyr2latSettingsDialogSuccess. + /// + /// In en, this message translates to: + /// **'The list of replacements has been updated'** + String get channels_cyr2latSettingsDialogSuccess; + + /// No description provided for @channels_cyr2latSettingsDialogWrongJSON. + /// + /// In en, this message translates to: + /// **'Invalid JSON: {error}'** + String channels_cyr2latSettingsDialogWrongJSON(Object error); + + /// No description provided for @channels_cyr2latSettingsDialogReset. + /// + /// In en, this message translates to: + /// **'Reset to default'** + String get channels_cyr2latSettingsDialogReset; + + /// No description provided for @channels_cyr2latSettingsDialogResetted. + /// + /// In en, this message translates to: + /// **'Cyr2Lat replacement settings reset to default'** + String get channels_cyr2latSettingsDialogResetted; + /// No description provided for @channels_channelUpdated. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index b3e12799..a34ae450 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -1174,6 +1174,43 @@ class AppLocalizationsBg extends AppLocalizations { @override String get channels_smazCompression => 'Компресия SMAZ'; + @override + String get channels_cyr2latCompression => 'Компресия Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Заменя някои кирилични символи с латиница при изпращане.'; + + @override + String get channels_cyr2latSettingsHeading => 'Настройки на Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Списък със замествания'; + + @override + String get channels_cyr2latSettingsDscr => + 'Редактиране на JSON конфигурацията за заместване на символи'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON карта за замествания'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Списъкът със замествания е актуализиран'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Неправилен JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Възстановяване на първоначалните настройки'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Настройките за заместване на Cyr2Lat са възстановени към първоначалните'; + @override String channels_channelUpdated(String name) { return 'Каналът \"$name\" е актуализиран'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index d7c16914..7f6cfb25 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1170,6 +1170,42 @@ class AppLocalizationsDe extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ-Komprimierung'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat-Komprimierung'; + + @override + String get channels_cyr2latCompressionDscr => + 'Ersetzt einige kyrillische Zeichen durch lateinische Zeichen, wenn sie gesendet werden.'; + + @override + String get channels_cyr2latSettingsHeading => 'Cyr2Lat-Einstellungen'; + + @override + String get channels_cyr2latSettingsSubheading => 'Ersetzungsliste'; + + @override + String get channels_cyr2latSettingsDscr => + 'JSON-Konfiguration für die Zeichenersetzung bearbeiten'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON-Ersetzungstabelle'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Ersetzungsliste aktualisiert'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Ungültiges JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => 'Auf Standard zurücksetzen'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Die Cyr2Lat-Ersetzungseinstellungen wurden auf die Standardeinstellungen zurückgesetzt'; + @override String channels_channelUpdated(String name) { return 'Kanal \"$name\" aktualisiert'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index a2a88b0d..c63079e2 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1150,6 +1150,42 @@ class AppLocalizationsEn extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ compression'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat compression'; + + @override + String get channels_cyr2latCompressionDscr => + 'Replaces some Cyrillic characters with Latin characters when sending.'; + + @override + String get channels_cyr2latSettingsHeading => 'Cyr2Lat Setup'; + + @override + String get channels_cyr2latSettingsSubheading => 'List of replacements'; + + @override + String get channels_cyr2latSettingsDscr => + 'Edit the JSON configuration of character replacement'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON replacement map'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'The list of replacements has been updated'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Invalid JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => 'Reset to default'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Cyr2Lat replacement settings reset to default'; + @override String channels_channelUpdated(String name) { return 'Channel \"$name\" updated'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index a1270124..4f2980c8 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1172,6 +1172,43 @@ class AppLocalizationsEs extends AppLocalizations { @override String get channels_smazCompression => 'Compresión SMAZ'; + @override + String get channels_cyr2latCompression => 'Compresión Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Reemplaza algunos caracteres cirílicos con caracteres latinos al enviar.'; + + @override + String get channels_cyr2latSettingsHeading => 'Configuración de Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Lista de sustituciones'; + + @override + String get channels_cyr2latSettingsDscr => + 'Editar la configuración JSON de sustitución de caracteres'; + + @override + String get channels_cyr2latSettingsDialogHint => 'Mapa JSON de sustituciones'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Lista de sustituciones actualizada'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'JSON incorrecto: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Restablecer valores predeterminados'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'La configuración de sustituciones de Cyr2Lat se ha restablecido a los valores predeterminados'; + @override String channels_channelUpdated(String name) { return 'Canal \"$name\" actualizado'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index a0063914..57105660 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1177,6 +1177,44 @@ class AppLocalizationsFr extends AppLocalizations { @override String get channels_smazCompression => 'Compression SMAZ'; + @override + String get channels_cyr2latCompression => 'Compression Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Remplace certains caractères cyrilliques par des caractères latins lors de l\'envoi.'; + + @override + String get channels_cyr2latSettingsHeading => 'Paramètres Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Liste des remplacements'; + + @override + String get channels_cyr2latSettingsDscr => + 'Modifier la configuration JSON des remplacements de caractères'; + + @override + String get channels_cyr2latSettingsDialogHint => + 'Tableau de remplacement JSON'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Liste de remplacement mise à jour'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'JSON incorrect : $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Réinitialiser les paramètres par défaut'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Les paramètres de remplacement Cyr2Lat ont été réinitialisés aux valeurs par défaut'; + @override String channels_channelUpdated(String name) { return 'Le canal \"$name\" a été mis à jour'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 1ad8558c..690fed15 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -1177,6 +1177,42 @@ class AppLocalizationsHu extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ kompresszió'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat kompresszió'; + + @override + String get channels_cyr2latCompressionDscr => + 'Néhány Cirill betűt Latin betűkkel helyettesít küldéskor.'; + + @override + String get channels_cyr2latSettingsHeading => 'Cyr2Lat beállítások'; + + @override + String get channels_cyr2latSettingsSubheading => 'Helyettesítési lista'; + + @override + String get channels_cyr2latSettingsDscr => + 'A karakterhelyettesítési JSON-konfiguráció szerkesztése'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON-csere táblázat'; + + @override + String get channels_cyr2latSettingsDialogSuccess => 'A csere lista frissítve'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Hibás JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Alapértelmezett értékekre állítás'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'A Cyr2Lat helyettesítési beállítások alapértelmezett értékekre lettek állítva'; + @override String channels_channelUpdated(String name) { return 'A $name csatorna frissítve'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 3a55559a..beea60d4 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1173,6 +1173,44 @@ class AppLocalizationsIt extends AppLocalizations { @override String get channels_smazCompression => 'Compressione SMAZ'; + @override + String get channels_cyr2latCompression => 'Compressione Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Sostituisce alcuni caratteri cirillici con caratteri latini durante l\'invio.'; + + @override + String get channels_cyr2latSettingsHeading => 'Impostazioni Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Elenco delle sostituzioni'; + + @override + String get channels_cyr2latSettingsDscr => + 'Modifica la configurazione JSON delle sostituzioni dei caratteri'; + + @override + String get channels_cyr2latSettingsDialogHint => + 'Mappa JSON delle sostituzioni'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Elenco delle sostituzioni aggiornato'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'JSON non corretto: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Ripristina impostazioni predefinite'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Le impostazioni di sostituzione Cyr2Lat sono state ripristinate alle impostazioni predefinite'; + @override String channels_channelUpdated(String name) { return 'Canale \"$name\" aggiornato'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index afb8c29b..6dadfa38 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -1117,6 +1117,39 @@ class AppLocalizationsJa extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ 圧縮'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat 圧縮'; + + @override + String get channels_cyr2latCompressionDscr => '送信時に一部のキリル文字をラテン文字に置き換えます。'; + + @override + String get channels_cyr2latSettingsHeading => 'cyr2latの設定'; + + @override + String get channels_cyr2latSettingsSubheading => '置換リスト'; + + @override + String get channels_cyr2latSettingsDscr => '文字置換のJSON設定を編集する'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON置換マップ'; + + @override + String get channels_cyr2latSettingsDialogSuccess => '置換リストが更新されました'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return '不正なJSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => '初期設定に戻す'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'cyr2latの置換設定が初期設定に戻されました'; + @override String channels_channelUpdated(String name) { return 'チャンネル「$name」が更新されました'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index ff4bd261..358c7b1b 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -1112,6 +1112,39 @@ class AppLocalizationsKo extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ 압축'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat 압축'; + + @override + String get channels_cyr2latCompressionDscr => '보낼 때 일부 키릴 문자를 라틴 문자로 바꿉니다.'; + + @override + String get channels_cyr2latSettingsHeading => 'Cyr2Lat 설정'; + + @override + String get channels_cyr2latSettingsSubheading => '변환 목록'; + + @override + String get channels_cyr2latSettingsDscr => '문자 변환 JSON 구성 편집'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON 변환 맵'; + + @override + String get channels_cyr2latSettingsDialogSuccess => '변환 목록이 업데이트되었습니다'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return '잘못된 JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => '초기값으로 초기화'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Cyr2Lat 변환 설정이 초기값으로 초기화되었습니다'; + @override String channels_channelUpdated(String name) { return '채널 \"$name\"이 업데이트되었습니다.'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index dd770e15..78908531 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1161,6 +1161,43 @@ class AppLocalizationsNl extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ compressie'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat compressie'; + + @override + String get channels_cyr2latCompressionDscr => + 'Vervangt sommige Cyrillische tekens door Latijnse tekens bij het verzenden.'; + + @override + String get channels_cyr2latSettingsHeading => 'Instellingen Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Lijst met vervangingen'; + + @override + String get channels_cyr2latSettingsDscr => + 'Bewerk de JSON-configuratie voor tekenvervanging'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON-vervangingskaart'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Lijst met vervangingen bijgewerkt'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Onjuiste JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Terugzetten naar standaard'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'De instellingen voor Cyr2Lat-vervangingen zijn teruggezet naar de standaardinstellingen'; + @override String channels_channelUpdated(String name) { return 'Kanaal \"$name\" is bijgewerkt'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 357dd7e3..fcb236fc 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1181,6 +1181,43 @@ class AppLocalizationsPl extends AppLocalizations { @override String get channels_smazCompression => 'Kompresja SMAZ'; + @override + String get channels_cyr2latCompression => 'Kompresja Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Zastępuje niektóre znaki cyrylicy alfabetem łacińskim podczas wysyłania.'; + + @override + String get channels_cyr2latSettingsHeading => 'Ustawienia Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Lista zamian'; + + @override + String get channels_cyr2latSettingsDscr => + 'Edytuj konfigurację JSON zamiany znaków'; + + @override + String get channels_cyr2latSettingsDialogHint => 'Mapa zamian JSON'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Lista zamian zaktualizowana'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Nieprawidłowy JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Przywróć ustawienia domyślne'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Ustawienia zamiany Cyr2Lat zostały przywrócone do wartości domyślnych'; + @override String channels_channelUpdated(String name) { return 'Kanał \"$name\" został zaktualizowany'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 2dfcd8bd..97c5a50f 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1172,6 +1172,43 @@ class AppLocalizationsPt extends AppLocalizations { @override String get channels_smazCompression => 'Compressão SMAZ'; + @override + String get channels_cyr2latCompression => 'Compressão Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Substitui alguns caracteres cirílicos por caracteres latinos ao enviar.'; + + @override + String get channels_cyr2latSettingsHeading => 'Configuração do Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Lista de substituições'; + + @override + String get channels_cyr2latSettingsDscr => + 'Editar a configuração JSON de substituição de caracteres'; + + @override + String get channels_cyr2latSettingsDialogHint => 'Mapa de substituições JSON'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Lista de substituições atualizada'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'JSON incorreto: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Redefinir para os valores iniciais'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'As configurações de substituição Cyr2Lat foram redefinidas para os valores iniciais'; + @override String channels_channelUpdated(String name) { return 'Canal \"$name\" atualizado'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 4fac42ce..a4d6dd07 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1172,6 +1172,41 @@ class AppLocalizationsRu extends AppLocalizations { @override String get channels_smazCompression => 'Сжатие SMAZ'; + @override + String get channels_cyr2latCompression => 'Сжатие Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Заменяет некоторые кириллические символы на латиницу при отправке.'; + + @override + String get channels_cyr2latSettingsHeading => 'Настройка Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Список замен'; + + @override + String get channels_cyr2latSettingsDscr => + 'Редактировать JSON-конфигурацию замены символов'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON-карта замен'; + + @override + String get channels_cyr2latSettingsDialogSuccess => 'Список замен обновлён'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Некорректный JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => 'Сбросить к начальным'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Настройки замен Cyr2Lat сброшены к начальным'; + @override String channels_channelUpdated(String name) { return 'Канал \"$name\" обновлён'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index c42e0249..5f523de0 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -1161,6 +1161,43 @@ class AppLocalizationsSk extends AppLocalizations { @override String get channels_smazCompression => 'Odstránenie kompresie SMAZ'; + @override + String get channels_cyr2latCompression => 'Odstránenie kompresie Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Pri odosielaní nahradí niektoré znaky cyriliky latinskými znakmi.'; + + @override + String get channels_cyr2latSettingsHeading => 'Nastavenia Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Zoznam nahradení'; + + @override + String get channels_cyr2latSettingsDscr => + 'Upravte konfiguráciu JSON pre nahradenie znakov'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON mapa nahradení'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Zoznam nahradení bol aktualizovaný'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Nesprávny JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => + 'Obnoviť predvolené nastavenia'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Nastavenia nahradzovania Cyr2Lat boli obnovené na predvolené'; + @override String channels_channelUpdated(String name) { return 'Kanál \"$name\" bol aktualizovaný'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 2d89aa41..91df3cf4 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -1159,6 +1159,42 @@ class AppLocalizationsSl extends AppLocalizations { @override String get channels_smazCompression => 'Kompresija SMAZ'; + @override + String get channels_cyr2latCompression => 'Kompresija Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Pri pošiljanju nekatere cirilice nadomesti z latiničnimi.'; + + @override + String get channels_cyr2latSettingsHeading => 'Nastavitve Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Seznam zamenjav'; + + @override + String get channels_cyr2latSettingsDscr => + 'Uredi JSON-konfiguracijo zamenjav znakov'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON-tabela zamenjav'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Seznam zamenjav je posodobljen'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Nepravilen JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => 'Ponastavi na privzete'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Nastavitve zamenjav Cyr2Lat so ponastavljene na privzete'; + @override String channels_channelUpdated(String name) { return 'Kanal $name je bil posodobljen'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 38e08939..2273c426 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -1151,6 +1151,42 @@ class AppLocalizationsSv extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ-komprimering'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat-komprimering'; + + @override + String get channels_cyr2latCompressionDscr => + 'Ersätter vissa kyrilliska tecken med latinska tecken när du skickar.'; + + @override + String get channels_cyr2latSettingsHeading => 'Inställningar för Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Ersättningslista'; + + @override + String get channels_cyr2latSettingsDscr => + 'Redigera JSON-konfigurationen för teckenersättning'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON-ersättningskarta'; + + @override + String get channels_cyr2latSettingsDialogSuccess => + 'Ersättningslistan har uppdaterats'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Felaktig JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => 'Återställ till standard'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Inställningarna för Cyr2Lat-ersättningar har återställts till standard'; + @override String channels_channelUpdated(String name) { return 'Kanalen \"$name\" har uppdaterats'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 8ed4b9ff..639cbd12 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -1166,6 +1166,41 @@ class AppLocalizationsUk extends AppLocalizations { @override String get channels_smazCompression => 'Стиснення SMAZ'; + @override + String get channels_cyr2latCompression => 'Стиснення Cyr2Lat'; + + @override + String get channels_cyr2latCompressionDscr => + 'Замінює деякі кириличні символи на латиницю при відправці.'; + + @override + String get channels_cyr2latSettingsHeading => 'Налаштування Cyr2Lat'; + + @override + String get channels_cyr2latSettingsSubheading => 'Список замін'; + + @override + String get channels_cyr2latSettingsDscr => + 'Редагувати JSON-конфігурацію заміни символів'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON-карта замін'; + + @override + String get channels_cyr2latSettingsDialogSuccess => 'Список замін оновлено'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'Некоректний JSON: $error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => 'Скинути до початкових'; + + @override + String get channels_cyr2latSettingsDialogResetted => + 'Налаштування замін Cyr2Lat скинуто до початкових'; + @override String channels_channelUpdated(String name) { return 'Канал «$name» оновлено'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 4f38c64a..0ccb9c59 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1099,6 +1099,38 @@ class AppLocalizationsZh extends AppLocalizations { @override String get channels_smazCompression => 'SMAZ 压缩'; + @override + String get channels_cyr2latCompression => 'Cyr2Lat 压缩'; + + @override + String get channels_cyr2latCompressionDscr => '发送时将一些西里尔字符替换为拉丁字符。'; + + @override + String get channels_cyr2latSettingsHeading => 'Cyr2Lat 設定'; + + @override + String get channels_cyr2latSettingsSubheading => '替換清單'; + + @override + String get channels_cyr2latSettingsDscr => '編輯 JSON 字元替換設定檔'; + + @override + String get channels_cyr2latSettingsDialogHint => 'JSON 替換映射表'; + + @override + String get channels_cyr2latSettingsDialogSuccess => '替換清單已更新'; + + @override + String channels_cyr2latSettingsDialogWrongJSON(Object error) { + return 'JSON 格式錯誤:$error'; + } + + @override + String get channels_cyr2latSettingsDialogReset => '還原為預設值'; + + @override + String get channels_cyr2latSettingsDialogResetted => 'Cyr2Lat 替換設定已還原為預設值'; + @override String channels_channelUpdated(String name) { return '频道 \"$name\" 已更新'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 96bdb845..8ffadd08 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "SMAZ compressie", + "channels_cyr2latCompression": "Cyr2Lat compressie", + "channels_cyr2latCompressionDscr": "Vervangt sommige Cyrillische tekens door Latijnse tekens bij het verzenden.", + "channels_cyr2latSettingsHeading": "Instellingen Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Lijst met vervangingen", + "channels_cyr2latSettingsDscr": "Bewerk de JSON-configuratie voor tekenvervanging", + "channels_cyr2latSettingsDialogHint": "JSON-vervangingskaart", + "channels_cyr2latSettingsDialogSuccess": "Lijst met vervangingen bijgewerkt", + "channels_cyr2latSettingsDialogWrongJSON": "Onjuiste JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Terugzetten naar standaard", + "channels_cyr2latSettingsDialogResetted": "De instellingen voor Cyr2Lat-vervangingen zijn teruggezet naar de standaardinstellingen", "channels_channelUpdated": "Kanaal \"{name}\" is bijgewerkt", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index b62a78af..d44f8c80 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -398,6 +398,16 @@ } }, "channels_smazCompression": "Kompresja SMAZ", + "channels_cyr2latCompression": "Kompresja Cyr2Lat", + "channels_cyr2latCompressionDscr": "Zastępuje niektóre znaki cyrylicy alfabetem łacińskim podczas wysyłania.", + "channels_cyr2latSettingsHeading": "Ustawienia Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Lista zamian", + "channels_cyr2latSettingsDscr": "Edytuj konfigurację JSON zamiany znaków", + "channels_cyr2latSettingsDialogHint": "Mapa zamian JSON", + "channels_cyr2latSettingsDialogSuccess": "Lista zamian zaktualizowana", + "channels_cyr2latSettingsDialogWrongJSON": "Nieprawidłowy JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Przywróć ustawienia domyślne", + "channels_cyr2latSettingsDialogResetted": "Ustawienia zamiany Cyr2Lat zostały przywrócone do wartości domyślnych", "channels_channelUpdated": "Kanał \"{name}\" został zaktualizowany", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index bf3e8936..67840825 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "Compressão SMAZ", + "channels_cyr2latCompression": "Compressão Cyr2Lat", + "channels_cyr2latCompressionDscr": "Substitui alguns caracteres cirílicos por caracteres latinos ao enviar.", + "channels_cyr2latSettingsHeading": "Configuração do Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Lista de substituições", + "channels_cyr2latSettingsDscr": "Editar a configuração JSON de substituição de caracteres", + "channels_cyr2latSettingsDialogHint": "Mapa de substituições JSON", + "channels_cyr2latSettingsDialogSuccess": "Lista de substituições atualizada", + "channels_cyr2latSettingsDialogWrongJSON": "JSON incorreto: {error}", + "channels_cyr2latSettingsDialogReset": "Redefinir para os valores iniciais", + "channels_cyr2latSettingsDialogResetted": "As configurações de substituição Cyr2Lat foram redefinidas para os valores iniciais", "channels_channelUpdated": "Canal \"{name}\" atualizado", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index a83d1394..a2052b02 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -252,6 +252,16 @@ "channels_channelAdded": "Канал \"{name}\" добавлен", "channels_editChannelTitle": "Изменить канал {index}", "channels_smazCompression": "Сжатие SMAZ", + "channels_cyr2latCompression": "Сжатие Cyr2Lat", + "channels_cyr2latCompressionDscr": "Заменяет некоторые кириллические символы на латиницу при отправке.", + "channels_cyr2latSettingsHeading": "Настройка Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Список замен", + "channels_cyr2latSettingsDscr": "Редактировать JSON-конфигурацию замены символов", + "channels_cyr2latSettingsDialogHint": "JSON-карта замен", + "channels_cyr2latSettingsDialogSuccess": "Список замен обновлён", + "channels_cyr2latSettingsDialogWrongJSON": "Некорректный JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Сбросить к начальным", + "channels_cyr2latSettingsDialogResetted": "Настройки замен Cyr2Lat сброшены к начальным", "channels_channelUpdated": "Канал \"{name}\" обновлён", "channels_publicChannelAdded": "Публичный канал добавлен", "channels_sortBy": "Сортировка", diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index e4466c30..27db3e77 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "Odstránenie kompresie SMAZ", + "channels_cyr2latCompression": "Odstránenie kompresie Cyr2Lat", + "channels_cyr2latCompressionDscr": "Pri odosielaní nahradí niektoré znaky cyriliky latinskými znakmi.", + "channels_cyr2latSettingsHeading": "Nastavenia Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Zoznam nahradení", + "channels_cyr2latSettingsDscr": "Upravte konfiguráciu JSON pre nahradenie znakov", + "channels_cyr2latSettingsDialogHint": "JSON mapa nahradení", + "channels_cyr2latSettingsDialogSuccess": "Zoznam nahradení bol aktualizovaný", + "channels_cyr2latSettingsDialogWrongJSON": "Nesprávny JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Obnoviť predvolené nastavenia", + "channels_cyr2latSettingsDialogResetted": "Nastavenia nahradzovania Cyr2Lat boli obnovené na predvolené", "channels_channelUpdated": "Kanál \"{name}\" bol aktualizovaný", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index f6a317ef..b941fe78 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "Kompresija SMAZ", + "channels_cyr2latCompression": "Kompresija Cyr2Lat", + "channels_cyr2latCompressionDscr": "Pri pošiljanju nekatere cirilice nadomesti z latiničnimi.", + "channels_cyr2latSettingsHeading": "Nastavitve Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Seznam zamenjav", + "channels_cyr2latSettingsDscr": "Uredi JSON-konfiguracijo zamenjav znakov", + "channels_cyr2latSettingsDialogHint": "JSON-tabela zamenjav", + "channels_cyr2latSettingsDialogSuccess": "Seznam zamenjav je posodobljen", + "channels_cyr2latSettingsDialogWrongJSON": "Nepravilen JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Ponastavi na privzete", + "channels_cyr2latSettingsDialogResetted": "Nastavitve zamenjav Cyr2Lat so ponastavljene na privzete", "channels_channelUpdated": "Kanal {name} je bil posodobljen", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index eab348c7..ca9abac8 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -388,6 +388,16 @@ } }, "channels_smazCompression": "SMAZ-komprimering", + "channels_cyr2latCompression": "Cyr2Lat-komprimering", + "channels_cyr2latCompressionDscr": "Ersätter vissa kyrilliska tecken med latinska tecken när du skickar.", + "channels_cyr2latSettingsHeading": "Inställningar för Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Ersättningslista", + "channels_cyr2latSettingsDscr": "Redigera JSON-konfigurationen för teckenersättning", + "channels_cyr2latSettingsDialogHint": "JSON-ersättningskarta", + "channels_cyr2latSettingsDialogSuccess": "Ersättningslistan har uppdaterats", + "channels_cyr2latSettingsDialogWrongJSON": "Felaktig JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Återställ till standard", + "channels_cyr2latSettingsDialogResetted": "Inställningarna för Cyr2Lat-ersättningar har återställts till standard", "channels_channelUpdated": "Kanalen \"{name}\" har uppdaterats", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index a005b369..826d3b84 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -389,6 +389,16 @@ } }, "channels_smazCompression": "Стиснення SMAZ", + "channels_cyr2latCompression": "Стиснення Cyr2Lat", + "channels_cyr2latCompressionDscr": "Замінює деякі кириличні символи на латиницю при відправці.", + "channels_cyr2latSettingsHeading": "Налаштування Cyr2Lat", + "channels_cyr2latSettingsSubheading": "Список замін", + "channels_cyr2latSettingsDscr": "Редагувати JSON-конфігурацію заміни символів", + "channels_cyr2latSettingsDialogHint": "JSON-карта замін", + "channels_cyr2latSettingsDialogSuccess": "Список замін оновлено", + "channels_cyr2latSettingsDialogWrongJSON": "Некоректний JSON: {error}", + "channels_cyr2latSettingsDialogReset": "Скинути до початкових", + "channels_cyr2latSettingsDialogResetted": "Налаштування замін Cyr2Lat скинуто до початкових", "channels_channelUpdated": "Канал «{name}» оновлено", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9dc2325c..6932a4bc 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -403,6 +403,16 @@ } }, "channels_smazCompression": "SMAZ 压缩", + "channels_cyr2latCompression": "Cyr2Lat 压缩", + "channels_cyr2latCompressionDscr": "发送时将一些西里尔字符替换为拉丁字符。", + "channels_cyr2latSettingsHeading": "Cyr2Lat 設定", + "channels_cyr2latSettingsSubheading": "替換清單", + "channels_cyr2latSettingsDscr": "編輯 JSON 字元替換設定檔", + "channels_cyr2latSettingsDialogHint": "JSON 替換映射表", + "channels_cyr2latSettingsDialogSuccess": "替換清單已更新", + "channels_cyr2latSettingsDialogWrongJSON": "JSON 格式錯誤:{error}", + "channels_cyr2latSettingsDialogReset": "還原為預設值", + "channels_cyr2latSettingsDialogResetted": "Cyr2Lat 替換設定已還原為預設值", "channels_channelUpdated": "频道 \"{name}\" 已更新", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/models/app_settings.dart b/lib/models/app_settings.dart index d3d3421d..d7c176e4 100644 --- a/lib/models/app_settings.dart +++ b/lib/models/app_settings.dart @@ -13,6 +13,31 @@ extension UnitSystemValue on UnitSystem { } } +const Map defaultCyr2LatCharMap = { + 'А': 'A', + 'В': 'B', + 'Е': 'E', + 'Ё': 'E', + 'З': '3', + 'К': 'K', + 'М': 'M', + 'Н': 'H', + 'О': 'O', + 'Р': 'P', + 'С': 'C', + 'Т': 'T', + 'Х': 'X', + 'Ь': 'b', + 'а': 'a', + 'е': 'e', + 'ё': 'e', + 'о': 'o', + 'р': 'p', + 'с': 'c', + 'у': 'y', + 'х': 'x', +}; + class AppSettings { static const Object _unset = Object(); @@ -57,6 +82,7 @@ class AppSettings { final String? translationModelSourceUrl; final String? translationSelectedModelId; final List translationDownloadedModels; + final Map cyr2latCharMap; AppSettings({ this.clearPathOnMaxRetry = false, @@ -100,10 +126,12 @@ class AppSettings { this.translationModelSourceUrl, this.translationSelectedModelId, List? translationDownloadedModels, + Map? cyr2latCharMap, }) : batteryChemistryByDeviceId = batteryChemistryByDeviceId ?? {}, batteryChemistryByRepeaterId = batteryChemistryByRepeaterId ?? {}, mutedChannels = mutedChannels ?? {}, - translationDownloadedModels = translationDownloadedModels ?? const []; + translationDownloadedModels = translationDownloadedModels ?? const [], + cyr2latCharMap = cyr2latCharMap ?? defaultCyr2LatCharMap; Map toJson() { return { @@ -150,6 +178,7 @@ class AppSettings { 'translation_downloaded_models': translationDownloadedModels .map((model) => model.toJson()) .toList(), + 'cyr2lat_char_map': cyr2latCharMap, }; } @@ -237,6 +266,11 @@ class AppSettings { ) .toList() ?? const [], + cyr2latCharMap: + (json['cyr2lat_char_map'] as Map?)?.map( + (key, value) => MapEntry(key.toString(), value.toString()), + ) ?? + defaultCyr2LatCharMap, ); } @@ -282,6 +316,7 @@ class AppSettings { Object? translationModelSourceUrl = _unset, Object? translationSelectedModelId = _unset, List? translationDownloadedModels, + Map? cyr2latCharMap, }) { return AppSettings( clearPathOnMaxRetry: clearPathOnMaxRetry ?? this.clearPathOnMaxRetry, @@ -345,6 +380,7 @@ class AppSettings { : translationSelectedModelId as String?, translationDownloadedModels: translationDownloadedModels ?? this.translationDownloadedModels, + cyr2latCharMap: cyr2latCharMap ?? this.cyr2latCharMap, ); } } diff --git a/lib/screens/app_settings_screen.dart b/lib/screens/app_settings_screen.dart index 80d8adbc..425248de 100644 --- a/lib/screens/app_settings_screen.dart +++ b/lib/screens/app_settings_screen.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -60,6 +61,8 @@ class AppSettingsScreen extends StatelessWidget { const SizedBox(height: 16), _buildMapSettingsCard(context, settingsService), const SizedBox(height: 16), + _buildCyr2LatCard(context, settingsService), + const SizedBox(height: 16), _buildDebugCard(context, settingsService), ], ); @@ -1255,6 +1258,116 @@ class AppSettingsScreen extends StatelessWidget { return '${sizeMb.toStringAsFixed(1)} MB • $source'; } + Widget _buildCyr2LatCard( + BuildContext context, + AppSettingsService settingsService, + ) { + return Card( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), + child: Text( + context.l10n.channels_cyr2latSettingsHeading, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + ), + ListTile( + leading: const Icon(Icons.translate), + + title: Text(context.l10n.channels_cyr2latSettingsSubheading), + subtitle: Text(context.l10n.channels_cyr2latSettingsDscr), + trailing: const Icon(Icons.chevron_right), + onTap: () => _showCyr2LatDialog(context, settingsService), + ), + ], + ), + ); + } + + void _showCyr2LatDialog( + BuildContext context, + AppSettingsService settingsService, + ) { + final controller = TextEditingController( + text: const JsonEncoder.withIndent( + ' ', + ).convert(settingsService.settings.cyr2latCharMap), + ); + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(context.l10n.channels_cyr2latSettingsDscr), + content: SingleChildScrollView( + child: TextField( + controller: controller, + maxLines: 20, + decoration: InputDecoration( + border: const OutlineInputBorder(), + hintText: context.l10n.channels_cyr2latSettingsDialogHint, + ), + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(context.l10n.common_cancel), + ), + TextButton( + onPressed: () { + try { + final json = + jsonDecode(controller.text) as Map; + final map = json.map( + (key, value) => MapEntry(key, value.toString()), + ); + final newSettings = settingsService.settings.copyWith( + cyr2latCharMap: map, + ); + settingsService.updateSettings(newSettings); + Navigator.pop(context); + showDismissibleSnackBar( + context, + content: Text( + context.l10n.channels_cyr2latSettingsDialogSuccess, + ), + ); + } catch (e) { + showDismissibleSnackBar( + context, + content: Text( + context.l10n.channels_cyr2latSettingsDialogWrongJSON( + e.toString(), + ), + ), + ); + } + }, + child: Text(context.l10n.common_save), + ), + TextButton( + onPressed: () { + final newSettings = settingsService.settings.copyWith( + cyr2latCharMap: defaultCyr2LatCharMap, + ); + settingsService.updateSettings(newSettings); + Navigator.pop(context); + showDismissibleSnackBar( + context, + content: Text( + context.l10n.channels_cyr2latSettingsDialogResetted, + ), + ); + }, + child: Text(context.l10n.channels_cyr2latSettingsDialogReset), + ), + ], + ), + ); + } + Widget _buildDebugCard( BuildContext context, AppSettingsService settingsService, diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index b203cbbe..4f43e9c3 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -11,6 +11,7 @@ import '../connector/meshcore_connector.dart'; import '../utils/platform_info.dart'; import '../helpers/chat_scroll_controller.dart'; import '../connector/meshcore_protocol.dart'; +import '../helpers/cyr2lat.dart'; import '../helpers/gif_helper.dart'; import '../helpers/reaction_helper.dart'; import '../helpers/snack_bar_builder.dart'; @@ -1100,7 +1101,12 @@ class _ChannelChatScreenState extends State { hintText: context.l10n.chat_typeMessage, onSubmitted: (_) => _sendMessage(), encoder: - connector.isChannelSmazEnabled(widget.channel.index) + (connector.isChannelSmazEnabled( + widget.channel.index, + ) || + connector.isChannelCyr2LatEnabled( + widget.channel.index, + )) ? (text) => connector.prepareChannelOutboundText( widget.channel.index, text, @@ -1213,6 +1219,15 @@ class _ChannelChatScreenState extends State { return; } + // When messageText is transformed with cyr2lat, it (generally) hasn't visual differences, + // but we getting messages doubles in chat screen (source text and transformed). + // To prevent, we'll perform transform of source before pass to main sender logic. + // We can pass whole text, senderName will be kept intact + if (connector.isChannelCyr2LatEnabled(widget.channel.index)) { + messageText = Cyr2Lat.encode(messageText); + } + // end transform + _textController.clear(); _cancelReply(); _textFieldFocusNode.requestFocus(); diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index 44c7a69c..868aac77 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -1404,6 +1404,7 @@ class _ChannelsScreenState extends State final nameController = TextEditingController(text: channel.name); final pskController = TextEditingController(text: channel.pskHex); bool smazEnabled = connector.isChannelSmazEnabled(channel.index); + bool cyr2latEnabled = connector.isChannelCyr2LatEnabled(channel.index); showDialog( context: context, @@ -1449,7 +1450,26 @@ class _ChannelsScreenState extends State contentPadding: EdgeInsets.zero, title: Text(dialogContext.l10n.channels_smazCompression), value: smazEnabled, - onChanged: (value) => setState(() => smazEnabled = value), + onChanged: (value) => setState(() { + smazEnabled = value; + if (smazEnabled) { + cyr2latEnabled = false; + } + }), + ), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: Text(dialogContext.l10n.channels_cyr2latCompression), + subtitle: Text( + dialogContext.l10n.channels_cyr2latCompressionDscr, + ), + value: cyr2latEnabled, + onChanged: (value) => setState(() { + cyr2latEnabled = value; + if (cyr2latEnabled) { + smazEnabled = false; + } + }), ), ], ), @@ -1482,6 +1502,10 @@ class _ChannelsScreenState extends State channel.index, smazEnabled, ); + await connector.setChannelCyr2LatEnabled( + channel.index, + cyr2latEnabled, + ); if (!context.mounted) return; showDismissibleSnackBar( context, diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index ffa8344b..b9cdf0ec 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -13,6 +13,7 @@ import 'package:latlong2/latlong.dart'; import '../connector/meshcore_connector.dart'; import '../connector/meshcore_protocol.dart'; +import '../helpers/cyr2lat.dart'; import '../helpers/reaction_helper.dart'; import '../widgets/message_status_icon.dart'; import '../helpers/chat_scroll_controller.dart'; @@ -574,9 +575,12 @@ class _ChatScreenState extends State { hintText: context.l10n.chat_typeMessage, onSubmitted: (_) => _sendMessage(connector), encoder: - connector.isContactSmazEnabled( - widget.contact.publicKeyHex, - ) + (connector.isContactSmazEnabled( + widget.contact.publicKeyHex, + ) || + connector.isContactCyr2LatEnabled( + widget.contact.publicKeyHex, + )) ? (text) => connector.prepareContactOutboundText( widget.contact, text, @@ -695,6 +699,18 @@ class _ChatScreenState extends State { return; } + // This is only for cyr2lat compression - to see the message being sent in the same format as the other person will receive + try { + if (connector.isContactCyr2LatEnabled( + _resolveContact(connector).publicKeyHex, + )) { + outgoingText = Cyr2Lat.encode(outgoingText); + } + } catch (_) { + // TODO maybe log + } + // end transform + _textController.clear(); _textFieldFocusNode.requestFocus(); connector.sendMessage( @@ -1196,8 +1212,12 @@ class _ChatScreenState extends State { void _showContactSettings(BuildContext context) { final connector = Provider.of(context, listen: false); connector.ensureContactSmazSettingLoaded(widget.contact.publicKeyHex); + connector.ensureContactCyr2LatSettingLoaded(widget.contact.publicKeyHex); final contact = widget.contact; bool smazEnabled = connector.isContactSmazEnabled(contact.publicKeyHex); + bool cyr2latEnabled = connector.isContactCyr2LatEnabled( + contact.publicKeyHex, + ); bool teleBaseEnabled = contact.teleBaseEnabled; bool teleLocEnabled = contact.teleLocEnabled; bool teleEnvEnabled = contact.teleEnvEnabled; @@ -1228,7 +1248,39 @@ class _ChatScreenState extends State { contact.publicKeyHex, value, ); - setDialogState(() => smazEnabled = value); + connector.setContactCyr2LatEnabled( + contact.publicKeyHex, + false, + ); + setDialogState(() { + smazEnabled = value; + if (smazEnabled) { + cyr2latEnabled = false; + } + }); + }, + ), + const Divider(height: 8), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: Text(context.l10n.channels_cyr2latCompression), + subtitle: Text(context.l10n.channels_cyr2latCompressionDscr), + value: cyr2latEnabled, + onChanged: (value) { + connector.setContactCyr2LatEnabled( + contact.publicKeyHex, + value, + ); + connector.setContactSmazEnabled( + contact.publicKeyHex, + false, + ); + setDialogState(() { + cyr2latEnabled = value; + if (cyr2latEnabled) { + smazEnabled = false; + } + }); }, ), const Divider(height: 8), diff --git a/lib/services/app_settings_service.dart b/lib/services/app_settings_service.dart index 283ce726..820fe0ec 100644 --- a/lib/services/app_settings_service.dart +++ b/lib/services/app_settings_service.dart @@ -4,6 +4,7 @@ import '../models/app_settings.dart'; import '../models/translation_support.dart'; import '../storage/prefs_manager.dart'; import '../utils/app_logger.dart'; +import '../helpers/cyr2lat.dart'; class AppSettingsService extends ChangeNotifier { static const String _settingsKey = 'app_settings'; @@ -32,16 +33,22 @@ class AppSettingsService extends ChangeNotifier { try { final json = jsonDecode(jsonStr) as Map; _settings = AppSettings.fromJson(json); + Cyr2Lat.setCharMap(_settings.cyr2latCharMap); notifyListeners(); } catch (e) { // If parsing fails, use defaults _settings = AppSettings(); + Cyr2Lat.setCharMap(_settings.cyr2latCharMap); } + } else { + _settings = AppSettings(); + Cyr2Lat.setCharMap(_settings.cyr2latCharMap); } } Future updateSettings(AppSettings newSettings) async { _settings = newSettings; + Cyr2Lat.setCharMap(_settings.cyr2latCharMap); notifyListeners(); final prefs = PrefsManager.instance; diff --git a/lib/storage/channel_settings_store.dart b/lib/storage/channel_settings_store.dart index 276826d7..eb637acd 100644 --- a/lib/storage/channel_settings_store.dart +++ b/lib/storage/channel_settings_store.dart @@ -3,12 +3,14 @@ import 'prefs_manager.dart'; class ChannelSettingsStore { static const String _keyPrefix = 'channel_smaz_'; + static const String _cyr2latKeyPrefix = 'channel_cyr2lat_'; String publicKeyHex = ''; set setPublicKeyHex(String value) => publicKeyHex = value.length > 10 ? value.substring(0, 10) : ''; String get keyFor => '$_keyPrefix$publicKeyHex'; + String get keyForCyr2Lat => '$_cyr2latKeyPrefix$publicKeyHex'; Future loadSmazEnabled(int channelIndex) async { if (publicKeyHex.isEmpty) { @@ -20,7 +22,7 @@ class ChannelSettingsStore { final prefs = PrefsManager.instance; final key = '$keyFor$channelIndex'; final oldKey = '$_keyPrefix$channelIndex'; - bool? enabled = prefs.getBool(oldKey); + bool? enabled = prefs.getBool(key); if (enabled == null) { // Attempt migration from legacy unscoped key on first load enabled = prefs.getBool(oldKey); @@ -46,4 +48,41 @@ class ChannelSettingsStore { final key = '$keyFor$channelIndex'; await prefs.setBool(key, enabled); } + + Future loadCyr2LatEnabled(int channelIndex) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot load channel Cyr2Lat settings.', + ); + return false; + } + final prefs = PrefsManager.instance; + final key = '$keyForCyr2Lat$channelIndex'; + final oldKey = '$_cyr2latKeyPrefix$channelIndex'; + bool? enabled = prefs.getBool(key); + if (enabled == null) { + // Attempt migration from legacy unscoped key on first load + enabled = prefs.getBool(oldKey); + prefs.remove(oldKey); + if (enabled != null) { + appLogger.info( + 'Migrating channel Cyr2Lat settings from legacy key $oldKey to scoped key $key', + ); + await prefs.setBool(key, enabled); + } + } + return enabled ?? false; + } + + Future saveCyr2LatEnabled(int channelIndex, bool enabled) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot save channel Cyr2Lat settings.', + ); + return; + } + final prefs = PrefsManager.instance; + final key = '$keyForCyr2Lat$channelIndex'; + await prefs.setBool(key, enabled); + } } diff --git a/lib/storage/contact_settings_store.dart b/lib/storage/contact_settings_store.dart index 94c6430e..5db15add 100644 --- a/lib/storage/contact_settings_store.dart +++ b/lib/storage/contact_settings_store.dart @@ -3,12 +3,14 @@ import 'prefs_manager.dart'; class ContactSettingsStore { static const String _keyPrefix = 'contact_smaz_'; + static const String _cyr2latKeyPrefix = 'contact_cyr2lat_'; String publicKeyHex = ''; set setPublicKeyHex(String value) => publicKeyHex = value.length > 10 ? value.substring(0, 10) : ''; String get keyFor => '$_keyPrefix$publicKeyHex'; + String get keyForCyr2Lat => '$_cyr2latKeyPrefix$publicKeyHex'; Future loadSmazEnabled(String contactKeyHex) async { if (publicKeyHex.isEmpty) { @@ -46,4 +48,41 @@ class ContactSettingsStore { final key = '$keyFor$contactKeyHex'; await prefs.setBool(key, enabled); } + + Future loadCyr2LatEnabled(String contactKeyHex) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot load contact Cyr2Lat settings.', + ); + return false; + } + final prefs = PrefsManager.instance; + final key = '$keyForCyr2Lat$contactKeyHex'; + final oldKey = '$_cyr2latKeyPrefix$contactKeyHex'; + bool? enabled = prefs.getBool(key); + if (enabled == null) { + // Attempt migration from legacy unscoped key on first load + enabled = prefs.getBool(oldKey); + prefs.remove(oldKey); + if (enabled != null) { + appLogger.info( + 'Migrating contact Cyr2Lat settings from legacy key $oldKey to scoped key $key', + ); + await prefs.setBool(key, enabled); + } + } + return prefs.getBool(key) ?? false; + } + + Future saveCyr2LatEnabled(String contactKeyHex, bool enabled) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot save contact Cyr2Lat settings.', + ); + return; + } + final prefs = PrefsManager.instance; + final key = '$keyForCyr2Lat$contactKeyHex'; + await prefs.setBool(key, enabled); + } } From f56c28a27de69bcfc2d92d37a50c365ae09b14ec Mon Sep 17 00:00:00 2001 From: HDDen <62592944+HDDen@users.noreply.github.com> Date: Sat, 25 Apr 2026 01:30:55 +0300 Subject: [PATCH 2/6] synced with last dev, added profiles for cyr2lat replacement dictionaries --- lib/connector/meshcore_connector.dart | 89 ++++++++ lib/l10n/app_bg.arb | 12 +- lib/l10n/app_de.arb | 12 +- lib/l10n/app_en.arb | 19 +- lib/l10n/app_es.arb | 23 ++- lib/l10n/app_fr.arb | 23 ++- lib/l10n/app_hu.arb | 23 ++- lib/l10n/app_it.arb | 23 ++- lib/l10n/app_ja.arb | 23 ++- lib/l10n/app_ko.arb | 23 ++- lib/l10n/app_localizations.dart | 76 +++++-- lib/l10n/app_localizations_bg.dart | 47 +++-- lib/l10n/app_localizations_de.dart | 46 +++-- lib/l10n/app_localizations_en.dart | 44 ++-- lib/l10n/app_localizations_es.dart | 47 +++-- lib/l10n/app_localizations_fr.dart | 46 +++-- lib/l10n/app_localizations_hu.dart | 45 +++-- lib/l10n/app_localizations_it.dart | 47 +++-- lib/l10n/app_localizations_ja.dart | 43 +++- lib/l10n/app_localizations_ko.dart | 43 +++- lib/l10n/app_localizations_nl.dart | 46 +++-- lib/l10n/app_localizations_pl.dart | 48 +++-- lib/l10n/app_localizations_pt.dart | 46 +++-- lib/l10n/app_localizations_ru.dart | 44 +++- lib/l10n/app_localizations_sk.dart | 47 +++-- lib/l10n/app_localizations_sl.dart | 46 +++-- lib/l10n/app_localizations_sv.dart | 45 +++-- lib/l10n/app_localizations_uk.dart | 44 +++- lib/l10n/app_localizations_zh.dart | 42 +++- lib/l10n/app_nl.arb | 23 ++- lib/l10n/app_pl.arb | 23 ++- lib/l10n/app_pt.arb | 21 +- lib/l10n/app_ru.arb | 23 ++- lib/l10n/app_sk.arb | 21 +- lib/l10n/app_sl.arb | 23 ++- lib/l10n/app_sv.arb | 23 ++- lib/l10n/app_uk.arb | 23 ++- lib/l10n/app_zh.arb | 21 +- lib/models/app_settings.dart | 110 +++++++++- lib/screens/app_settings_screen.dart | 256 ++++++++++++++++++++---- lib/screens/channels_screen.dart | 36 ++++ lib/screens/chat_screen.dart | 37 ++++ lib/services/app_settings_service.dart | 52 +++++ lib/storage/channel_settings_store.dart | 43 ++-- lib/storage/contact_settings_store.dart | 44 ++-- 45 files changed, 1547 insertions(+), 394 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 6e3a1b9f..2de74a63 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -285,10 +285,12 @@ class MeshCoreConnector extends ChangeNotifier { List _cachedChannels = []; final Map _channelSmazEnabled = {}; final Map _channelCyr2LatEnabled = {}; + final Map _channelCyr2LatProfileId = {}; bool _lastSentWasCliCommand = false; // Track if last sent message was a CLI command final Map _contactSmazEnabled = {}; final Map _contactCyr2LatEnabled = {}; + final Map _contactCyr2LatProfileId = {}; final Set _knownContactKeys = {}; final Map _contactUnreadCount = {}; final Map _repeaterBatterySnapshots = {}; @@ -611,10 +613,12 @@ class MeshCoreConnector extends ChangeNotifier { } bool isChannelCyr2LatEnabled(int channelIndex) { + _ensureChannelCyr2LatSettingLoaded(channelIndex); return _channelCyr2LatEnabled[channelIndex] ?? false; } bool isContactCyr2LatEnabled(String contactKeyHex) { + _ensureContactCyr2LatSettingLoaded(contactKeyHex); return _contactCyr2LatEnabled[contactKeyHex] ?? false; } @@ -4483,6 +4487,63 @@ class MeshCoreConnector extends ChangeNotifier { }); } + void _ensureContactCyr2LatProfileLoaded(String contactKeyHex) { + if (_contactCyr2LatProfileId.containsKey(contactKeyHex)) return; + _contactSettingsStore.loadCyr2LatProfileId(contactKeyHex).then((profileId) { + if (_contactCyr2LatProfileId[contactKeyHex] == profileId) return; + _contactCyr2LatProfileId[contactKeyHex] = profileId; + notifyListeners(); + }); + } + + void _ensureChannelCyr2LatSettingLoaded(int channelIndex) { + if (_channelCyr2LatEnabled.containsKey(channelIndex)) return; + _channelSettingsStore.loadCyr2LatEnabled(channelIndex).then((enabled) { + if (_channelCyr2LatEnabled[channelIndex] == enabled) return; + _channelCyr2LatEnabled[channelIndex] = enabled; + notifyListeners(); + }); + } + + void _ensureChannelCyr2LatProfileLoaded(int channelIndex) { + if (_channelCyr2LatProfileId.containsKey(channelIndex)) return; + _channelSettingsStore.loadCyr2LatProfileId(channelIndex).then((profileId) { + if (_channelCyr2LatProfileId[channelIndex] == profileId) return; + _channelCyr2LatProfileId[channelIndex] = profileId; + notifyListeners(); + }); + } + + String? getChannelCyr2LatProfileId(int channelIndex) { + _ensureChannelCyr2LatProfileLoaded(channelIndex); + return _channelCyr2LatProfileId[channelIndex]; + } + + Future setChannelCyr2LatProfileId( + int channelIndex, + String? profileId, + ) async { + if (_channelCyr2LatProfileId[channelIndex] == profileId) return; + _channelCyr2LatProfileId[channelIndex] = profileId; + await _channelSettingsStore.saveCyr2LatProfileId(channelIndex, profileId); + notifyListeners(); + } + + String? getContactCyr2LatProfileId(String contactKeyHex) { + _ensureContactCyr2LatProfileLoaded(contactKeyHex); + return _contactCyr2LatProfileId[contactKeyHex]; + } + + Future setContactCyr2LatProfileId( + String contactKeyHex, + String? profileId, + ) async { + if (_contactCyr2LatProfileId[contactKeyHex] == profileId) return; + _contactCyr2LatProfileId[contactKeyHex] = profileId; + await _contactSettingsStore.saveCyr2LatProfileId(contactKeyHex, profileId); + notifyListeners(); + } + /// Prepares contact outbound text by applying SMAZ encoding if enabled. /// This should be used to transform text before computing ACK hashes. String prepareContactOutboundText(Contact contact, String text) { @@ -4495,6 +4556,20 @@ class MeshCoreConnector extends ChangeNotifier { if (isContactSmazEnabled(contact.publicKeyHex)) { return Smaz.encodeIfSmaller(text); } else if (isContactCyr2LatEnabled(contact.publicKeyHex)) { + final profileId = getContactCyr2LatProfileId(contact.publicKeyHex); + final profile = profileId != null && _appSettingsService != null + ? _appSettingsService!.getCyr2LatProfileById(profileId) + : null; + if (profile != null) { + Cyr2Lat.setCharMap(profile.charMap); + } else { + // Use global profile + final globalProfile = _appSettingsService + ?.getSelectedCyr2LatProfile(); + if (globalProfile != null) { + Cyr2Lat.setCharMap(globalProfile.charMap); + } + } return Cyr2Lat.encode(text); } } @@ -4509,6 +4584,20 @@ class MeshCoreConnector extends ChangeNotifier { if (isChannelSmazEnabled(channelIndex)) { return Smaz.encodeIfSmaller(text); } else if (isChannelCyr2LatEnabled(channelIndex)) { + final profileId = getChannelCyr2LatProfileId(channelIndex); + final profile = profileId != null && _appSettingsService != null + ? _appSettingsService!.getCyr2LatProfileById(profileId) + : null; + if (profile != null) { + Cyr2Lat.setCharMap(profile.charMap); + } else { + // Use global profile + final globalProfile = _appSettingsService + ?.getSelectedCyr2LatProfile(); + if (globalProfile != null) { + Cyr2Lat.setCharMap(globalProfile.charMap); + } + } return Cyr2Lat.encode(text); } } diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 87475384..c6ee97f1 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Списък със замествания", "channels_cyr2latSettingsDscr": "Редактиране на JSON конфигурацията за заместване на символи", "channels_cyr2latSettingsDialogHint": "JSON карта за замествания", - "channels_cyr2latSettingsDialogSuccess": "Списъкът със замествания е актуализиран", "channels_cyr2latSettingsDialogWrongJSON": "Неправилен JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Възстановяване на първоначалните настройки", - "channels_cyr2latSettingsDialogResetted": "Настройките за заместване на Cyr2Lat са възстановени към първоначалните", + "settings_cyr2latProfileAdd": "Добавяне на профил Cyr2Lat", + "settings_cyr2latProfileName": "Име на профила", + "settings_cyr2latProfileNameEmpty": "Името на профила не може да бъде празно", + "settings_cyr2latProfileAdded": "Профилът е добавен успешно", + "settings_cyr2latProfileUpdated": "Профилът е актуализиран успешно", + "settings_cyr2latProfileEdit": "Редактиране на Cyr2Lat профил", + "settings_cyr2latProfileDelete": "Изтриване на профил Cyr2Lat", + "settings_cyr2latProfileDeleted": "Профилът беше изтрит успешно", + "settings_cyr2latProfileDeleteDscr": "Сигурен ли сте, че искате да изтриете профила \"{name}\"?", "channels_channelUpdated": "Каналът \"{name}\" е актуализиран", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index cd591f68..d0ea6129 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Ersetzungsliste", "channels_cyr2latSettingsDscr": "JSON-Konfiguration für die Zeichenersetzung bearbeiten", "channels_cyr2latSettingsDialogHint": "JSON-Ersetzungstabelle", - "channels_cyr2latSettingsDialogSuccess": "Ersetzungsliste aktualisiert", "channels_cyr2latSettingsDialogWrongJSON": "Ungültiges JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Auf Standard zurücksetzen", - "channels_cyr2latSettingsDialogResetted": "Die Cyr2Lat-Ersetzungseinstellungen wurden auf die Standardeinstellungen zurückgesetzt", + "settings_cyr2latProfileAdd": "Cyr2Lat-Profil hinzufügen", + "settings_cyr2latProfileName": "Profilname", + "settings_cyr2latProfileNameEmpty": "Der Profilname darf nicht leer sein", + "settings_cyr2latProfileAdded": "Profil erfolgreich hinzugefügt", + "settings_cyr2latProfileUpdated": "Profil erfolgreich aktualisiert", + "settings_cyr2latProfileEdit": "Cyr2Lat-Profil bearbeiten", + "settings_cyr2latProfileDelete": "Cyr2Lat-Profil löschen", + "settings_cyr2latProfileDeleted": "Profil erfolgreich gelöscht", + "settings_cyr2latProfileDeleteDscr": "Möchten Sie das Profil \"{name}\" wirklich löschen?", "channels_channelUpdated": "Kanal \"{name}\" aktualisiert", "@channels_channelUpdated": { "placeholders": { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 217f783b..8d7addb6 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -579,15 +579,12 @@ "channels_cyr2latSettingsSubheading": "List of replacements", "channels_cyr2latSettingsDscr": "Edit the JSON configuration of character replacement", "channels_cyr2latSettingsDialogHint": "JSON replacement map", - "channels_cyr2latSettingsDialogSuccess": "The list of replacements has been updated", "channels_cyr2latSettingsDialogWrongJSON": "Invalid JSON: {error}", "@channels_cyr2latSettingsDialogWrongJSON": { "placeholders": { "error": {} } }, - "channels_cyr2latSettingsDialogReset": "Reset to default", - "channels_cyr2latSettingsDialogResetted": "Cyr2Lat replacement settings reset to default", "channels_channelUpdated": "Channel \"{name}\" updated", "@channels_channelUpdated": { "placeholders": { @@ -596,6 +593,22 @@ } } }, + "settings_cyr2latProfileAdd": "Add Cyr2Lat Profile", + "settings_cyr2latProfileName": "Profile Name", + "settings_cyr2latProfileNameEmpty": "Profile name cannot be empty", + "settings_cyr2latProfileAdded": "Profile added successfully", + "settings_cyr2latProfileUpdated": "Profile updated successfully", + "settings_cyr2latProfileEdit": "Edit Cyr2Lat Profile", + "settings_cyr2latProfileDelete": "Delete Cyr2Lat Profile", + "settings_cyr2latProfileDeleted": "Profile deleted successfully", + "settings_cyr2latProfileDeleteDscr": "Are you sure you want to delete the profile \"{name}\"?", + "@settings_cyr2latProfileDeleteDscr": { + "placeholders": { + "name": { + "type": "String" + } + } + }, "channels_publicChannelAdded": "Public channel added", "channels_sortBy": "Sort by", "channels_sortManual": "Manual", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 41b5a554..0a949eb7 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Lista de sustituciones", "channels_cyr2latSettingsDscr": "Editar la configuración JSON de sustitución de caracteres", "channels_cyr2latSettingsDialogHint": "Mapa JSON de sustituciones", - "channels_cyr2latSettingsDialogSuccess": "Lista de sustituciones actualizada", "channels_cyr2latSettingsDialogWrongJSON": "JSON incorrecto: {error}", - "channels_cyr2latSettingsDialogReset": "Restablecer valores predeterminados", - "channels_cyr2latSettingsDialogResetted": "La configuración de sustituciones de Cyr2Lat se ha restablecido a los valores predeterminados", + "settings_cyr2latProfileAdd": "Añadir perfil Cyr2Lat", + "settings_cyr2latProfileName": "Nombre del perfil", + "settings_cyr2latProfileNameEmpty": "El nombre del perfil no puede estar vacío", + "settings_cyr2latProfileAdded": "Perfil añadido correctamente", + "settings_cyr2latProfileUpdated": "Perfil actualizado correctamente", + "settings_cyr2latProfileEdit": "Editar perfil Cyr2Lat", + "settings_cyr2latProfileDelete": "Eliminar perfil Cyr2Lat", + "settings_cyr2latProfileDeleted": "Perfil eliminado correctamente", + "settings_cyr2latProfileDeleteDscr": "¿Está seguro de que desea eliminar el perfil \"{name}\"?", "channels_channelUpdated": "Canal \"{name}\" actualizado", "@channels_channelUpdated": { "placeholders": { @@ -1960,6 +1966,13 @@ "contact_teleBaseSubtitle": "Permitir el intercambio de nivel de batería y telemetría básica", "contact_teleEnv": "Entorno de Telemetría", "contact_teleEnvSubtitle": "Permitir el intercambio de datos de sensores de entorno", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_initialRouteWeight": "Peso inicial de la ruta", "appSettings_maxRouteWeight": "Peso máximo permitido para la ruta", "appSettings_initialRouteWeightSubtitle": "Peso inicial para rutas recién descubiertas", @@ -1972,6 +1985,7 @@ "appSettings_maxMessageRetriesSubtitle": "Número de intentos de reintento antes de marcar un mensaje como fallido.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modo de telemetría actualizado", + "settings_multiAck": "Multi-ACKs: {value}", "map_showOverlaps": "Superposiciones de tecla repetidora", "map_runTraceWithReturnPath": "Volver atrás por el mismo camino.", "@radioStats_noiseFloor": { @@ -2103,6 +2117,5 @@ "repeater_guest": "Información sobre repetidores", "chat_sendMessage": "Enviar mensaje", "repeater_guestTools": "Herramientas para invitados", - "room_guest": "Información del servidor", - "settings_multiAck": "Múltiples respuestas de confirmación" + "room_guest": "Información del servidor" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index ff2f2dc3..e91fad86 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading" : "Liste des remplacements", "channels_cyr2latSettingsDscr" : "Modifier la configuration JSON des remplacements de caractères", "channels_cyr2latSettingsDialogHint": "Tableau de remplacement JSON", - "channels_cyr2latSettingsDialogSuccess": "Liste de remplacement mise à jour", "channels_cyr2latSettingsDialogWrongJSON": "JSON incorrect : {error}", - "channels_cyr2latSettingsDialogReset": "Réinitialiser les paramètres par défaut", - "channels_cyr2latSettingsDialogResetted": "Les paramètres de remplacement Cyr2Lat ont été réinitialisés aux valeurs par défaut", + "settings_cyr2latProfileAdd" : "Ajouter un profil Cyr2Lat", + "settings_cyr2latProfileName" : "Nom du profil", + "settings_cyr2latProfileNameEmpty" : "Le nom du profil ne peut pas être vide", + "settings_cyr2latProfileAdded": "Profil ajouté avec succès", + "settings_cyr2latProfileUpdated": "Profil mis à jour avec succès", + "settings_cyr2latProfileEdit": "Modifier le profil Cyr2Lat", + "settings_cyr2latProfileDelete" : "Supprimer le profil Cyr2Lat", + "settings_cyr2latProfileDeleted" : "Profil supprimé avec succès", + "settings_cyr2latProfileDeleteDscr" : "Êtes-vous sûr de vouloir supprimer le profil \"{name}\"?", "channels_channelUpdated": "Le canal \"{name}\" a été mis à jour", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "contact_lastSeen": "Dernière fois vu", "contact_clearChat": "Effacer la conversation", "contact_teleBaseSubtitle": "Autoriser le partage du niveau de batterie et de la télémétrie de base", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_maxRouteWeightSubtitle": "Poids maximal qu'un itinéraire peut accumuler grâce à des livraisons réussies.", "appSettings_initialRouteWeight": "Poids initial de l'itinéraire", "appSettings_maxRouteWeight": "Poids maximal autorisé pour le trajet", @@ -1943,6 +1956,7 @@ "appSettings_maxMessageRetries": "Nombre maximal de tentatives de récupération de messages", "appSettings_maxMessageRetriesSubtitle": "Nombre de tentatives de relance avant de marquer un message comme ayant échoué.", "path_routeWeight": "{weight}/{max}", + "settings_multiAck": "Multi-ACKs : {value}", "settings_telemetryModeUpdated": "Le mode télémétrie a été mis à jour", "map_showOverlaps": "Chevauchement de la touche répétitive", "map_runTraceWithReturnPath": "Revenir sur le même chemin.", @@ -2075,6 +2089,5 @@ "repeater_guestTools": "Outils pour les invités", "chat_sendMessage": "Envoyer un message", "room_guest": "Informations sur le serveur", - "repeater_guest": "Informations sur les répéteurs", - "settings_multiAck": "Plusieurs accusés de réception" + "repeater_guest": "Informations sur les répéteurs" } diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index 28e5afd1..bd4a0bb8 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -554,10 +554,16 @@ "channels_cyr2latSettingsSubheading": "Helyettesítési lista", "channels_cyr2latSettingsDscr": "A karakterhelyettesítési JSON-konfiguráció szerkesztése", "channels_cyr2latSettingsDialogHint": "JSON-csere táblázat", - "channels_cyr2latSettingsDialogSuccess": "A csere lista frissítve", "channels_cyr2latSettingsDialogWrongJSON": "Hibás JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Alapértelmezett értékekre állítás", - "channels_cyr2latSettingsDialogResetted": "A Cyr2Lat helyettesítési beállítások alapértelmezett értékekre lettek állítva", + "settings_cyr2latProfileAdd": "Cyr2Lat-profil hozzáadása", + "settings_cyr2latProfileName": "Profil neve", + "settings_cyr2latProfileNameEmpty": "A profil neve nem lehet üres", + "settings_cyr2latProfileAdded": "A profil hozzáadása sikeres", + "settings_cyr2latProfileUpdated": "A profil frissítése sikeres", + "settings_cyr2latProfileEdit": "Cyr2Lat profil szerkesztése", + "settings_cyr2latProfileDelete": "Cyr2Lat profil törlése", + "settings_cyr2latProfileDeleted": "A profil törlése sikeresen megtörtént", + "settings_cyr2latProfileDeleteDscr": "Biztosan törölni szeretné a \"{name}\" profilt?", "channels_channelUpdated": "A {name} csatorna frissítve", "@channels_channelUpdated": { "placeholders": { @@ -2022,6 +2028,13 @@ "radioStats_stripWaiting": "Rádió adatok begyűjtése…", "radioStats_settingsTile": "Rádió statisztikák", "radioStats_settingsSubtitle": "Háttérzaj, RSSI, zaj-sűrűség, és a használat időtartama", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "settings_denyAll": "Elutasítom", "settings_privacySettingsDescription": "Válassza ki, hogy az eszközének melyik információkat oszt meg másokkal.", "settings_privacySubtitle": "Ellenőrizd, hogy milyen információkat osztanak meg.", @@ -2033,6 +2046,7 @@ "settings_telemetryEnvironmentMode": "Adatkapcsolati környezeti mód", "settings_advertLocation": "Reklám megjelenési hely", "settings_advertLocationSubtitle": "A hirdetés tartalmazza a helyszínt.", + "settings_multiAck": "Többszöri visszaigazolások: {value}", "settings_telemetryModeUpdated": "A telemetriamód frissítve", "contact_info": "Kapcsolattartási információk", "contact_settings": "Kapcsolat beállítások", @@ -2113,6 +2127,5 @@ "repeater_guestTools": "Vendégek számára elérhető eszközök", "room_guest": "Szoba szerver információk", "chat_sendMessage": "Üzenet küldése", - "repeater_guest": "Adatok a repeaterről", - "settings_multiAck": "Többszörös visszaigazolások" + "repeater_guest": "Adatok a repeaterről" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 93af4e62..414ee2b4 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Elenco delle sostituzioni", "channels_cyr2latSettingsDscr": "Modifica la configurazione JSON delle sostituzioni dei caratteri", "channels_cyr2latSettingsDialogHint": "Mappa JSON delle sostituzioni", - "channels_cyr2latSettingsDialogSuccess": "Elenco delle sostituzioni aggiornato", "channels_cyr2latSettingsDialogWrongJSON": "JSON non corretto: {error}", - "channels_cyr2latSettingsDialogReset": "Ripristina impostazioni predefinite", - "channels_cyr2latSettingsDialogResetted": "Le impostazioni di sostituzione Cyr2Lat sono state ripristinate alle impostazioni predefinite", + "settings_cyr2latProfileAdd": "Aggiungi profilo Cyr2Lat", + "settings_cyr2latProfileName": "Nome profilo", + "settings_cyr2latProfileNameEmpty": "Il nome del profilo non può essere vuoto", + "settings_cyr2latProfileAdded": "Profilo aggiunto con successo", + "settings_cyr2latProfileUpdated": "Profilo aggiornato con successo", + "settings_cyr2latProfileEdit": "Modifica profilo Cyr2Lat", + "settings_cyr2latProfileDelete": "Elimina profilo Cyr2Lat", + "settings_cyr2latProfileDeleted": "Profilo eliminato con successo", + "settings_cyr2latProfileDeleteDscr": "Sei sicuro di voler eliminare il profilo \"{name}\"?", "channels_channelUpdated": "Canale \"{name}\" aggiornato", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "contact_teleBaseSubtitle": "Consenti la condivisione del livello della batteria e della telemetria di base", "contact_teleEnvSubtitle": "Consenti la condivisione dei dati del sensore ambientale", "contact_teleEnv": "Ambiente di telemetria", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_initialRouteWeight": "Peso iniziale del percorso", "appSettings_initialRouteWeightSubtitle": "Peso di partenza per nuovi percorsi", "appSettings_maxRouteWeightSubtitle": "Il peso massimo che un percorso può accumulare grazie a consegne di successo.", @@ -1944,6 +1957,7 @@ "appSettings_maxMessageRetriesSubtitle": "Numero di tentativi di riprova prima di considerare un messaggio come fallito.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modalità telemetria aggiornata", + "settings_multiAck": "Multi-ACKs: {value}", "map_showOverlaps": "Sovrapposizioni della chiave ripetitore", "map_runTraceWithReturnPath": "Tornare indietro sullo stesso percorso", "@radioStats_noiseFloor": { @@ -2075,6 +2089,5 @@ "repeater_guest": "Informazioni sul ripetitore", "repeater_guestTools": "Strumenti per gli ospiti", "chat_sendMessage": "Invia messaggio", - "room_guest": "Informazioni sul server", - "settings_multiAck": "ACK multipli" + "room_guest": "Informazioni sul server" } diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index b00b4627..4faebd75 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -554,10 +554,16 @@ "channels_cyr2latSettingsSubheading": "置換リスト", "channels_cyr2latSettingsDscr": "文字置換のJSON設定を編集する", "channels_cyr2latSettingsDialogHint": "JSON置換マップ", - "channels_cyr2latSettingsDialogSuccess": "置換リストが更新されました", "channels_cyr2latSettingsDialogWrongJSON": "不正なJSON: {error}", - "channels_cyr2latSettingsDialogReset": "初期設定に戻す", - "channels_cyr2latSettingsDialogResetted": "cyr2latの置換設定が初期設定に戻されました", + "settings_cyr2latProfileAdd": "Cyr2Latプロファイルの追加", + "settings_cyr2latProfileName": "プロファイル名", + "settings_cyr2latProfileNameEmpty": "プロファイル名は空にできません", + "settings_cyr2latProfileAdded": "プロファイルが正常に追加されました", + "settings_cyr2latProfileUpdated": "プロファイルの更新に成功しました", + "settings_cyr2latProfileEdit": "Cyr2Latプロファイルを編集", + "settings_cyr2latProfileDelete": "Cyr2Latプロファイルを削除", + "settings_cyr2latProfileDeleted": "プロファイルの削除に成功しました", + "settings_cyr2latProfileDeleteDscr": "プロファイル \"{name}\" を削除してもよろしいですか?", "channels_channelUpdated": "チャンネル「{name}」が更新されました", "@channels_channelUpdated": { "placeholders": { @@ -2022,6 +2028,13 @@ "radioStats_stripWaiting": "ラジオの統計情報を取得中…", "radioStats_settingsTile": "ラジオの統計", "radioStats_settingsSubtitle": "ノイズレベル、RSSI、SNR、および通信時間", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "settings_privacy": "プライバシー設定", "settings_privacySubtitle": "共有する情報の内容を管理する。", "settings_denyAll": "すべてを否定", @@ -2033,6 +2046,7 @@ "settings_telemetryEnvironmentMode": "テレメトリ環境モード", "settings_advertLocation": "広告掲載場所", "settings_advertLocationSubtitle": "広告に場所を記載してください。", + "settings_multiAck": "複数のACK:{value}", "settings_telemetryModeUpdated": "テレメトリモードが更新されました", "contact_info": "連絡先", "contact_settings": "連絡設定", @@ -2113,6 +2127,5 @@ "room_guest": "ルームサーバーに関する情報", "chat_sendMessage": "メッセージを送信する", "repeater_guest": "繰り返し送信に関する情報", - "repeater_guestTools": "ゲスト向けツール", - "settings_multiAck": "複数のACK(応答)" + "repeater_guestTools": "ゲスト向けツール" } diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index fd598f1e..b2dc056c 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -554,10 +554,16 @@ "channels_cyr2latSettingsSubheading": "변환 목록", "channels_cyr2latSettingsDscr": "문자 변환 JSON 구성 편집", "channels_cyr2latSettingsDialogHint": "JSON 변환 맵", - "channels_cyr2latSettingsDialogSuccess": "변환 목록이 업데이트되었습니다", "channels_cyr2latSettingsDialogWrongJSON": "잘못된 JSON: {error}", - "channels_cyr2latSettingsDialogReset": "초기값으로 초기화", - "channels_cyr2latSettingsDialogResetted": "Cyr2Lat 변환 설정이 초기값으로 초기화되었습니다", + "settings_cyr2latProfileAdd": "Cyr2Lat 프로필 추가", + "settings_cyr2latProfileName": "프로필 이름", + "settings_cyr2latProfileNameEmpty": "프로필 이름은 비워둘 수 없습니다", + "settings_cyr2latProfileAdded": "프로필이 성공적으로 추가되었습니다", + "settings_cyr2latProfileUpdated": "프로필이 성공적으로 업데이트되었습니다", + "settings_cyr2latProfileEdit": "Cyr2Lat 프로필 편집", + "settings_cyr2latProfileDelete": "Cyr2Lat 프로필 삭제", + "settings_cyr2latProfileDeleted": "프로필이 성공적으로 삭제되었습니다", + "settings_cyr2latProfileDeleteDscr": "\"{name}\" 프로필을 삭제하시겠습니까?", "channels_channelUpdated": "채널 \"{name}\"이 업데이트되었습니다.", "@channels_channelUpdated": { "placeholders": { @@ -2022,6 +2028,13 @@ "radioStats_stripWaiting": "라디오 통계 가져오기…", "radioStats_settingsTile": "라디오 통계", "radioStats_settingsSubtitle": "잡음 수준, RSSI, 신호 대 잡음비, 통신 시간", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "settings_privacy": "개인 정보 설정", "settings_privacySubtitle": "어떤 정보를 공유할지 통제하세요.", "settings_privacySettingsDescription": "어떤 정보를 기기가 다른 사람들과 공유할지 선택하세요.", @@ -2033,6 +2046,7 @@ "settings_telemetryEnvironmentMode": "텔레메트리 환경 모드", "settings_advertLocation": "광고 위치", "settings_advertLocationSubtitle": "광고에 위치 정보를 포함하세요.", + "settings_multiAck": "다중 ACK: {value}", "settings_telemetryModeUpdated": "텔레메트리 모드 업데이트 완료", "contact_info": "연락처", "contact_settings": "연락처 설정", @@ -2113,6 +2127,5 @@ "repeater_guestTools": "손님용 도구", "chat_sendMessage": "메시지를 보내기", "repeater_guest": "반복 장비 정보", - "room_guest": "서버 정보", - "settings_multiAck": "다중 ACK" + "room_guest": "서버 정보" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index dc641d7d..e84ece65 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -901,8 +901,8 @@ abstract class AppLocalizations { /// No description provided for @settings_multiAck. /// /// In en, this message translates to: - /// **'Multi-ACKs'** - String get settings_multiAck; + /// **'Multi-ACKs: {value}'** + String settings_multiAck(String value); /// No description provided for @settings_telemetryModeUpdated. /// @@ -2212,36 +2212,72 @@ abstract class AppLocalizations { /// **'JSON replacement map'** String get channels_cyr2latSettingsDialogHint; - /// No description provided for @channels_cyr2latSettingsDialogSuccess. - /// - /// In en, this message translates to: - /// **'The list of replacements has been updated'** - String get channels_cyr2latSettingsDialogSuccess; - /// No description provided for @channels_cyr2latSettingsDialogWrongJSON. /// /// In en, this message translates to: /// **'Invalid JSON: {error}'** String channels_cyr2latSettingsDialogWrongJSON(Object error); - /// No description provided for @channels_cyr2latSettingsDialogReset. - /// - /// In en, this message translates to: - /// **'Reset to default'** - String get channels_cyr2latSettingsDialogReset; - - /// No description provided for @channels_cyr2latSettingsDialogResetted. - /// - /// In en, this message translates to: - /// **'Cyr2Lat replacement settings reset to default'** - String get channels_cyr2latSettingsDialogResetted; - /// No description provided for @channels_channelUpdated. /// /// In en, this message translates to: /// **'Channel \"{name}\" updated'** String channels_channelUpdated(String name); + /// No description provided for @settings_cyr2latProfileAdd. + /// + /// In en, this message translates to: + /// **'Add Cyr2Lat Profile'** + String get settings_cyr2latProfileAdd; + + /// No description provided for @settings_cyr2latProfileName. + /// + /// In en, this message translates to: + /// **'Profile Name'** + String get settings_cyr2latProfileName; + + /// No description provided for @settings_cyr2latProfileNameEmpty. + /// + /// In en, this message translates to: + /// **'Profile name cannot be empty'** + String get settings_cyr2latProfileNameEmpty; + + /// No description provided for @settings_cyr2latProfileAdded. + /// + /// In en, this message translates to: + /// **'Profile added successfully'** + String get settings_cyr2latProfileAdded; + + /// No description provided for @settings_cyr2latProfileUpdated. + /// + /// In en, this message translates to: + /// **'Profile updated successfully'** + String get settings_cyr2latProfileUpdated; + + /// No description provided for @settings_cyr2latProfileEdit. + /// + /// In en, this message translates to: + /// **'Edit Cyr2Lat Profile'** + String get settings_cyr2latProfileEdit; + + /// No description provided for @settings_cyr2latProfileDelete. + /// + /// In en, this message translates to: + /// **'Delete Cyr2Lat Profile'** + String get settings_cyr2latProfileDelete; + + /// No description provided for @settings_cyr2latProfileDeleted. + /// + /// In en, this message translates to: + /// **'Profile deleted successfully'** + String get settings_cyr2latProfileDeleted; + + /// No description provided for @settings_cyr2latProfileDeleteDscr. + /// + /// In en, this message translates to: + /// **'Are you sure you want to delete the profile \"{name}\"?'** + String settings_cyr2latProfileDeleteDscr(String name); + /// No description provided for @channels_publicChannelAdded. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 5dd71318..0c1cc089 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -437,7 +437,9 @@ class AppLocalizationsBg extends AppLocalizations { 'Включи местоположение в обявата'; @override - String get settings_multiAck => 'Множество потвърждения'; + String settings_multiAck(String value) { + return 'Мулти-потвърди: $value'; + } @override String get settings_telemetryModeUpdated => 'Режим на телеметрията е обновен'; @@ -1192,28 +1194,47 @@ class AppLocalizationsBg extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON карта за замествания'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Списъкът със замествания е актуализиран'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Неправилен JSON: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => - 'Възстановяване на първоначалните настройки'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'Настройките за заместване на Cyr2Lat са възстановени към първоначалните'; - @override String channels_channelUpdated(String name) { return 'Каналът \"$name\" е актуализиран'; } + @override + String get settings_cyr2latProfileAdd => 'Добавяне на профил Cyr2Lat'; + + @override + String get settings_cyr2latProfileName => 'Име на профила'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Името на профила не може да бъде празно'; + + @override + String get settings_cyr2latProfileAdded => 'Профилът е добавен успешно'; + + @override + String get settings_cyr2latProfileUpdated => + 'Профилът е актуализиран успешно'; + + @override + String get settings_cyr2latProfileEdit => 'Редактиране на Cyr2Lat профил'; + + @override + String get settings_cyr2latProfileDelete => 'Изтриване на профил Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Профилът беше изтрит успешно'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Сигурен ли сте, че искате да изтриете профила \"$name\"?'; + } + @override String get channels_publicChannelAdded => 'Публичен канал добавен'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index e3a35040..6fdbdd9a 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -435,7 +435,9 @@ class AppLocalizationsDe extends AppLocalizations { 'Ort in der Anzeige einbeziehen'; @override - String get settings_multiAck => 'Mehrere Bestätigungen'; + String settings_multiAck(String value) { + return 'Mehrfach-Bestätigungen: $value'; + } @override String get settings_telemetryModeUpdated => 'Telemetriemodus aktualisiert'; @@ -1188,27 +1190,47 @@ class AppLocalizationsDe extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON-Ersetzungstabelle'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Ersetzungsliste aktualisiert'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Ungültiges JSON: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => 'Auf Standard zurücksetzen'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'Die Cyr2Lat-Ersetzungseinstellungen wurden auf die Standardeinstellungen zurückgesetzt'; - @override String channels_channelUpdated(String name) { return 'Kanal \"$name\" aktualisiert'; } + @override + String get settings_cyr2latProfileAdd => 'Cyr2Lat-Profil hinzufügen'; + + @override + String get settings_cyr2latProfileName => 'Profilname'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Der Profilname darf nicht leer sein'; + + @override + String get settings_cyr2latProfileAdded => 'Profil erfolgreich hinzugefügt'; + + @override + String get settings_cyr2latProfileUpdated => + 'Profil erfolgreich aktualisiert'; + + @override + String get settings_cyr2latProfileEdit => 'Cyr2Lat-Profil bearbeiten'; + + @override + String get settings_cyr2latProfileDelete => 'Cyr2Lat-Profil löschen'; + + @override + String get settings_cyr2latProfileDeleted => 'Profil erfolgreich gelöscht'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Möchten Sie das Profil \"$name\" wirklich löschen?'; + } + @override String get channels_publicChannelAdded => 'Öffentlicher Kanal hinzugefügt'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 8f7cfd12..36fbe875 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -427,7 +427,9 @@ class AppLocalizationsEn extends AppLocalizations { String get settings_advertLocationSubtitle => 'Include location in advert.'; @override - String get settings_multiAck => 'Multi-ACKs'; + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } @override String get settings_telemetryModeUpdated => 'Telemetry mode updated'; @@ -1168,27 +1170,45 @@ class AppLocalizationsEn extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON replacement map'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'The list of replacements has been updated'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Invalid JSON: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => 'Reset to default'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'Cyr2Lat replacement settings reset to default'; - @override String channels_channelUpdated(String name) { return 'Channel \"$name\" updated'; } + @override + String get settings_cyr2latProfileAdd => 'Add Cyr2Lat Profile'; + + @override + String get settings_cyr2latProfileName => 'Profile Name'; + + @override + String get settings_cyr2latProfileNameEmpty => 'Profile name cannot be empty'; + + @override + String get settings_cyr2latProfileAdded => 'Profile added successfully'; + + @override + String get settings_cyr2latProfileUpdated => 'Profile updated successfully'; + + @override + String get settings_cyr2latProfileEdit => 'Edit Cyr2Lat Profile'; + + @override + String get settings_cyr2latProfileDelete => 'Delete Cyr2Lat Profile'; + + @override + String get settings_cyr2latProfileDeleted => 'Profile deleted successfully'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Are you sure you want to delete the profile \"$name\"?'; + } + @override String get channels_publicChannelAdded => 'Public channel added'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 432cfad8..469b7779 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -434,7 +434,9 @@ class AppLocalizationsEs extends AppLocalizations { String get settings_advertLocationSubtitle => 'Incluir ubicación en anuncio'; @override - String get settings_multiAck => 'Múltiples respuestas de confirmación'; + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } @override String get settings_telemetryModeUpdated => 'Modo de telemetría actualizado'; @@ -1190,28 +1192,47 @@ class AppLocalizationsEs extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'Mapa JSON de sustituciones'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Lista de sustituciones actualizada'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'JSON incorrecto: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => - 'Restablecer valores predeterminados'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'La configuración de sustituciones de Cyr2Lat se ha restablecido a los valores predeterminados'; - @override String channels_channelUpdated(String name) { return 'Canal \"$name\" actualizado'; } + @override + String get settings_cyr2latProfileAdd => 'Añadir perfil Cyr2Lat'; + + @override + String get settings_cyr2latProfileName => 'Nombre del perfil'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'El nombre del perfil no puede estar vacío'; + + @override + String get settings_cyr2latProfileAdded => 'Perfil añadido correctamente'; + + @override + String get settings_cyr2latProfileUpdated => + 'Perfil actualizado correctamente'; + + @override + String get settings_cyr2latProfileEdit => 'Editar perfil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Eliminar perfil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Perfil eliminado correctamente'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return '¿Está seguro de que desea eliminar el perfil \"$name\"?'; + } + @override String get channels_publicChannelAdded => 'Canal público añadido'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index a86c72b9..addba543 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -438,7 +438,9 @@ class AppLocalizationsFr extends AppLocalizations { 'Inclure l\'emplacement dans l\'annonce'; @override - String get settings_multiAck => 'Plusieurs accusés de réception'; + String settings_multiAck(String value) { + return 'Multi-ACKs : $value'; + } @override String get settings_telemetryModeUpdated => @@ -1196,28 +1198,46 @@ class AppLocalizationsFr extends AppLocalizations { String get channels_cyr2latSettingsDialogHint => 'Tableau de remplacement JSON'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Liste de remplacement mise à jour'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'JSON incorrect : $error'; } - @override - String get channels_cyr2latSettingsDialogReset => - 'Réinitialiser les paramètres par défaut'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'Les paramètres de remplacement Cyr2Lat ont été réinitialisés aux valeurs par défaut'; - @override String channels_channelUpdated(String name) { return 'Le canal \"$name\" a été mis à jour'; } + @override + String get settings_cyr2latProfileAdd => 'Ajouter un profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileName => 'Nom du profil'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Le nom du profil ne peut pas être vide'; + + @override + String get settings_cyr2latProfileAdded => 'Profil ajouté avec succès'; + + @override + String get settings_cyr2latProfileUpdated => 'Profil mis à jour avec succès'; + + @override + String get settings_cyr2latProfileEdit => 'Modifier le profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Supprimer le profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Profil supprimé avec succès'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Êtes-vous sûr de vouloir supprimer le profil \"$name\"?'; + } + @override String get channels_publicChannelAdded => 'Le canal public a été ajouté'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index b61afab5..82850575 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -437,7 +437,9 @@ class AppLocalizationsHu extends AppLocalizations { 'A hirdetés tartalmazza a helyszínt.'; @override - String get settings_multiAck => 'Többszörös visszaigazolások'; + String settings_multiAck(String value) { + return 'Többszöri visszaigazolások: $value'; + } @override String get settings_telemetryModeUpdated => 'A telemetriamód frissítve'; @@ -1195,27 +1197,46 @@ class AppLocalizationsHu extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON-csere táblázat'; - @override - String get channels_cyr2latSettingsDialogSuccess => 'A csere lista frissítve'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Hibás JSON: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => - 'Alapértelmezett értékekre állítás'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'A Cyr2Lat helyettesítési beállítások alapértelmezett értékekre lettek állítva'; - @override String channels_channelUpdated(String name) { return 'A $name csatorna frissítve'; } + @override + String get settings_cyr2latProfileAdd => 'Cyr2Lat-profil hozzáadása'; + + @override + String get settings_cyr2latProfileName => 'Profil neve'; + + @override + String get settings_cyr2latProfileNameEmpty => 'A profil neve nem lehet üres'; + + @override + String get settings_cyr2latProfileAdded => 'A profil hozzáadása sikeres'; + + @override + String get settings_cyr2latProfileUpdated => 'A profil frissítése sikeres'; + + @override + String get settings_cyr2latProfileEdit => 'Cyr2Lat profil szerkesztése'; + + @override + String get settings_cyr2latProfileDelete => 'Cyr2Lat profil törlése'; + + @override + String get settings_cyr2latProfileDeleted => + 'A profil törlése sikeresen megtörtént'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Biztosan törölni szeretné a \"$name\" profilt?'; + } + @override String get channels_publicChannelAdded => 'A nyilvános csatorna hozzáadva'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 3df5b4b3..567e9dc7 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -437,7 +437,9 @@ class AppLocalizationsIt extends AppLocalizations { 'Includi la posizione nell\'annuncio'; @override - String get settings_multiAck => 'ACK multipli'; + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } @override String get settings_telemetryModeUpdated => 'Modalità telemetria aggiornata'; @@ -1192,28 +1194,47 @@ class AppLocalizationsIt extends AppLocalizations { String get channels_cyr2latSettingsDialogHint => 'Mappa JSON delle sostituzioni'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Elenco delle sostituzioni aggiornato'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'JSON non corretto: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => - 'Ripristina impostazioni predefinite'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'Le impostazioni di sostituzione Cyr2Lat sono state ripristinate alle impostazioni predefinite'; - @override String channels_channelUpdated(String name) { return 'Canale \"$name\" aggiornato'; } + @override + String get settings_cyr2latProfileAdd => 'Aggiungi profilo Cyr2Lat'; + + @override + String get settings_cyr2latProfileName => 'Nome profilo'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Il nome del profilo non può essere vuoto'; + + @override + String get settings_cyr2latProfileAdded => 'Profilo aggiunto con successo'; + + @override + String get settings_cyr2latProfileUpdated => + 'Profilo aggiornato con successo'; + + @override + String get settings_cyr2latProfileEdit => 'Modifica profilo Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Elimina profilo Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Profilo eliminato con successo'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Sei sicuro di voler eliminare il profilo \"$name\"?'; + } + @override String get channels_publicChannelAdded => 'Canale pubblico aggiunto'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index e9ac268f..55e755e2 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -414,7 +414,9 @@ class AppLocalizationsJa extends AppLocalizations { String get settings_advertLocationSubtitle => '広告に場所を記載してください。'; @override - String get settings_multiAck => '複数のACK(応答)'; + String settings_multiAck(String value) { + return '複数のACK:$value'; + } @override String get settings_telemetryModeUpdated => 'テレメトリモードが更新されました'; @@ -1133,26 +1135,45 @@ class AppLocalizationsJa extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON置換マップ'; - @override - String get channels_cyr2latSettingsDialogSuccess => '置換リストが更新されました'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return '不正なJSON: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => '初期設定に戻す'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'cyr2latの置換設定が初期設定に戻されました'; - @override String channels_channelUpdated(String name) { return 'チャンネル「$name」が更新されました'; } + @override + String get settings_cyr2latProfileAdd => 'Cyr2Latプロファイルの追加'; + + @override + String get settings_cyr2latProfileName => 'プロファイル名'; + + @override + String get settings_cyr2latProfileNameEmpty => 'プロファイル名は空にできません'; + + @override + String get settings_cyr2latProfileAdded => 'プロファイルが正常に追加されました'; + + @override + String get settings_cyr2latProfileUpdated => 'プロファイルの更新に成功しました'; + + @override + String get settings_cyr2latProfileEdit => 'Cyr2Latプロファイルを編集'; + + @override + String get settings_cyr2latProfileDelete => 'Cyr2Latプロファイルを削除'; + + @override + String get settings_cyr2latProfileDeleted => 'プロファイルの削除に成功しました'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'プロファイル \"$name\" を削除してもよろしいですか?'; + } + @override String get channels_publicChannelAdded => 'パブリックチャンネルが追加されました'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 83c9c970..b8ec8a63 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -414,7 +414,9 @@ class AppLocalizationsKo extends AppLocalizations { String get settings_advertLocationSubtitle => '광고에 위치 정보를 포함하세요.'; @override - String get settings_multiAck => '다중 ACK'; + String settings_multiAck(String value) { + return '다중 ACK: $value'; + } @override String get settings_telemetryModeUpdated => '텔레메트리 모드 업데이트 완료'; @@ -1128,26 +1130,45 @@ class AppLocalizationsKo extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON 변환 맵'; - @override - String get channels_cyr2latSettingsDialogSuccess => '변환 목록이 업데이트되었습니다'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return '잘못된 JSON: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => '초기값으로 초기화'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'Cyr2Lat 변환 설정이 초기값으로 초기화되었습니다'; - @override String channels_channelUpdated(String name) { return '채널 \"$name\"이 업데이트되었습니다.'; } + @override + String get settings_cyr2latProfileAdd => 'Cyr2Lat 프로필 추가'; + + @override + String get settings_cyr2latProfileName => '프로필 이름'; + + @override + String get settings_cyr2latProfileNameEmpty => '프로필 이름은 비워둘 수 없습니다'; + + @override + String get settings_cyr2latProfileAdded => '프로필이 성공적으로 추가되었습니다'; + + @override + String get settings_cyr2latProfileUpdated => '프로필이 성공적으로 업데이트되었습니다'; + + @override + String get settings_cyr2latProfileEdit => 'Cyr2Lat 프로필 편집'; + + @override + String get settings_cyr2latProfileDelete => 'Cyr2Lat 프로필 삭제'; + + @override + String get settings_cyr2latProfileDeleted => '프로필이 성공적으로 삭제되었습니다'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return '\"$name\" 프로필을 삭제하시겠습니까?'; + } + @override String get channels_publicChannelAdded => '공개 채널 추가'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index e259b4f8..28e377cb 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -432,7 +432,9 @@ class AppLocalizationsNl extends AppLocalizations { 'Locatie opnemen in advertentie'; @override - String get settings_multiAck => 'Meerdere bevestigingen'; + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } @override String get settings_telemetryModeUpdated => 'Telemetrie-modus bijgewerkt'; @@ -1179,28 +1181,46 @@ class AppLocalizationsNl extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON-vervangingskaart'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Lijst met vervangingen bijgewerkt'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Onjuiste JSON: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => - 'Terugzetten naar standaard'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'De instellingen voor Cyr2Lat-vervangingen zijn teruggezet naar de standaardinstellingen'; - @override String channels_channelUpdated(String name) { return 'Kanaal \"$name\" is bijgewerkt'; } + @override + String get settings_cyr2latProfileAdd => 'Cyr2Lat-profiel toevoegen'; + + @override + String get settings_cyr2latProfileName => 'Profielnaam'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Profielnaam mag niet leeg zijn'; + + @override + String get settings_cyr2latProfileAdded => 'Profiel succesvol toegevoegd'; + + @override + String get settings_cyr2latProfileUpdated => 'Profiel succesvol bijgewerkt'; + + @override + String get settings_cyr2latProfileEdit => 'Cyr2Lat-profiel bewerken'; + + @override + String get settings_cyr2latProfileDelete => 'Cyr2Lat-profiel verwijderen'; + + @override + String get settings_cyr2latProfileDeleted => 'Profiel succesvol verwijderd'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Weet u zeker dat u het profiel \"$name\" wilt verwijderen?'; + } + @override String get channels_publicChannelAdded => 'Open kanaal toegevoegd'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index f09f9e8b..e49f235a 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -439,7 +439,9 @@ class AppLocalizationsPl extends AppLocalizations { 'Uwzględnij lokalizację w ogłoszeniu'; @override - String get settings_multiAck => 'Wielokrotne potwierdzenia odbioru'; + String settings_multiAck(String value) { + return 'Wielokrotne ACK: $value'; + } @override String get settings_telemetryModeUpdated => @@ -1199,28 +1201,48 @@ class AppLocalizationsPl extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'Mapa zamian JSON'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Lista zamian zaktualizowana'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Nieprawidłowy JSON: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => - 'Przywróć ustawienia domyślne'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'Ustawienia zamiany Cyr2Lat zostały przywrócone do wartości domyślnych'; - @override String channels_channelUpdated(String name) { return 'Kanał \"$name\" został zaktualizowany'; } + @override + String get settings_cyr2latProfileAdd => 'Dodaj profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileName => 'Nazwa profilu'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Nazwa profilu nie może być pusta'; + + @override + String get settings_cyr2latProfileAdded => 'Profil dodano pomyślnie'; + + @override + String get settings_cyr2latProfileUpdated => + 'Profil został pomyślnie zaktualizowany'; + + @override + String get settings_cyr2latProfileEdit => 'Edytuj profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Usuń profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => + 'Profil został pomyślnie usunięty'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Czy na pewno chcesz usunąć profil \"$name\"?'; + } + @override String get channels_publicChannelAdded => 'Kanał publiczny dodany'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 6c40393f..0d8d32f8 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -436,7 +436,9 @@ class AppLocalizationsPt extends AppLocalizations { 'Incluir localização no anúncio'; @override - String get settings_multiAck => 'Multi-ACKs'; + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } @override String get settings_telemetryModeUpdated => 'Modo de telemetria atualizado'; @@ -1190,28 +1192,46 @@ class AppLocalizationsPt extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'Mapa de substituições JSON'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Lista de substituições atualizada'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'JSON incorreto: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => - 'Redefinir para os valores iniciais'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'As configurações de substituição Cyr2Lat foram redefinidas para os valores iniciais'; - @override String channels_channelUpdated(String name) { return 'Canal \"$name\" atualizado'; } + @override + String get settings_cyr2latProfileAdd => 'Adicionar perfil Cyr2Lat'; + + @override + String get settings_cyr2latProfileName => 'Nome do perfil'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'O nome do perfil não pode estar vazio'; + + @override + String get settings_cyr2latProfileAdded => 'Perfil adicionado com sucesso'; + + @override + String get settings_cyr2latProfileUpdated => 'Perfil atualizado com sucesso'; + + @override + String get settings_cyr2latProfileEdit => 'Editar perfil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Eliminar perfil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Perfil eliminado com sucesso'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Tem a certeza de que deseja eliminar o perfil \"$name\"?'; + } + @override String get channels_publicChannelAdded => 'Canal público adicionado'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 42ca97c1..6ae16611 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -436,7 +436,9 @@ class AppLocalizationsRu extends AppLocalizations { 'Включить местоположение в объявление'; @override - String get settings_multiAck => 'Несколько подтверждений'; + String settings_multiAck(String value) { + return 'Мульти-ACK: $value'; + } @override String get settings_telemetryModeUpdated => 'Режим телеметрии обновлен'; @@ -1190,26 +1192,46 @@ class AppLocalizationsRu extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON-карта замен'; - @override - String get channels_cyr2latSettingsDialogSuccess => 'Список замен обновлён'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Некорректный JSON: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => 'Сбросить к начальным'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'Настройки замен Cyr2Lat сброшены к начальным'; - @override String channels_channelUpdated(String name) { return 'Канал \"$name\" обновлён'; } + @override + String get settings_cyr2latProfileAdd => 'Добавить профиль Cyr2Lat'; + + @override + String get settings_cyr2latProfileName => 'Название профиля'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Название профиля не может быть пустым'; + + @override + String get settings_cyr2latProfileAdded => 'Профиль добавлен'; + + @override + String get settings_cyr2latProfileUpdated => 'Профиль успешно обновлен'; + + @override + String get settings_cyr2latProfileEdit => 'Редактировать профиль Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Удалить профиль Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Профиль успешно удален'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Вы действительно хотите удалить профиль \"$name\"?'; + } + @override String get channels_publicChannelAdded => 'Публичный канал добавлен'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 670f31c2..0e95654f 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -430,7 +430,9 @@ class AppLocalizationsSk extends AppLocalizations { String get settings_advertLocationSubtitle => 'Zahrnúť polohu do inzerátu'; @override - String get settings_multiAck => 'Viaceré ACK'; + String settings_multiAck(String value) { + return 'Viaceré ACK: $value'; + } @override String get settings_telemetryModeUpdated => @@ -1179,28 +1181,47 @@ class AppLocalizationsSk extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON mapa nahradení'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Zoznam nahradení bol aktualizovaný'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Nesprávny JSON: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => - 'Obnoviť predvolené nastavenia'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'Nastavenia nahradzovania Cyr2Lat boli obnovené na predvolené'; - @override String channels_channelUpdated(String name) { return 'Kanál \"$name\" bol aktualizovaný'; } + @override + String get settings_cyr2latProfileAdd => 'Pridať profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileName => 'Názov profilu'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Názov profilu nesmie byť prázdny'; + + @override + String get settings_cyr2latProfileAdded => 'Profil bol úspešne pridaný'; + + @override + String get settings_cyr2latProfileUpdated => + 'Profil bol úspešne aktualizovaný'; + + @override + String get settings_cyr2latProfileEdit => 'Upraviť profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Odstrániť profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Profil bol úspešne odstránený'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Naozaj chcete odstrániť profil \"$name\"?'; + } + @override String get channels_publicChannelAdded => 'Veľký kanál pridaný'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index f16bc4d2..b6264ead 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -430,7 +430,9 @@ class AppLocalizationsSl extends AppLocalizations { String get settings_advertLocationSubtitle => 'Vključi lokacijo v oglas.'; @override - String get settings_multiAck => 'Več potrdil'; + String settings_multiAck(String value) { + return 'Večkratni potrditvi: $value'; + } @override String get settings_telemetryModeUpdated => 'Način telemetrije posodobljen'; @@ -1177,27 +1179,47 @@ class AppLocalizationsSl extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON-tabela zamenjav'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Seznam zamenjav je posodobljen'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Nepravilen JSON: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => 'Ponastavi na privzete'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'Nastavitve zamenjav Cyr2Lat so ponastavljene na privzete'; - @override String channels_channelUpdated(String name) { return 'Kanal $name je bil posodobljen'; } + @override + String get settings_cyr2latProfileAdd => 'Dodaj profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileName => 'Ime profila'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Ime profila ne sme biti prazno'; + + @override + String get settings_cyr2latProfileAdded => 'Profil je bil uspešno dodan'; + + @override + String get settings_cyr2latProfileUpdated => + 'Profil je bil uspešno posodobljen'; + + @override + String get settings_cyr2latProfileEdit => 'Uredi profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Izbriši profil Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Profil je bil uspešno izbrisan'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Ali res želite izbrisati profil \"$name\"?'; + } + @override String get channels_publicChannelAdded => 'javna skupnost dodana'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 4d906ba5..c540b99d 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -428,7 +428,9 @@ class AppLocalizationsSv extends AppLocalizations { String get settings_advertLocationSubtitle => 'Inkludera plats i annonsen'; @override - String get settings_multiAck => 'Flera bekräftelser'; + String settings_multiAck(String value) { + return 'Multi-ACKs: $value'; + } @override String get settings_telemetryModeUpdated => 'Telemetri-läge uppdaterat'; @@ -1169,27 +1171,46 @@ class AppLocalizationsSv extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON-ersättningskarta'; - @override - String get channels_cyr2latSettingsDialogSuccess => - 'Ersättningslistan har uppdaterats'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Felaktig JSON: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => 'Återställ till standard'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'Inställningarna för Cyr2Lat-ersättningar har återställts till standard'; - @override String channels_channelUpdated(String name) { return 'Kanalen \"$name\" har uppdaterats'; } + @override + String get settings_cyr2latProfileAdd => 'Lägg till Cyr2Lat-profil'; + + @override + String get settings_cyr2latProfileName => 'Profilnamn'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Profilnamnet får inte vara tomt'; + + @override + String get settings_cyr2latProfileAdded => 'Profilen har lagts till'; + + @override + String get settings_cyr2latProfileUpdated => 'Profilen har uppdaterats'; + + @override + String get settings_cyr2latProfileEdit => 'Redigera Cyr2Lat-profil'; + + @override + String get settings_cyr2latProfileDelete => 'Ta bort Cyr2Lat-profil'; + + @override + String get settings_cyr2latProfileDeleted => 'Profilen har tagits bort'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Är du säker på att du vill ta bort profilen \"$name\"?'; + } + @override String get channels_publicChannelAdded => 'Allmänt kanal tillagd'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index e46e63c3..a5d63ff6 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -432,7 +432,9 @@ class AppLocalizationsUk extends AppLocalizations { 'Включити місце розташування в оголошення'; @override - String get settings_multiAck => 'Багато підтверджень'; + String settings_multiAck(String value) { + return 'Багатократне підтвердження: $value'; + } @override String get settings_telemetryModeUpdated => 'Режим телеметрії оновлено'; @@ -1184,26 +1186,46 @@ class AppLocalizationsUk extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON-карта замін'; - @override - String get channels_cyr2latSettingsDialogSuccess => 'Список замін оновлено'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'Некоректний JSON: $error'; } - @override - String get channels_cyr2latSettingsDialogReset => 'Скинути до початкових'; - - @override - String get channels_cyr2latSettingsDialogResetted => - 'Налаштування замін Cyr2Lat скинуто до початкових'; - @override String channels_channelUpdated(String name) { return 'Канал «$name» оновлено'; } + @override + String get settings_cyr2latProfileAdd => 'Додати профіль Cyr2Lat'; + + @override + String get settings_cyr2latProfileName => 'Назва профілю'; + + @override + String get settings_cyr2latProfileNameEmpty => + 'Назва профілю не може бути порожньою'; + + @override + String get settings_cyr2latProfileAdded => 'Профіль успішно додано'; + + @override + String get settings_cyr2latProfileUpdated => 'Профіль успішно оновлено'; + + @override + String get settings_cyr2latProfileEdit => 'Редагувати профіль Cyr2Lat'; + + @override + String get settings_cyr2latProfileDelete => 'Видалити профіль Cyr2Lat'; + + @override + String get settings_cyr2latProfileDeleted => 'Профіль успішно видалено'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return 'Ви впевнені, що хочете видалити профіль \"$name\"?'; + } + @override String get channels_publicChannelAdded => 'Публічний канал додано'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index f66ec205..0a5e2565 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -408,7 +408,9 @@ class AppLocalizationsZh extends AppLocalizations { String get settings_advertLocationSubtitle => '在广告中包含位置'; @override - String get settings_multiAck => '多重ACK'; + String settings_multiAck(String value) { + return '多重ACK:$value'; + } @override String get settings_telemetryModeUpdated => '遥测模式已更新'; @@ -1115,25 +1117,45 @@ class AppLocalizationsZh extends AppLocalizations { @override String get channels_cyr2latSettingsDialogHint => 'JSON 替換映射表'; - @override - String get channels_cyr2latSettingsDialogSuccess => '替換清單已更新'; - @override String channels_cyr2latSettingsDialogWrongJSON(Object error) { return 'JSON 格式錯誤:$error'; } - @override - String get channels_cyr2latSettingsDialogReset => '還原為預設值'; - - @override - String get channels_cyr2latSettingsDialogResetted => 'Cyr2Lat 替換設定已還原為預設值'; - @override String channels_channelUpdated(String name) { return '频道 \"$name\" 已更新'; } + @override + String get settings_cyr2latProfileAdd => '新增 Cyr2Lat 設定檔'; + + @override + String get settings_cyr2latProfileName => '設定檔名稱'; + + @override + String get settings_cyr2latProfileNameEmpty => '設定檔名稱不能為空'; + + @override + String get settings_cyr2latProfileAdded => '設定檔已成功新增'; + + @override + String get settings_cyr2latProfileUpdated => '設定檔已成功更新'; + + @override + String get settings_cyr2latProfileEdit => '編輯 Cyr2Lat 設定檔'; + + @override + String get settings_cyr2latProfileDelete => '刪除 Cyr2Lat 設定檔'; + + @override + String get settings_cyr2latProfileDeleted => '設定檔已成功刪除'; + + @override + String settings_cyr2latProfileDeleteDscr(String name) { + return '您確定要刪除設定檔 \"$name\" 嗎?'; + } + @override String get channels_publicChannelAdded => '已添加公共频道'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 913bea1c..f7fc5a4b 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Lijst met vervangingen", "channels_cyr2latSettingsDscr": "Bewerk de JSON-configuratie voor tekenvervanging", "channels_cyr2latSettingsDialogHint": "JSON-vervangingskaart", - "channels_cyr2latSettingsDialogSuccess": "Lijst met vervangingen bijgewerkt", "channels_cyr2latSettingsDialogWrongJSON": "Onjuiste JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Terugzetten naar standaard", - "channels_cyr2latSettingsDialogResetted": "De instellingen voor Cyr2Lat-vervangingen zijn teruggezet naar de standaardinstellingen", + "settings_cyr2latProfileAdd": "Cyr2Lat-profiel toevoegen", + "settings_cyr2latProfileName": "Profielnaam", + "settings_cyr2latProfileNameEmpty": "Profielnaam mag niet leeg zijn", + "settings_cyr2latProfileAdded": "Profiel succesvol toegevoegd", + "settings_cyr2latProfileUpdated": "Profiel succesvol bijgewerkt", + "settings_cyr2latProfileEdit": "Cyr2Lat-profiel bewerken", + "settings_cyr2latProfileDelete": "Cyr2Lat-profiel verwijderen", + "settings_cyr2latProfileDeleted": "Profiel succesvol verwijderd", + "settings_cyr2latProfileDeleteDscr": "Weet u zeker dat u het profiel \"{name}\" wilt verwijderen?", "channels_channelUpdated": "Kanaal \"{name}\" is bijgewerkt", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "contact_lastSeen": "Laatst gezien", "contact_clearChat": "Chat leegmaken", "contact_teleBaseSubtitle": "Sta delen van batterij niveau en basis telemetrie toe", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_maxRouteWeightSubtitle": "Het maximale gewicht dat een route kan bereiken door succesvolle leveringen.", "appSettings_initialRouteWeight": "เริ่มต้น gewicht van de route", "appSettings_maxRouteWeight": "Maximale gewicht voor de route", @@ -1944,6 +1957,7 @@ "appSettings_maxMessageRetriesSubtitle": "Aantal pogingen om een bericht opnieuw te versturen voordat het als mislukt wordt gemarkeerd", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetrie-modus bijgewerkt", + "settings_multiAck": "Multi-ACKs: {value}", "map_showOverlaps": "Herhalingssleutel overlapt", "map_runTraceWithReturnPath": "Terugkeren op hetzelfde pad.", "@radioStats_noiseFloor": { @@ -2075,6 +2089,5 @@ "repeater_guestTools": "Gastenfuncties", "room_guest": "Informatie over de server", "chat_sendMessage": "Verzend bericht", - "repeater_guest": "Informatie over herhalingsapparatuur", - "settings_multiAck": "Meerdere bevestigingen" + "repeater_guest": "Informatie over herhalingsapparatuur" } diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 17b3aa6e..a59b394e 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -404,10 +404,16 @@ "channels_cyr2latSettingsSubheading": "Lista zamian", "channels_cyr2latSettingsDscr": "Edytuj konfigurację JSON zamiany znaków", "channels_cyr2latSettingsDialogHint": "Mapa zamian JSON", - "channels_cyr2latSettingsDialogSuccess": "Lista zamian zaktualizowana", "channels_cyr2latSettingsDialogWrongJSON": "Nieprawidłowy JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Przywróć ustawienia domyślne", - "channels_cyr2latSettingsDialogResetted": "Ustawienia zamiany Cyr2Lat zostały przywrócone do wartości domyślnych", + "settings_cyr2latProfileAdd": "Dodaj profil Cyr2Lat", + "settings_cyr2latProfileName": "Nazwa profilu", + "settings_cyr2latProfileNameEmpty": "Nazwa profilu nie może być pusta", + "settings_cyr2latProfileAdded": "Profil dodano pomyślnie", + "settings_cyr2latProfileUpdated": "Profil został pomyślnie zaktualizowany", + "settings_cyr2latProfileEdit": "Edytuj profil Cyr2Lat", + "settings_cyr2latProfileDelete": "Usuń profil Cyr2Lat", + "settings_cyr2latProfileDeleted": "Profil został pomyślnie usunięty", + "settings_cyr2latProfileDeleteDscr": "Czy na pewno chcesz usunąć profil \"{name}\"?", "channels_channelUpdated": "Kanał \"{name}\" został zaktualizowany", "@channels_channelUpdated": { "placeholders": { @@ -1970,6 +1976,13 @@ "contact_settings": "Ustawienia kontaktowe", "contact_lastSeen": "Ostatnio widziany", "contact_teleBaseSubtitle": "Pozwól na udostępnianie poziomu naładowania baterii i podstawowych danych telemetrycznych", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_initialRouteWeight": "Początkowa waga trasy", "appSettings_maxRouteWeight": "Maksymalny dopuszczalny ciężar pojazdu", "appSettings_initialRouteWeightSubtitle": "Początkowa waga dla nowych, odkrytych ścieżek", @@ -1982,6 +1995,7 @@ "appSettings_maxMessageRetriesSubtitle": "Liczba prób ponownego wysłania wiadomości przed oznaczaniem jej jako nieudanej", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Tryb telemetryczny zaktualizowany", + "settings_multiAck": "Wielokrotne ACK: {value}", "map_showOverlaps": "Nakładające się klucze przekaźników", "map_runTraceWithReturnPath": "Wróć tą samą ścieżką", "@radioStats_noiseFloor": { @@ -2113,6 +2127,5 @@ "chat_sendMessage": "Wyślij wiadomość", "repeater_guestTools": "Narzędzia dla gości", "repeater_guest": "Informacje dotyczące urządzenia powtarzającego", - "room_guest": "Informacje o serwerze", - "settings_multiAck": "Wielokrotne potwierdzenia odbioru" + "room_guest": "Informacje o serwerze" } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 80fdcd73..e49bc38e 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Lista de substituições", "channels_cyr2latSettingsDscr": "Editar a configuração JSON de substituição de caracteres", "channels_cyr2latSettingsDialogHint": "Mapa de substituições JSON", - "channels_cyr2latSettingsDialogSuccess": "Lista de substituições atualizada", "channels_cyr2latSettingsDialogWrongJSON": "JSON incorreto: {error}", - "channels_cyr2latSettingsDialogReset": "Redefinir para os valores iniciais", - "channels_cyr2latSettingsDialogResetted": "As configurações de substituição Cyr2Lat foram redefinidas para os valores iniciais", + "settings_cyr2latProfileAdd": "Adicionar perfil Cyr2Lat", + "settings_cyr2latProfileName": "Nome do perfil", + "settings_cyr2latProfileNameEmpty": "O nome do perfil não pode estar vazio", + "settings_cyr2latProfileAdded": "Perfil adicionado com sucesso", + "settings_cyr2latProfileUpdated": "Perfil atualizado com sucesso", + "settings_cyr2latProfileEdit": "Editar perfil Cyr2Lat", + "settings_cyr2latProfileDelete": "Eliminar perfil Cyr2Lat", + "settings_cyr2latProfileDeleted": "Perfil eliminado com sucesso", + "settings_cyr2latProfileDeleteDscr": "Tem a certeza de que deseja eliminar o perfil \"{name}\"?", "channels_channelUpdated": "Canal \"{name}\" atualizado", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "contact_telemetry": "Telemetria", "contact_settings": "Configurações de Contato", "contact_teleBaseSubtitle": "Permitir compartilhamento do nível da bateria e telemetria básica", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_initialRouteWeight": "Peso Inicial da Rota", "appSettings_maxRouteWeight": "Peso Máximo da Rota", "appSettings_maxRouteWeightSubtitle": "Peso máximo que um determinado percurso pode acumular com entregas bem-sucedidas.", @@ -1944,7 +1957,7 @@ "appSettings_maxMessageRetriesSubtitle": "Número de tentativas de reenvio antes de classificar uma mensagem como falha.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modo de telemetria atualizado", - "settings_multiAck": "Multi-ACKs", + "settings_multiAck": "Multi-ACKs: {value}", "map_showOverlaps": "Sobreposições da Chave Repeater", "map_runTraceWithReturnPath": "Retornar ao mesmo caminho.", "@radioStats_noiseFloor": { diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index e3ea5cd4..d3a0bcbf 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -258,10 +258,16 @@ "channels_cyr2latSettingsSubheading": "Список замен", "channels_cyr2latSettingsDscr": "Редактировать JSON-конфигурацию замены символов", "channels_cyr2latSettingsDialogHint": "JSON-карта замен", - "channels_cyr2latSettingsDialogSuccess": "Список замен обновлён", "channels_cyr2latSettingsDialogWrongJSON": "Некорректный JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Сбросить к начальным", - "channels_cyr2latSettingsDialogResetted": "Настройки замен Cyr2Lat сброшены к начальным", + "settings_cyr2latProfileAdd": "Добавить профиль Cyr2Lat", + "settings_cyr2latProfileName": "Название профиля", + "settings_cyr2latProfileNameEmpty": "Название профиля не может быть пустым", + "settings_cyr2latProfileAdded": "Профиль добавлен", + "settings_cyr2latProfileUpdated": "Профиль успешно обновлен", + "settings_cyr2latProfileEdit": "Редактировать профиль Cyr2Lat", + "settings_cyr2latProfileDelete": "Удалить профиль Cyr2Lat", + "settings_cyr2latProfileDeleted": "Профиль успешно удален", + "settings_cyr2latProfileDeleteDscr": "Вы действительно хотите удалить профиль \"{name}\"?", "channels_channelUpdated": "Канал \"{name}\" обновлён", "channels_publicChannelAdded": "Публичный канал добавлен", "channels_sortBy": "Сортировка", @@ -1172,6 +1178,13 @@ "contact_clearChat": "Очистить чат", "contact_lastSeen": "Последний раз видели", "contact_teleBaseSubtitle": "Разрешить обмен уровнем заряда батареи и базовой телеметрией", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_maxRouteWeight": "Максимальный допустимый вес маршрута", "appSettings_maxRouteWeightSubtitle": "Максимальный вес, который может быть перевезён по определённому маршруту при успешных доставках.", "appSettings_initialRouteWeightSubtitle": "Начальный вес для новых, только что открытых маршрутов", @@ -1184,6 +1197,7 @@ "appSettings_maxMessageRetriesSubtitle": "Количество попыток повторной отправки сообщения перед тем, как пометить его как неудачное.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрии обновлен", + "settings_multiAck": "Мульти-ACK: {value}", "map_showOverlaps": "Перекрытия ключа повтора", "map_runTraceWithReturnPath": "Вернуться обратно по тому же пути", "@radioStats_noiseFloor": { @@ -1315,6 +1329,5 @@ "chat_sendMessage": "Отправить сообщение", "repeater_guest": "Информация о ретрансляторе", "room_guest": "Информация о сервере", - "repeater_guestTools": "Инструменты для гостей", - "settings_multiAck": "Несколько подтверждений" + "repeater_guestTools": "Инструменты для гостей" } diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 2718789e..c7a66a5e 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Zoznam nahradení", "channels_cyr2latSettingsDscr": "Upravte konfiguráciu JSON pre nahradenie znakov", "channels_cyr2latSettingsDialogHint": "JSON mapa nahradení", - "channels_cyr2latSettingsDialogSuccess": "Zoznam nahradení bol aktualizovaný", "channels_cyr2latSettingsDialogWrongJSON": "Nesprávny JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Obnoviť predvolené nastavenia", - "channels_cyr2latSettingsDialogResetted": "Nastavenia nahradzovania Cyr2Lat boli obnovené na predvolené", + "settings_cyr2latProfileAdd": "Pridať profil Cyr2Lat", + "settings_cyr2latProfileName": "Názov profilu", + "settings_cyr2latProfileNameEmpty": "Názov profilu nesmie byť prázdny", + "settings_cyr2latProfileAdded": "Profil bol úspešne pridaný", + "settings_cyr2latProfileUpdated": "Profil bol úspešne aktualizovaný", + "settings_cyr2latProfileEdit": "Upraviť profil Cyr2Lat", + "settings_cyr2latProfileDelete": "Odstrániť profil Cyr2Lat", + "settings_cyr2latProfileDeleted": "Profil bol úspešne odstránený", + "settings_cyr2latProfileDeleteDscr": "Naozaj chcete odstrániť profil \"{name}\"?", "channels_channelUpdated": "Kanál \"{name}\" bol aktualizovaný", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "contact_lastSeen": "Naposledy videný", "contact_teleBase": "Báza telemetrie", "contact_teleEnvSubtitle": "Povoliť zdieľanie údajov senzorov prostredia", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_maxRouteWeightSubtitle": "Maximálna hmotnosť, ktorú môže trás prenášať vďaka úspešným zásielkam.", "appSettings_initialRouteWeightSubtitle": "Počiatočná váha pre nové, objavené cesty", "appSettings_initialRouteWeight": "Počiatočná váha trasy", @@ -1944,7 +1957,7 @@ "appSettings_maxMessageRetriesSubtitle": "Počet pokusov o odošleť pred označením správy ako neúspešnej", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Režim telemetrie bol aktualizovaný", - "settings_multiAck": "Viaceré ACK", + "settings_multiAck": "Viaceré ACK: {value}", "map_showOverlaps": "Prekrývanie opakovača kľúča", "map_runTraceWithReturnPath": "Vráťte sa späť po tej istej ceste.", "@radioStats_noiseFloor": { diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index da42f814..84f8068e 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Seznam zamenjav", "channels_cyr2latSettingsDscr": "Uredi JSON-konfiguracijo zamenjav znakov", "channels_cyr2latSettingsDialogHint": "JSON-tabela zamenjav", - "channels_cyr2latSettingsDialogSuccess": "Seznam zamenjav je posodobljen", "channels_cyr2latSettingsDialogWrongJSON": "Nepravilen JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Ponastavi na privzete", - "channels_cyr2latSettingsDialogResetted": "Nastavitve zamenjav Cyr2Lat so ponastavljene na privzete", + "settings_cyr2latProfileAdd": "Dodaj profil Cyr2Lat", + "settings_cyr2latProfileName": "Ime profila", + "settings_cyr2latProfileNameEmpty": "Ime profila ne sme biti prazno", + "settings_cyr2latProfileAdded": "Profil je bil uspešno dodan", + "settings_cyr2latProfileUpdated": "Profil je bil uspešno posodobljen", + "settings_cyr2latProfileEdit": "Uredi profil Cyr2Lat", + "settings_cyr2latProfileDelete": "Izbriši profil Cyr2Lat", + "settings_cyr2latProfileDeleted": "Profil je bil uspešno izbrisan", + "settings_cyr2latProfileDeleteDscr": "Ali res želite izbrisati profil \"{name}\"?", "channels_channelUpdated": "Kanal {name} je bil posodobljen", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "contact_teleEnv": "Okolje telemetrije", "contact_teleEnvSubtitle": "Dovoli deljenje podatkov okoljskih senzorjev", "contact_teleLocSubtitle": "Dovoli deljenje podatkov o lokaciji", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_maxRouteWeightSubtitle": "Največja teža, ki jo lahko pot doseže s uspešnimi dostavnami.", "appSettings_initialRouteWeight": "Izvirna teža poti", "appSettings_initialRouteWeightSubtitle": "Izguba teže za nove, odkriti poti", @@ -1943,6 +1956,7 @@ "appSettings_maxMessageRetries": "Najve število poskusov pošiljanja sporočil", "appSettings_maxMessageRetriesSubtitle": "Število poskusov ponovnega poslanja, preden se sporočilo označuje kot neuspešno", "path_routeWeight": "{weight}/{max}", + "settings_multiAck": "Večkratni potrditvi: {value}", "settings_telemetryModeUpdated": "Način telemetrije posodobljen", "map_showOverlaps": "Prekrivanje ključa ponovnega predvajanja", "map_runTraceWithReturnPath": "Vrni se nazaj po isti poti.", @@ -2075,6 +2089,5 @@ "repeater_guest": "Informacije o ponovljalniku", "chat_sendMessage": "Pošlji sporočilo", "room_guest": "Informacije o strežniku", - "repeater_guestTools": "Naložila za goste", - "settings_multiAck": "Več potrdil" + "repeater_guestTools": "Naložila za goste" } diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index c714cf42..f8da46fe 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -394,10 +394,16 @@ "channels_cyr2latSettingsSubheading": "Ersättningslista", "channels_cyr2latSettingsDscr": "Redigera JSON-konfigurationen för teckenersättning", "channels_cyr2latSettingsDialogHint": "JSON-ersättningskarta", - "channels_cyr2latSettingsDialogSuccess": "Ersättningslistan har uppdaterats", "channels_cyr2latSettingsDialogWrongJSON": "Felaktig JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Återställ till standard", - "channels_cyr2latSettingsDialogResetted": "Inställningarna för Cyr2Lat-ersättningar har återställts till standard", + "settings_cyr2latProfileAdd": "Lägg till Cyr2Lat-profil", + "settings_cyr2latProfileName": "Profilnamn", + "settings_cyr2latProfileNameEmpty": "Profilnamnet får inte vara tomt", + "settings_cyr2latProfileAdded": "Profilen har lagts till", + "settings_cyr2latProfileUpdated": "Profilen har uppdaterats", + "settings_cyr2latProfileEdit": "Redigera Cyr2Lat-profil", + "settings_cyr2latProfileDelete": "Ta bort Cyr2Lat-profil", + "settings_cyr2latProfileDeleted": "Profilen har tagits bort", + "settings_cyr2latProfileDeleteDscr": "Är du säker på att du vill ta bort profilen \"{name}\"?", "channels_channelUpdated": "Kanalen \"{name}\" har uppdaterats", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "contact_teleBaseSubtitle": "Tillåt delning av batterinivå och grundläggande telemetri", "contact_teleLoc": "Telemetridata plats", "contact_teleLocSubtitle": "Tillåt delning av platsdata", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_initialRouteWeightSubtitle": "Initial vikt för nyligen upptäckta vägar", "appSettings_maxRouteWeight": "Maximalt tillåtet vikt för rutten", "appSettings_maxRouteWeightSubtitle": "Maximal vikt som en leveransväg kan ackumulera från framgångsrika leveranser.", @@ -1944,6 +1957,7 @@ "appSettings_maxMessageRetriesSubtitle": "Antal försök att skicka om ett meddelande innan det markeras som misslyckat.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetri-läge uppdaterat", + "settings_multiAck": "Multi-ACKs: {value}", "map_showOverlaps": "Repeater-nyckelöverlappningar", "map_runTraceWithReturnPath": "Gå tillbaka på samma väg", "@radioStats_noiseFloor": { @@ -2075,6 +2089,5 @@ "repeater_guest": "Information om repetorer", "chat_sendMessage": "Skicka meddelande", "repeater_guestTools": "Gästverktyg", - "room_guest": "Information om servern", - "settings_multiAck": "Flera bekräftelser" + "room_guest": "Information om servern" } diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index cd73efe1..d3245985 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -395,10 +395,16 @@ "channels_cyr2latSettingsSubheading": "Список замін", "channels_cyr2latSettingsDscr": "Редагувати JSON-конфігурацію заміни символів", "channels_cyr2latSettingsDialogHint": "JSON-карта замін", - "channels_cyr2latSettingsDialogSuccess": "Список замін оновлено", "channels_cyr2latSettingsDialogWrongJSON": "Некоректний JSON: {error}", - "channels_cyr2latSettingsDialogReset": "Скинути до початкових", - "channels_cyr2latSettingsDialogResetted": "Налаштування замін Cyr2Lat скинуто до початкових", + "settings_cyr2latProfileAdd": "Додати профіль Cyr2Lat", + "settings_cyr2latProfileName": "Назва профілю", + "settings_cyr2latProfileNameEmpty": "Назва профілю не може бути порожньою", + "settings_cyr2latProfileAdded": "Профіль успішно додано", + "settings_cyr2latProfileUpdated": "Профіль успішно оновлено", + "settings_cyr2latProfileEdit": "Редагувати профіль Cyr2Lat", + "settings_cyr2latProfileDelete": "Видалити профіль Cyr2Lat", + "settings_cyr2latProfileDeleted": "Профіль успішно видалено", + "settings_cyr2latProfileDeleteDscr": "Ви впевнені, що хочете видалити профіль \"{name}\"?", "channels_channelUpdated": "Канал «{name}» оновлено", "@channels_channelUpdated": { "placeholders": { @@ -1932,6 +1938,13 @@ "contact_lastSeen": "Останній раз бачили", "contact_teleEnv": "Середовище телеметрії", "contact_teleEnvSubtitle": "Дозволити спільний доступ до даних датчиків середовища", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_initialRouteWeight": "Початкова вартість маршруту", "appSettings_initialRouteWeightSubtitle": "Початкова вага для нових відкритих шляхів", "appSettings_maxRouteWeight": "Максимальна вага маршруту", @@ -1944,6 +1957,7 @@ "appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрії оновлено", + "settings_multiAck": "Багатократне підтвердження: {value}", "map_showOverlaps": "Перекриття ключа повторювача", "map_runTraceWithReturnPath": "Повернутися назад тим же шляхом", "@radioStats_noiseFloor": { @@ -2075,6 +2089,5 @@ "repeater_guestTools": "Інструменти для гостей", "repeater_guest": "Інформація про ретранслятор", "room_guest": "Інформація про сервер кімнати", - "chat_sendMessage": "Надіслати повідомлення", - "settings_multiAck": "Багато підтверджень" + "chat_sendMessage": "Надіслати повідомлення" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 644ccadd..beb4d0cb 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -409,10 +409,16 @@ "channels_cyr2latSettingsSubheading": "替換清單", "channels_cyr2latSettingsDscr": "編輯 JSON 字元替換設定檔", "channels_cyr2latSettingsDialogHint": "JSON 替換映射表", - "channels_cyr2latSettingsDialogSuccess": "替換清單已更新", "channels_cyr2latSettingsDialogWrongJSON": "JSON 格式錯誤:{error}", - "channels_cyr2latSettingsDialogReset": "還原為預設值", - "channels_cyr2latSettingsDialogResetted": "Cyr2Lat 替換設定已還原為預設值", + "settings_cyr2latProfileAdd": "新增 Cyr2Lat 設定檔", + "settings_cyr2latProfileName": "設定檔名稱", + "settings_cyr2latProfileNameEmpty": "設定檔名稱不能為空", + "settings_cyr2latProfileAdded": "設定檔已成功新增", + "settings_cyr2latProfileUpdated": "設定檔已成功更新", + "settings_cyr2latProfileEdit": "編輯 Cyr2Lat 設定檔", + "settings_cyr2latProfileDelete": "刪除 Cyr2Lat 設定檔", + "settings_cyr2latProfileDeleted": "設定檔已成功刪除", + "settings_cyr2latProfileDeleteDscr": "您確定要刪除設定檔 \"{name}\" 嗎?", "channels_channelUpdated": "频道 \"{name}\" 已更新", "@channels_channelUpdated": { "placeholders": { @@ -1937,6 +1943,13 @@ "contact_settings": "联系人设置", "contact_teleLocSubtitle": "允许共享位置数据", "contact_telemetry": "遥测数据", + "@settings_multiAck": { + "placeholders": { + "value": { + "type": "String" + } + } + }, "appSettings_maxRouteWeight": "最大路径重量", "appSettings_initialRouteWeightSubtitle": "新发现路径的初始重量", "appSettings_initialRouteWeight": "初始路线权重", @@ -1948,7 +1961,7 @@ "appSettings_maxMessageRetries": "最大消息重试次数", "appSettings_maxMessageRetriesSubtitle": "在将消息标记为失败之前,允许尝试的次数", "path_routeWeight": "{weight}/{max}", - "settings_multiAck": "多重ACK", + "settings_multiAck": "多重ACK:{value}", "settings_telemetryModeUpdated": "遥测模式已更新", "map_showOverlaps": "重复键重叠", "map_runTraceWithReturnPath": "沿着相同的路径返回", diff --git a/lib/models/app_settings.dart b/lib/models/app_settings.dart index d7c176e4..4e95311f 100644 --- a/lib/models/app_settings.dart +++ b/lib/models/app_settings.dart @@ -38,6 +38,42 @@ const Map defaultCyr2LatCharMap = { 'х': 'x', }; +class Cyr2LatProfile { + final String id; + final String name; + final Map charMap; + + Cyr2LatProfile({required this.id, required this.name, required this.charMap}); + + Map toJson() { + return {'id': id, 'name': name, 'char_map': charMap}; + } + + factory Cyr2LatProfile.fromJson(Map json) { + return Cyr2LatProfile( + id: json['id'] as String, + name: json['name'] as String, + charMap: + (json['char_map'] as Map?)?.map( + (key, value) => MapEntry(key.toString(), value.toString()), + ) ?? + {}, + ); + } + + Cyr2LatProfile copyWith({ + String? id, + String? name, + Map? charMap, + }) { + return Cyr2LatProfile( + id: id ?? this.id, + name: name ?? this.name, + charMap: charMap ?? this.charMap, + ); + } +} + class AppSettings { static const Object _unset = Object(); @@ -82,7 +118,16 @@ class AppSettings { final String? translationModelSourceUrl; final String? translationSelectedModelId; final List translationDownloadedModels; - final Map cyr2latCharMap; + final List cyr2latProfiles; + final String selectedCyr2latProfileId; + + Map get cyr2latCharMap { + final profile = cyr2latProfiles.firstWhere( + (p) => p.id == selectedCyr2latProfileId, + orElse: () => cyr2latProfiles.first, + ); + return profile.charMap; + } AppSettings({ this.clearPathOnMaxRetry = false, @@ -126,12 +171,22 @@ class AppSettings { this.translationModelSourceUrl, this.translationSelectedModelId, List? translationDownloadedModels, - Map? cyr2latCharMap, + List? cyr2latProfiles, + String? selectedCyr2latProfileId, }) : batteryChemistryByDeviceId = batteryChemistryByDeviceId ?? {}, batteryChemistryByRepeaterId = batteryChemistryByRepeaterId ?? {}, mutedChannels = mutedChannels ?? {}, translationDownloadedModels = translationDownloadedModels ?? const [], - cyr2latCharMap = cyr2latCharMap ?? defaultCyr2LatCharMap; + cyr2latProfiles = + cyr2latProfiles ?? + [ + Cyr2LatProfile( + id: 'default', + name: 'Default', + charMap: defaultCyr2LatCharMap, + ), + ], + selectedCyr2latProfileId = selectedCyr2latProfileId ?? 'default'; Map toJson() { return { @@ -178,7 +233,10 @@ class AppSettings { 'translation_downloaded_models': translationDownloadedModels .map((model) => model.toJson()) .toList(), - 'cyr2lat_char_map': cyr2latCharMap, + 'cyr2lat_profiles': cyr2latProfiles + .map((profile) => profile.toJson()) + .toList(), + 'selected_cyr2lat_profile_id': selectedCyr2latProfileId, }; } @@ -266,11 +324,38 @@ class AppSettings { ) .toList() ?? const [], - cyr2latCharMap: - (json['cyr2lat_char_map'] as Map?)?.map( - (key, value) => MapEntry(key.toString(), value.toString()), - ) ?? - defaultCyr2LatCharMap, + cyr2latProfiles: + (json['cyr2lat_profiles'] as List?) + ?.map( + (entry) => Cyr2LatProfile.fromJson( + Map.from(entry as Map), + ), + ) + .toList() ?? + // Backward compatibility: if old cyr2lat_char_map exists, create a profile from it + (json['cyr2lat_char_map'] != null + ? [ + Cyr2LatProfile( + id: 'migrated', + name: 'Migrated Profile', + charMap: + (json['cyr2lat_char_map'] as Map?)?.map( + (key, value) => + MapEntry(key.toString(), value.toString()), + ) ?? + defaultCyr2LatCharMap, + ), + ] + : [ + Cyr2LatProfile( + id: 'default', + name: 'Default', + charMap: defaultCyr2LatCharMap, + ), + ]), + selectedCyr2latProfileId: + json['selected_cyr2lat_profile_id'] as String? ?? + (json['cyr2lat_char_map'] != null ? 'migrated' : 'default'), ); } @@ -316,7 +401,8 @@ class AppSettings { Object? translationModelSourceUrl = _unset, Object? translationSelectedModelId = _unset, List? translationDownloadedModels, - Map? cyr2latCharMap, + List? cyr2latProfiles, + String? selectedCyr2latProfileId, }) { return AppSettings( clearPathOnMaxRetry: clearPathOnMaxRetry ?? this.clearPathOnMaxRetry, @@ -380,7 +466,9 @@ class AppSettings { : translationSelectedModelId as String?, translationDownloadedModels: translationDownloadedModels ?? this.translationDownloadedModels, - cyr2latCharMap: cyr2latCharMap ?? this.cyr2latCharMap, + cyr2latProfiles: cyr2latProfiles ?? this.cyr2latProfiles, + selectedCyr2latProfileId: + selectedCyr2latProfileId ?? this.selectedCyr2latProfileId, ); } } diff --git a/lib/screens/app_settings_screen.dart b/lib/screens/app_settings_screen.dart index 425248de..051a6f7f 100644 --- a/lib/screens/app_settings_screen.dart +++ b/lib/screens/app_settings_screen.dart @@ -1262,6 +1262,7 @@ class AppSettingsScreen extends StatelessWidget { BuildContext context, AppSettingsService settingsService, ) { + final selectedProfile = settingsService.getSelectedCyr2LatProfile(); return Card( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1273,41 +1274,109 @@ class AppSettingsScreen extends StatelessWidget { style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), ), - ListTile( - leading: const Icon(Icons.translate), - - title: Text(context.l10n.channels_cyr2latSettingsSubheading), - subtitle: Text(context.l10n.channels_cyr2latSettingsDscr), - trailing: const Icon(Icons.chevron_right), - onTap: () => _showCyr2LatDialog(context, settingsService), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: DropdownButtonFormField( + initialValue: settingsService.settings.selectedCyr2latProfileId, + decoration: InputDecoration( + labelText: context.l10n.channels_cyr2latSettingsSubheading, + border: const OutlineInputBorder(), + ), + items: settingsService.settings.cyr2latProfiles.map((profile) { + return DropdownMenuItem( + value: profile.id, + child: Text(profile.name), + ); + }).toList(), + onChanged: (value) { + if (value != null) { + settingsService.setSelectedCyr2LatProfile(value); + } + }, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: () => + _showAddCyr2LatProfileDialog(context, settingsService), + icon: const Icon(Icons.add), + label: Text(context.l10n.common_add), + ), + ), + const SizedBox(width: 8), + Expanded( + child: OutlinedButton.icon( + onPressed: () => _showEditCyr2LatProfileDialog( + context, + settingsService, + selectedProfile, + ), + icon: const Icon(Icons.edit), + label: Text(context.l10n.common_edit), + ), + ), + const SizedBox(width: 8), + Expanded( + child: OutlinedButton.icon( + onPressed: + settingsService.settings.cyr2latProfiles.length > 1 + ? () => _showDeleteCyr2LatProfileDialog( + context, + settingsService, + selectedProfile, + ) + : null, + icon: const Icon(Icons.delete), + label: Text(context.l10n.common_delete), + ), + ), + ], + ), ), ], ), ); } - void _showCyr2LatDialog( + void _showAddCyr2LatProfileDialog( BuildContext context, AppSettingsService settingsService, ) { - final controller = TextEditingController( - text: const JsonEncoder.withIndent( - ' ', - ).convert(settingsService.settings.cyr2latCharMap), + final nameController = TextEditingController(); + final jsonController = TextEditingController( + text: const JsonEncoder.withIndent(' ').convert(defaultCyr2LatCharMap), ); showDialog( context: context, builder: (context) => AlertDialog( - title: Text(context.l10n.channels_cyr2latSettingsDscr), + title: Text(context.l10n.settings_cyr2latProfileAdd), content: SingleChildScrollView( - child: TextField( - controller: controller, - maxLines: 20, - decoration: InputDecoration( - border: const OutlineInputBorder(), - hintText: context.l10n.channels_cyr2latSettingsDialogHint, - ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: nameController, + decoration: InputDecoration( + labelText: context.l10n.settings_cyr2latProfileName, + border: const OutlineInputBorder(), + ), + ), + const SizedBox(height: 16), + TextField( + controller: jsonController, + maxLines: 15, + decoration: InputDecoration( + labelText: context.l10n.channels_cyr2latSettingsDialogHint, + border: const OutlineInputBorder(), + hintText: context.l10n.channels_cyr2latSettingsDscr, + ), + ), + ], ), ), actions: [ @@ -1316,23 +1385,31 @@ class AppSettingsScreen extends StatelessWidget { child: Text(context.l10n.common_cancel), ), TextButton( - onPressed: () { + onPressed: () async { + if (nameController.text.isEmpty) { + showDismissibleSnackBar( + context, + content: Text(context.l10n.settings_cyr2latProfileNameEmpty), + ); + return; + } try { final json = - jsonDecode(controller.text) as Map; + jsonDecode(jsonController.text) as Map; final map = json.map( (key, value) => MapEntry(key, value.toString()), ); - final newSettings = settingsService.settings.copyWith( - cyr2latCharMap: map, + final profile = Cyr2LatProfile( + id: DateTime.now().millisecondsSinceEpoch.toString(), + name: nameController.text, + charMap: map, ); - settingsService.updateSettings(newSettings); + await settingsService.addCyr2LatProfile(profile); + if (!context.mounted) return; Navigator.pop(context); showDismissibleSnackBar( context, - content: Text( - context.l10n.channels_cyr2latSettingsDialogSuccess, - ), + content: Text(context.l10n.settings_cyr2latProfileAdded), ); } catch (e) { showDismissibleSnackBar( @@ -1347,21 +1424,126 @@ class AppSettingsScreen extends StatelessWidget { }, child: Text(context.l10n.common_save), ), + ], + ), + ); + } + + void _showEditCyr2LatProfileDialog( + BuildContext context, + AppSettingsService settingsService, + Cyr2LatProfile profile, + ) { + final nameController = TextEditingController(text: profile.name); + final jsonController = TextEditingController( + text: const JsonEncoder.withIndent(' ').convert(profile.charMap), + ); + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(context.l10n.settings_cyr2latProfileEdit), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: nameController, + decoration: InputDecoration( + labelText: context.l10n.settings_cyr2latProfileName, + border: const OutlineInputBorder(), + ), + ), + const SizedBox(height: 16), + TextField( + controller: jsonController, + maxLines: 15, + decoration: InputDecoration( + labelText: context.l10n.channels_cyr2latSettingsDialogHint, + border: const OutlineInputBorder(), + hintText: context.l10n.channels_cyr2latSettingsDscr, + ), + ), + ], + ), + ), + actions: [ TextButton( - onPressed: () { - final newSettings = settingsService.settings.copyWith( - cyr2latCharMap: defaultCyr2LatCharMap, - ); - settingsService.updateSettings(newSettings); + onPressed: () => Navigator.pop(context), + child: Text(context.l10n.common_cancel), + ), + TextButton( + onPressed: () async { + if (nameController.text.isEmpty) { + showDismissibleSnackBar( + context, + content: Text(context.l10n.settings_cyr2latProfileNameEmpty), + ); + return; + } + try { + final json = + jsonDecode(jsonController.text) as Map; + final map = json.map( + (key, value) => MapEntry(key, value.toString()), + ); + final updatedProfile = profile.copyWith( + name: nameController.text, + charMap: map, + ); + await settingsService.updateCyr2LatProfile(updatedProfile); + if (!context.mounted) return; + Navigator.pop(context); + showDismissibleSnackBar( + context, + content: Text(context.l10n.settings_cyr2latProfileUpdated), + ); + } catch (e) { + showDismissibleSnackBar( + context, + content: Text( + context.l10n.channels_cyr2latSettingsDialogWrongJSON( + e.toString(), + ), + ), + ); + } + }, + child: Text(context.l10n.common_save), + ), + ], + ), + ); + } + + void _showDeleteCyr2LatProfileDialog( + BuildContext context, + AppSettingsService settingsService, + Cyr2LatProfile profile, + ) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(context.l10n.settings_cyr2latProfileDelete), + content: Text( + context.l10n.settings_cyr2latProfileDeleteDscr(profile.name), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(context.l10n.common_cancel), + ), + TextButton( + onPressed: () async { + await settingsService.removeCyr2LatProfile(profile.id); + if (!context.mounted) return; Navigator.pop(context); showDismissibleSnackBar( context, - content: Text( - context.l10n.channels_cyr2latSettingsDialogResetted, - ), + content: Text(context.l10n.settings_cyr2latProfileDeleted), ); }, - child: Text(context.l10n.channels_cyr2latSettingsDialogReset), + child: Text(context.l10n.common_delete), ), ], ), diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index 868aac77..f12018bd 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -1401,10 +1401,17 @@ class _ChannelsScreenState extends State MeshCoreConnector connector, Channel channel, ) { + final appSettingsService = Provider.of( + context, + listen: false, + ); final nameController = TextEditingController(text: channel.name); final pskController = TextEditingController(text: channel.pskHex); bool smazEnabled = connector.isChannelSmazEnabled(channel.index); bool cyr2latEnabled = connector.isChannelCyr2LatEnabled(channel.index); + String? selectedCyr2LatProfileId = connector.getChannelCyr2LatProfileId( + channel.index, + ); showDialog( context: context, @@ -1471,6 +1478,31 @@ class _ChannelsScreenState extends State } }), ), + if (cyr2latEnabled) ...[ + Padding( + padding: const EdgeInsets.fromLTRB(0, 8, 0, 8), + child: DropdownButtonFormField( + initialValue: selectedCyr2LatProfileId, + decoration: InputDecoration( + labelText: dialogContext + .l10n + .channels_cyr2latSettingsSubheading, + border: const OutlineInputBorder(), + ), + items: appSettingsService.settings.cyr2latProfiles.map(( + profile, + ) { + return DropdownMenuItem( + value: profile.id, + child: Text(profile.name), + ); + }).toList(), + onChanged: (value) => setState(() { + selectedCyr2LatProfileId = value; + }), + ), + ), + ], ], ), ), @@ -1506,6 +1538,10 @@ class _ChannelsScreenState extends State channel.index, cyr2latEnabled, ); + await connector.setChannelCyr2LatProfileId( + channel.index, + selectedCyr2LatProfileId, + ); if (!context.mounted) return; showDismissibleSnackBar( context, diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index b9cdf0ec..9ec84416 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -1211,6 +1211,10 @@ class _ChatScreenState extends State { void _showContactSettings(BuildContext context) { final connector = Provider.of(context, listen: false); + final appSettingsService = Provider.of( + context, + listen: false, + ); connector.ensureContactSmazSettingLoaded(widget.contact.publicKeyHex); connector.ensureContactCyr2LatSettingLoaded(widget.contact.publicKeyHex); final contact = widget.contact; @@ -1218,6 +1222,9 @@ class _ChatScreenState extends State { bool cyr2latEnabled = connector.isContactCyr2LatEnabled( contact.publicKeyHex, ); + String? selectedCyr2LatProfileId = connector.getContactCyr2LatProfileId( + contact.publicKeyHex, + ); bool teleBaseEnabled = contact.teleBaseEnabled; bool teleLocEnabled = contact.teleLocEnabled; bool teleEnvEnabled = contact.teleEnvEnabled; @@ -1283,6 +1290,36 @@ class _ChatScreenState extends State { }); }, ), + if (cyr2latEnabled) ...[ + Padding( + padding: const EdgeInsets.fromLTRB(0, 8, 0, 8), + child: DropdownButtonFormField( + initialValue: selectedCyr2LatProfileId, + decoration: InputDecoration( + labelText: + context.l10n.channels_cyr2latSettingsSubheading, + border: const OutlineInputBorder(), + ), + items: appSettingsService.settings.cyr2latProfiles.map(( + profile, + ) { + return DropdownMenuItem( + value: profile.id, + child: Text(profile.name), + ); + }).toList(), + onChanged: (value) { + connector.setContactCyr2LatProfileId( + contact.publicKeyHex, + value, + ); + setDialogState(() { + selectedCyr2LatProfileId = value; + }); + }, + ), + ), + ], const Divider(height: 8), SwitchListTile( contentPadding: EdgeInsets.zero, diff --git a/lib/services/app_settings_service.dart b/lib/services/app_settings_service.dart index 820fe0ec..7b3d5848 100644 --- a/lib/services/app_settings_service.dart +++ b/lib/services/app_settings_service.dart @@ -260,4 +260,56 @@ class AppSettingsService extends ChangeNotifier { _settings.copyWith(translationDownloadedModels: value), ); } + + Cyr2LatProfile getSelectedCyr2LatProfile() { + return _settings.cyr2latProfiles.firstWhere( + (p) => p.id == _settings.selectedCyr2latProfileId, + orElse: () => _settings.cyr2latProfiles.first, + ); + } + + Cyr2LatProfile? getCyr2LatProfileById(String profileId) { + return _settings.cyr2latProfiles.cast().firstWhere( + (p) => p?.id == profileId, + orElse: () => null, + ); + } + + Future setSelectedCyr2LatProfile(String profileId) async { + await updateSettings( + _settings.copyWith(selectedCyr2latProfileId: profileId), + ); + } + + Future addCyr2LatProfile(Cyr2LatProfile profile) async { + final updated = List.from(_settings.cyr2latProfiles) + ..add(profile); + await updateSettings(_settings.copyWith(cyr2latProfiles: updated)); + } + + Future updateCyr2LatProfile(Cyr2LatProfile updatedProfile) async { + final updated = _settings.cyr2latProfiles + .map((p) => p.id == updatedProfile.id ? updatedProfile : p) + .toList(); + await updateSettings(_settings.copyWith(cyr2latProfiles: updated)); + } + + Future removeCyr2LatProfile(String profileId) async { + if (_settings.cyr2latProfiles.length <= 1) { + return; // Don't remove the last profile + } + final updated = _settings.cyr2latProfiles + .where((p) => p.id != profileId) + .toList(); + var newSelectedId = _settings.selectedCyr2latProfileId; + if (newSelectedId == profileId) { + newSelectedId = updated.first.id; + } + await updateSettings( + _settings.copyWith( + cyr2latProfiles: updated, + selectedCyr2latProfileId: newSelectedId, + ), + ); + } } diff --git a/lib/storage/channel_settings_store.dart b/lib/storage/channel_settings_store.dart index eb637acd..e0b390f8 100644 --- a/lib/storage/channel_settings_store.dart +++ b/lib/storage/channel_settings_store.dart @@ -58,20 +58,7 @@ class ChannelSettingsStore { } final prefs = PrefsManager.instance; final key = '$keyForCyr2Lat$channelIndex'; - final oldKey = '$_cyr2latKeyPrefix$channelIndex'; - bool? enabled = prefs.getBool(key); - if (enabled == null) { - // Attempt migration from legacy unscoped key on first load - enabled = prefs.getBool(oldKey); - prefs.remove(oldKey); - if (enabled != null) { - appLogger.info( - 'Migrating channel Cyr2Lat settings from legacy key $oldKey to scoped key $key', - ); - await prefs.setBool(key, enabled); - } - } - return enabled ?? false; + return prefs.getBool(key) ?? false; } Future saveCyr2LatEnabled(int channelIndex, bool enabled) async { @@ -85,4 +72,32 @@ class ChannelSettingsStore { final key = '$keyForCyr2Lat$channelIndex'; await prefs.setBool(key, enabled); } + + Future loadCyr2LatProfileId(int channelIndex) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot load channel settings.', + ); + return null; + } + final prefs = PrefsManager.instance; + final key = '${keyForCyr2Lat}profile_$channelIndex'; + return prefs.getString(key); + } + + Future saveCyr2LatProfileId(int channelIndex, String? profileId) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot save channel settings.', + ); + return; + } + final prefs = PrefsManager.instance; + final key = '${keyForCyr2Lat}profile_$channelIndex'; + if (profileId == null) { + await prefs.remove(key); + } else { + await prefs.setString(key, profileId); + } + } } diff --git a/lib/storage/contact_settings_store.dart b/lib/storage/contact_settings_store.dart index 5db15add..682f1af6 100644 --- a/lib/storage/contact_settings_store.dart +++ b/lib/storage/contact_settings_store.dart @@ -58,19 +58,6 @@ class ContactSettingsStore { } final prefs = PrefsManager.instance; final key = '$keyForCyr2Lat$contactKeyHex'; - final oldKey = '$_cyr2latKeyPrefix$contactKeyHex'; - bool? enabled = prefs.getBool(key); - if (enabled == null) { - // Attempt migration from legacy unscoped key on first load - enabled = prefs.getBool(oldKey); - prefs.remove(oldKey); - if (enabled != null) { - appLogger.info( - 'Migrating contact Cyr2Lat settings from legacy key $oldKey to scoped key $key', - ); - await prefs.setBool(key, enabled); - } - } return prefs.getBool(key) ?? false; } @@ -85,4 +72,35 @@ class ContactSettingsStore { final key = '$keyForCyr2Lat$contactKeyHex'; await prefs.setBool(key, enabled); } + + Future loadCyr2LatProfileId(String contactKeyHex) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot load contact settings.', + ); + return null; + } + final prefs = PrefsManager.instance; + final key = '${keyForCyr2Lat}profile_$contactKeyHex'; + return prefs.getString(key); + } + + Future saveCyr2LatProfileId( + String contactKeyHex, + String? profileId, + ) async { + if (publicKeyHex.isEmpty) { + appLogger.warn( + 'Public key hex is not set. Cannot save contact settings.', + ); + return; + } + final prefs = PrefsManager.instance; + final key = '${keyForCyr2Lat}profile_$contactKeyHex'; + if (profileId == null) { + await prefs.remove(key); + } else { + await prefs.setString(key, profileId); + } + } } From 38f6e427964fc113a6729cdf82a358ea9e4a33d8 Mon Sep 17 00:00:00 2001 From: HDDen <62592944+HDDen@users.noreply.github.com> Date: Sat, 25 Apr 2026 02:15:18 +0300 Subject: [PATCH 3/6] just fixed conflict of cyr2lat with PR #405 --- lib/l10n/app_es.arb | 11 ++--------- lib/l10n/app_fr.arb | 11 ++--------- lib/l10n/app_hu.arb | 11 ++--------- lib/l10n/app_it.arb | 11 ++--------- lib/l10n/app_ja.arb | 11 ++--------- lib/l10n/app_ko.arb | 11 ++--------- lib/l10n/app_localizations.dart | 4 ++-- lib/l10n/app_localizations_bg.dart | 4 +--- lib/l10n/app_localizations_de.dart | 4 +--- lib/l10n/app_localizations_en.dart | 4 +--- lib/l10n/app_localizations_es.dart | 4 +--- lib/l10n/app_localizations_fr.dart | 4 +--- lib/l10n/app_localizations_hu.dart | 4 +--- lib/l10n/app_localizations_it.dart | 4 +--- lib/l10n/app_localizations_ja.dart | 4 +--- lib/l10n/app_localizations_ko.dart | 4 +--- lib/l10n/app_localizations_nl.dart | 4 +--- lib/l10n/app_localizations_pl.dart | 4 +--- lib/l10n/app_localizations_pt.dart | 4 +--- lib/l10n/app_localizations_ru.dart | 4 +--- lib/l10n/app_localizations_sk.dart | 4 +--- lib/l10n/app_localizations_sl.dart | 4 +--- lib/l10n/app_localizations_sv.dart | 4 +--- lib/l10n/app_localizations_uk.dart | 4 +--- lib/l10n/app_localizations_zh.dart | 4 +--- lib/l10n/app_nl.arb | 11 ++--------- lib/l10n/app_pl.arb | 11 ++--------- lib/l10n/app_pt.arb | 9 +-------- lib/l10n/app_ru.arb | 11 ++--------- lib/l10n/app_sk.arb | 9 +-------- lib/l10n/app_sl.arb | 11 ++--------- lib/l10n/app_sv.arb | 11 ++--------- lib/l10n/app_uk.arb | 11 ++--------- lib/l10n/app_zh.arb | 9 +-------- 34 files changed, 47 insertions(+), 188 deletions(-) diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 0a949eb7..1ec25943 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1966,13 +1966,6 @@ "contact_teleBaseSubtitle": "Permitir el intercambio de nivel de batería y telemetría básica", "contact_teleEnv": "Entorno de Telemetría", "contact_teleEnvSubtitle": "Permitir el intercambio de datos de sensores de entorno", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_initialRouteWeight": "Peso inicial de la ruta", "appSettings_maxRouteWeight": "Peso máximo permitido para la ruta", "appSettings_initialRouteWeightSubtitle": "Peso inicial para rutas recién descubiertas", @@ -1985,7 +1978,6 @@ "appSettings_maxMessageRetriesSubtitle": "Número de intentos de reintento antes de marcar un mensaje como fallido.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modo de telemetría actualizado", - "settings_multiAck": "Multi-ACKs: {value}", "map_showOverlaps": "Superposiciones de tecla repetidora", "map_runTraceWithReturnPath": "Volver atrás por el mismo camino.", "@radioStats_noiseFloor": { @@ -2117,5 +2109,6 @@ "repeater_guest": "Información sobre repetidores", "chat_sendMessage": "Enviar mensaje", "repeater_guestTools": "Herramientas para invitados", - "room_guest": "Información del servidor" + "room_guest": "Información del servidor", + "settings_multiAck": "Múltiples respuestas de confirmación" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index e91fad86..3657eaa8 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1938,13 +1938,6 @@ "contact_lastSeen": "Dernière fois vu", "contact_clearChat": "Effacer la conversation", "contact_teleBaseSubtitle": "Autoriser le partage du niveau de batterie et de la télémétrie de base", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_maxRouteWeightSubtitle": "Poids maximal qu'un itinéraire peut accumuler grâce à des livraisons réussies.", "appSettings_initialRouteWeight": "Poids initial de l'itinéraire", "appSettings_maxRouteWeight": "Poids maximal autorisé pour le trajet", @@ -1956,7 +1949,6 @@ "appSettings_maxMessageRetries": "Nombre maximal de tentatives de récupération de messages", "appSettings_maxMessageRetriesSubtitle": "Nombre de tentatives de relance avant de marquer un message comme ayant échoué.", "path_routeWeight": "{weight}/{max}", - "settings_multiAck": "Multi-ACKs : {value}", "settings_telemetryModeUpdated": "Le mode télémétrie a été mis à jour", "map_showOverlaps": "Chevauchement de la touche répétitive", "map_runTraceWithReturnPath": "Revenir sur le même chemin.", @@ -2089,5 +2081,6 @@ "repeater_guestTools": "Outils pour les invités", "chat_sendMessage": "Envoyer un message", "room_guest": "Informations sur le serveur", - "repeater_guest": "Informations sur les répéteurs" + "repeater_guest": "Informations sur les répéteurs", + "settings_multiAck": "Plusieurs accusés de réception" } diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index bd4a0bb8..d1b1dc43 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -2028,13 +2028,6 @@ "radioStats_stripWaiting": "Rádió adatok begyűjtése…", "radioStats_settingsTile": "Rádió statisztikák", "radioStats_settingsSubtitle": "Háttérzaj, RSSI, zaj-sűrűség, és a használat időtartama", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "settings_denyAll": "Elutasítom", "settings_privacySettingsDescription": "Válassza ki, hogy az eszközének melyik információkat oszt meg másokkal.", "settings_privacySubtitle": "Ellenőrizd, hogy milyen információkat osztanak meg.", @@ -2046,7 +2039,6 @@ "settings_telemetryEnvironmentMode": "Adatkapcsolati környezeti mód", "settings_advertLocation": "Reklám megjelenési hely", "settings_advertLocationSubtitle": "A hirdetés tartalmazza a helyszínt.", - "settings_multiAck": "Többszöri visszaigazolások: {value}", "settings_telemetryModeUpdated": "A telemetriamód frissítve", "contact_info": "Kapcsolattartási információk", "contact_settings": "Kapcsolat beállítások", @@ -2127,5 +2119,6 @@ "repeater_guestTools": "Vendégek számára elérhető eszközök", "room_guest": "Szoba szerver információk", "chat_sendMessage": "Üzenet küldése", - "repeater_guest": "Adatok a repeaterről" + "repeater_guest": "Adatok a repeaterről", + "settings_multiAck": "Többszörös visszaigazolások" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 414ee2b4..a4df4373 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -1938,13 +1938,6 @@ "contact_teleBaseSubtitle": "Consenti la condivisione del livello della batteria e della telemetria di base", "contact_teleEnvSubtitle": "Consenti la condivisione dei dati del sensore ambientale", "contact_teleEnv": "Ambiente di telemetria", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_initialRouteWeight": "Peso iniziale del percorso", "appSettings_initialRouteWeightSubtitle": "Peso di partenza per nuovi percorsi", "appSettings_maxRouteWeightSubtitle": "Il peso massimo che un percorso può accumulare grazie a consegne di successo.", @@ -1957,7 +1950,6 @@ "appSettings_maxMessageRetriesSubtitle": "Numero di tentativi di riprova prima di considerare un messaggio come fallito.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modalità telemetria aggiornata", - "settings_multiAck": "Multi-ACKs: {value}", "map_showOverlaps": "Sovrapposizioni della chiave ripetitore", "map_runTraceWithReturnPath": "Tornare indietro sullo stesso percorso", "@radioStats_noiseFloor": { @@ -2089,5 +2081,6 @@ "repeater_guest": "Informazioni sul ripetitore", "repeater_guestTools": "Strumenti per gli ospiti", "chat_sendMessage": "Invia messaggio", - "room_guest": "Informazioni sul server" + "room_guest": "Informazioni sul server", + "settings_multiAck": "ACK multipli" } diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 4faebd75..3a57c10c 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -2028,13 +2028,6 @@ "radioStats_stripWaiting": "ラジオの統計情報を取得中…", "radioStats_settingsTile": "ラジオの統計", "radioStats_settingsSubtitle": "ノイズレベル、RSSI、SNR、および通信時間", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "settings_privacy": "プライバシー設定", "settings_privacySubtitle": "共有する情報の内容を管理する。", "settings_denyAll": "すべてを否定", @@ -2046,7 +2039,6 @@ "settings_telemetryEnvironmentMode": "テレメトリ環境モード", "settings_advertLocation": "広告掲載場所", "settings_advertLocationSubtitle": "広告に場所を記載してください。", - "settings_multiAck": "複数のACK:{value}", "settings_telemetryModeUpdated": "テレメトリモードが更新されました", "contact_info": "連絡先", "contact_settings": "連絡設定", @@ -2127,5 +2119,6 @@ "room_guest": "ルームサーバーに関する情報", "chat_sendMessage": "メッセージを送信する", "repeater_guest": "繰り返し送信に関する情報", - "repeater_guestTools": "ゲスト向けツール" + "repeater_guestTools": "ゲスト向けツール", + "settings_multiAck": "複数のACK(応答)" } diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index b2dc056c..870420cc 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -2028,13 +2028,6 @@ "radioStats_stripWaiting": "라디오 통계 가져오기…", "radioStats_settingsTile": "라디오 통계", "radioStats_settingsSubtitle": "잡음 수준, RSSI, 신호 대 잡음비, 통신 시간", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "settings_privacy": "개인 정보 설정", "settings_privacySubtitle": "어떤 정보를 공유할지 통제하세요.", "settings_privacySettingsDescription": "어떤 정보를 기기가 다른 사람들과 공유할지 선택하세요.", @@ -2046,7 +2039,6 @@ "settings_telemetryEnvironmentMode": "텔레메트리 환경 모드", "settings_advertLocation": "광고 위치", "settings_advertLocationSubtitle": "광고에 위치 정보를 포함하세요.", - "settings_multiAck": "다중 ACK: {value}", "settings_telemetryModeUpdated": "텔레메트리 모드 업데이트 완료", "contact_info": "연락처", "contact_settings": "연락처 설정", @@ -2127,5 +2119,6 @@ "repeater_guestTools": "손님용 도구", "chat_sendMessage": "메시지를 보내기", "repeater_guest": "반복 장비 정보", - "room_guest": "서버 정보" + "room_guest": "서버 정보", + "settings_multiAck": "다중 ACK" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index e84ece65..3c205111 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -901,8 +901,8 @@ abstract class AppLocalizations { /// No description provided for @settings_multiAck. /// /// In en, this message translates to: - /// **'Multi-ACKs: {value}'** - String settings_multiAck(String value); + /// **'Multi-ACKs'** + String get settings_multiAck; /// No description provided for @settings_telemetryModeUpdated. /// diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 0c1cc089..858ee760 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -437,9 +437,7 @@ class AppLocalizationsBg extends AppLocalizations { 'Включи местоположение в обявата'; @override - String settings_multiAck(String value) { - return 'Мулти-потвърди: $value'; - } + String get settings_multiAck => 'Множество потвърждения'; @override String get settings_telemetryModeUpdated => 'Режим на телеметрията е обновен'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 6fdbdd9a..0164a039 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -435,9 +435,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Ort in der Anzeige einbeziehen'; @override - String settings_multiAck(String value) { - return 'Mehrfach-Bestätigungen: $value'; - } + String get settings_multiAck => 'Mehrere Bestätigungen'; @override String get settings_telemetryModeUpdated => 'Telemetriemodus aktualisiert'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 36fbe875..c419c10d 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -427,9 +427,7 @@ class AppLocalizationsEn extends AppLocalizations { String get settings_advertLocationSubtitle => 'Include location in advert.'; @override - String settings_multiAck(String value) { - return 'Multi-ACKs: $value'; - } + String get settings_multiAck => 'Multi-ACKs'; @override String get settings_telemetryModeUpdated => 'Telemetry mode updated'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 469b7779..4c4c6964 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -434,9 +434,7 @@ class AppLocalizationsEs extends AppLocalizations { String get settings_advertLocationSubtitle => 'Incluir ubicación en anuncio'; @override - String settings_multiAck(String value) { - return 'Multi-ACKs: $value'; - } + String get settings_multiAck => 'Múltiples respuestas de confirmación'; @override String get settings_telemetryModeUpdated => 'Modo de telemetría actualizado'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index addba543..fd947e5f 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -438,9 +438,7 @@ class AppLocalizationsFr extends AppLocalizations { 'Inclure l\'emplacement dans l\'annonce'; @override - String settings_multiAck(String value) { - return 'Multi-ACKs : $value'; - } + String get settings_multiAck => 'Plusieurs accusés de réception'; @override String get settings_telemetryModeUpdated => diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 82850575..8da5f29c 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -437,9 +437,7 @@ class AppLocalizationsHu extends AppLocalizations { 'A hirdetés tartalmazza a helyszínt.'; @override - String settings_multiAck(String value) { - return 'Többszöri visszaigazolások: $value'; - } + String get settings_multiAck => 'Többszörös visszaigazolások'; @override String get settings_telemetryModeUpdated => 'A telemetriamód frissítve'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 567e9dc7..6cc619bd 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -437,9 +437,7 @@ class AppLocalizationsIt extends AppLocalizations { 'Includi la posizione nell\'annuncio'; @override - String settings_multiAck(String value) { - return 'Multi-ACKs: $value'; - } + String get settings_multiAck => 'ACK multipli'; @override String get settings_telemetryModeUpdated => 'Modalità telemetria aggiornata'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 55e755e2..313ec234 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -414,9 +414,7 @@ class AppLocalizationsJa extends AppLocalizations { String get settings_advertLocationSubtitle => '広告に場所を記載してください。'; @override - String settings_multiAck(String value) { - return '複数のACK:$value'; - } + String get settings_multiAck => '複数のACK(応答)'; @override String get settings_telemetryModeUpdated => 'テレメトリモードが更新されました'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index b8ec8a63..51738923 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -414,9 +414,7 @@ class AppLocalizationsKo extends AppLocalizations { String get settings_advertLocationSubtitle => '광고에 위치 정보를 포함하세요.'; @override - String settings_multiAck(String value) { - return '다중 ACK: $value'; - } + String get settings_multiAck => '다중 ACK'; @override String get settings_telemetryModeUpdated => '텔레메트리 모드 업데이트 완료'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 28e377cb..45d21e10 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -432,9 +432,7 @@ class AppLocalizationsNl extends AppLocalizations { 'Locatie opnemen in advertentie'; @override - String settings_multiAck(String value) { - return 'Multi-ACKs: $value'; - } + String get settings_multiAck => 'Meerdere bevestigingen'; @override String get settings_telemetryModeUpdated => 'Telemetrie-modus bijgewerkt'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index e49f235a..c8973fa1 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -439,9 +439,7 @@ class AppLocalizationsPl extends AppLocalizations { 'Uwzględnij lokalizację w ogłoszeniu'; @override - String settings_multiAck(String value) { - return 'Wielokrotne ACK: $value'; - } + String get settings_multiAck => 'Wielokrotne potwierdzenia odbioru'; @override String get settings_telemetryModeUpdated => diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 0d8d32f8..464c30f0 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -436,9 +436,7 @@ class AppLocalizationsPt extends AppLocalizations { 'Incluir localização no anúncio'; @override - String settings_multiAck(String value) { - return 'Multi-ACKs: $value'; - } + String get settings_multiAck => 'Multi-ACKs'; @override String get settings_telemetryModeUpdated => 'Modo de telemetria atualizado'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 6ae16611..3f780e89 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -436,9 +436,7 @@ class AppLocalizationsRu extends AppLocalizations { 'Включить местоположение в объявление'; @override - String settings_multiAck(String value) { - return 'Мульти-ACK: $value'; - } + String get settings_multiAck => 'Несколько подтверждений'; @override String get settings_telemetryModeUpdated => 'Режим телеметрии обновлен'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 0e95654f..a9fb036f 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -430,9 +430,7 @@ class AppLocalizationsSk extends AppLocalizations { String get settings_advertLocationSubtitle => 'Zahrnúť polohu do inzerátu'; @override - String settings_multiAck(String value) { - return 'Viaceré ACK: $value'; - } + String get settings_multiAck => 'Viaceré ACK'; @override String get settings_telemetryModeUpdated => diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index b6264ead..9bf1cde8 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -430,9 +430,7 @@ class AppLocalizationsSl extends AppLocalizations { String get settings_advertLocationSubtitle => 'Vključi lokacijo v oglas.'; @override - String settings_multiAck(String value) { - return 'Večkratni potrditvi: $value'; - } + String get settings_multiAck => 'Več potrdil'; @override String get settings_telemetryModeUpdated => 'Način telemetrije posodobljen'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index c540b99d..af7e9fbb 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -428,9 +428,7 @@ class AppLocalizationsSv extends AppLocalizations { String get settings_advertLocationSubtitle => 'Inkludera plats i annonsen'; @override - String settings_multiAck(String value) { - return 'Multi-ACKs: $value'; - } + String get settings_multiAck => 'Flera bekräftelser'; @override String get settings_telemetryModeUpdated => 'Telemetri-läge uppdaterat'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index a5d63ff6..e02237bb 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -432,9 +432,7 @@ class AppLocalizationsUk extends AppLocalizations { 'Включити місце розташування в оголошення'; @override - String settings_multiAck(String value) { - return 'Багатократне підтвердження: $value'; - } + String get settings_multiAck => 'Багато підтверджень'; @override String get settings_telemetryModeUpdated => 'Режим телеметрії оновлено'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 0a5e2565..7ad23bb9 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -408,9 +408,7 @@ class AppLocalizationsZh extends AppLocalizations { String get settings_advertLocationSubtitle => '在广告中包含位置'; @override - String settings_multiAck(String value) { - return '多重ACK:$value'; - } + String get settings_multiAck => '多重ACK'; @override String get settings_telemetryModeUpdated => '遥测模式已更新'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index f7fc5a4b..54871bda 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -1938,13 +1938,6 @@ "contact_lastSeen": "Laatst gezien", "contact_clearChat": "Chat leegmaken", "contact_teleBaseSubtitle": "Sta delen van batterij niveau en basis telemetrie toe", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_maxRouteWeightSubtitle": "Het maximale gewicht dat een route kan bereiken door succesvolle leveringen.", "appSettings_initialRouteWeight": "เริ่มต้น gewicht van de route", "appSettings_maxRouteWeight": "Maximale gewicht voor de route", @@ -1957,7 +1950,6 @@ "appSettings_maxMessageRetriesSubtitle": "Aantal pogingen om een bericht opnieuw te versturen voordat het als mislukt wordt gemarkeerd", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetrie-modus bijgewerkt", - "settings_multiAck": "Multi-ACKs: {value}", "map_showOverlaps": "Herhalingssleutel overlapt", "map_runTraceWithReturnPath": "Terugkeren op hetzelfde pad.", "@radioStats_noiseFloor": { @@ -2089,5 +2081,6 @@ "repeater_guestTools": "Gastenfuncties", "room_guest": "Informatie over de server", "chat_sendMessage": "Verzend bericht", - "repeater_guest": "Informatie over herhalingsapparatuur" + "repeater_guest": "Informatie over herhalingsapparatuur", + "settings_multiAck": "Meerdere bevestigingen" } diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index a59b394e..50a4da53 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1976,13 +1976,6 @@ "contact_settings": "Ustawienia kontaktowe", "contact_lastSeen": "Ostatnio widziany", "contact_teleBaseSubtitle": "Pozwól na udostępnianie poziomu naładowania baterii i podstawowych danych telemetrycznych", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_initialRouteWeight": "Początkowa waga trasy", "appSettings_maxRouteWeight": "Maksymalny dopuszczalny ciężar pojazdu", "appSettings_initialRouteWeightSubtitle": "Początkowa waga dla nowych, odkrytych ścieżek", @@ -1995,7 +1988,6 @@ "appSettings_maxMessageRetriesSubtitle": "Liczba prób ponownego wysłania wiadomości przed oznaczaniem jej jako nieudanej", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Tryb telemetryczny zaktualizowany", - "settings_multiAck": "Wielokrotne ACK: {value}", "map_showOverlaps": "Nakładające się klucze przekaźników", "map_runTraceWithReturnPath": "Wróć tą samą ścieżką", "@radioStats_noiseFloor": { @@ -2127,5 +2119,6 @@ "chat_sendMessage": "Wyślij wiadomość", "repeater_guestTools": "Narzędzia dla gości", "repeater_guest": "Informacje dotyczące urządzenia powtarzającego", - "room_guest": "Informacje o serwerze" + "room_guest": "Informacje o serwerze", + "settings_multiAck": "Wielokrotne potwierdzenia odbioru" } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index e49bc38e..0fd70c79 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -1938,13 +1938,6 @@ "contact_telemetry": "Telemetria", "contact_settings": "Configurações de Contato", "contact_teleBaseSubtitle": "Permitir compartilhamento do nível da bateria e telemetria básica", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_initialRouteWeight": "Peso Inicial da Rota", "appSettings_maxRouteWeight": "Peso Máximo da Rota", "appSettings_maxRouteWeightSubtitle": "Peso máximo que um determinado percurso pode acumular com entregas bem-sucedidas.", @@ -1957,7 +1950,7 @@ "appSettings_maxMessageRetriesSubtitle": "Número de tentativas de reenvio antes de classificar uma mensagem como falha.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Modo de telemetria atualizado", - "settings_multiAck": "Multi-ACKs: {value}", + "settings_multiAck": "Multi-ACKs", "map_showOverlaps": "Sobreposições da Chave Repeater", "map_runTraceWithReturnPath": "Retornar ao mesmo caminho.", "@radioStats_noiseFloor": { diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index d3a0bcbf..765f335e 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -1178,13 +1178,6 @@ "contact_clearChat": "Очистить чат", "contact_lastSeen": "Последний раз видели", "contact_teleBaseSubtitle": "Разрешить обмен уровнем заряда батареи и базовой телеметрией", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_maxRouteWeight": "Максимальный допустимый вес маршрута", "appSettings_maxRouteWeightSubtitle": "Максимальный вес, который может быть перевезён по определённому маршруту при успешных доставках.", "appSettings_initialRouteWeightSubtitle": "Начальный вес для новых, только что открытых маршрутов", @@ -1197,7 +1190,6 @@ "appSettings_maxMessageRetriesSubtitle": "Количество попыток повторной отправки сообщения перед тем, как пометить его как неудачное.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрии обновлен", - "settings_multiAck": "Мульти-ACK: {value}", "map_showOverlaps": "Перекрытия ключа повтора", "map_runTraceWithReturnPath": "Вернуться обратно по тому же пути", "@radioStats_noiseFloor": { @@ -1329,5 +1321,6 @@ "chat_sendMessage": "Отправить сообщение", "repeater_guest": "Информация о ретрансляторе", "room_guest": "Информация о сервере", - "repeater_guestTools": "Инструменты для гостей" + "repeater_guestTools": "Инструменты для гостей", + "settings_multiAck": "Несколько подтверждений" } diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index c7a66a5e..52139257 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -1938,13 +1938,6 @@ "contact_lastSeen": "Naposledy videný", "contact_teleBase": "Báza telemetrie", "contact_teleEnvSubtitle": "Povoliť zdieľanie údajov senzorov prostredia", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_maxRouteWeightSubtitle": "Maximálna hmotnosť, ktorú môže trás prenášať vďaka úspešným zásielkam.", "appSettings_initialRouteWeightSubtitle": "Počiatočná váha pre nové, objavené cesty", "appSettings_initialRouteWeight": "Počiatočná váha trasy", @@ -1957,7 +1950,7 @@ "appSettings_maxMessageRetriesSubtitle": "Počet pokusov o odošleť pred označením správy ako neúspešnej", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Režim telemetrie bol aktualizovaný", - "settings_multiAck": "Viaceré ACK: {value}", + "settings_multiAck": "Viaceré ACK", "map_showOverlaps": "Prekrývanie opakovača kľúča", "map_runTraceWithReturnPath": "Vráťte sa späť po tej istej ceste.", "@radioStats_noiseFloor": { diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index 84f8068e..7e2e3f4c 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -1938,13 +1938,6 @@ "contact_teleEnv": "Okolje telemetrije", "contact_teleEnvSubtitle": "Dovoli deljenje podatkov okoljskih senzorjev", "contact_teleLocSubtitle": "Dovoli deljenje podatkov o lokaciji", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_maxRouteWeightSubtitle": "Največja teža, ki jo lahko pot doseže s uspešnimi dostavnami.", "appSettings_initialRouteWeight": "Izvirna teža poti", "appSettings_initialRouteWeightSubtitle": "Izguba teže za nove, odkriti poti", @@ -1956,7 +1949,6 @@ "appSettings_maxMessageRetries": "Najve število poskusov pošiljanja sporočil", "appSettings_maxMessageRetriesSubtitle": "Število poskusov ponovnega poslanja, preden se sporočilo označuje kot neuspešno", "path_routeWeight": "{weight}/{max}", - "settings_multiAck": "Večkratni potrditvi: {value}", "settings_telemetryModeUpdated": "Način telemetrije posodobljen", "map_showOverlaps": "Prekrivanje ključa ponovnega predvajanja", "map_runTraceWithReturnPath": "Vrni se nazaj po isti poti.", @@ -2089,5 +2081,6 @@ "repeater_guest": "Informacije o ponovljalniku", "chat_sendMessage": "Pošlji sporočilo", "room_guest": "Informacije o strežniku", - "repeater_guestTools": "Naložila za goste" + "repeater_guestTools": "Naložila za goste", + "settings_multiAck": "Več potrdil" } diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index f8da46fe..08c3b33c 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -1938,13 +1938,6 @@ "contact_teleBaseSubtitle": "Tillåt delning av batterinivå och grundläggande telemetri", "contact_teleLoc": "Telemetridata plats", "contact_teleLocSubtitle": "Tillåt delning av platsdata", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_initialRouteWeightSubtitle": "Initial vikt för nyligen upptäckta vägar", "appSettings_maxRouteWeight": "Maximalt tillåtet vikt för rutten", "appSettings_maxRouteWeightSubtitle": "Maximal vikt som en leveransväg kan ackumulera från framgångsrika leveranser.", @@ -1957,7 +1950,6 @@ "appSettings_maxMessageRetriesSubtitle": "Antal försök att skicka om ett meddelande innan det markeras som misslyckat.", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Telemetri-läge uppdaterat", - "settings_multiAck": "Multi-ACKs: {value}", "map_showOverlaps": "Repeater-nyckelöverlappningar", "map_runTraceWithReturnPath": "Gå tillbaka på samma väg", "@radioStats_noiseFloor": { @@ -2089,5 +2081,6 @@ "repeater_guest": "Information om repetorer", "chat_sendMessage": "Skicka meddelande", "repeater_guestTools": "Gästverktyg", - "room_guest": "Information om servern" + "room_guest": "Information om servern", + "settings_multiAck": "Flera bekräftelser" } diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index d3245985..f376a1ed 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1938,13 +1938,6 @@ "contact_lastSeen": "Останній раз бачили", "contact_teleEnv": "Середовище телеметрії", "contact_teleEnvSubtitle": "Дозволити спільний доступ до даних датчиків середовища", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_initialRouteWeight": "Початкова вартість маршруту", "appSettings_initialRouteWeightSubtitle": "Початкова вага для нових відкритих шляхів", "appSettings_maxRouteWeight": "Максимальна вага маршруту", @@ -1957,7 +1950,6 @@ "appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале", "path_routeWeight": "{weight}/{max}", "settings_telemetryModeUpdated": "Режим телеметрії оновлено", - "settings_multiAck": "Багатократне підтвердження: {value}", "map_showOverlaps": "Перекриття ключа повторювача", "map_runTraceWithReturnPath": "Повернутися назад тим же шляхом", "@radioStats_noiseFloor": { @@ -2089,5 +2081,6 @@ "repeater_guestTools": "Інструменти для гостей", "repeater_guest": "Інформація про ретранслятор", "room_guest": "Інформація про сервер кімнати", - "chat_sendMessage": "Надіслати повідомлення" + "chat_sendMessage": "Надіслати повідомлення", + "settings_multiAck": "Багато підтверджень" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index beb4d0cb..1df214f9 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1943,13 +1943,6 @@ "contact_settings": "联系人设置", "contact_teleLocSubtitle": "允许共享位置数据", "contact_telemetry": "遥测数据", - "@settings_multiAck": { - "placeholders": { - "value": { - "type": "String" - } - } - }, "appSettings_maxRouteWeight": "最大路径重量", "appSettings_initialRouteWeightSubtitle": "新发现路径的初始重量", "appSettings_initialRouteWeight": "初始路线权重", @@ -1961,7 +1954,7 @@ "appSettings_maxMessageRetries": "最大消息重试次数", "appSettings_maxMessageRetriesSubtitle": "在将消息标记为失败之前,允许尝试的次数", "path_routeWeight": "{weight}/{max}", - "settings_multiAck": "多重ACK:{value}", + "settings_multiAck": "多重ACK", "settings_telemetryModeUpdated": "遥测模式已更新", "map_showOverlaps": "重复键重叠", "map_runTraceWithReturnPath": "沿着相同的路径返回", From f63d50f0da54959ef21c7ed46cc91bb5cbe01117 Mon Sep 17 00:00:00 2001 From: HDDen <62592944+HDDen@users.noreply.github.com> Date: Fri, 1 May 2026 01:38:31 +0300 Subject: [PATCH 4/6] sync last dev with cyr2lat --- lib/connector/meshcore_protocol.dart | 2 +- lib/l10n/app_localizations_ru.dart | 4 +- lib/l10n/app_ru.arb | 2 + lib/screens/channel_message_path_screen.dart | 101 ++++++++++++++++++- lib/screens/channels_screen.dart | 9 +- lib/screens/chat_screen.dart | 10 +- lib/screens/contacts_screen.dart | 21 ++-- lib/screens/line_of_sight_map_screen.dart | 99 ++++++++++++++++++ lib/screens/map_cache_screen.dart | 82 ++++++++++++++- lib/screens/map_screen.dart | 85 +++++++++++++++- lib/screens/path_trace_map.dart | 88 +++++++++++++++- lib/services/background_service.dart | 3 +- lib/services/translation_service.dart | 61 +++++++++-- linux/flutter/generated_plugins.cmake | 1 + test/l10n/contact_localization_test.dart | 20 +--- untranslated.json | 5 - windows/flutter/generated_plugins.cmake | 1 + 17 files changed, 528 insertions(+), 66 deletions(-) diff --git a/lib/connector/meshcore_protocol.dart b/lib/connector/meshcore_protocol.dart index 396d78b3..02e75db3 100644 --- a/lib/connector/meshcore_protocol.dart +++ b/lib/connector/meshcore_protocol.dart @@ -320,7 +320,7 @@ const int maxPathSize = 64; const int pathHashSize = 1; const int maxNameSize = 32; const int maxFrameSize = 172; -const int appProtocolVersion = 3; +const int appProtocolVersion = 4; // Matches firmware MAX_TEXT_LEN (10 * CIPHER_BLOCK_SIZE). const int maxTextPayloadBytes = 160; const int _sendTextMsgOverheadBytes = diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 870a2cdc..c16d091a 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1632,10 +1632,10 @@ class AppLocalizationsRu extends AppLocalizations { } @override - String get chat_markAsUnread => 'Mark as Unread'; + String get chat_markAsUnread => 'Пометить как непрочитанные'; @override - String get chat_newMessages => 'New messages'; + String get chat_newMessages => 'Новые сообщения'; @override String get chat_openLink => 'Открыть ссылку?'; diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 80b4b9ff..7087dd37 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -376,6 +376,8 @@ "chat_direct": "Прямой", "chat_poiShared": "Точка интереса отправлена", "chat_unread": "Непрочитанных: {count}", + "chat_markAsUnread": "Пометить как непрочитанные", + "chat_newMessages": "Новые сообщения", "map_title": "Карта нод", "map_noNodesWithLocation": "Нет нод с данными о местоположении", "map_nodesNeedGps": "Ноды должны передавать свои GPS-координаты, чтобы отображаться на карте", diff --git a/lib/screens/channel_message_path_screen.dart b/lib/screens/channel_message_path_screen.dart index 53769d40..afb2b906 100644 --- a/lib/screens/channel_message_path_screen.dart +++ b/lib/screens/channel_message_path_screen.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; @@ -304,6 +304,8 @@ class ChannelMessagePathMapScreen extends StatefulWidget { class _ChannelMessagePathMapScreenState extends State { static const double _labelZoomThreshold = 8.5; + static const double _mapMinZoom = 2.0; + static const double _mapMaxZoom = 18.0; final MapController _mapController = MapController(); Uint8List? _selectedPath; @@ -330,6 +332,18 @@ class _ChannelMessagePathMapScreenState } } + @override + void dispose() { + _mapController.dispose(); + super.dispose(); + } + + bool _isDesktopPlatform(TargetPlatform platform) { + return platform == TargetPlatform.linux || + platform == TargetPlatform.windows || + platform == TargetPlatform.macOS; + } + double _getPathDistance(List points) { double totalDistance = 0.0; final distanceCalculator = Distance(); @@ -357,6 +371,70 @@ class _ChannelMessagePathMapScreenState }); } + void _zoomMapBy(double delta) { + final camera = _mapController.camera; + final nextZoom = (camera.zoom + delta) + .clamp(_mapMinZoom, _mapMaxZoom) + .toDouble(); + _mapController.move(camera.center, nextZoom); + } + + void _resetMapView({ + required LatLng initialCenter, + required double initialZoom, + required LatLngBounds? bounds, + }) { + if (bounds != null) { + _mapController.fitCamera( + CameraFit.bounds( + bounds: bounds, + padding: const EdgeInsets.all(64), + maxZoom: 16, + ), + ); + return; + } + _mapController.move(initialCenter, initialZoom); + } + + Widget _buildDesktopMapControls({ + required LatLng initialCenter, + required double initialZoom, + required LatLngBounds? bounds, + }) { + return Positioned( + left: 16, + top: 16, + child: Card( + elevation: 4, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.add), + tooltip: 'Zoom in', + onPressed: () => _zoomMapBy(1), + ), + IconButton( + icon: const Icon(Icons.remove), + tooltip: 'Zoom out', + onPressed: () => _zoomMapBy(-1), + ), + IconButton( + icon: const Icon(Icons.my_location), + tooltip: 'Center map', + onPressed: () => _resetMapView( + initialCenter: initialCenter, + initialZoom: initialZoom, + bounds: bounds, + ), + ), + ], + ), + ), + ); + } + @override Widget build(BuildContext context) { return Consumer( @@ -372,6 +450,7 @@ class _ChannelMessagePathMapScreenState primaryPath, widget.message.pathVariants, ); + final isDesktop = _isDesktopPlatform(defaultTargetPlatform); final selectedPathTmp = _resolveSelectedPath( _selectedPath, observedPaths, @@ -451,10 +530,20 @@ class _ChannelMessagePathMapScreenState padding: const EdgeInsets.all(64), maxZoom: 16, ), - minZoom: 2.0, - maxZoom: 18.0, + minZoom: _mapMinZoom, + maxZoom: _mapMaxZoom, interactionOptions: InteractionOptions( flags: ~InteractiveFlag.rotate, + scrollWheelVelocity: isDesktop ? 0.012 : 0.005, + cursorKeyboardRotationOptions: + CursorKeyboardRotationOptions.disabled(), + keyboardOptions: isDesktop + ? const KeyboardOptions( + enableArrowKeysPanning: true, + enableWASDPanning: true, + enableRFZooming: true, + ) + : const KeyboardOptions.disabled(), ), onPositionChanged: (camera, hasGesture) { final shouldShow = camera.zoom >= _labelZoomThreshold; @@ -486,6 +575,12 @@ class _ChannelMessagePathMapScreenState ), ], ), + if (isDesktop) + _buildDesktopMapControls( + initialCenter: initialCenter, + initialZoom: initialZoom, + bounds: bounds, + ), if (observedPaths.length > 1) _buildPathSelector(context, observedPaths, selectedIndex, ( index, diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index 0bd062f4..ffd936a4 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -492,8 +492,9 @@ class _ChannelsScreenState extends State ], ), onTap: () async { - final unread = - connector.getUnreadCountForChannelIndex(channel.index); + final unread = connector.getUnreadCountForChannelIndex( + channel.index, + ); connector.markChannelRead(channel.index); await Future.delayed(const Duration(milliseconds: 50)); if (context.mounted) { @@ -1557,7 +1558,9 @@ class _ChannelsScreenState extends State if (!context.mounted) return; showDismissibleSnackBar( context, - content: Text(context.l10n.channels_channelUpdateFailed('$e')), + content: Text( + context.l10n.channels_channelUpdateFailed('$e'), + ), ); } }, diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index 46d89977..8d3bc66c 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -1234,8 +1234,14 @@ class _ChatScreenState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildInfoRow(context.l10n.chat_type, contact.typeLabel(context.l10n)), - _buildInfoRow(context.l10n.chat_path, contact.pathLabel(context.l10n)), + _buildInfoRow( + context.l10n.chat_type, + contact.typeLabel(context.l10n), + ), + _buildInfoRow( + context.l10n.chat_path, + contact.pathLabel(context.l10n), + ), _buildInfoRow( context.l10n.contact_lastSeen, _formatContactLastMessage(contact.lastMessageAt), diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index f2bd7374..a4cc35ca 100644 --- a/lib/screens/contacts_screen.dart +++ b/lib/screens/contacts_screen.dart @@ -932,16 +932,15 @@ class _ContactsScreenState extends State _showRoomLogin(context, contact, RoomLoginDestination.chat); } else { final connector = context.read(); - final unread = - connector.getUnreadCountForContactKey(contact.publicKeyHex); + final unread = connector.getUnreadCountForContactKey( + contact.publicKeyHex, + ); connector.markContactRead(contact.publicKeyHex); Navigator.push( context, MaterialPageRoute( - builder: (context) => ChatScreen( - contact: contact, - initialUnreadCount: unread, - ), + builder: (context) => + ChatScreen(contact: contact, initialUnreadCount: unread), ), ); } @@ -998,8 +997,9 @@ class _ContactsScreenState extends State room: room, onLogin: (password, isAdmin) { final connector = context.read(); - final unread = - connector.getUnreadCountForContactKey(room.publicKeyHex); + final unread = connector.getUnreadCountForContactKey( + room.publicKeyHex, + ); connector.markContactRead(room.publicKeyHex); Navigator.push( context, @@ -1011,10 +1011,7 @@ class _ContactsScreenState extends State password: password, isAdmin: isAdmin, ) - : ChatScreen( - contact: room, - initialUnreadCount: unread, - ), + : ChatScreen(contact: room, initialUnreadCount: unread), ), ); }, diff --git a/lib/screens/line_of_sight_map_screen.dart b/lib/screens/line_of_sight_map_screen.dart index e88f4b9e..3ba79d59 100644 --- a/lib/screens/line_of_sight_map_screen.dart +++ b/lib/screens/line_of_sight_map_screen.dart @@ -1,6 +1,7 @@ import 'dart:math' as math; import 'dart:ui' as ui; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; @@ -56,8 +57,11 @@ class _LineOfSightMapScreenState extends State { static const double _maxAntennaFeet = 400.0; static const double _maxAntennaMeters = _maxAntennaFeet / _metersToFeet; static const double _labelZoomThreshold = 8.5; + static const double _mapMinZoom = 2.0; + static const double _mapMaxZoom = 18.0; final LineOfSightService _lineOfSightService = LineOfSightService(); + final MapController _mapController = MapController(); bool _loading = false; String? _error; @@ -99,10 +103,85 @@ class _LineOfSightMapScreenState extends State { @override void dispose() { + _mapController.dispose(); _lineOfSightService.dispose(); super.dispose(); } + bool _isDesktopPlatform(TargetPlatform platform) { + return platform == TargetPlatform.linux || + platform == TargetPlatform.windows || + platform == TargetPlatform.macOS; + } + + void _zoomMapBy(double delta) { + final camera = _mapController.camera; + final nextZoom = (camera.zoom + delta) + .clamp(_mapMinZoom, _mapMaxZoom) + .toDouble(); + _mapController.move(camera.center, nextZoom); + } + + void _resetMapView({ + required LatLng initialCenter, + required double initialZoom, + required LatLngBounds? bounds, + }) { + if (bounds != null) { + _mapController.fitCamera( + CameraFit.bounds( + bounds: bounds, + padding: const EdgeInsets.all(64), + maxZoom: 16, + ), + ); + return; + } + _mapController.move(initialCenter, initialZoom); + } + + Widget _buildDesktopMapControls({ + required LatLng initialCenter, + required double initialZoom, + required LatLngBounds? bounds, + }) { + final screenHeight = MediaQuery.of(context).size.height; + final topOffset = _showHud + ? math.min(screenHeight * 0.52 + 24, screenHeight - 220) + : 12.0; + return Positioned( + top: topOffset, + left: 12, + child: Card( + elevation: 4, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.add), + tooltip: 'Zoom in', + onPressed: () => _zoomMapBy(1), + ), + IconButton( + icon: const Icon(Icons.remove), + tooltip: 'Zoom out', + onPressed: () => _zoomMapBy(-1), + ), + IconButton( + icon: const Icon(Icons.my_location), + tooltip: 'Center map', + onPressed: () => _resetMapView( + initialCenter: initialCenter, + initialZoom: initialZoom, + bounds: bounds, + ), + ), + ], + ), + ), + ); + } + Future _runLos() async { final start = _start; final end = _end; @@ -325,6 +404,7 @@ class _LineOfSightMapScreenState extends State { ? LatLngBounds.fromPoints(mapPoints) : null; final initialZoom = mapPoints.length > 1 ? 13.0 : 2.0; + final isDesktop = _isDesktopPlatform(defaultTargetPlatform); if (!_didReceivePositionUpdate) { _showMarkerLabels = initialZoom >= _labelZoomThreshold; } @@ -350,6 +430,7 @@ class _LineOfSightMapScreenState extends State { body: Stack( children: [ FlutterMap( + mapController: _mapController, options: MapOptions( initialCenter: initialCenter, initialZoom: initialZoom, @@ -362,7 +443,19 @@ class _LineOfSightMapScreenState extends State { ), interactionOptions: InteractionOptions( flags: ~InteractiveFlag.rotate, + scrollWheelVelocity: isDesktop ? 0.012 : 0.005, + cursorKeyboardRotationOptions: + CursorKeyboardRotationOptions.disabled(), + keyboardOptions: isDesktop + ? const KeyboardOptions( + enableArrowKeysPanning: true, + enableWASDPanning: true, + enableRFZooming: true, + ) + : const KeyboardOptions.disabled(), ), + minZoom: _mapMinZoom, + maxZoom: _mapMaxZoom, onLongPress: (_, point) => _addCustomPoint(point), onPositionChanged: (camera, hasGesture) { final shouldShow = camera.zoom >= _labelZoomThreshold; @@ -389,6 +482,12 @@ class _LineOfSightMapScreenState extends State { ), ], ), + if (isDesktop) + _buildDesktopMapControls( + initialCenter: initialCenter, + initialZoom: initialZoom, + bounds: bounds, + ), if (_showHud) Positioned( left: 12, diff --git a/lib/screens/map_cache_screen.dart b/lib/screens/map_cache_screen.dart index 1eb59a86..4057e0ec 100644 --- a/lib/screens/map_cache_screen.dart +++ b/lib/screens/map_cache_screen.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; @@ -18,6 +19,9 @@ class MapCacheScreen extends StatefulWidget { } class _MapCacheScreenState extends State { + static const double _mapMinZoom = 2.0; + static const double _mapMaxZoom = 18.0; + final MapController _mapController = MapController(); LatLngBounds? _selectedBounds; @@ -43,6 +47,61 @@ class _MapCacheScreenState extends State { super.dispose(); } + bool _isDesktopPlatform(TargetPlatform platform) { + return platform == TargetPlatform.linux || + platform == TargetPlatform.windows || + platform == TargetPlatform.macOS; + } + + void _zoomMapBy(double delta) { + final camera = _mapController.camera; + final nextZoom = (camera.zoom + delta) + .clamp(_mapMinZoom, _mapMaxZoom) + .toDouble(); + _mapController.move(camera.center, nextZoom); + } + + void _resetMapView() { + final bounds = _selectedBounds; + if (bounds != null) { + _mapController.fitCamera( + CameraFit.bounds(bounds: bounds, padding: const EdgeInsets.all(48)), + ); + return; + } + _mapController.move(const LatLng(0, 0), 2.0); + } + + Widget _buildDesktopMapControls() { + return Positioned( + top: 12, + left: 12, + child: Card( + elevation: 4, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.add), + tooltip: 'Zoom in', + onPressed: () => _zoomMapBy(1), + ), + IconButton( + icon: const Icon(Icons.remove), + tooltip: 'Zoom out', + onPressed: () => _zoomMapBy(-1), + ), + IconButton( + icon: const Icon(Icons.my_location), + tooltip: 'Center map', + onPressed: _resetMapView, + ), + ], + ), + ), + ); + } + void _loadSettings() { final settings = context.read().settings; final bounds = MapTileCacheService.boundsFromJson(settings.mapCacheBounds); @@ -222,6 +281,7 @@ class _MapCacheScreenState extends State { final tileCache = context.read(); final selectedBounds = _selectedBounds; final l10n = context.l10n; + final isDesktop = _isDesktopPlatform(defaultTargetPlatform); final progressValue = _estimatedTiles == 0 ? 0.0 : (_completedTiles / _estimatedTiles).clamp(0.0, 1.0).toDouble(); @@ -238,11 +298,24 @@ class _MapCacheScreenState extends State { children: [ FlutterMap( mapController: _mapController, - options: const MapOptions( - initialCenter: LatLng(0, 0), + options: MapOptions( + initialCenter: const LatLng(0, 0), initialZoom: 2.0, - minZoom: 2.0, - maxZoom: 18.0, + minZoom: _mapMinZoom, + maxZoom: _mapMaxZoom, + interactionOptions: InteractionOptions( + flags: ~InteractiveFlag.rotate, + scrollWheelVelocity: isDesktop ? 0.012 : 0.005, + cursorKeyboardRotationOptions: + CursorKeyboardRotationOptions.disabled(), + keyboardOptions: isDesktop + ? const KeyboardOptions( + enableArrowKeysPanning: true, + enableWASDPanning: true, + enableRFZooming: true, + ) + : const KeyboardOptions.disabled(), + ), ), children: [ TileLayer( @@ -265,6 +338,7 @@ class _MapCacheScreenState extends State { ), ], ), + if (isDesktop) _buildDesktopMapControls(), Positioned( top: 12, right: 12, diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index efc1656f..9108e2b4 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -1,6 +1,5 @@ import 'dart:collection'; import 'dart:math'; -import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -58,6 +57,8 @@ class MapScreen extends StatefulWidget { class _MapScreenState extends State { // Zoom level at which node labels start to appear static const double _labelZoomThreshold = 14.0; + static const double _mapMinZoom = 2.0; + static const double _mapMaxZoom = 18.0; final MapController _mapController = MapController(); final MapMarkerService _markerService = MapMarkerService(); @@ -150,11 +151,62 @@ class _MapScreenState extends State { return zoom.clamp(4.0, 15.0); } + bool _isDesktopPlatform(TargetPlatform platform) { + return platform == TargetPlatform.linux || + platform == TargetPlatform.windows || + platform == TargetPlatform.macOS; + } + + void _zoomMapBy(double delta) { + final camera = _mapController.camera; + final nextZoom = (camera.zoom + delta) + .clamp(_mapMinZoom, _mapMaxZoom) + .toDouble(); + _mapController.move(camera.center, nextZoom); + } + + Widget _buildDesktopMapControls( + BuildContext context, { + required LatLng center, + required double zoom, + required bool hasPathSelector, + }) { + return Positioned( + left: 16, + top: hasPathSelector ? null : 16, + bottom: hasPathSelector ? 16 : null, + child: Card( + elevation: 4, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.add), + tooltip: 'Zoom in', + onPressed: () => _zoomMapBy(1), + ), + IconButton( + icon: const Icon(Icons.remove), + tooltip: 'Zoom out', + onPressed: () => _zoomMapBy(-1), + ), + IconButton( + icon: const Icon(Icons.my_location), + tooltip: 'Center map', + onPressed: () => _mapController.move(center, zoom), + ), + ], + ), + ), + ); + } + @override Widget build(BuildContext context) { return Consumer3( builder: (context, connector, settingsService, pathHistory, child) { final tileCache = context.read(); + final isDesktop = _isDesktopPlatform(defaultTargetPlatform); final settings = settingsService.settings; final allContacts = connector.allContacts; @@ -451,10 +503,20 @@ class _MapScreenState extends State { options: MapOptions( initialCenter: center, initialZoom: initialZoom, - minZoom: 2.0, - maxZoom: 18.0, + minZoom: _mapMinZoom, + maxZoom: _mapMaxZoom, interactionOptions: InteractionOptions( flags: ~InteractiveFlag.rotate, + scrollWheelVelocity: isDesktop ? 0.012 : 0.005, + cursorKeyboardRotationOptions: + CursorKeyboardRotationOptions.disabled(), + keyboardOptions: isDesktop + ? const KeyboardOptions( + enableArrowKeysPanning: true, + enableWASDPanning: true, + enableRFZooming: true, + ) + : const KeyboardOptions.disabled(), ), onTap: (_, latLng) { if (_isSelectingPoi) { @@ -598,6 +660,13 @@ class _MapScreenState extends State { sharedMarkers.length, guessedLocations.length, ), + if (isDesktop) + _buildDesktopMapControls( + context, + center: center, + zoom: initialZoom, + hasPathSelector: _isBuildingPathTrace, + ), if (_isBuildingPathTrace) _buildPathTraceOverlay(), ], ), @@ -1480,8 +1549,14 @@ class _MapScreenState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildInfoRow(context.l10n.map_type, contact.typeLabel(context.l10n)), - _buildInfoRow(context.l10n.map_path, contact.pathLabel(context.l10n)), + _buildInfoRow( + context.l10n.map_type, + contact.typeLabel(context.l10n), + ), + _buildInfoRow( + context.l10n.map_path, + contact.pathLabel(context.l10n), + ), if (contact.hasLocation) _buildInfoRow( context.l10n.map_location, diff --git a/lib/screens/path_trace_map.dart b/lib/screens/path_trace_map.dart index 7f3b4eb5..c810570d 100644 --- a/lib/screens/path_trace_map.dart +++ b/lib/screens/path_trace_map.dart @@ -76,9 +76,12 @@ class PathTraceMapScreen extends StatefulWidget { class _PathTraceMapScreenState extends State { static const double _labelZoomThreshold = 8.5; + static const double _mapMinZoom = 2.0; + static const double _mapMaxZoom = 18.0; //miles to meters conversion for filtering out repeaters that are too far from the last known GPS hop to be a likely match, to avoid false matches that throw off the inferred positions of other hops in the path static const double _maxRepeaterMatchDistanceMeters = 40 * 1609.344; + final MapController _mapController = MapController(); StreamSubscription? _frameSubscription; Timer? _timeoutTimer; @@ -116,11 +119,74 @@ class _PathTraceMapScreenState extends State { @override void dispose() { + _mapController.dispose(); _frameSubscription?.cancel(); _timeoutTimer?.cancel(); super.dispose(); } + bool _isDesktopPlatform(TargetPlatform platform) { + return platform == TargetPlatform.linux || + platform == TargetPlatform.windows || + platform == TargetPlatform.macOS; + } + + void _zoomMapBy(double delta) { + final camera = _mapController.camera; + final nextZoom = (camera.zoom + delta) + .clamp(_mapMinZoom, _mapMaxZoom) + .toDouble(); + _mapController.move(camera.center, nextZoom); + } + + void _resetMapView() { + final bounds = _bounds; + if (bounds != null) { + _mapController.fitCamera( + CameraFit.bounds( + bounds: bounds, + padding: const EdgeInsets.all(64), + maxZoom: 16, + ), + ); + return; + } + final center = _initialCenter; + if (center != null) { + _mapController.move(center, _initialZoom); + } + } + + Widget _buildDesktopMapControls() { + return Positioned( + top: 16, + left: 16, + child: Card( + elevation: 4, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.add), + tooltip: 'Zoom in', + onPressed: () => _zoomMapBy(1), + ), + IconButton( + icon: const Icon(Icons.remove), + tooltip: 'Zoom out', + onPressed: () => _zoomMapBy(-1), + ), + IconButton( + icon: const Icon(Icons.my_location), + tooltip: 'Center map', + onPressed: _resetMapView, + ), + ], + ), + ), + ); + } + Uint8List buildPath(Uint8List pathBytes) { Uint8List traceBytes; @@ -519,6 +585,8 @@ class _PathTraceMapScreenState extends State { ), if (_hasData) _buildMapPathTrace(context, tileCache, _targetContact), + if (_hasData && _isDesktopPlatform(defaultTargetPlatform)) + _buildDesktopMapControls(), if (_points.isEmpty && !_hasData && !_isLoading && @@ -801,10 +869,24 @@ class _PathTraceMapScreenState extends State { MapTileCacheService tileCache, Contact? target, ) { + final isDesktop = _isDesktopPlatform(defaultTargetPlatform); return FlutterMap( key: _mapKey, + mapController: _mapController, options: MapOptions( - interactionOptions: InteractionOptions(flags: ~InteractiveFlag.rotate), + interactionOptions: InteractionOptions( + flags: ~InteractiveFlag.rotate, + scrollWheelVelocity: isDesktop ? 0.012 : 0.005, + cursorKeyboardRotationOptions: + CursorKeyboardRotationOptions.disabled(), + keyboardOptions: isDesktop + ? const KeyboardOptions( + enableArrowKeysPanning: true, + enableWASDPanning: true, + enableRFZooming: true, + ) + : const KeyboardOptions.disabled(), + ), initialCenter: _initialCenter!, initialZoom: _initialZoom, initialCameraFit: _bounds == null @@ -814,8 +896,8 @@ class _PathTraceMapScreenState extends State { padding: const EdgeInsets.all(64), maxZoom: 16, ), - minZoom: 2.0, - maxZoom: 18.0, + minZoom: _mapMinZoom, + maxZoom: _mapMaxZoom, onPositionChanged: (camera, hasGesture) { final shouldShow = camera.zoom >= _labelZoomThreshold; if (shouldShow != _showNodeLabels && mounted) { diff --git a/lib/services/background_service.dart b/lib/services/background_service.dart index 336ecdd4..23d5fafe 100644 --- a/lib/services/background_service.dart +++ b/lib/services/background_service.dart @@ -65,8 +65,7 @@ class BackgroundService { return AppLocalizations.delegate.load(overrideLocale); } } - final preferred = - WidgetsBinding.instance.platformDispatcher.locales; + final preferred = WidgetsBinding.instance.platformDispatcher.locales; final match = basicLocaleListResolution(preferred, supported); return AppLocalizations.delegate.load(match); } diff --git a/lib/services/translation_service.dart b/lib/services/translation_service.dart index 7d76efab..294a7848 100644 --- a/lib/services/translation_service.dart +++ b/lib/services/translation_service.dart @@ -519,12 +519,11 @@ class TranslationService extends ChangeNotifier { } String? _heuristicLanguageCode(String text) { - if (RegExp(r'[іїєґІЇЄҐ]').hasMatch(text)) { - return 'uk'; - } - if (RegExp(r'[а-яёА-ЯЁ]').hasMatch(text)) { - return 'ru'; + final trimmed = text.trim(); + if (trimmed.isEmpty) { + return null; } + if (RegExp(r'[ぁ-んァ-ン]').hasMatch(text)) { return 'ja'; } @@ -534,9 +533,55 @@ class TranslationService extends ChangeNotifier { if (RegExp(r'[\u4e00-\u9fff]').hasMatch(text)) { return 'zh'; } - // Latin-script languages can't be reliably distinguished by characters - // alone — return null so the translator always attempts translation. - return null; + + final lower = trimmed.toLowerCase(); + final patterns = { + 'uk': r'\b(привіт|дякую|будь|ласка|як|де|не|так|це|є|най|ще|може|для)\b', + 'ru': + r'\b(что|это|как|не|да|нет|он|она|они|быть|есть|для|сегодня|если|уже|может)\b', + 'bg': r'\b(ще|няма|благодаря|моля|това|какво|тук|ние|вие|не|със|за)\b', + 'de': + r'\b(der|die|das|und|ist|nicht|ein|eine|ich|für|mit|auf|zu|auch|als|an|im|am|es|dem|den|sich|von)\b', + 'en': + r'\b(the|and|is|you|for|with|from|not|that|this|have|be|are|was|were|but|can|will|your|what|when|how|they)\b', + 'es': + r'\b(el|la|los|las|es|que|de|en|con|por|para|no|un|una|se|como|su|al|del|está)\b', + 'fr': + r'\b(le|la|les|un|une|et|est|que|qui|pour|dans|pas|avec|sur|ne|vous|il|elle|des|ce|cette|je|tu|nous|vous)\b', + 'it': + r'\b(il|la|lo|un|una|che|di|da|in|per|con|non|si|mi|ti|noi|voi|lui|lei)\b', + 'pt': + r'\b(os|as|que|de|do|da|em|para|com|por|não|uma|um|se|você|também)\b', + 'nl': + r'\b(de|het|een|en|is|niet|dat|wat|je|ik|op|aan|voor|met|als|nog|zijn)\b', + 'sv': + r'\b(och|är|det|att|som|en|på|inte|har|var|men|du|jag|vi|ni|den|detta)\b', + 'pl': + r'\b(na|się|nie|jest|to|że|do|od|dla|czy|tak|ale|ma|jak|on|ona|my)\b', + 'sk': r'\b(je|na|so|že|do|od|za|si|to|ten|tá|tí|ako|má|nie|som|sa)\b', + 'sl': r'\b(in|je|na|se|da|za|od|ne|to|ta|so|kako|bo|sem|si)\b', + 'hu': + r'\b(az|és|nem|van|volt|hogy|mit|mire|ki|mi|ez|azért|is|de|ha|te|ő|mi|itt)\b', + }; + + final scores = {}; + for (final entry in patterns.entries) { + scores[entry.key] = RegExp( + entry.value, + caseSensitive: false, + ).allMatches(lower).length; + } + + final sorted = scores.entries.toList() + ..sort((a, b) => b.value.compareTo(a.value)); + if (sorted.isEmpty || sorted.first.value == 0) { + return null; + } + if (sorted.length > 1 && sorted.first.value == sorted[1].value) { + return null; + } + + return sorted.first.key; } String _languageLabel(String code) { diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 379e36fa..93e46829 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST flserial + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/test/l10n/contact_localization_test.dart b/test/l10n/contact_localization_test.dart index 87df23a7..62f8b6c8 100644 --- a/test/l10n/contact_localization_test.dart +++ b/test/l10n/contact_localization_test.dart @@ -36,10 +36,7 @@ void main() { }); test('repeater', () { - expect( - _contact(type: advTypeRepeater).typeLabel(l10n), - 'Repeater', - ); + expect(_contact(type: advTypeRepeater).typeLabel(l10n), 'Repeater'); }); test('room', () { @@ -57,24 +54,15 @@ void main() { group('Contact.pathLabel (override)', () { test('override < 0 -> flood (forced)', () { - expect( - _contact(pathOverride: -1).pathLabel(l10n), - 'Flood (forced)', - ); + expect(_contact(pathOverride: -1).pathLabel(l10n), 'Flood (forced)'); }); test('override == 0 -> direct (forced)', () { - expect( - _contact(pathOverride: 0).pathLabel(l10n), - 'Direct (forced)', - ); + expect(_contact(pathOverride: 0).pathLabel(l10n), 'Direct (forced)'); }); test('override > 0 -> hops (forced)', () { - expect( - _contact(pathOverride: 3).pathLabel(l10n), - '3 hops (forced)', - ); + expect(_contact(pathOverride: 3).pathLabel(l10n), '3 hops (forced)'); }); test('override takes precedence over pathLength', () { diff --git a/untranslated.json b/untranslated.json index ec563845..fe697333 100644 --- a/untranslated.json +++ b/untranslated.json @@ -54,11 +54,6 @@ "chat_newMessages" ], - "ru": [ - "chat_markAsUnread", - "chat_newMessages" - ], - "sk": [ "chat_markAsUnread", "chat_newMessages" diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index f02857f4..533a1712 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST flserial flutter_local_notifications_windows + jni ) set(PLUGIN_BUNDLED_LIBRARIES) From 1947cd9f3e5ab5da915dd948880b47cdda03e614 Mon Sep 17 00:00:00 2001 From: HDDen <62592944+HDDen@users.noreply.github.com> Date: Fri, 1 May 2026 01:42:02 +0300 Subject: [PATCH 5/6] sync fix --- linux/flutter/generated_plugins.cmake | 1 - windows/flutter/generated_plugins.cmake | 1 - 2 files changed, 2 deletions(-) diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 93e46829..379e36fa 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -8,7 +8,6 @@ list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST flserial - jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 533a1712..f02857f4 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -11,7 +11,6 @@ list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST flserial flutter_local_notifications_windows - jni ) set(PLUGIN_BUNDLED_LIBRARIES) From 75b0d198bc5e03be830af6c0d23d77ab0165ee74 Mon Sep 17 00:00:00 2001 From: HDDen <62592944+HDDen@users.noreply.github.com> Date: Fri, 1 May 2026 01:50:06 +0300 Subject: [PATCH 6/6] Update translation_service.dart --- lib/services/translation_service.dart | 40 ++++++++++----------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/lib/services/translation_service.dart b/lib/services/translation_service.dart index 294a7848..46cf7ea8 100644 --- a/lib/services/translation_service.dart +++ b/lib/services/translation_service.dart @@ -537,39 +537,27 @@ class TranslationService extends ChangeNotifier { final lower = trimmed.toLowerCase(); final patterns = { 'uk': r'\b(привіт|дякую|будь|ласка|як|де|не|так|це|є|най|ще|може|для)\b', - 'ru': - r'\b(что|это|как|не|да|нет|он|она|они|быть|есть|для|сегодня|если|уже|может)\b', + 'ru': r'\b(что|это|как|не|да|нет|он|она|они|быть|есть|для|сегодня|если|уже|может)\b', 'bg': r'\b(ще|няма|благодаря|моля|това|какво|тук|ние|вие|не|със|за)\b', - 'de': - r'\b(der|die|das|und|ist|nicht|ein|eine|ich|für|mit|auf|zu|auch|als|an|im|am|es|dem|den|sich|von)\b', - 'en': - r'\b(the|and|is|you|for|with|from|not|that|this|have|be|are|was|were|but|can|will|your|what|when|how|they)\b', - 'es': - r'\b(el|la|los|las|es|que|de|en|con|por|para|no|un|una|se|como|su|al|del|está)\b', - 'fr': - r'\b(le|la|les|un|une|et|est|que|qui|pour|dans|pas|avec|sur|ne|vous|il|elle|des|ce|cette|je|tu|nous|vous)\b', - 'it': - r'\b(il|la|lo|un|una|che|di|da|in|per|con|non|si|mi|ti|noi|voi|lui|lei)\b', - 'pt': - r'\b(os|as|que|de|do|da|em|para|com|por|não|uma|um|se|você|também)\b', - 'nl': - r'\b(de|het|een|en|is|niet|dat|wat|je|ik|op|aan|voor|met|als|nog|zijn)\b', - 'sv': - r'\b(och|är|det|att|som|en|på|inte|har|var|men|du|jag|vi|ni|den|detta)\b', - 'pl': - r'\b(na|się|nie|jest|to|że|do|od|dla|czy|tak|ale|ma|jak|on|ona|my)\b', + 'de': r'\b(der|die|das|und|ist|nicht|ein|eine|ich|für|mit|auf|zu|auch|als|an|im|am|es|dem|den|sich|von)\b', + 'en': r'\b(the|and|is|you|for|with|from|not|that|this|have|be|are|was|were|but|can|will|your|what|when|how|they)\b', + 'es': r'\b(el|la|los|las|es|que|de|en|con|por|para|no|un|una|se|como|su|al|del|está)\b', + 'fr': r'\b(le|la|les|un|une|et|est|que|qui|pour|dans|pas|avec|sur|ne|vous|il|elle|des|ce|cette|je|tu|nous|vous)\b', + 'it': r'\b(il|la|lo|un|una|che|di|da|in|per|con|non|si|mi|ti|noi|voi|lui|lei)\b', + 'pt': r'\b(os|as|que|de|do|da|em|para|com|por|não|uma|um|se|você|também)\b', + 'nl': r'\b(de|het|een|en|is|niet|dat|wat|je|ik|op|aan|voor|met|als|nog|zijn)\b', + 'sv': r'\b(och|är|det|att|som|en|på|inte|har|var|men|du|jag|vi|ni|den|detta)\b', + 'pl': r'\b(na|się|nie|jest|to|że|do|od|dla|czy|tak|ale|ma|jak|on|ona|my)\b', 'sk': r'\b(je|na|so|že|do|od|za|si|to|ten|tá|tí|ako|má|nie|som|sa)\b', 'sl': r'\b(in|je|na|se|da|za|od|ne|to|ta|so|kako|bo|sem|si)\b', - 'hu': - r'\b(az|és|nem|van|volt|hogy|mit|mire|ki|mi|ez|azért|is|de|ha|te|ő|mi|itt)\b', + 'hu': r'\b(az|és|nem|van|volt|hogy|mit|mire|ki|mi|ez|azért|is|de|ha|te|ő|mi|itt)\b', }; final scores = {}; for (final entry in patterns.entries) { - scores[entry.key] = RegExp( - entry.value, - caseSensitive: false, - ).allMatches(lower).length; + scores[entry.key] = RegExp(entry.value, caseSensitive: false) + .allMatches(lower) + .length; } final sorted = scores.entries.toList()