diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 5f28d62..171031d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -2755,5 +2755,129 @@ "batteryOptimizationOpenFailed": "Couldn't open battery settings", "@batteryOptimizationOpenFailed": { "description": "Snackbar shown when the battery-optimization settings screen could not be opened (#443)." + }, + "stockConfig_selectAll": "Select All", + "@stockConfig_selectAll": { + "description": "Button selecting every section on the stock config export/import screens (#568)." + }, + "stockConfig_deselectAll": "Deselect All", + "@stockConfig_deselectAll": { + "description": "Button clearing every section on the stock config export/import screens (#568)." + }, + "stockConfig_sectionName": "Name", + "@stockConfig_sectionName": { + "description": "Config section holding the device's display name (#568)." + }, + "stockConfig_sectionIdentity": "Private Identity Key", + "@stockConfig_sectionIdentity": { + "description": "Config section holding the node's key pair (#568)." + }, + "stockConfig_sectionRadio": "Radio Settings", + "@stockConfig_sectionRadio": { + "description": "Config section holding LoRa radio parameters (#568)." + }, + "stockConfig_sectionPosition": "Position Settings", + "@stockConfig_sectionPosition": { + "description": "Config section holding the device's advertised position (#568)." + }, + "stockConfig_sectionOther": "Other Settings", + "@stockConfig_sectionOther": { + "description": "Config section holding miscellaneous device preferences (#568)." + }, + "stockConfig_sectionAutoAdd": "Auto Add Settings", + "@stockConfig_sectionAutoAdd": { + "description": "Config section holding automatic contact-add preferences (#568)." + }, + "stockConfig_sectionChannels": "Channels ({count})", + "@stockConfig_sectionChannels": { + "description": "Config section holding channels, with how many there are (#568).", + "placeholders": { "count": { "type": "int" } } + }, + "stockConfig_sectionContacts": "Contacts ({count})", + "@stockConfig_sectionContacts": { + "description": "Config section holding contacts, with how many there are (#568).", + "placeholders": { "count": { "type": "int" } } + }, + "stockConfig_identityWarning": "Your private key must be kept secret. It should only be exported as a backup.", + "@stockConfig_identityWarning": { + "description": "Warning shown beside the identity section when exporting (#568)." + }, + "stockConfig_publicKeyLabel": "Public Key: {key}", + "@stockConfig_publicKeyLabel": { + "description": "Abbreviated public key shown under the identity section (#568).", + "placeholders": { "key": { "type": "String" } } + }, + "stockConfig_privateKeyHidden": "Private Key: hidden", + "@stockConfig_privateKeyHidden": { + "description": "Placeholder shown instead of the private key, which is never displayed (#568)." + }, + "exportConfig_title": "Export Config", + "@exportConfig_title": { + "description": "Title of the stock config export screen (#574)." + }, + "exportConfig_instruction": "Please select data to export.", + "@exportConfig_instruction": { + "description": "Banner instructing the user to pick sections to export (#574)." + }, + "exportConfig_allChannels": "All channels will be exported.", + "@exportConfig_allChannels": { + "description": "Note that channels export as a whole, with no per-channel choice (#574)." + }, + "exportConfig_allContacts": "All contacts will be exported.", + "@exportConfig_allContacts": { + "description": "Note that contacts export as a whole, with no per-contact choice (#574)." + }, + "exportConfig_nothingSelected": "Select at least one section to export.", + "@exportConfig_nothingSelected": { + "description": "Shown when the user tries to export with every section unchecked (#574)." + }, + "exportConfig_lossyNotice": "This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.", + "@exportConfig_lossyNotice": { + "description": "Explains that an export drops Offband-only contact fields (#574)." + }, + "exportConfig_omittedTitle": "Some sections could not be exported", + "@exportConfig_omittedTitle": { + "description": "Title of the dialog listing sections that could not be gathered (#574)." + }, + "exportConfig_omittedUnsupported": "{section}: this radio's firmware was built without it.", + "@exportConfig_omittedUnsupported": { + "description": "Reason line when firmware lacks the feature entirely, so retrying cannot help (#574).", + "placeholders": { "section": { "type": "String" } } + }, + "exportConfig_omittedNoReply": "{section}: the device did not respond.", + "@exportConfig_omittedNoReply": { + "description": "Reason line when the device did not answer in time (#574).", + "placeholders": { "section": { "type": "String" } } + }, + "exportConfig_omittedRejected": "{section}: the device refused the request.", + "@exportConfig_omittedRejected": { + "description": "Reason line when the device refused the request (#574).", + "placeholders": { "section": { "type": "String" } } + }, + "exportConfig_omittedUnavailable": "{section}: the device has not reported this yet.", + "@exportConfig_omittedUnavailable": { + "description": "Reason line when the value is not known to the app yet (#574).", + "placeholders": { "section": { "type": "String" } } + }, + "exportConfig_exportAnyway": "Export anyway", + "@exportConfig_exportAnyway": { + "description": "Confirms writing the file even though some sections are missing (#574)." + }, + "exportConfig_cancel": "Cancel", + "@exportConfig_cancel": { + "description": "Dismisses the export without writing a file (#574)." + }, + "exportConfig_shareSubject": "MeshCore config", + "@exportConfig_shareSubject": { + "description": "Subject line used when sharing an exported config file (#574)." + }, + "exportConfig_failed": "Export failed: {message}", + "@exportConfig_failed": { + "description": "Snackbar shown when writing the export file failed (#574).", + "placeholders": { "message": { "type": "String" } } + }, + "exportConfig_notConnected": "Connect to a device before exporting.", + "@exportConfig_notConnected": { + "description": "Shown when the export screen has no connected radio to read from (#574)." } } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 05661a2..bdc06f9 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -8247,6 +8247,180 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Couldn\'t open battery settings'** String get batteryOptimizationOpenFailed; + + /// Button selecting every section on the stock config export/import screens (#568). + /// + /// In en, this message translates to: + /// **'Select All'** + String get stockConfig_selectAll; + + /// Button clearing every section on the stock config export/import screens (#568). + /// + /// In en, this message translates to: + /// **'Deselect All'** + String get stockConfig_deselectAll; + + /// Config section holding the device's display name (#568). + /// + /// In en, this message translates to: + /// **'Name'** + String get stockConfig_sectionName; + + /// Config section holding the node's key pair (#568). + /// + /// In en, this message translates to: + /// **'Private Identity Key'** + String get stockConfig_sectionIdentity; + + /// Config section holding LoRa radio parameters (#568). + /// + /// In en, this message translates to: + /// **'Radio Settings'** + String get stockConfig_sectionRadio; + + /// Config section holding the device's advertised position (#568). + /// + /// In en, this message translates to: + /// **'Position Settings'** + String get stockConfig_sectionPosition; + + /// Config section holding miscellaneous device preferences (#568). + /// + /// In en, this message translates to: + /// **'Other Settings'** + String get stockConfig_sectionOther; + + /// Config section holding automatic contact-add preferences (#568). + /// + /// In en, this message translates to: + /// **'Auto Add Settings'** + String get stockConfig_sectionAutoAdd; + + /// Config section holding channels, with how many there are (#568). + /// + /// In en, this message translates to: + /// **'Channels ({count})'** + String stockConfig_sectionChannels(int count); + + /// Config section holding contacts, with how many there are (#568). + /// + /// In en, this message translates to: + /// **'Contacts ({count})'** + String stockConfig_sectionContacts(int count); + + /// Warning shown beside the identity section when exporting (#568). + /// + /// In en, this message translates to: + /// **'Your private key must be kept secret. It should only be exported as a backup.'** + String get stockConfig_identityWarning; + + /// Abbreviated public key shown under the identity section (#568). + /// + /// In en, this message translates to: + /// **'Public Key: {key}'** + String stockConfig_publicKeyLabel(String key); + + /// Placeholder shown instead of the private key, which is never displayed (#568). + /// + /// In en, this message translates to: + /// **'Private Key: hidden'** + String get stockConfig_privateKeyHidden; + + /// Title of the stock config export screen (#574). + /// + /// In en, this message translates to: + /// **'Export Config'** + String get exportConfig_title; + + /// Banner instructing the user to pick sections to export (#574). + /// + /// In en, this message translates to: + /// **'Please select data to export.'** + String get exportConfig_instruction; + + /// Note that channels export as a whole, with no per-channel choice (#574). + /// + /// In en, this message translates to: + /// **'All channels will be exported.'** + String get exportConfig_allChannels; + + /// Note that contacts export as a whole, with no per-contact choice (#574). + /// + /// In en, this message translates to: + /// **'All contacts will be exported.'** + String get exportConfig_allContacts; + + /// Shown when the user tries to export with every section unchecked (#574). + /// + /// In en, this message translates to: + /// **'Select at least one section to export.'** + String get exportConfig_nothingSelected; + + /// Explains that an export drops Offband-only contact fields (#574). + /// + /// In en, this message translates to: + /// **'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'** + String get exportConfig_lossyNotice; + + /// Title of the dialog listing sections that could not be gathered (#574). + /// + /// In en, this message translates to: + /// **'Some sections could not be exported'** + String get exportConfig_omittedTitle; + + /// Reason line when firmware lacks the feature entirely, so retrying cannot help (#574). + /// + /// In en, this message translates to: + /// **'{section}: this radio\'s firmware was built without it.'** + String exportConfig_omittedUnsupported(String section); + + /// Reason line when the device did not answer in time (#574). + /// + /// In en, this message translates to: + /// **'{section}: the device did not respond.'** + String exportConfig_omittedNoReply(String section); + + /// Reason line when the device refused the request (#574). + /// + /// In en, this message translates to: + /// **'{section}: the device refused the request.'** + String exportConfig_omittedRejected(String section); + + /// Reason line when the value is not known to the app yet (#574). + /// + /// In en, this message translates to: + /// **'{section}: the device has not reported this yet.'** + String exportConfig_omittedUnavailable(String section); + + /// Confirms writing the file even though some sections are missing (#574). + /// + /// In en, this message translates to: + /// **'Export anyway'** + String get exportConfig_exportAnyway; + + /// Dismisses the export without writing a file (#574). + /// + /// In en, this message translates to: + /// **'Cancel'** + String get exportConfig_cancel; + + /// Subject line used when sharing an exported config file (#574). + /// + /// In en, this message translates to: + /// **'MeshCore config'** + String get exportConfig_shareSubject; + + /// Snackbar shown when writing the export file failed (#574). + /// + /// In en, this message translates to: + /// **'Export failed: {message}'** + String exportConfig_failed(String message); + + /// Shown when the export screen has no connected radio to read from (#574). + /// + /// In en, this message translates to: + /// **'Connect to a device before exporting.'** + String get exportConfig_notConnected; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 9a60600..b2fad61 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -4841,4 +4841,111 @@ class AppLocalizationsBg extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 43a3de2..bf17cd3 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -4858,4 +4858,111 @@ class AppLocalizationsDe extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 9426ce1..326ec5d 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -4764,4 +4764,111 @@ class AppLocalizationsEn extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index c3127f0..1b76ef3 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -4846,4 +4846,111 @@ class AppLocalizationsEs extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 7d06f98..9f30a7b 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -4874,4 +4874,111 @@ class AppLocalizationsFr extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 3c72de8..21f420d 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -4863,4 +4863,111 @@ class AppLocalizationsHu extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index e8ba848..a4b8de1 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -4851,4 +4851,111 @@ class AppLocalizationsIt extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 6350be7..195af03 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -4617,4 +4617,111 @@ class AppLocalizationsJa extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 3cb99bb..5b76bf2 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -4618,4 +4618,111 @@ class AppLocalizationsKo extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 10e2bb6..20bd6fd 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -4826,4 +4826,111 @@ class AppLocalizationsNl extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index b2a33a7..8cbd6ee 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -4863,4 +4863,111 @@ class AppLocalizationsPl extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 8fbf97f..c4c0578 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -4839,4 +4839,111 @@ class AppLocalizationsPt extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 95040df..5ef3c3b 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -4857,4 +4857,111 @@ class AppLocalizationsRu extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index d896b0b..f1bf528 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -4823,4 +4823,111 @@ class AppLocalizationsSk extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index b1996fd..34e3dad 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -4821,4 +4821,111 @@ class AppLocalizationsSl extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index f118c64..4f092ef 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -4796,4 +4796,111 @@ class AppLocalizationsSv extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 2043089..55047d8 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -4858,4 +4858,111 @@ class AppLocalizationsUk extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 610a991..5bb31a0 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -4491,4 +4491,111 @@ class AppLocalizationsZh extends AppLocalizations { @override String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; + + @override + String get stockConfig_selectAll => 'Select All'; + + @override + String get stockConfig_deselectAll => 'Deselect All'; + + @override + String get stockConfig_sectionName => 'Name'; + + @override + String get stockConfig_sectionIdentity => 'Private Identity Key'; + + @override + String get stockConfig_sectionRadio => 'Radio Settings'; + + @override + String get stockConfig_sectionPosition => 'Position Settings'; + + @override + String get stockConfig_sectionOther => 'Other Settings'; + + @override + String get stockConfig_sectionAutoAdd => 'Auto Add Settings'; + + @override + String stockConfig_sectionChannels(int count) { + return 'Channels ($count)'; + } + + @override + String stockConfig_sectionContacts(int count) { + return 'Contacts ($count)'; + } + + @override + String get stockConfig_identityWarning => + 'Your private key must be kept secret. It should only be exported as a backup.'; + + @override + String stockConfig_publicKeyLabel(String key) { + return 'Public Key: $key'; + } + + @override + String get stockConfig_privateKeyHidden => 'Private Key: hidden'; + + @override + String get exportConfig_title => 'Export Config'; + + @override + String get exportConfig_instruction => 'Please select data to export.'; + + @override + String get exportConfig_allChannels => 'All channels will be exported.'; + + @override + String get exportConfig_allContacts => 'All contacts will be exported.'; + + @override + String get exportConfig_nothingSelected => + 'Select at least one section to export.'; + + @override + String get exportConfig_lossyNotice => + 'This is the MeshCore stock format, so it cannot carry Offband-only data such as path overrides. Importing this file back into Offband will not restore that data.'; + + @override + String get exportConfig_omittedTitle => 'Some sections could not be exported'; + + @override + String exportConfig_omittedUnsupported(String section) { + return '$section: this radio\'s firmware was built without it.'; + } + + @override + String exportConfig_omittedNoReply(String section) { + return '$section: the device did not respond.'; + } + + @override + String exportConfig_omittedRejected(String section) { + return '$section: the device refused the request.'; + } + + @override + String exportConfig_omittedUnavailable(String section) { + return '$section: the device has not reported this yet.'; + } + + @override + String get exportConfig_exportAnyway => 'Export anyway'; + + @override + String get exportConfig_cancel => 'Cancel'; + + @override + String get exportConfig_shareSubject => 'MeshCore config'; + + @override + String exportConfig_failed(String message) { + return 'Export failed: $message'; + } + + @override + String get exportConfig_notConnected => + 'Connect to a device before exporting.'; } diff --git a/lib/screens/export_config_screen.dart b/lib/screens/export_config_screen.dart new file mode 100644 index 0000000..b8c6662 --- /dev/null +++ b/lib/screens/export_config_screen.dart @@ -0,0 +1,310 @@ +/// Stock-compatible config export (#574, epic #568). +/// +/// Mirrors the stock app's Export Config screen: a list of sections, Select All +/// and Deselect All, and **everything checked by default**. The asymmetry with +/// the import screen (which starts with nothing checked) is deliberate and +/// copied from stock: opt out on the way out, opt in on the way in. +library; + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../connector/meshcore_connector.dart'; +import '../l10n/l10n.dart'; +import '../services/stock_config_export_service.dart'; +import '../utils/stock_config_file.dart'; +import '../widgets/adaptive_app_bar_title.dart'; + +class ExportConfigScreen extends StatefulWidget { + const ExportConfigScreen({super.key}); + + @override + State createState() => _ExportConfigScreenState(); +} + +class _ExportConfigScreenState extends State { + /// Every section starts selected, matching stock. + final Set _selected = StockConfigSection.values.toSet(); + bool _busy = false; + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final connector = context.watch(); + + return Scaffold( + appBar: AppBar( + title: AdaptiveAppBarTitle(l10n.exportConfig_title), + centerTitle: true, + actions: [ + IconButton( + icon: const Icon(Icons.check), + onPressed: _busy ? null : () => _export(connector), + tooltip: l10n.exportConfig_title, + ), + ], + ), + body: ListView( + children: [ + _Banner(text: l10n.exportConfig_instruction), + _Banner(text: l10n.exportConfig_lossyNotice, subdued: true), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + onPressed: () => + setState(() => _selected.addAll(StockConfigSection.values)), + child: Text(l10n.stockConfig_selectAll), + ), + TextButton( + onPressed: () => setState(_selected.clear), + child: Text(l10n.stockConfig_deselectAll), + ), + ], + ), + for (final section in StockConfigSection.values) + _SectionTile( + title: sectionLabel(context, section, connector), + subtitle: _subtitleFor(section, connector), + selected: _selected.contains(section), + onChanged: (value) => setState(() { + if (value) { + _selected.add(section); + } else { + _selected.remove(section); + } + }), + ), + ], + ), + ); + } + + String? _subtitleFor( + StockConfigSection section, + MeshCoreConnector connector, + ) { + final l10n = context.l10n; + switch (section) { + case StockConfigSection.name: + return connector.selfName; + case StockConfigSection.identity: + final key = connector.selfPublicKeyHex; + final abbreviated = key.length > 12 + ? '${key.substring(0, 6)}...${key.substring(key.length - 6)}' + : key; + // The private key is never rendered, not even abbreviated. + return '${l10n.stockConfig_identityWarning}\n' + '${l10n.stockConfig_publicKeyLabel(abbreviated)}\n' + '${l10n.stockConfig_privateKeyHidden}'; + case StockConfigSection.radioSettings: + final freq = connector.currentFreqHz; + final bw = connector.currentBwHz; + if (freq == null || bw == null) return null; + // currentFreqHz is kHz despite its name; currentBwHz really is Hz. + return '${l10n.settings_frequency}: ${freq / 1000} MHz\n' + '${l10n.settings_bandwidth}: ${bw / 1000} kHz\n' + '${l10n.settings_spreadingFactor}: ${connector.currentSf}\n' + '${l10n.settings_codingRate}: ${connector.currentCr}\n' + '${l10n.settings_txPower}: ${connector.currentTxPower}'; + case StockConfigSection.positionSettings: + return '${connector.selfLatitude ?? 0}, ${connector.selfLongitude ?? 0}'; + case StockConfigSection.otherSettings: + return null; + case StockConfigSection.autoAddSettings: + return null; + case StockConfigSection.channels: + return l10n.exportConfig_allChannels; + case StockConfigSection.contacts: + return l10n.exportConfig_allContacts; + } + } + + Future _export(MeshCoreConnector connector) async { + final l10n = context.l10n; + final messenger = ScaffoldMessenger.of(context); + + if (!connector.isConnected) { + messenger.showSnackBar( + SnackBar(content: Text(l10n.exportConfig_notConnected)), + ); + return; + } + if (_selected.isEmpty) { + messenger.showSnackBar( + SnackBar(content: Text(l10n.exportConfig_nothingSelected)), + ); + return; + } + + setState(() => _busy = true); + try { + final result = await StockConfigExportService( + connector, + ).build(sections: Set.of(_selected)); + + // A section the user asked for that could not be gathered is surfaced + // before the file is written, never dropped quietly. + if (!result.isComplete) { + if (!mounted) return; + final proceed = await _confirmOmissions(result); + if (proceed != true) return; + } + if (!mounted) return; + + await saveStockConfigFile( + context, + json: result.config.encode(), + fileName: stockConfigFileName(connector.selfName, DateTime.now()), + subject: l10n.exportConfig_shareSubject, + ); + } catch (e) { + messenger.showSnackBar( + SnackBar(content: Text(l10n.exportConfig_failed('$e'))), + ); + } finally { + if (mounted) setState(() => _busy = false); + } + } + + Future _confirmOmissions(StockConfigExportResult result) { + final l10n = context.l10n; + final connector = context.read(); + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(l10n.exportConfig_omittedTitle), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (final entry in result.omitted.entries) + Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Text( + _omissionText( + context, + sectionLabel(context, entry.key, connector), + entry.value, + ), + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text(l10n.exportConfig_cancel), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text(l10n.exportConfig_exportAnyway), + ), + ], + ), + ); + } +} + +String _omissionText( + BuildContext context, + String section, + StockConfigOmission reason, +) { + final l10n = context.l10n; + switch (reason) { + case StockConfigOmission.unsupported: + return l10n.exportConfig_omittedUnsupported(section); + case StockConfigOmission.noReply: + return l10n.exportConfig_omittedNoReply(section); + case StockConfigOmission.rejected: + return l10n.exportConfig_omittedRejected(section); + case StockConfigOmission.unavailable: + return l10n.exportConfig_omittedUnavailable(section); + } +} + +/// Section titles, shared with the import screen so the two cannot drift. +String sectionLabel( + BuildContext context, + StockConfigSection section, + MeshCoreConnector connector, +) { + final l10n = context.l10n; + switch (section) { + case StockConfigSection.name: + return l10n.stockConfig_sectionName; + case StockConfigSection.identity: + return l10n.stockConfig_sectionIdentity; + case StockConfigSection.radioSettings: + return l10n.stockConfig_sectionRadio; + case StockConfigSection.positionSettings: + return l10n.stockConfig_sectionPosition; + case StockConfigSection.otherSettings: + return l10n.stockConfig_sectionOther; + case StockConfigSection.autoAddSettings: + return l10n.stockConfig_sectionAutoAdd; + case StockConfigSection.channels: + return l10n.stockConfig_sectionChannels( + connector.channels.where((c) => !c.isEmpty).length, + ); + case StockConfigSection.contacts: + return l10n.stockConfig_sectionContacts(connector.contacts.length); + } +} + +class _SectionTile extends StatelessWidget { + const _SectionTile({ + required this.title, + required this.selected, + required this.onChanged, + this.subtitle, + }); + + final String title; + final String? subtitle; + final bool selected; + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + return CheckboxListTile( + value: selected, + onChanged: (value) => onChanged(value ?? false), + controlAffinity: ListTileControlAffinity.trailing, + isThreeLine: subtitle != null, + title: Text(title), + subtitle: subtitle == null ? null : Text(subtitle!), + ); + } +} + +class _Banner extends StatelessWidget { + const _Banner({required this.text, this.subdued = false}); + + final String text; + final bool subdued; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Container( + width: double.infinity, + color: theme.colorScheme.surfaceContainerHighest, + padding: const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.info_outline, size: 18, color: theme.colorScheme.primary), + const SizedBox(width: 8), + Expanded( + child: Text( + text, + style: subdued ? theme.textTheme.bodySmall : null, + ), + ), + ], + ), + ); + } +} diff --git a/lib/utils/stock_config_file.dart b/lib/utils/stock_config_file.dart new file mode 100644 index 0000000..b21d887 --- /dev/null +++ b/lib/utils/stock_config_file.dart @@ -0,0 +1,70 @@ +/// Naming and writing of stock-compatible config files (#574, epic #568). +library; + +import 'dart:io' show File; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:path_provider/path_provider.dart'; + +import 'log_export.dart'; +import 'log_web_download.dart'; + +/// Characters Windows forbids in a file name, plus the path separators. +final _illegalFileNameChars = RegExp(r'[<>:"/\\|?*\x00-\x1F]'); + +/// Builds the file name stock uses: `_meshcore_config_`. +/// +/// Real device names contain emoji and, in at least one observed case, a +/// trailing space, so the name is sanitized for the filesystem here. The name +/// written *inside* the file is never touched: it must round-trip exactly. +String stockConfigFileName(String? deviceName, DateTime at) { + final safeName = _sanitizeForFileName(deviceName ?? ''); + final stamp = [ + at.year.toString().padLeft(4, '0'), + at.month.toString().padLeft(2, '0'), + at.day.toString().padLeft(2, '0'), + ].join('-'); + final time = [ + at.hour.toString().padLeft(2, '0'), + at.minute.toString().padLeft(2, '0'), + at.second.toString().padLeft(2, '0'), + ].join(); + final prefix = safeName.isEmpty ? '' : '${safeName}_'; + return '${prefix}meshcore_config_$stamp-$time.json'; +} + +String _sanitizeForFileName(String name) { + final cleaned = name.replaceAll(_illegalFileNameChars, ''); + // Windows also rejects names ending in a space or a dot. + return cleaned.replaceAll(RegExp(r'[ .]+$'), '').trim(); +} + +/// Writes [json] out as [fileName], using each platform's normal mechanism: +/// the share sheet on mobile, a Save As dialog on desktop, a browser download +/// on web. Reuses [LogExport], which already owns that per-platform logic, so +/// there is only one place where export behavior can drift. +Future saveStockConfigFile( + BuildContext context, { + required String json, + required String fileName, + String? subject, +}) async { + if (kIsWeb) { + downloadTextFile(fileName, json); + return; + } + // A temp file is the handoff format LogExport works in. It holds the node's + // private key when the identity section was included, so it is written under + // the app's own temp directory and never anywhere shared. + final dir = await getTemporaryDirectory(); + final file = File('${dir.path}/$fileName'); + await file.writeAsString(json, flush: true); + if (!context.mounted) return; + await LogExport.shareFile( + context, + file, + subject: subject, + fileName: fileName, + ); +} diff --git a/test/utils/stock_config_file_test.dart b/test/utils/stock_config_file_test.dart new file mode 100644 index 0000000..ad798a0 --- /dev/null +++ b/test/utils/stock_config_file_test.dart @@ -0,0 +1,56 @@ +// #574 (epic #568): the export file name has to match stock's convention and +// survive real device names, which contain emoji and trailing spaces. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:meshcore_open/utils/stock_config_file.dart'; + +void main() { + final at = DateTime(2026, 8, 12, 22, 16, 25); + + test('matches stock: name, then meshcore_config, then a stamp', () { + expect( + stockConfigFileName('Strycher-RAK-CAR', at), + 'Strycher-RAK-CAR_meshcore_config_2026-08-12-221625.json', + ); + }); + + test('single-digit months, days and times are zero padded', () { + expect( + stockConfigFileName('Node', DateTime(2026, 1, 2, 3, 4, 5)), + 'Node_meshcore_config_2026-01-02-030405.json', + ); + }); + + test('emoji in a device name are kept, as stock keeps them', () { + expect( + stockConfigFileName('Strycher T1000\u{1F6F0}️', at), + startsWith('Strycher T1000\u{1F6F0}️_meshcore_config_'), + ); + }); + + test('a trailing space is dropped, since Windows rejects it', () { + // Observed in a real export: the device name ended in a space. + expect( + stockConfigFileName('Strycher-Wio-L1 ', at), + 'Strycher-Wio-L1_meshcore_config_2026-08-12-221625.json', + ); + }); + + test('path separators and reserved characters are stripped', () { + expect( + stockConfigFileName(r'a/b\c:d*e?f"gi|j', at), + 'abcdefghij_meshcore_config_2026-08-12-221625.json', + ); + }); + + test('a device with no name still produces a usable file name', () { + expect( + stockConfigFileName(null, at), + 'meshcore_config_2026-08-12-221625.json', + ); + expect( + stockConfigFileName(' ', at), + 'meshcore_config_2026-08-12-221625.json', + ); + }); +} diff --git a/untranslated.json b/untranslated.json index 79eeb12..fc035b7 100644 --- a/untranslated.json +++ b/untranslated.json @@ -63,6 +63,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -104,7 +109,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "de": [ @@ -171,6 +205,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -212,7 +251,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "es": [ @@ -279,6 +347,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -320,7 +393,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "fr": [ @@ -387,6 +489,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -428,7 +535,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "hu": [ @@ -495,6 +631,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -536,7 +677,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "it": [ @@ -603,6 +773,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -644,7 +819,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "ja": [ @@ -711,6 +915,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -752,7 +961,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "ko": [ @@ -819,6 +1057,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -860,7 +1103,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "nl": [ @@ -927,6 +1199,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -968,7 +1245,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "pl": [ @@ -1035,6 +1341,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1076,7 +1387,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "pt": [ @@ -1143,6 +1483,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1184,7 +1529,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "ru": [ @@ -1251,6 +1625,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1292,7 +1671,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "sk": [ @@ -1359,6 +1767,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1400,7 +1813,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "sl": [ @@ -1467,6 +1909,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1508,7 +1955,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "sv": [ @@ -1575,6 +2051,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1616,7 +2097,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "uk": [ @@ -1683,6 +2193,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1724,7 +2239,36 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ], "zh": [ @@ -1791,6 +2335,11 @@ "map_alwaysShowNames", "path_suggestedRoute", "path_searchRepeaters", + "repeater_cliCommandTimeout", + "repeater_cliLateResponse", + "repeater_cliUnmatchedResponse", + "repeater_lateResponseDiscarded", + "repeater_lateResponseReceived", "channelPath_routeDirect", "channelPath_routeFlood", "channelPath_snrLabel", @@ -1832,6 +2381,35 @@ "batteryOptimizationBody", "batteryOptimizationOpenSettings", "batteryOptimizationDismiss", - "batteryOptimizationOpenFailed" + "batteryOptimizationOpenFailed", + "stockConfig_selectAll", + "stockConfig_deselectAll", + "stockConfig_sectionName", + "stockConfig_sectionIdentity", + "stockConfig_sectionRadio", + "stockConfig_sectionPosition", + "stockConfig_sectionOther", + "stockConfig_sectionAutoAdd", + "stockConfig_sectionChannels", + "stockConfig_sectionContacts", + "stockConfig_identityWarning", + "stockConfig_publicKeyLabel", + "stockConfig_privateKeyHidden", + "exportConfig_title", + "exportConfig_instruction", + "exportConfig_allChannels", + "exportConfig_allContacts", + "exportConfig_nothingSelected", + "exportConfig_lossyNotice", + "exportConfig_omittedTitle", + "exportConfig_omittedUnsupported", + "exportConfig_omittedNoReply", + "exportConfig_omittedRejected", + "exportConfig_omittedUnavailable", + "exportConfig_exportAnyway", + "exportConfig_cancel", + "exportConfig_shareSubject", + "exportConfig_failed", + "exportConfig_notConnected" ] }