Localize onboarding copy
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user