From 9bba2c2960526ca0623bb7ee1b8ec1c21c24fdb8 Mon Sep 17 00:00:00 2001 From: torlando-tech <239676438+torlando-tech@users.noreply.github.com> Date: Fri, 9 Oct 2026 17:11:17 +0000 Subject: [PATCH 01/10] ui: rename Background Transport card to Reticulum Network, add start/stop Replace the settings card: header 'Reticulum Network', subtext 'Manually stop or start the background Reticulum service.', status indicator, and a single Start/Stop swap button (red Stop when running, accent gradient Start when stopped). The card is now an ExpandableSettingsCard matching the other settings cards, with the existing explainer sheet (BackgroundTransportView) accessible via 'Learn more & set up'. Stop is non-persistent: it calls tunnel.disable() (clears on-demand so the tunnel actually stays down this session) but does not touch the backgroundDeliveryEnabled flag, so the tunnel auto-restarts on the next app launch (default-enabled behavior). Start calls install() (re-arms on-demand) + start() to reconnect in the current session. Fixes the .background ternary type mismatch caught by the Model B device build: both branches are now LinearGradient (Theme.accentGradient is a LinearGradient, Theme.error is a Color). --- .../Views/Settings/SettingsView.swift | 162 ++++++++++-------- 1 file changed, 90 insertions(+), 72 deletions(-) diff --git a/Sources/ColumbaApp/Views/Settings/SettingsView.swift b/Sources/ColumbaApp/Views/Settings/SettingsView.swift index 124f2a18..bc29002b 100644 --- a/Sources/ColumbaApp/Views/Settings/SettingsView.swift +++ b/Sources/ColumbaApp/Views/Settings/SettingsView.swift @@ -57,8 +57,12 @@ struct SettingsView: View { @AppStorage(ComposerKeyboardPreference.key) private var sendsOnReturn = ComposerKeyboardPreference.defaultValue #if COLUMBA_RUNTIME_MODEL_B - /// Presents the background-transport explainer / enable sheet. - @State private var showBackgroundTransport = false + /// Expanded state for the Reticulum Network card (Settings). + @State private var isReticulumExpanded = false + /// Transient busy state for the card's Start/Stop action. + @State private var isReticulumActionWorking = false + /// Presents the Reticulum Network explainer / first-time consent sheet. + @State private var showReticulumExplainer = false #endif @State private var interfaceRepository: InterfaceRepository? /// Persisted across body re-evaluations so showRNodeWizard=true is not lost @@ -75,12 +79,12 @@ struct SettingsView: View { // Network networkCard(vm) - // Background Transport — the Model B Network Extension VPN - // tunnel that keeps the experimental native node alive when - // the app is backgrounded. It is compiled entirely out of the - // shipping Python product. + // Reticulum Network — the Model B in-NE RNS runtime. + // Manually start/stop it; defaults to enabled on app + // launch. Compiled entirely out of the shipping Python + // product. #if COLUMBA_RUNTIME_MODEL_B - backgroundTransportCard() + reticulumNetworkCard() #endif // Delivery & Retrieval @@ -526,97 +530,111 @@ struct SettingsView: View { // card and its NetworkExtension types are absent from the shipping Python app. @ViewBuilder - private func backgroundTransportCard() -> some View { + private func reticulumNetworkCard() -> some View { if let tunnel = appServices.tunnelManager { - VStack(alignment: .leading, spacing: 12) { - HStack(spacing: 10) { - Image(systemName: "antenna.radiowaves.left.and.right.circle.fill") - .font(.system(size: 18, weight: .semibold)) - .foregroundStyle(Theme.accentColor) - - Text("Background Transport") - .font(.headline) - .foregroundStyle(Theme.textPrimary) + ExpandableSettingsCard( + icon: "antenna.radiowaves.left.and.right.circle.fill", + title: "Reticulum Network", + isExpanded: $isReticulumExpanded + ) { + VStack(alignment: .leading, spacing: 12) { + Text("Manually stop or start the background Reticulum service.") + .font(.caption) + .foregroundStyle(Theme.textSecondary) - Spacer() + HStack(spacing: 6) { + Circle() + .fill(reticulumStatusColor(tunnel)) + .frame(width: 8, height: 8) + Text(reticulumStatusLabel(tunnel)) + .font(.caption) + .foregroundStyle(reticulumStatusColor(tunnel)) + } - // Quick toggle for users who've already set this up. - // Enabling installs the VPN profile (which also arms - // on-demand connect) before starting, matching the - // explainer screen's Enable path. The full explainer + - // first-time consent lives behind "Learn more & set up". - Toggle("", isOn: Binding( - get: { tunnel.isRunning }, - set: { newValue in - Task { - if newValue { - try? await tunnel.install() - try? await tunnel.start() + // Stop/Start the in-NE RNS runtime. Stop is non-persistent: + // disable() clears the on-demand connect rule so the tunnel + // actually stays down for this session (a bare stop() would + // let iOS auto-reconnect it shortly after, making Stop feel + // broken). It does NOT touch the backgroundDeliveryEnabled + // flag, so the default-enabled behavior is preserved — on the + // next app launch ensureBackgroundDeliveryTunnel() sees the + // flag still set, re-installs + re-arms on-demand, and brings + // the tunnel back up without prompting. Start reinstalls the + // profile (re-arms on-demand) and reconnects in this session. + Button { + guard !isReticulumActionWorking else { return } + let stopping = tunnel.isRunning + isReticulumActionWorking = true + Task { + defer { isReticulumActionWorking = false } + do { + if stopping { + try await tunnel.disable() } else { - // disable() clears on-demand so iOS won't - // auto-reconnect (a bare stop() would). - try? await tunnel.disable() + try await tunnel.install() + try await tunnel.start() } + } catch { + DiagLog.log("[RNS-CARD] start/stop failed: \(error.localizedDescription)") } } - )) - .labelsHidden() - .tint(Theme.accentColor) - } - - Text("Keep TCP and LAN connections alive when the app is backgrounded. Enables receiving messages and notifications without opening the app.") - .font(.caption) - .foregroundStyle(Theme.textSecondary) - - HStack(spacing: 6) { - Circle() - .fill(backgroundTransportStatusColor(tunnel)) - .frame(width: 8, height: 8) - - Text(backgroundTransportStatusLabel(tunnel)) - .font(.caption) - .foregroundStyle(backgroundTransportStatusColor(tunnel)) - } + } label: { + Group { + if isReticulumActionWorking { + ProgressView().tint(.white) + } else { + Text(tunnel.isRunning ? "Stop" : "Start") + } + } + .font(.subheadline.weight(.semibold)) + .foregroundStyle(.white) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background(tunnel.isRunning + ? LinearGradient(colors: [Theme.error], startPoint: .top, endPoint: .bottom) + : Theme.accentGradient) + .clipShape(RoundedRectangle(cornerRadius: Theme.cornerRadiusMedium)) + } + .disabled(isReticulumActionWorking) - Button { - showBackgroundTransport = true - } label: { - HStack(spacing: 8) { - Image(systemName: "info.circle") - .font(.system(size: 14, weight: .medium)) - Text("Learn more & set up") - .font(.system(size: 15, weight: .medium)) + Button { + showReticulumExplainer = true + } label: { + HStack(spacing: 8) { + Image(systemName: "info.circle") + .font(.system(size: 14, weight: .medium)) + Text("Learn more & set up") + .font(.system(size: 15, weight: .medium)) + } + .foregroundStyle(Theme.textPrimary) + .frame(maxWidth: .infinity) + .padding(.vertical, 12) + .background(Theme.backgroundTertiary) + .clipShape(RoundedRectangle(cornerRadius: Theme.cornerRadiusMedium)) } - .foregroundStyle(Theme.textPrimary) - .frame(maxWidth: .infinity) - .padding(.vertical, 12) - .background(Theme.backgroundTertiary) - .clipShape(RoundedRectangle(cornerRadius: Theme.cornerRadiusMedium)) } } - .padding(16) - .glassCard() - .sheet(isPresented: $showBackgroundTransport) { + .sheet(isPresented: $showReticulumExplainer) { BackgroundTransportView(tunnel: tunnel) { - showBackgroundTransport = false + showReticulumExplainer = false } } } } - private func backgroundTransportStatusLabel(_ tunnel: TunnelManager) -> String { + private func reticulumStatusLabel(_ tunnel: TunnelManager) -> String { switch tunnel.status { case .connected: return "Running" - case .connecting: return "Connecting…" + case .connecting: return "Starting…" case .reasserting: return "Reconnecting…" - case .disconnecting: return "Disconnecting…" + case .disconnecting: return "Stopping…" case .invalid: return "Not configured" case .disconnected: return "Stopped" @unknown default: return tunnel.isEnabled ? "Enabled" : "Stopped" } } - private func backgroundTransportStatusColor(_ tunnel: TunnelManager) -> Color { + private func reticulumStatusColor(_ tunnel: TunnelManager) -> Color { switch tunnel.status { case .connected: return Theme.success case .connecting, .reasserting, .disconnecting: return Theme.warning From 8d5c2af6a68bc2593280403568025edd4058074c Mon Sep 17 00:00:00 2001 From: torlando-tech <239676438+torlando-tech@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:02:48 +0000 Subject: [PATCH 02/10] i18n: localize Reticulum Network card strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wrap the card's user-facing strings in String(localized:) following the codebase's literal-based convention, and add the new keys to the Localizable.xcstrings catalog. Covers the status labels (Running, Starting…, Reconnecting…, Stopping…, Not configured, Stopped, Enabled), the card title/subtext, the Start/Stop button, and the 'Learn more & set up' button. --- .../Resources/Localizable.xcstrings | 110 ++++++++++++++++++ .../Views/Settings/SettingsView.swift | 22 ++-- 2 files changed, 121 insertions(+), 11 deletions(-) diff --git a/Sources/ColumbaApp/Resources/Localizable.xcstrings b/Sources/ColumbaApp/Resources/Localizable.xcstrings index 25127c6e..253f11db 100644 --- a/Sources/ColumbaApp/Resources/Localizable.xcstrings +++ b/Sources/ColumbaApp/Resources/Localizable.xcstrings @@ -3054,6 +3054,116 @@ } } } + }, + "Running": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Running" + } + } + } + }, + "Starting…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Starting…" + } + } + } + }, + "Reconnecting…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reconnecting…" + } + } + } + }, + "Stopping…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stopping…" + } + } + } + }, + "Not configured": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not configured" + } + } + } + }, + "Stopped": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stopped" + } + } + } + }, + "Reticulum Network": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reticulum Network" + } + } + } + }, + "Manually stop or start the background Reticulum service.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Manually stop or start the background Reticulum service." + } + } + } + }, + "Start": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Start" + } + } + } + }, + "Stop": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop" + } + } + } + }, + "Learn more & set up": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Learn more & set up" + } + } + } } }, "version": "1.0" diff --git a/Sources/ColumbaApp/Views/Settings/SettingsView.swift b/Sources/ColumbaApp/Views/Settings/SettingsView.swift index bc29002b..2a241421 100644 --- a/Sources/ColumbaApp/Views/Settings/SettingsView.swift +++ b/Sources/ColumbaApp/Views/Settings/SettingsView.swift @@ -534,11 +534,11 @@ struct SettingsView: View { if let tunnel = appServices.tunnelManager { ExpandableSettingsCard( icon: "antenna.radiowaves.left.and.right.circle.fill", - title: "Reticulum Network", + title: String(localized: "Reticulum Network"), isExpanded: $isReticulumExpanded ) { VStack(alignment: .leading, spacing: 12) { - Text("Manually stop or start the background Reticulum service.") + Text(String(localized: "Manually stop or start the background Reticulum service.")) .font(.caption) .foregroundStyle(Theme.textSecondary) @@ -583,7 +583,7 @@ struct SettingsView: View { if isReticulumActionWorking { ProgressView().tint(.white) } else { - Text(tunnel.isRunning ? "Stop" : "Start") + Text(tunnel.isRunning ? String(localized: "Stop") : String(localized: "Start")) } } .font(.subheadline.weight(.semibold)) @@ -603,7 +603,7 @@ struct SettingsView: View { HStack(spacing: 8) { Image(systemName: "info.circle") .font(.system(size: 14, weight: .medium)) - Text("Learn more & set up") + Text(String(localized: "Learn more & set up")) .font(.system(size: 15, weight: .medium)) } .foregroundStyle(Theme.textPrimary) @@ -624,13 +624,13 @@ struct SettingsView: View { private func reticulumStatusLabel(_ tunnel: TunnelManager) -> String { switch tunnel.status { - case .connected: return "Running" - case .connecting: return "Starting…" - case .reasserting: return "Reconnecting…" - case .disconnecting: return "Stopping…" - case .invalid: return "Not configured" - case .disconnected: return "Stopped" - @unknown default: return tunnel.isEnabled ? "Enabled" : "Stopped" + case .connected: return String(localized: "Running") + case .connecting: return String(localized: "Starting…") + case .reasserting: return String(localized: "Reconnecting…") + case .disconnecting: return String(localized: "Stopping…") + case .invalid: return String(localized: "Not configured") + case .disconnected: return String(localized: "Stopped") + @unknown default: return tunnel.isEnabled ? String(localized: "Enabled") : String(localized: "Stopped") } } From 5ce8f81d67656455d0bc9924e12259218fa64e70 Mon Sep 17 00:00:00 2001 From: torlando-tech <239676438+torlando-tech@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:22:34 +0000 Subject: [PATCH 03/10] ui: VPNBadgeExplainer hardcodes 'Reticulum is running' text Drop the featureName parameter from VPNBadgeExplainer - the VPN badge is tied to the in-NE Reticulum runtime on every surface, so the indicator explanation now reads 'It stays visible while Reticulum is running' instead of parameterizing 'background transport'/'background delivery'. Localize the string and add it to the catalog. Update both call sites (settings BackgroundTransportView and onboarding BackgroundDeliveryPage) to the no-arg form and fix the now-stale doc comment. --- Sources/ColumbaApp/Resources/Localizable.xcstrings | 10 ++++++++++ .../Views/Components/BackgroundVPNExplainer.swift | 7 +++---- .../Views/Onboarding/BackgroundDeliveryPage.swift | 8 ++++---- .../Views/Settings/BackgroundTransportView.swift | 2 +- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Sources/ColumbaApp/Resources/Localizable.xcstrings b/Sources/ColumbaApp/Resources/Localizable.xcstrings index 253f11db..34846d08 100644 --- a/Sources/ColumbaApp/Resources/Localizable.xcstrings +++ b/Sources/ColumbaApp/Resources/Localizable.xcstrings @@ -3164,6 +3164,16 @@ } } } + }, + "The indicator means iOS has an active network extension. It stays visible while Reticulum is running.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The indicator means iOS has an active network extension. It stays visible while Reticulum is running." + } + } + } } }, "version": "1.0" diff --git a/Sources/ColumbaApp/Views/Components/BackgroundVPNExplainer.swift b/Sources/ColumbaApp/Views/Components/BackgroundVPNExplainer.swift index 53ddfb8f..2835d0a0 100644 --- a/Sources/ColumbaApp/Views/Components/BackgroundVPNExplainer.swift +++ b/Sources/ColumbaApp/Views/Components/BackgroundVPNExplainer.swift @@ -42,11 +42,10 @@ struct VPNNotCommercialExplainer: View { } /// "The VPN badge" explainer — pre-empts the iOS status-bar VPN badge, the thing that -/// makes the feature look like a traditional VPN. `featureName` fills the trailing -/// sentence so each surface uses its own term ("background transport" vs "delivery"). +/// makes the feature look like a traditional VPN. The indicator is tied to the in-NE +/// Reticulum runtime, so the trailing sentence reads "…while Reticulum is running". @available(iOS 17.0, macOS 14.0, *) struct VPNBadgeExplainer: View { - let featureName: String var body: some View { VStack(alignment: .leading, spacing: 10) { @@ -67,7 +66,7 @@ struct VPNBadgeExplainer: View { .fixedSize(horizontal: false, vertical: true) } - Text("The indicator means iOS has an active network extension. It stays visible while \(featureName) is enabled.") + Text(String(localized: "The indicator means iOS has an active network extension. It stays visible while Reticulum is running.")) .font(.caption) .foregroundStyle(Theme.textSecondary) .fixedSize(horizontal: false, vertical: true) diff --git a/Sources/ColumbaApp/Views/Onboarding/BackgroundDeliveryPage.swift b/Sources/ColumbaApp/Views/Onboarding/BackgroundDeliveryPage.swift index e45312c8..7f633fd9 100644 --- a/Sources/ColumbaApp/Views/Onboarding/BackgroundDeliveryPage.swift +++ b/Sources/ColumbaApp/Views/Onboarding/BackgroundDeliveryPage.swift @@ -157,11 +157,11 @@ struct BackgroundDeliveryPage: View { .explainerCardStyle() } - /// "The VPN badge" note, shared with settings via `VPNBadgeExplainer`. The feature - /// name reads "background delivery" to match this flow (settings says "background - /// transport"). + /// "The VPN badge" note, shared with settings via `VPNBadgeExplainer`. The + /// indicator text reads "…while Reticulum is running" (the badge is tied to + /// the in-NE Reticulum runtime on every surface). private var badgeCard: some View { - VPNBadgeExplainer(featureName: "background delivery") + VPNBadgeExplainer() .explainerCardStyle() } } diff --git a/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift b/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift index 1b8647da..da529ddc 100644 --- a/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift +++ b/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift @@ -161,7 +161,7 @@ struct BackgroundTransportView: View { // MARK: - Status-Bar Badge Card private var badgeCard: some View { - VPNBadgeExplainer(featureName: "background transport") + VPNBadgeExplainer() .padding(16) .glassCard() } From e1492a31ef46362abaed6d8d78668991e5d756fa Mon Sep 17 00:00:00 2001 From: torlando-tech <239676438+torlando-tech@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:50:04 +0000 Subject: [PATCH 04/10] ui: VPNBadgeExplainer relay text - drop 'may' 'carries Columba and Reticulum traffic, which may use the relay' -> 'which uses the relay'. The relay carries both traffic types, not conditionally. --- .../ColumbaApp/Views/Components/BackgroundVPNExplainer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ColumbaApp/Views/Components/BackgroundVPNExplainer.swift b/Sources/ColumbaApp/Views/Components/BackgroundVPNExplainer.swift index 2835d0a0..cf14b9c3 100644 --- a/Sources/ColumbaApp/Views/Components/BackgroundVPNExplainer.swift +++ b/Sources/ColumbaApp/Views/Components/BackgroundVPNExplainer.swift @@ -35,7 +35,7 @@ struct VPNNotCommercialExplainer: View { ) VPNExplainerUI.explainerRow( icon: "iphone", - text: "The component carries Columba and Reticulum traffic, which may use the relay and interfaces you configured." + text: "The component carries Columba and Reticulum traffic, which uses the relay and interfaces you configured." ) } } From 8fa793d9805e8d6bdf2878666b0d0e083ac41742 Mon Sep 17 00:00:00 2001 From: torlando-tech <239676438+torlando-tech@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:56:48 +0000 Subject: [PATCH 05/10] i18n: localize all VPN explainer strings Wrap every user-facing string in the VPN explainer views (VPNNotCommercialExplainer, VPNBadgeExplainer, VPNExplainerUI call sites) in String(localized:) to match the codebase idiom. The VPNExplainerUI.sectionHeader/explainerRow helpers now take String instead of LocalizedStringKey so all call sites are explicit. 'VPN' badge glyph stays (untranslatable acronym). Added 8 new keys to Localizable.xcstrings (the remaining 3 were already present). Also updated the BackgroundTransportView explainerCard call sites to match. --- .../Resources/Localizable.xcstrings | 80 +++++++++++++++++++ .../Components/BackgroundVPNExplainer.swift | 16 ++-- .../Settings/BackgroundTransportView.swift | 8 +- 3 files changed, 92 insertions(+), 12 deletions(-) diff --git a/Sources/ColumbaApp/Resources/Localizable.xcstrings b/Sources/ColumbaApp/Resources/Localizable.xcstrings index 34846d08..7eb79462 100644 --- a/Sources/ColumbaApp/Resources/Localizable.xcstrings +++ b/Sources/ColumbaApp/Resources/Localizable.xcstrings @@ -3174,6 +3174,86 @@ } } } + }, + "Not a commercial VPN": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not a commercial VPN" + } + } + } + }, + "Your other apps and web traffic are not proxied or routed through Columba.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Your other apps and web traffic are not proxied or routed through Columba." + } + } + } + }, + "The component carries Columba and Reticulum traffic, which uses the relay and interfaces you configured.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The component carries Columba and Reticulum traffic, which uses the relay and interfaces you configured." + } + } + } + }, + "The VPN indicator": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The VPN indicator" + } + } + } + }, + "What it does": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "What it does" + } + } + } + }, + "Receives messages, calls, and announcements even when Columba isn't open.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Receives messages, calls, and announcements even when Columba isn't open." + } + } + } + }, + "Keeps your TCP and local-network (LAN) links connected to the Reticulum mesh in the background.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Keeps your TCP and local-network (LAN) links connected to the Reticulum mesh in the background." + } + } + } + }, + "Uses more battery and data than running only in the foreground.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Uses more battery and data than running only in the foreground." + } + } + } } }, "version": "1.0" diff --git a/Sources/ColumbaApp/Views/Components/BackgroundVPNExplainer.swift b/Sources/ColumbaApp/Views/Components/BackgroundVPNExplainer.swift index cf14b9c3..190d4380 100644 --- a/Sources/ColumbaApp/Views/Components/BackgroundVPNExplainer.swift +++ b/Sources/ColumbaApp/Views/Components/BackgroundVPNExplainer.swift @@ -22,20 +22,20 @@ import SwiftUI struct VPNNotCommercialExplainer: View { var body: some View { VStack(alignment: .leading, spacing: 10) { - VPNExplainerUI.sectionHeader(icon: "lock.shield.fill", title: "Not a commercial VPN") + VPNExplainerUI.sectionHeader(icon: "lock.shield.fill", title: String(localized: "Not a commercial VPN")) - Text("Columba uses Apple's VPN system to keep its own network component available in the background. It is not a commercial VPN service.") + Text(String(localized: "Columba uses Apple's VPN system to keep its own network component available in the background. It is not a commercial VPN service.")) .font(.caption) .foregroundStyle(Theme.textSecondary) .fixedSize(horizontal: false, vertical: true) VPNExplainerUI.explainerRow( icon: "checkmark.shield.fill", - text: "Your other apps and web traffic are not proxied or routed through Columba." + text: String(localized: "Your other apps and web traffic are not proxied or routed through Columba.") ) VPNExplainerUI.explainerRow( icon: "iphone", - text: "The component carries Columba and Reticulum traffic, which uses the relay and interfaces you configured." + text: String(localized: "The component carries Columba and Reticulum traffic, which uses the relay and interfaces you configured.") ) } } @@ -49,7 +49,7 @@ struct VPNBadgeExplainer: View { var body: some View { VStack(alignment: .leading, spacing: 10) { - VPNExplainerUI.sectionHeader(icon: "rectangle.topthird.inset.filled", title: "The VPN indicator") + VPNExplainerUI.sectionHeader(icon: "rectangle.topthird.inset.filled", title: String(localized: "The VPN indicator")) HStack(spacing: 10) { Text("VPN") @@ -60,7 +60,7 @@ struct VPNBadgeExplainer: View { .background(Theme.accentColor) .clipShape(RoundedRectangle(cornerRadius: 4)) - Text("While this is on, iOS shows a VPN indicator.") + Text(String(localized: "While this is on, iOS shows a VPN indicator.")) .font(.subheadline) .foregroundStyle(Theme.textPrimary) .fixedSize(horizontal: false, vertical: true) @@ -80,7 +80,7 @@ struct VPNBadgeExplainer: View { @available(iOS 17.0, macOS 14.0, *) @MainActor enum VPNExplainerUI { - static func sectionHeader(icon: String, title: LocalizedStringKey) -> some View { + static func sectionHeader(icon: String, title: String) -> some View { HStack(spacing: 8) { Image(systemName: icon) .font(.system(size: 16, weight: .semibold)) @@ -92,7 +92,7 @@ enum VPNExplainerUI { } } - static func explainerRow(icon: String, text: LocalizedStringKey) -> some View { + static func explainerRow(icon: String, text: String) -> some View { HStack(alignment: .top, spacing: 12) { Image(systemName: icon) .font(.system(size: 16)) diff --git a/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift b/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift index da529ddc..627e61bd 100644 --- a/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift +++ b/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift @@ -139,19 +139,19 @@ struct BackgroundTransportView: View { private var explainerCard: some View { VStack(alignment: .leading, spacing: 12) { - VPNExplainerUI.sectionHeader(icon: "questionmark.circle", title: "What it does") + VPNExplainerUI.sectionHeader(icon: "questionmark.circle", title: String(localized: "What it does")) VPNExplainerUI.explainerRow( icon: "tray.and.arrow.down.fill", - text: "Receives messages, calls, and announcements even when Columba isn't open." + text: String(localized: "Receives messages, calls, and announcements even when Columba isn't open.") ) VPNExplainerUI.explainerRow( icon: "point.3.connected.trianglepath.dotted", - text: "Keeps your TCP and local-network (LAN) links connected to the Reticulum mesh in the background." + text: String(localized: "Keeps your TCP and local-network (LAN) links connected to the Reticulum mesh in the background.") ) VPNExplainerUI.explainerRow( icon: "bolt.fill", - text: "Uses more battery and data than running only in the foreground." + text: String(localized: "Uses more battery and data than running only in the foreground.") ) } .padding(16) From 252e5f604af692553394009b978134e9a3ef2b21 Mon Sep 17 00:00:00 2001 From: Torlando <239676438+torlando-tech@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:15:58 -0400 Subject: [PATCH 06/10] Simplify localization string for Reticulum mesh --- Sources/ColumbaApp/Resources/Localizable.xcstrings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ColumbaApp/Resources/Localizable.xcstrings b/Sources/ColumbaApp/Resources/Localizable.xcstrings index 7eb79462..bc9553f1 100644 --- a/Sources/ColumbaApp/Resources/Localizable.xcstrings +++ b/Sources/ColumbaApp/Resources/Localizable.xcstrings @@ -3235,12 +3235,12 @@ } } }, - "Keeps your TCP and local-network (LAN) links connected to the Reticulum mesh in the background.": { + "Keeps you connected to the Reticulum mesh in the background.": { "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Keeps your TCP and local-network (LAN) links connected to the Reticulum mesh in the background." + "value": "Keeps you connected to the Reticulum mesh in the background." } } } From aa84ef73d8ff2606608ef997bd6d74a9933c9998 Mon Sep 17 00:00:00 2001 From: Torlando <239676438+torlando-tech@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:16:55 -0400 Subject: [PATCH 07/10] Simplify VPN connection explanation text --- Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift b/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift index 627e61bd..9802eff7 100644 --- a/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift +++ b/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift @@ -147,7 +147,7 @@ struct BackgroundTransportView: View { ) VPNExplainerUI.explainerRow( icon: "point.3.connected.trianglepath.dotted", - text: String(localized: "Keeps your TCP and local-network (LAN) links connected to the Reticulum mesh in the background.") + text: String(localized: "Keeps you connected to the Reticulum mesh in the background.") ) VPNExplainerUI.explainerRow( icon: "bolt.fill", From a4e6aa664d21ce3e3e8f2f03b73583797f2199d3 Mon Sep 17 00:00:00 2001 From: torlando-tech <239676438+torlando-tech@users.noreply.github.com> Date: Fri, 9 Oct 2026 19:18:20 +0000 Subject: [PATCH 08/10] ui: remove 'Uses more battery' battery line from VPN explainer --- Sources/ColumbaApp/Resources/Localizable.xcstrings | 10 ---------- .../Views/Settings/BackgroundTransportView.swift | 4 ---- 2 files changed, 14 deletions(-) diff --git a/Sources/ColumbaApp/Resources/Localizable.xcstrings b/Sources/ColumbaApp/Resources/Localizable.xcstrings index bc9553f1..adfb85af 100644 --- a/Sources/ColumbaApp/Resources/Localizable.xcstrings +++ b/Sources/ColumbaApp/Resources/Localizable.xcstrings @@ -3244,16 +3244,6 @@ } } } - }, - "Uses more battery and data than running only in the foreground.": { - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Uses more battery and data than running only in the foreground." - } - } - } } }, "version": "1.0" diff --git a/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift b/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift index 9802eff7..79f29cb7 100644 --- a/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift +++ b/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift @@ -149,10 +149,6 @@ struct BackgroundTransportView: View { icon: "point.3.connected.trianglepath.dotted", text: String(localized: "Keeps you connected to the Reticulum mesh in the background.") ) - VPNExplainerUI.explainerRow( - icon: "bolt.fill", - text: String(localized: "Uses more battery and data than running only in the foreground.") - ) } .padding(16) .glassCard() From e76a46f897421a3f72576024acb6ae4968d8cd26 Mon Sep 17 00:00:00 2001 From: Torlando <239676438+torlando-tech@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:45:16 -0400 Subject: [PATCH 09/10] Update text for VPN explainer in BackgroundTransportView --- Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift b/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift index 79f29cb7..104bf85c 100644 --- a/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift +++ b/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift @@ -147,7 +147,7 @@ struct BackgroundTransportView: View { ) VPNExplainerUI.explainerRow( icon: "point.3.connected.trianglepath.dotted", - text: String(localized: "Keeps you connected to the Reticulum mesh in the background.") + text: String(localized: "Keeps you connected to the Reticulum network in the background.") ) } .padding(16) From 99965d90023b808c0fefa2df21e543ecb0cf3816 Mon Sep 17 00:00:00 2001 From: Torlando <239676438+torlando-tech@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:45:46 -0400 Subject: [PATCH 10/10] Update localization from 'mesh' to 'network' --- Sources/ColumbaApp/Resources/Localizable.xcstrings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ColumbaApp/Resources/Localizable.xcstrings b/Sources/ColumbaApp/Resources/Localizable.xcstrings index adfb85af..217b5e7e 100644 --- a/Sources/ColumbaApp/Resources/Localizable.xcstrings +++ b/Sources/ColumbaApp/Resources/Localizable.xcstrings @@ -3235,12 +3235,12 @@ } } }, - "Keeps you connected to the Reticulum mesh in the background.": { + "Keeps you connected to the Reticulum network in the background.": { "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Keeps you connected to the Reticulum mesh in the background." + "value": "Keeps you connected to the Reticulum network in the background." } } }