commit 1d9857abf0b672f7abd79ebc01c5dad1cfa1176b
parent 0c8c5faae96f808d30f9d666c7b4dfd63c9a8e75
Author: triesap <tyson@radroots.org>
Date: Tue, 21 Apr 2026 06:25:46 +0000
app: localize startup copy boundaries
- map startup notices and issue summaries through localized app text keys
- localize signer source and permission preview labels in the startup shell
- extend source guards and launcher tests for the keyed startup copy contract
- keep runtime fallback copy calm while preserving technical matcher literals
Diffstat:
5 files changed, 277 insertions(+), 34 deletions(-)
diff --git a/crates/launchers/desktop/src/source_guards.rs b/crates/launchers/desktop/src/source_guards.rs
@@ -27,7 +27,6 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[
"6.500",
"Salad mix",
"USD",
- "Untitled draft",
"/tmp/radroots/data/apps/app",
"/tmp/radroots/logs/apps/app",
"{}.{:02}",
@@ -59,10 +58,10 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[
"buyer.detail_open_failed",
"buyer.order_open_failed",
"buyer.repeat_demand_failed",
- "bunker uri",
"bunker://466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27?relay=wss%3A%2F%2Frelay.radroots.example",
"buyer.fulfillment_filter_update_failed",
"buyer.search_query_update_failed",
+ "desktop runtime roots require HOME for macos",
"failed to add buyer product to cart",
"failed to open buyer order detail",
"failed to place buyer order",
@@ -138,7 +137,6 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[
"today-reminder-chip",
"https://auth.example/challenge",
"identity",
- "none",
"npub1",
"guest",
"orders",
@@ -208,9 +206,20 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[
"products.search_query_update_failed",
"products.stock_update_failed",
"products.sort_update_failed",
+ "discovery url does not contain a remote signer uri",
+ "enter a bunker or discovery url to continue",
+ "enter a bunker or discovery url from the signer; raw nostrconnect client uris are signer-side only",
+ "invalid discovery url:",
+ "invalid discovery url: relative URL without a base",
+ "invalid remote signer uri:",
+ "invalid remote signer uri: invalid public key",
+ "a remote signer connection is already pending approval",
+ "raw nostrconnect client uris are signer-side only",
+ "remote signer",
"remote signer connection failed: relay refused the request",
"remote signer did not respond yet",
"runtime unavailable",
+ "sign_event:kind:1",
"shell",
"shell-account-entry",
"shell-account-label",
@@ -247,11 +256,12 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[
"settings.about.conflict_resolution_failed",
"failed to refresh sync from the about panel",
"failed to resolve sync conflict from the about panel",
- "sign_event:kind:1, switch_relays",
+ "switch_relays",
"startup-title-radroots",
"startup-title-starting",
"wss://relay.radroots.example",
"{currency_code} {dollars}.{cents:02}",
+ "{}, {}",
"{}: {}",
"{} {} {}.",
"{quantity} {unit_label}",
@@ -280,6 +290,20 @@ const REQUIRED_WINDOW_COPY_KEYS: &[&str] = &[
"AppTextKey::HomeSetupSignerPendingTitle",
"AppTextKey::HomeSetupSignerAuthChallengeTitle",
"AppTextKey::HomeSetupSignerApprovedTitle",
+ "AppTextKey::HomeSetupIssueUnavailableBody",
+ "AppTextKey::HomeSetupErrorStartupFailed",
+ "AppTextKey::HomeSetupSignerSourceValueBunkerUri",
+ "AppTextKey::HomeSetupSignerSourceValueDiscoveryUrl",
+ "AppTextKey::HomeSetupSignerPermissionSignEventKind1",
+ "AppTextKey::HomeSetupSignerPermissionSwitchRelays",
+ "AppTextKey::HomeSetupSignerPermissionAdditional",
+ "AppTextKey::HomeSetupSignerErrorEnterSource",
+ "AppTextKey::HomeSetupSignerErrorUseSignerUri",
+ "AppTextKey::HomeSetupSignerErrorMissingDiscoveryUri",
+ "AppTextKey::HomeSetupSignerErrorInvalidDiscoveryUrl",
+ "AppTextKey::HomeSetupSignerErrorInvalidRemoteSignerUri",
+ "AppTextKey::HomeSetupSignerErrorPendingApprovalExists",
+ "AppTextKey::HomeSetupSignerErrorConnectionFailed",
"AppTextKey::HomeFarmSetupOnboardingTitle",
"AppTextKey::HomeFarmSetupOnboardingBody",
"AppTextKey::HomeFarmSetupOnboardingAction",
diff --git a/crates/launchers/desktop/src/window.rs b/crates/launchers/desktop/src/window.rs
@@ -30,7 +30,8 @@ use radroots_app_models::{
};
use radroots_app_remote_signer::{
RadrootsAppRemoteSignerApprovedSession, RadrootsAppRemoteSignerPendingPollOutcome,
- RadrootsAppRemoteSignerPendingSession, radroots_app_remote_signer_connect_pending,
+ RadrootsAppRemoteSignerPendingSession, RadrootsAppRemoteSignerSource,
+ radroots_app_remote_signer_connect_pending,
radroots_app_remote_signer_poll_pending_session_with_progress,
radroots_app_remote_signer_preview, radroots_app_remote_signer_requested_permissions,
};
@@ -6881,7 +6882,8 @@ fn about_status_rows(runtime: &DesktopAppRuntimeSummary) -> Vec<LabelValueRow> {
app_shared_text(AppTextKey::MetadataStartupIssue),
runtime
.startup_issue
- .clone()
+ .as_deref()
+ .map(startup_issue_summary_text)
.unwrap_or_else(|| app_text(AppTextKey::ValueNone)),
));
@@ -8509,6 +8511,7 @@ fn startup_home_shell(
cx: &App,
) -> impl IntoElement {
let surface = startup_home_surface(runtime);
+ let startup_notice = startup_notice.map(startup_notice_text);
app_window_shell(
APP_UI_THEME.foundation.surfaces.window_background,
@@ -8552,7 +8555,7 @@ fn startup_home_shell(
on_browse_marketplace,
cx,
))
- .when_some(startup_notice, |this, error| {
+ .when_some(startup_notice, |this, error: String| {
this.child(
div()
.w_full()
@@ -8580,7 +8583,7 @@ fn startup_home_shell(
on_connect_signer,
cx,
))
- .when_some(startup_notice, |this, error| {
+ .when_some(startup_notice, |this, error: String| {
this.child(
div().w_full().text_center().child(
home_body_text(error.to_owned()),
@@ -8661,7 +8664,7 @@ fn startup_home_tagline() -> impl IntoElement {
fn startup_signer_entry_surface(
signer_entry: Option<&StartupSignerEntryState>,
connect_state: &StartupSignerConnectState,
- startup_notice: Option<&str>,
+ startup_notice: Option<String>,
on_submit_signer: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
on_back: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
cx: &App,
@@ -8676,7 +8679,10 @@ fn startup_signer_entry_surface(
{
None
} else {
- preview.as_ref().err().cloned()
+ preview
+ .as_ref()
+ .err()
+ .map(|error| startup_notice_text(error))
};
let submit_enabled =
preview.is_ok() && matches!(connect_state, StartupSignerConnectState::Idle);
@@ -8771,25 +8777,19 @@ fn startup_signer_entry_surface(
on_back,
cx,
))
- .when_some(startup_notice, |this, notice| {
- this.child(
- div()
- .w_full()
- .text_center()
- .child(home_body_text(notice.to_owned())),
- )
+ .when_some(startup_notice, |this, notice: String| {
+ this.child(div().w_full().text_center().child(home_body_text(notice)))
})
}
fn startup_signer_preview_summary(input: &str) -> Result<StartupSignerPreviewSummary, String> {
let target = radroots_app_remote_signer_preview(input).map_err(|error| error.to_string())?;
- let requested_permissions = target.requested_permission_labels();
Ok(StartupSignerPreviewSummary {
- source_label: target.source_label().to_owned(),
+ source_label: startup_signer_source_text(target.source),
signer_npub: target.signer_identity.public_key_npub.clone(),
relays_label: startup_signer_csv_or_none(target.relays.as_slice()),
- permissions_label: startup_signer_csv_or_none(requested_permissions.as_slice()),
+ permissions_label: startup_signer_permissions_label(target.requested_permission_labels()),
})
}
@@ -8844,7 +8844,7 @@ fn startup_signer_source_input_is_editable(connect_state: &StartupSignerConnectS
fn startup_signer_csv_or_none(values: &[String]) -> String {
if values.is_empty() {
- return "none".to_owned();
+ return app_text(AppTextKey::ValueNone);
}
values.join(", ")
@@ -8861,7 +8861,15 @@ fn startup_signer_requested_permissions_label() -> String {
}
fn startup_signer_permissions_label(permissions: Vec<String>) -> String {
- startup_signer_csv_or_none(permissions.as_slice())
+ if permissions.is_empty() {
+ return app_text(AppTextKey::ValueNone);
+ }
+
+ permissions
+ .into_iter()
+ .map(|permission| startup_signer_permission_text(permission.as_str()))
+ .collect::<Vec<_>>()
+ .join(", ")
}
fn startup_signer_status_spec(
@@ -8894,11 +8902,57 @@ fn startup_signer_transport_failure_requires_notice(message: &str) -> bool {
message != "remote signer did not respond yet"
}
+fn startup_issue_summary_text(_startup_issue: &str) -> String {
+ app_text(AppTextKey::HomeSetupIssueUnavailableBody)
+}
+
+fn startup_signer_source_text(source: RadrootsAppRemoteSignerSource) -> String {
+ app_text(match source {
+ RadrootsAppRemoteSignerSource::BunkerUri => AppTextKey::HomeSetupSignerSourceValueBunkerUri,
+ RadrootsAppRemoteSignerSource::DiscoveryUrl => {
+ AppTextKey::HomeSetupSignerSourceValueDiscoveryUrl
+ }
+ })
+}
+
+fn startup_signer_permission_text(permission: &str) -> String {
+ app_text(match permission {
+ "sign_event:kind:1" => AppTextKey::HomeSetupSignerPermissionSignEventKind1,
+ "switch_relays" => AppTextKey::HomeSetupSignerPermissionSwitchRelays,
+ _ => AppTextKey::HomeSetupSignerPermissionAdditional,
+ })
+}
+
+fn startup_notice_text(message: &str) -> String {
+ app_text(match message {
+ "enter a bunker or discovery url to continue" => {
+ AppTextKey::HomeSetupSignerErrorEnterSource
+ }
+ "discovery url does not contain a remote signer uri" => {
+ AppTextKey::HomeSetupSignerErrorMissingDiscoveryUri
+ }
+ "a remote signer connection is already pending approval" => {
+ AppTextKey::HomeSetupSignerErrorPendingApprovalExists
+ }
+ _ if message.contains("raw nostrconnect client uris are signer-side only") => {
+ AppTextKey::HomeSetupSignerErrorUseSignerUri
+ }
+ _ if message.starts_with("invalid discovery url:") => {
+ AppTextKey::HomeSetupSignerErrorInvalidDiscoveryUrl
+ }
+ _ if message.starts_with("invalid remote signer uri:") => {
+ AppTextKey::HomeSetupSignerErrorInvalidRemoteSignerUri
+ }
+ _ if message.contains("remote signer") => AppTextKey::HomeSetupSignerErrorConnectionFailed,
+ _ => AppTextKey::HomeSetupErrorStartupFailed,
+ })
+}
+
fn startup_home_body(runtime: &DesktopAppRuntimeSummary) -> impl IntoElement {
- let body = runtime
- .startup_issue
- .clone()
- .unwrap_or_else(|| app_shared_text(AppTextKey::HomeTodayEmptySetupBody).to_string());
+ let body = runtime.startup_issue.as_deref().map_or_else(
+ || app_shared_text(AppTextKey::HomeTodayEmptySetupBody).to_string(),
+ startup_issue_summary_text,
+ );
div().w_full().text_center().child(home_body_text(body))
}
@@ -11605,9 +11659,9 @@ mod tests {
presented_farmer_reminder, product_display_title, reminder_action_target,
reminder_deadline_text, reminder_delivery_state_key, reminder_urgency_color,
reminder_urgency_key, settings_auto_focus_target, startup_home_surface,
- startup_signer_preview_summary, startup_signer_preview_summary_for_connect_state,
- startup_signer_source_input_is_editable, startup_signer_status_spec,
- startup_signer_transport_failure_requires_notice,
+ startup_issue_summary_text, startup_notice_text, startup_signer_preview_summary,
+ startup_signer_preview_summary_for_connect_state, startup_signer_source_input_is_editable,
+ startup_signer_status_spec, startup_signer_transport_failure_requires_notice,
};
use crate::runtime::{
DesktopAppRuntimeMetadataSummary, DesktopAppRuntimeSummary, DesktopAppSyncConflictSummary,
@@ -12275,8 +12329,14 @@ mod tests {
#[test]
fn blank_product_titles_fall_back_to_the_untitled_copy() {
- assert_eq!(product_display_title(""), "Untitled draft");
- assert_eq!(product_display_title(" "), "Untitled draft");
+ assert_eq!(
+ product_display_title(""),
+ app_text(AppTextKey::ProductsUntitledDraft)
+ );
+ assert_eq!(
+ product_display_title(" "),
+ app_text(AppTextKey::ProductsUntitledDraft)
+ );
assert_eq!(product_display_title("Salad mix"), "Salad mix");
}
@@ -12287,12 +12347,19 @@ mod tests {
)
.expect("preview");
- assert_eq!(preview.source_label, "bunker uri");
+ assert_eq!(
+ preview.source_label,
+ app_text(AppTextKey::HomeSetupSignerSourceValueBunkerUri)
+ );
assert!(preview.signer_npub.starts_with("npub1"));
assert_eq!(preview.relays_label, "wss://relay.radroots.example");
assert_eq!(
preview.permissions_label,
- "sign_event:kind:1, switch_relays"
+ format!(
+ "{}, {}",
+ app_text(AppTextKey::HomeSetupSignerPermissionSignEventKind1),
+ app_text(AppTextKey::HomeSetupSignerPermissionSwitchRelays)
+ )
);
}
@@ -12389,7 +12456,11 @@ mod tests {
assert_eq!(preview.relays_label, "wss://relay.radroots.example");
assert_eq!(
preview.permissions_label,
- "sign_event:kind:1, switch_relays"
+ format!(
+ "{}, {}",
+ app_text(AppTextKey::HomeSetupSignerPermissionSignEventKind1),
+ app_text(AppTextKey::HomeSetupSignerPermissionSwitchRelays)
+ )
);
}
@@ -12404,6 +12475,56 @@ mod tests {
}
#[test]
+ fn startup_signer_notice_copy_maps_known_signer_failures() {
+ assert_eq!(
+ startup_notice_text("enter a bunker or discovery url to continue"),
+ app_text(AppTextKey::HomeSetupSignerErrorEnterSource)
+ );
+ assert_eq!(
+ startup_notice_text(
+ "enter a bunker or discovery url from the signer; raw nostrconnect client uris are signer-side only"
+ ),
+ app_text(AppTextKey::HomeSetupSignerErrorUseSignerUri)
+ );
+ assert_eq!(
+ startup_notice_text("discovery url does not contain a remote signer uri"),
+ app_text(AppTextKey::HomeSetupSignerErrorMissingDiscoveryUri)
+ );
+ assert_eq!(
+ startup_notice_text("invalid discovery url: relative URL without a base"),
+ app_text(AppTextKey::HomeSetupSignerErrorInvalidDiscoveryUrl)
+ );
+ assert_eq!(
+ startup_notice_text("invalid remote signer uri: invalid public key"),
+ app_text(AppTextKey::HomeSetupSignerErrorInvalidRemoteSignerUri)
+ );
+ assert_eq!(
+ startup_notice_text("a remote signer connection is already pending approval"),
+ app_text(AppTextKey::HomeSetupSignerErrorPendingApprovalExists)
+ );
+ assert_eq!(
+ startup_notice_text("remote signer connection failed: relay refused the request"),
+ app_text(AppTextKey::HomeSetupSignerErrorConnectionFailed)
+ );
+ assert_eq!(
+ startup_notice_text("failed to add relay `{relay_url}`: {error}"),
+ app_text(AppTextKey::HomeSetupErrorStartupFailed)
+ );
+ }
+
+ #[test]
+ fn startup_issue_copy_fails_closed_to_a_localized_summary() {
+ assert_eq!(
+ startup_issue_summary_text("runtime unavailable"),
+ app_text(AppTextKey::HomeSetupIssueUnavailableBody)
+ );
+ assert_eq!(
+ startup_issue_summary_text("desktop runtime roots require HOME for macos"),
+ app_text(AppTextKey::HomeSetupIssueUnavailableBody)
+ );
+ }
+
+ #[test]
fn reminder_action_target_prefers_order_detail_before_pack_day() {
let order_id = radroots_app_models::OrderId::new();
let fulfillment_window_id = FulfillmentWindowId::new();
diff --git a/crates/shared/i18n/src/keys.rs b/crates/shared/i18n/src/keys.rs
@@ -75,6 +75,20 @@ define_app_text_keys! {
HomeSetupSignerPendingTitle => "home.setup.signer.pending_title",
HomeSetupSignerAuthChallengeTitle => "home.setup.signer.auth_challenge_title",
HomeSetupSignerApprovedTitle => "home.setup.signer.approved_title",
+ HomeSetupIssueUnavailableBody => "home.setup.issue.unavailable_body",
+ HomeSetupErrorStartupFailed => "home.setup.error.startup_failed",
+ HomeSetupSignerSourceValueBunkerUri => "home.setup.signer.source_value.bunker_uri",
+ HomeSetupSignerSourceValueDiscoveryUrl => "home.setup.signer.source_value.discovery_url",
+ HomeSetupSignerPermissionSignEventKind1 => "home.setup.signer.permission.sign_event_kind_1",
+ HomeSetupSignerPermissionSwitchRelays => "home.setup.signer.permission.switch_relays",
+ HomeSetupSignerPermissionAdditional => "home.setup.signer.permission.additional",
+ HomeSetupSignerErrorEnterSource => "home.setup.signer.error.enter_source",
+ HomeSetupSignerErrorUseSignerUri => "home.setup.signer.error.use_signer_uri",
+ HomeSetupSignerErrorMissingDiscoveryUri => "home.setup.signer.error.missing_discovery_uri",
+ HomeSetupSignerErrorInvalidDiscoveryUrl => "home.setup.signer.error.invalid_discovery_url",
+ HomeSetupSignerErrorInvalidRemoteSignerUri => "home.setup.signer.error.invalid_remote_signer_uri",
+ HomeSetupSignerErrorPendingApprovalExists => "home.setup.signer.error.pending_approval_exists",
+ HomeSetupSignerErrorConnectionFailed => "home.setup.signer.error.connection_failed",
HomeFarmSetupOnboardingTitle => "home.farm_setup.onboarding.title",
HomeFarmSetupOnboardingBody => "home.farm_setup.onboarding.body",
HomeFarmSetupOnboardingAction => "home.farm_setup.onboarding.action",
diff --git a/crates/shared/i18n/src/lib.rs b/crates/shared/i18n/src/lib.rs
@@ -583,6 +583,20 @@ mod tests {
"HomeSetupSignerPendingTitle => \"home.setup.signer.pending_title\"",
"HomeSetupSignerAuthChallengeTitle => \"home.setup.signer.auth_challenge_title\"",
"HomeSetupSignerApprovedTitle => \"home.setup.signer.approved_title\"",
+ "HomeSetupIssueUnavailableBody => \"home.setup.issue.unavailable_body\"",
+ "HomeSetupErrorStartupFailed => \"home.setup.error.startup_failed\"",
+ "HomeSetupSignerSourceValueBunkerUri => \"home.setup.signer.source_value.bunker_uri\"",
+ "HomeSetupSignerSourceValueDiscoveryUrl => \"home.setup.signer.source_value.discovery_url\"",
+ "HomeSetupSignerPermissionSignEventKind1 => \"home.setup.signer.permission.sign_event_kind_1\"",
+ "HomeSetupSignerPermissionSwitchRelays => \"home.setup.signer.permission.switch_relays\"",
+ "HomeSetupSignerPermissionAdditional => \"home.setup.signer.permission.additional\"",
+ "HomeSetupSignerErrorEnterSource => \"home.setup.signer.error.enter_source\"",
+ "HomeSetupSignerErrorUseSignerUri => \"home.setup.signer.error.use_signer_uri\"",
+ "HomeSetupSignerErrorMissingDiscoveryUri => \"home.setup.signer.error.missing_discovery_uri\"",
+ "HomeSetupSignerErrorInvalidDiscoveryUrl => \"home.setup.signer.error.invalid_discovery_url\"",
+ "HomeSetupSignerErrorInvalidRemoteSignerUri => \"home.setup.signer.error.invalid_remote_signer_uri\"",
+ "HomeSetupSignerErrorPendingApprovalExists => \"home.setup.signer.error.pending_approval_exists\"",
+ "HomeSetupSignerErrorConnectionFailed => \"home.setup.signer.error.connection_failed\"",
] {
assert!(
source.contains(entry),
@@ -638,6 +652,62 @@ mod tests {
app_text(AppTextKey::HomeSetupSignerApprovedTitle),
"Signer approved"
);
+ assert_eq!(
+ app_text(AppTextKey::HomeSetupIssueUnavailableBody),
+ "Radroots couldn't start normally on this device. Check the local setup and try again."
+ );
+ assert_eq!(
+ app_text(AppTextKey::HomeSetupErrorStartupFailed),
+ "Couldn't finish startup right now. Check the connection and try again."
+ );
+ assert_eq!(
+ app_text(AppTextKey::HomeSetupSignerSourceValueBunkerUri),
+ "Bunker URI"
+ );
+ assert_eq!(
+ app_text(AppTextKey::HomeSetupSignerSourceValueDiscoveryUrl),
+ "Discovery URL"
+ );
+ assert_eq!(
+ app_text(AppTextKey::HomeSetupSignerPermissionSignEventKind1),
+ "Sign notes"
+ );
+ assert_eq!(
+ app_text(AppTextKey::HomeSetupSignerPermissionSwitchRelays),
+ "Switch relays"
+ );
+ assert_eq!(
+ app_text(AppTextKey::HomeSetupSignerPermissionAdditional),
+ "Additional permission"
+ );
+ assert_eq!(
+ app_text(AppTextKey::HomeSetupSignerErrorEnterSource),
+ "Paste a bunker URI or discovery URL from your signer to continue."
+ );
+ assert_eq!(
+ app_text(AppTextKey::HomeSetupSignerErrorUseSignerUri),
+ "Use a bunker URI or discovery URL from your signer."
+ );
+ assert_eq!(
+ app_text(AppTextKey::HomeSetupSignerErrorMissingDiscoveryUri),
+ "The discovery URL is missing the signer address."
+ );
+ assert_eq!(
+ app_text(AppTextKey::HomeSetupSignerErrorInvalidDiscoveryUrl),
+ "That discovery URL isn't valid. Check it and try again."
+ );
+ assert_eq!(
+ app_text(AppTextKey::HomeSetupSignerErrorInvalidRemoteSignerUri),
+ "That signer address isn't valid. Check it and try again."
+ );
+ assert_eq!(
+ app_text(AppTextKey::HomeSetupSignerErrorPendingApprovalExists),
+ "A signer connection is already waiting for approval."
+ );
+ assert_eq!(
+ app_text(AppTextKey::HomeSetupSignerErrorConnectionFailed),
+ "Couldn't continue with the signer. Check the signer and try again."
+ );
}
#[test]
diff --git a/i18n/locales/en/messages.json b/i18n/locales/en/messages.json
@@ -55,6 +55,20 @@
"home.setup.signer.pending_title": "Waiting for signer approval",
"home.setup.signer.auth_challenge_title": "Continue in your signer",
"home.setup.signer.approved_title": "Signer approved",
+ "home.setup.issue.unavailable_body": "Radroots couldn't start normally on this device. Check the local setup and try again.",
+ "home.setup.error.startup_failed": "Couldn't finish startup right now. Check the connection and try again.",
+ "home.setup.signer.source_value.bunker_uri": "Bunker URI",
+ "home.setup.signer.source_value.discovery_url": "Discovery URL",
+ "home.setup.signer.permission.sign_event_kind_1": "Sign notes",
+ "home.setup.signer.permission.switch_relays": "Switch relays",
+ "home.setup.signer.permission.additional": "Additional permission",
+ "home.setup.signer.error.enter_source": "Paste a bunker URI or discovery URL from your signer to continue.",
+ "home.setup.signer.error.use_signer_uri": "Use a bunker URI or discovery URL from your signer.",
+ "home.setup.signer.error.missing_discovery_uri": "The discovery URL is missing the signer address.",
+ "home.setup.signer.error.invalid_discovery_url": "That discovery URL isn't valid. Check it and try again.",
+ "home.setup.signer.error.invalid_remote_signer_uri": "That signer address isn't valid. Check it and try again.",
+ "home.setup.signer.error.pending_approval_exists": "A signer connection is already waiting for approval.",
+ "home.setup.signer.error.connection_failed": "Couldn't continue with the signer. Check the signer and try again.",
"home.farm_setup.onboarding.title": "Set up your farm",
"home.farm_setup.onboarding.body": "Add the basics now. You can change them later.",
"home.farm_setup.onboarding.action": "Set up your farm",