diff --git a/FuelBoard/AlertsView.swift b/FuelBoard/AlertsView.swift index e5508cd..3e4b44a 100644 --- a/FuelBoard/AlertsView.swift +++ b/FuelBoard/AlertsView.swift @@ -38,7 +38,7 @@ struct AlertsView: View { /// "5 miles" or "8 km" — for the "Follow search" rows. private var searchInUnitLabel: String { let shown = distanceUnit.displayMiles(stationLimit) // stationLimit is always miles - return "\(shown) \(distanceUnit.label(for: Double(shown)))" + return String(format: String(localized: "%lld %@", comment: "Distance label"), shown, distanceUnit.label(for: Double(shown))) } /// A miles value (5/10/15) shown truthfully in the user's unit, e.g. @@ -46,7 +46,7 @@ struct AlertsView: View { /// what the option really means. private func distanceLabel(forMiles miles: Int) -> String { let shown = distanceUnit.displayMiles(miles) - return "\(shown) \(distanceUnit.label(for: Double(shown)))" + return String(format: String(localized: "%lld %@", comment: "Distance label"), shown, distanceUnit.label(for: Double(shown))) } /// Situational guidance for the currently-selected alert radius, shown @@ -57,23 +57,23 @@ struct AlertsView: View { private var alertRadiusGuidance: String { switch alertChoice.wrappedValue { case .followSearch: - return "Set-and-forget: alerts behave exactly like the Stations-tab search distance (capped at 8 miles for reliable geofencing)." + return String(localized: "Set-and-forget: alerts behave exactly like the Stations-tab search distance (capped at 8 miles for reliable geofencing).", comment: "Alert radius guidance for following search distance") case .fixed(let value): - let optionLabel = "\(value) \(distanceUnit.label(for: Double(value)))" + let optionLabel = String(format: String(localized: "%lld %@", comment: "Distance label"), value, distanceUnit.label(for: Double(value))) let body: String switch value { case 1: - body = "Town driving. Fires most often — you're rarely inside the circle for long, so pings are meaningful. Best for stop-start local trips." + body = String(localized: "Town driving. Fires most often — you're rarely inside the circle for long, so pings are meaningful. Best for stop-start local trips.", comment: "Alert radius guidance") case 2: - body = "City or suburb commute. Slightly earlier heads-up than the smallest option; still fires regularly around town." + body = String(localized: "City or suburb commute. Slightly earlier heads-up than the smallest option; still fires regularly around town.", comment: "Alert radius guidance") case 3: - body = "Mixed driving. Middle ground: heads-up a few minutes out, still works in most towns." + body = String(localized: "Mixed driving. Middle ground: heads-up a few minutes out, still works in most towns.", comment: "Alert radius guidance") case 5: - body = "A-roads and motorway exits. Wide early warning for longer trips — but in town it rarely fires, because you're usually already inside the circle." + body = String(localized: "A-roads and motorway exits. Wide early warning for longer trips — but in town it rarely fires, because you're usually already inside the circle.", comment: "Alert radius guidance") default: - body = "Long-distance journeys. Maximum reach: a ping when a cheap station is coming up from far out. In town this essentially never fires." + body = String(localized: "Long-distance journeys. Maximum reach: a ping when a cheap station is coming up from far out. In town this essentially never fires.", comment: "Alert radius guidance") } - return "\(optionLabel) — \(body)" + return String(format: String(localized: "%@ — %@", comment: "Alert radius guidance with option label"), optionLabel, body) } } @@ -134,74 +134,74 @@ struct AlertsView: View { NavigationStack { List { Section { - Toggle("Cheapest favourite", isOn: $favouriteDropEnabled) - Picker("Fuel", selection: $favouriteDropFuel) { + Toggle(String(localized: "Cheapest favourite", comment: "Favourite price-drop alert toggle"), isOn: $favouriteDropEnabled) + Picker(String(localized: "Fuel", comment: "Fuel picker title"), selection: $favouriteDropFuel) { ForEach(FuelType.allCases) { fuel in Text(fuel.displayName).tag(fuel) } } - Button("Check now", action: onCheckFavouriteDropNow) + Button(String(localized: "Check now", comment: "Manual favourite price-drop check button"), action: onCheckFavouriteDropNow) .disabled(!favouriteDropEnabled) } header: { - Text("Price-drop alerts") + Text(String(localized: "Price-drop alerts", comment: "Favourite price-drop alerts section title")) } footer: { - Text("Watches your cheapest saved favourite for the fuel you pick here. FuelBoard alerts when a different favourite becomes the cheapest, or when your current cheapest favourite drops by at least 1.0p. This is separate from the nearby-geofence alert, so you can monitor favourites without extra location noise.") + Text(String(localized: "Watches your cheapest saved favourite for the fuel you pick here. FuelBoard alerts when a different favourite becomes the cheapest, or when your current cheapest favourite drops by at least 1.0p. This is separate from the nearby-geofence alert, so you can monitor favourites without extra location noise.", comment: "Favourite price-drop alerts section footer")) } Section { - Toggle("Cheapest station", isOn: $enabled) - Picker("Fuel", selection: $fuel) { + Toggle(String(localized: "Cheapest station", comment: "Proximity alert toggle"), isOn: $enabled) + Picker(String(localized: "Fuel", comment: "Fuel picker title"), selection: $fuel) { ForEach(FuelType.allCases) { fuel in Text(fuel.displayName).tag(fuel) } } - Picker("Radius", selection: alertChoice) { - Text("Follow search (\(searchInUnitLabel))").tag(AlertRadiusChoice.followSearch) + Picker(String(localized: "Radius", comment: "Alert radius picker title"), selection: alertChoice) { + Text(String(format: String(localized: "Follow search (%@)", comment: "Follow current station search distance option"), searchInUnitLabel)).tag(AlertRadiusChoice.followSearch) ForEach(FuelStore.alertRadiusOptions, id: \.self) { value in - Text("\(value) \(distanceUnit.label(for: Double(value)))").tag(AlertRadiusChoice.fixed(value)) + Text(String(format: String(localized: "%lld %@", comment: "Distance label"), value, distanceUnit.label(for: Double(value)))).tag(AlertRadiusChoice.fixed(value)) } } Text(alertRadiusGuidance) .font(.caption) .foregroundStyle(.secondary) if enabled && monitoredCount == 0 { - Text("No stations monitored yet — open the Stations tab to load prices first.") + Text(String(localized: "No stations monitored yet — open the Stations tab to load prices first.", comment: "No monitored stations hint")) .font(.caption) .foregroundStyle(.secondary) } } header: { - Text("Proximity alerts") + Text(String(localized: "Proximity alerts", comment: "Proximity alerts section title")) } footer: { - Text("When you approach a station that is the cheapest within the radius, FuelBoard sends a notification — even with the app closed. Nearby stations selling \(fuel.displayName.lowercased()) are watched — favourites get priority — and each station alerts at most once per hour. Follow search mirrors the Stations-tab distance, capped at 8 miles for reliable geofencing. Smaller radii ping more often but later; larger radii ping rarely in town but earlier from afar.") + Text(String(format: String(localized: "When you approach a station that is the cheapest within the radius, FuelBoard sends a notification — even with the app closed. Nearby stations selling %@ are watched — favourites get priority — and each station alerts at most once per hour. Follow search mirrors the Stations-tab distance, capped at 8 miles for reliable geofencing. Smaller radii ping more often but later; larger radii ping rarely in town but earlier from afar.", comment: "Proximity alerts section footer"), fuel.displayName.lowercased())) } if enabled, let lastAlert { - Section("Last alert") { + Section(String(localized: "Last alert", comment: "Last alert section title")) { Label(lastAlert, systemImage: "bell.badge.fill") .font(.footnote) } } Section { - Toggle("Cheapest station", isOn: $liveActivityEnabled) - Picker("Fuel", selection: $liveActivityFuel) { + Toggle(String(localized: "Cheapest station", comment: "Live Activity toggle"), isOn: $liveActivityEnabled) + Picker(String(localized: "Fuel", comment: "Fuel picker title"), selection: $liveActivityFuel) { ForEach(FuelType.allCases) { fuel in Text(fuel.displayName).tag(fuel) } } - Picker("Distance", selection: laChoice) { - Text("Follow search (\(searchInUnitLabel))").tag(LADistanceChoice.followSearch) + Picker(String(localized: "Distance", comment: "Distance picker title"), selection: laChoice) { + Text(String(format: String(localized: "Follow search (%@)", comment: "Follow current station search distance option"), searchInUnitLabel)).tag(LADistanceChoice.followSearch) ForEach(FuelStore.stationRadiusOptions, id: \.self) { miles in Text(distanceLabel(forMiles: miles)).tag(LADistanceChoice.fixed(miles)) } } } header: { - Text("Live Activity") + Text(String(localized: "Live Activity", comment: "Live Activity section title")) } footer: { - Text("Shows the cheapest station for the fuel and distance you pick here on the Lock Screen and Dynamic Island — independent of the Stations tab (choose Follow search to mirror its distance instead). Updates as you drive; tap to open directions. Live Activities can be disabled in System Settings → Live Activities.") + Text(String(localized: "Shows the cheapest station for the fuel and distance you pick here on the Lock Screen and Dynamic Island — independent of the Stations tab (choose Follow search to mirror its distance instead). Updates as you drive; tap to open directions. Live Activities can be disabled in System Settings → Live Activities.", comment: "Live Activity section footer")) } } - .navigationTitle("Alerts") + .navigationTitle(String(localized: "Alerts", comment: "Alerts tab title")) } } } diff --git a/FuelBoard/FavouritesView.swift b/FuelBoard/FavouritesView.swift index 7d539f2..6289da6 100644 --- a/FuelBoard/FavouritesView.swift +++ b/FuelBoard/FavouritesView.swift @@ -106,9 +106,9 @@ struct FavouritesView: View { Image(systemName: "star") .font(.system(size: 40)) .foregroundStyle(.secondary) - Text("No favourites yet") + Text(String(localized: "No favourites yet", comment: "Favourites empty-state title")) .font(.headline) - Text("Tap the star on any station in the Stations tab to pin it here. The first station in each list becomes the favourite used in single widgets — drag to put your pick first.") + Text(String(localized: "Tap the star on any station in the Stations tab to pin it here. The first station in each list becomes the favourite used in single widgets — drag to put your pick first.", comment: "Favourites empty-state explanation")) .font(.caption) .foregroundStyle(.secondary) .multilineTextAlignment(.center) @@ -117,7 +117,7 @@ struct FavouritesView: View { .padding(.vertical, 24) } } else { - Section("Fuel type") { + Section(String(localized: "Fuel type", comment: "Fuel type section title")) { FuelTypeSegmentedPicker( selection: $fuel, fuels: availableFuels @@ -142,21 +142,21 @@ struct FavouritesView: View { } .onMove(perform: moveFavourite) } header: { - Text("\(activeFuel.displayName) favourites") + Text(String(format: String(localized: "%@ favourites", comment: "Favourites section title for fuel type"), activeFuel.displayName)) } footer: { - Text("The first station is the favourite used in single widgets. Tap Edit, then drag to reorder.") + Text(String(localized: "The first station is the favourite used in single widgets. Tap Edit, then drag to reorder.", comment: "Favourites reorder explanation")) } if let cheapest = cheapest { Section { - Text("\(cheapest.name) is your cheapest \(activeFuel.displayName) favourite. Favourites are monitored automatically — switch to the Alerts tab and enable alerts to get pinged when you approach one that's the cheapest within the radius.") + Text(String(format: String(localized: "%@ is your cheapest %@ favourite. Favourites are monitored automatically — switch to the Alerts tab and enable alerts to get pinged when you approach one that's the cheapest within the radius.", comment: "Cheapest favourite explanation"), cheapest.name, activeFuel.displayName)) .font(.caption) .foregroundStyle(.secondary) } } } } - .navigationTitle("Favourites") + .navigationTitle(String(localized: "Favourites", comment: "Favourites tab title")) .toolbar { if !favourites.isEmpty { ToolbarItemGroup(placement: .topBarTrailing) { @@ -164,7 +164,7 @@ struct FavouritesView: View { showTrends = true } label: { Image(systemName: "chart.xyaxis.line") - .accessibilityLabel("Trends") + .accessibilityLabel(String(localized: "Trends", comment: "Trends button accessibility label")) } EditButton() } diff --git a/FuelBoard/en.lproj/Localizable.strings b/FuelBoard/en.lproj/Localizable.strings index 9939a52..a11dff5 100644 --- a/FuelBoard/en.lproj/Localizable.strings +++ b/FuelBoard/en.lproj/Localizable.strings @@ -182,3 +182,19 @@ "Check your internet connection" = "Check your internet connection"; "Tap to try again" = "Tap to try again"; "Check your internet connection. Tap to try again" = "Check your internet connection. Tap to try again"; + +/* Alerts and favourites localisation pass */ +"Cheapest favourite" = "Cheapest favourite"; +"Check now" = "Check now"; +"Price-drop alerts" = "Price-drop alerts"; +"Watches your cheapest saved favourite for the fuel you pick here. FuelBoard alerts when a different favourite becomes the cheapest, or when your current cheapest favourite drops by at least 1.0p. This is separate from the nearby-geofence alert, so you can monitor favourites without extra location noise." = "Watches your cheapest saved favourite for the fuel you pick here. FuelBoard alerts when a different favourite becomes the cheapest, or when your current cheapest favourite drops by at least 1.0p. This is separate from the nearby-geofence alert, so you can monitor favourites without extra location noise."; +"Cheapest station" = "Cheapest station"; +"Proximity alerts" = "Proximity alerts"; +"When you approach a station that is the cheapest within the radius, FuelBoard sends a notification — even with the app closed. Nearby stations selling %@ are watched — favourites get priority — and each station alerts at most once per hour. Follow search mirrors the Stations-tab distance, capped at 8 miles for reliable geofencing. Smaller radii ping more often but later; larger radii ping rarely in town but earlier from afar." = "When you approach a station that is the cheapest within the radius, FuelBoard sends a notification — even with the app closed. Nearby stations selling %@ are watched — favourites get priority — and each station alerts at most once per hour. Follow search mirrors the Stations-tab distance, capped at 8 miles for reliable geofencing. Smaller radii ping more often but later; larger radii ping rarely in town but earlier from afar."; +"Set-and-forget: alerts behave exactly like the Stations-tab search distance (capped at 8 miles for reliable geofencing)." = "Set-and-forget: alerts behave exactly like the Stations-tab search distance (capped at 8 miles for reliable geofencing)."; +"Town driving. Fires most often — you're rarely inside the circle for long, so pings are meaningful. Best for stop-start local trips." = "Town driving. Fires most often — you're rarely inside the circle for long, so pings are meaningful. Best for stop-start local trips."; +"City or suburb commute. Slightly earlier heads-up than the smallest option; still fires regularly around town." = "City or suburb commute. Slightly earlier heads-up than the smallest option; still fires regularly around town."; +"Mixed driving. Middle ground: heads-up a few minutes out, still works in most towns." = "Mixed driving. Middle ground: heads-up a few minutes out, still works in most towns."; +"A-roads and motorway exits. Wide early warning for longer trips — but in town it rarely fires, because you're usually already inside the circle." = "A-roads and motorway exits. Wide early warning for longer trips — but in town it rarely fires, because you're usually already inside the circle."; +"Long-distance journeys. Maximum reach: a ping when a cheap station is coming up from far out. In town this essentially never fires." = "Long-distance journeys. Maximum reach: a ping when a cheap station is coming up from far out. In town this essentially never fires."; +"%@ — %@" = "%@ — %@";