Localize onboarding copy

This commit is contained in:
FuelBoard Contributor
2026-10-01 12:54:43 +01:00
parent 50c543c776
commit 824923b2ac
+31 -30
View File
@@ -4,8 +4,9 @@ import UserNotifications
import Network
/// First-launch onboarding: introduces FuelBoard, then walks the user through
/// the three system permissions (location, notifications, local network for
/// data loading) with in-context prompts. In production it appears once at
/// the two system permissions (location and notifications) with in-context
/// prompts. Price data downloads over HTTPS with no Local Network permission.
/// In production it appears once at
/// initial launch (driven by `FuelStore.loadHasCompletedOnboarding()`); a
/// test button in the Alerts tab re-opens it anytime.
struct OnboardingView: View {
@@ -97,23 +98,23 @@ struct OnboardingView: View {
}
.padding(.bottom, 28)
Text("Welcome to FuelBoard")
Text(String(localized: "Welcome to FuelBoard", comment: "Onboarding welcome title"))
.font(.largeTitle.bold())
.multilineTextAlignment(.center)
Text("The cheapest petrol, diesel and premium fuel near you — from the official UK Fuel Finder data.")
Text(String(localized: "The cheapest petrol, diesel and premium fuel near you — from the official UK Fuel Finder data.", comment: "Onboarding welcome subtitle"))
.font(.body)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 32)
.padding(.top, 12)
featureRow(icon: "globe.europe.africa.fill", text: "UK-wide prices — 8,000+ stations, updated twice a day")
featureRow(icon: "scope", text: "Cheapest within your chosen radius, or closest station first")
featureRow(icon: "star.fill", text: "Favourites with instant price comparison")
featureRow(icon: "chart.bar.fill", text: "Green/amber/red rating vs the best nearby price")
featureRow(icon: "square.grid.2x2.fill", text: "Home-screen widget showing the cheapest nearby")
featureRow(icon: "bell.fill", text: "Alerts when you approach the cheapest station")
featureRow(icon: "globe.europe.africa.fill", text: String(localized: "UK-wide prices — 8,000+ stations, updated twice a day", comment: "Onboarding feature row"))
featureRow(icon: "scope", text: String(localized: "Cheapest within your chosen radius, or closest station first", comment: "Onboarding feature row"))
featureRow(icon: "star.fill", text: String(localized: "Favourites with instant price comparison", comment: "Onboarding feature row"))
featureRow(icon: "chart.bar.fill", text: String(localized: "Green/amber/red rating vs the best nearby price", comment: "Onboarding feature row"))
featureRow(icon: "square.grid.2x2.fill", text: String(localized: "Home-screen widget showing the cheapest nearby", comment: "Onboarding feature row"))
featureRow(icon: "bell.fill", text: String(localized: "Alerts when you approach the cheapest station", comment: "Onboarding feature row"))
Spacer()
}
@@ -129,10 +130,10 @@ struct OnboardingView: View {
}
.padding(.bottom, 28)
Text("Prices near you")
Text(String(localized: "Prices near you", comment: "Onboarding location permission title"))
.font(.largeTitle.bold())
Text("FuelBoard uses your location to show prices around you and rank stations by distance. Your position never leaves the device.")
Text(String(localized: "FuelBoard uses your location to show prices around you and rank stations by distance. Your position never leaves the device.", comment: "Onboarding location permission explanation"))
.font(.body)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
@@ -142,7 +143,7 @@ struct OnboardingView: View {
permissionStatusLabel(
granted: prompter.locationGranted,
denied: prompter.locationDenied,
deniedText: "Location was denied. You can still browse all stations, but \"nearest\" sorting needs it."
deniedText: String(localized: "Location was denied. You can still browse all stations, but \"nearest\" sorting needs it.", comment: "Onboarding location denied text")
)
.padding(.top, 24)
@@ -160,10 +161,10 @@ struct OnboardingView: View {
}
.padding(.bottom, 28)
Text("Cheapest-station alerts")
Text(String(localized: "Cheapest-station alerts", comment: "Onboarding notifications title"))
.font(.largeTitle.bold())
Text("FuelBoard can notify you when you're approaching the cheapest station in your alert radius — even with the app closed. You can switch this off in the Alerts tab.")
Text(String(localized: "FuelBoard can notify you when you're approaching the cheapest station in your alert radius — even with the app closed. You can switch this off in the Alerts tab.", comment: "Onboarding notifications explanation"))
.font(.body)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
@@ -173,7 +174,7 @@ struct OnboardingView: View {
permissionStatusLabel(
granted: prompter.notificationsGranted,
denied: prompter.notificationsDenied,
deniedText: "Notifications were denied — you can still use FuelBoard, just without alert banners."
deniedText: String(localized: "Notifications were denied — you can still use FuelBoard, just without alert banners.", comment: "Onboarding notifications denied text")
)
.padding(.top, 24)
@@ -191,10 +192,10 @@ struct OnboardingView: View {
}
.padding(.bottom, 28)
Text("Prices, ready when you are")
Text(String(localized: "Prices, ready when you are", comment: "Onboarding data title"))
.font(.largeTitle.bold())
Text("FuelBoard downloads the latest prices from the internet automatically — the full UK dataset, refreshed twice a day. No extra permissions needed.")
Text(String(localized: "FuelBoard downloads the latest prices from the internet automatically — the full UK dataset, refreshed twice a day. No extra permissions needed.", comment: "Onboarding data explanation"))
.font(.body)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
@@ -218,10 +219,10 @@ struct OnboardingView: View {
}
.padding(.bottom, 28)
Text("You're all set")
Text(String(localized: "You're all set", comment: "Onboarding final title"))
.font(.largeTitle.bold())
Text("Find your cheapest fuel, add favourites, drop the widget on your Home Screen, and let alerts point you to the best price nearby.")
Text(String(localized: "Find your cheapest fuel, add favourites, drop the widget on your Home Screen, and let alerts point you to the best price nearby.", comment: "Onboarding final explanation"))
.font(.body)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
@@ -238,10 +239,10 @@ struct OnboardingView: View {
private var bottomAction: some View {
switch page {
case 0:
primaryButton("Continue") { page = 1 }
primaryButton(String(localized: "Continue", comment: "Onboarding continue button")) { page = 1 }
case 1:
primaryButton(
prompter.locationDenied ? "Open Settings" : (prompter.locationGranted ? "Continue" : "Allow Location Access")
prompter.locationDenied ? String(localized: "Open Settings", comment: "Open iOS Settings button") : (prompter.locationGranted ? String(localized: "Continue", comment: "Onboarding continue button") : String(localized: "Allow Location Access", comment: "Request location permission button"))
) {
if prompter.locationDenied {
openSettings()
@@ -258,7 +259,7 @@ struct OnboardingView: View {
}
case 2:
primaryButton(
prompter.notificationsDenied ? "Continue without alerts" : (prompter.notificationsGranted ? "Continue" : "Allow Notifications")
prompter.notificationsDenied ? String(localized: "Continue without alerts", comment: "Continue after notification permission denied") : (prompter.notificationsGranted ? String(localized: "Continue", comment: "Onboarding continue button") : String(localized: "Allow Notifications", comment: "Request notifications permission button"))
) {
if prompter.notificationsDenied || prompter.notificationsGranted {
page = 3 // decided either way — move on
@@ -269,13 +270,13 @@ struct OnboardingView: View {
}
case 3:
// No permission on this page — prices need none. Straight on.
primaryButton("Continue") { page = 4 }
primaryButton(String(localized: "Continue", comment: "Onboarding continue button")) { page = 4 }
default:
primaryButton("Start Using FuelBoard") { finish() }
primaryButton(String(localized: "Start Using FuelBoard", comment: "Finish onboarding button")) { finish() }
}
}
private func primaryButton(_ title: LocalizedStringKey, action: @escaping () -> Void) -> some View {
private func primaryButton(_ title: String, action: @escaping () -> Void) -> some View {
Button(action: action) {
Text(title)
.font(.headline)
@@ -285,7 +286,7 @@ struct OnboardingView: View {
.buttonStyle(.borderedProminent)
}
private func featureRow(icon: String, text: LocalizedStringKey) -> some View {
private func featureRow(icon: String, text: String) -> some View {
HStack(spacing: 12) {
Image(systemName: icon)
.font(.system(size: 20, weight: .semibold))
@@ -299,10 +300,10 @@ struct OnboardingView: View {
.padding(.vertical, 10)
}
private func permissionStatusLabel(granted: Bool, denied: Bool, deniedText: LocalizedStringKey) -> some View {
private func permissionStatusLabel(granted: Bool, denied: Bool, deniedText: String) -> some View {
Group {
if granted {
Label("Allowed", systemImage: "checkmark.circle.fill")
Label(String(localized: "Allowed", comment: "Permission granted status"), systemImage: "checkmark.circle.fill")
.foregroundStyle(.green)
} else if denied {
Text(deniedText)
@@ -311,7 +312,7 @@ struct OnboardingView: View {
.multilineTextAlignment(.center)
.padding(.horizontal, 32)
} else {
Text("The system prompt will appear next.")
Text(String(localized: "The system prompt will appear next.", comment: "Permission prompt pending status"))
.font(.footnote)
.foregroundStyle(.secondary)
}