diff --git a/MapboxNavigation/Resources/Base.lproj/Localizable.strings b/MapboxNavigation/Resources/Base.lproj/Localizable.strings index 6139fadf79e..dc921028716 100644 --- a/MapboxNavigation/Resources/Base.lproj/Localizable.strings +++ b/MapboxNavigation/Resources/Base.lproj/Localizable.strings @@ -1,5 +1,8 @@ -/* Format for speech string; 1 = way name; 2 = distance */ -"CONTINUE" = "Continue on %1$@ for %2$@"; +/* Format for speech string after completing a maneuver and starting a new step; 1 = distance */ +"CONTINUE" = "Continue for %@"; + +/* Format for speech string after completing a maneuver and starting a new step; 1 = way name; 2 = distance */ +"CONTINUE_ON_ROAD" = "Continue on %1$@ for %2$@"; /* Format string for less than; 1 = duration remaining */ "LESS_THAN" = "<%@"; diff --git a/MapboxNavigation/Resources/ca.lproj/Localizable.strings b/MapboxNavigation/Resources/ca.lproj/Localizable.strings index ef07c081fe7..25db9925ffc 100644 --- a/MapboxNavigation/Resources/ca.lproj/Localizable.strings +++ b/MapboxNavigation/Resources/ca.lproj/Localizable.strings @@ -1,5 +1,5 @@ -/* Format for speech string; 1 = way name; 2 = distance */ -"CONTINUE" = "Continua per %1$@ cap a %2$@"; +/* Format for speech string after completing a maneuver and starting a new step; 1 = way name; 2 = distance */ +"CONTINUE_ON_ROAD" = "Continua per %1$@ cap a %2$@"; /* Format string for less than; 1 = duration remaining */ "LESS_THAN" = "<%@"; diff --git a/MapboxNavigation/Resources/es.lproj/Localizable.strings b/MapboxNavigation/Resources/es.lproj/Localizable.strings index cf8918f5fc9..3eeb74c65fb 100644 --- a/MapboxNavigation/Resources/es.lproj/Localizable.strings +++ b/MapboxNavigation/Resources/es.lproj/Localizable.strings @@ -1,5 +1,5 @@ -/* Format for speech string; 1 = way name; 2 = distance */ -"CONTINUE" = "Continúe en %1$@ por %2$@"; +/* Format for speech string after completing a maneuver and starting a new step; 1 = way name; 2 = distance */ +"CONTINUE_ON_ROAD" = "Continúe en %1$@ por %2$@"; /* Format string for less than; 1 = duration remaining */ "LESS_THAN" = "<%@"; diff --git a/MapboxNavigation/Resources/fr.lproj/Localizable.strings b/MapboxNavigation/Resources/fr.lproj/Localizable.strings index 331e65d5bb6..f487bfbabe6 100644 --- a/MapboxNavigation/Resources/fr.lproj/Localizable.strings +++ b/MapboxNavigation/Resources/fr.lproj/Localizable.strings @@ -1,5 +1,5 @@ -/* Format for speech string; 1 = way name; 2 = distance */ -"CONTINUE" = "Continuez sur %1$@ pendant %2$@"; +/* Format for speech string after completing a maneuver and starting a new step; 1 = way name; 2 = distance */ +"CONTINUE_ON_ROAD" = "Continuez sur %1$@ pendant %2$@"; /* Format string for less than; 1 = duration remaining */ "LESS_THAN" = "<%@"; diff --git a/MapboxNavigation/Resources/hu.lproj/Localizable.strings b/MapboxNavigation/Resources/hu.lproj/Localizable.strings index aede3a59cd1..2872556a526 100644 --- a/MapboxNavigation/Resources/hu.lproj/Localizable.strings +++ b/MapboxNavigation/Resources/hu.lproj/Localizable.strings @@ -1,5 +1,5 @@ -/* Format for speech string; 1 = way name; 2 = distance */ -"CONTINUE" = "Continue on %1$@ for %2$@"; +/* Format for speech string after completing a maneuver and starting a new step; 1 = way name; 2 = distance */ +"CONTINUE_ON_ROAD" = "Continue on %1$@ for %2$@"; /* Format string for less than; 1 = duration remaining */ "LESS_THAN" = "<%@"; diff --git a/MapboxNavigation/Resources/lt.lproj/Localizable.strings b/MapboxNavigation/Resources/lt.lproj/Localizable.strings index 778c50c9c84..d57a883e3ad 100644 --- a/MapboxNavigation/Resources/lt.lproj/Localizable.strings +++ b/MapboxNavigation/Resources/lt.lproj/Localizable.strings @@ -1,5 +1,5 @@ -/* Format for speech string; 1 = way name; 2 = distance */ -"CONTINUE" = "Tęskite %1$@ %2$@"; +/* Format for speech string after completing a maneuver and starting a new step; 1 = way name; 2 = distance */ +"CONTINUE_ON_ROAD" = "Tęskite %1$@ %2$@"; /* Format string for less than; 1 = duration remaining */ "LESS_THAN" = "iki %@"; diff --git a/MapboxNavigation/Resources/sv.lproj/Localizable.strings b/MapboxNavigation/Resources/sv.lproj/Localizable.strings index f97c44fc961..810b852203c 100644 --- a/MapboxNavigation/Resources/sv.lproj/Localizable.strings +++ b/MapboxNavigation/Resources/sv.lproj/Localizable.strings @@ -1,5 +1,5 @@ -/* Format for speech string; 1 = way name; 2 = distance */ -"CONTINUE" = "Fortsätt på %1$@ i %2$@"; +/* Format for speech string after completing a maneuver and starting a new step; 1 = way name; 2 = distance */ +"CONTINUE_ON_ROAD" = "Fortsätt på %1$@ i %2$@"; /* Format string for less than; 1 = duration remaining */ "LESS_THAN" = "<%@"; diff --git a/MapboxNavigation/Resources/vi.lproj/Localizable.strings b/MapboxNavigation/Resources/vi.lproj/Localizable.strings index 6945e488ca3..0aa305822ce 100644 --- a/MapboxNavigation/Resources/vi.lproj/Localizable.strings +++ b/MapboxNavigation/Resources/vi.lproj/Localizable.strings @@ -1,5 +1,5 @@ -/* Format for speech string; 1 = way name; 2 = distance */ -"CONTINUE" = "Chạy tiếp trên %1$@ cho %2$@"; +/* Format for speech string after completing a maneuver and starting a new step; 1 = way name; 2 = distance */ +"CONTINUE_ON_ROAD" = "Chạy tiếp trên %1$@ cho %2$@"; /* Format string for less than; 1 = duration remaining */ "LESS_THAN" = "<%@"; diff --git a/MapboxNavigation/Resources/zh-Hans.lproj/Localizable.strings b/MapboxNavigation/Resources/zh-Hans.lproj/Localizable.strings index 5dc34570f18..d735d09028b 100644 --- a/MapboxNavigation/Resources/zh-Hans.lproj/Localizable.strings +++ b/MapboxNavigation/Resources/zh-Hans.lproj/Localizable.strings @@ -1,5 +1,5 @@ -/* Format for speech string; 1 = way name; 2 = distance */ -"CONTINUE" = "继续沿%@行驶%@"; +/* Format for speech string after completing a maneuver and starting a new step; 1 = way name; 2 = distance */ +"CONTINUE_ON_ROAD" = "继续沿%@行驶%@"; /* Format string for less than; 1 = duration remaining */ "LESS_THAN" = "<%@"; diff --git a/MapboxNavigation/RouteManeuverViewController.swift b/MapboxNavigation/RouteManeuverViewController.swift index 168b4e6aed3..60f93eb0425 100644 --- a/MapboxNavigation/RouteManeuverViewController.swift +++ b/MapboxNavigation/RouteManeuverViewController.swift @@ -170,8 +170,7 @@ class RouteManeuverViewController: UIViewController { } func updateStreetNameForStep() { - let isMotorway = step?.intersections?.first?.outletRoadClasses?.contains(.motorway) ?? false - if isMotorway, let codes = step?.codes, let digitRange = codes.first?.rangeOfCharacter(from: .decimalDigits), !digitRange.isEmpty { + if let step = step, step.isNumberedMotorway, let codes = step.codes { destinationLabel.unabridgedText = codes.joined(separator: NSLocalizedString("REF_DELIMITER", bundle: .mapboxNavigation, value: " / ", comment: "Delimiter between route numbers in a road concurrency")) } else if let name = step?.names?.first { destinationLabel.unabridgedText = name diff --git a/MapboxNavigation/RouteStepFormatter.swift b/MapboxNavigation/RouteStepFormatter.swift index c4767bf0fa3..1e58f2ff3c5 100644 --- a/MapboxNavigation/RouteStepFormatter.swift +++ b/MapboxNavigation/RouteStepFormatter.swift @@ -38,3 +38,47 @@ public class RouteStepFormatter: Formatter { return false } } + +extension RouteStep { + /** + Returns true if the route travels on a motorway primarily identified by a route number rather than a road name. + */ + var isNumberedMotorway: Bool { + guard intersections?.first?.outletRoadClasses?.contains(.motorway) == true else { + return false + } + guard let codes = codes, let digitRange = codes.first?.rangeOfCharacter(from: .decimalDigits) else { + return false + } + return !digitRange.isEmpty + } + + /** + Returns a string describing the step’s road by its name, route number, or both, depending on the kind of road. + + - parameter markedUpWithSSML: True to wrap the name and route number in SSML tags that cause them to be read as addresses. + - returns: A string describing the step’s road, or `nil` if the step lacks the information needed to describe the step. + */ + func roadDescription(markedUpWithSSML: Bool) -> String? { + let addressSSML = { (text: String?) -> String? in + guard let text = text else { + return nil + } + return markedUpWithSSML ? "\(text.addingXMLEscapes)" : text + } + + let nameSSML = addressSSML(names?.first) + let codeSSML = addressSSML(codes?.first) + + if let codeSSML = codeSSML, nameSSML == nil || isNumberedMotorway { + return codeSSML + } else if let nameSSML = nameSSML { + if let codeSSML = codeSSML { + return String.localizedStringWithFormat(NSLocalizedString("NAME_AND_REF", bundle: .mapboxNavigation, value: "%@ (%@)", comment: "Format for speech string; 1 = way name; 2 = way route number"), nameSSML, codeSSML) + } else { + return nameSSML + } + } + return nil + } +} diff --git a/MapboxNavigation/RouteVoiceController.swift b/MapboxNavigation/RouteVoiceController.swift index 52720f19575..f6c8dfd469a 100644 --- a/MapboxNavigation/RouteVoiceController.swift +++ b/MapboxNavigation/RouteVoiceController.swift @@ -226,11 +226,17 @@ open class RouteVoiceController: NSObject, AVSpeechSynthesizerDelegate, AVAudioP if routeProgress.currentLegProgress.currentStep.maneuverType == .depart && alertLevel == .depart { if userDistance < minimumDistanceForHighAlert { text = String.localizedStringWithFormat(NSLocalizedString("LINKED_WITH_DISTANCE_UTTERANCE_FORMAT", bundle: .mapboxNavigation, value: "%@, then in %@, %@", comment: "Format for speech string; 1 = current instruction; 2 = formatted distance to the following linked instruction; 3 = that linked instruction"), currentInstruction!, escapeIfNecessary(maneuverVoiceDistanceFormatter.string(from: userDistance)), upComingInstruction) + } else if let roadDescription = step.roadDescription(markedUpWithSSML: markUpWithSSML) { + text = String.localizedStringWithFormat(NSLocalizedString("CONTINUE_ON_ROAD", bundle: .mapboxNavigation, value: "Continue on %@ for %@", comment: "Format for speech string after completing a maneuver and starting a new step; 1 = way name; 2 = distance"), roadDescription, escapeIfNecessary(maneuverVoiceDistanceFormatter.string(from: userDistance))) } else { - text = String.localizedStringWithFormat(NSLocalizedString("CONTINUE", bundle: .mapboxNavigation, value: "Continue on %@ for %@", comment: "Format for speech string; 1 = way name; 2 = distance"), localizeRoadDescription(step, markUpWithSSML: markUpWithSSML), escapeIfNecessary(maneuverVoiceDistanceFormatter.string(from: userDistance))) + text = String.localizedStringWithFormat(NSLocalizedString("CONTINUE", bundle: .mapboxNavigation, value: "Continue for %@", comment: "Format for speech string after completing a maneuver and starting a new step; 1 = distance"), escapeIfNecessary(maneuverVoiceDistanceFormatter.string(from: userDistance))) } } else if routeProgress.currentLegProgress.currentStep.distance > 2_000 && routeProgress.currentLegProgress.alertUserLevel == .low { - text = String.localizedStringWithFormat(NSLocalizedString("CONTINUE", bundle: .mapboxNavigation, value: "Continue on %@ for %@", comment: "Format for speech string; 1 = way name; 2 = distance"), localizeRoadDescription(step, markUpWithSSML: markUpWithSSML), escapeIfNecessary(maneuverVoiceDistanceFormatter.string(from: userDistance))) + if let roadDescription = step.roadDescription(markedUpWithSSML: markUpWithSSML) { + text = String.localizedStringWithFormat(NSLocalizedString("CONTINUE_ON_ROAD", bundle: .mapboxNavigation, value: "Continue on %@ for %@", comment: "Format for speech string after completing a maneuver and starting a new step; 1 = way name; 2 = distance"), roadDescription, escapeIfNecessary(maneuverVoiceDistanceFormatter.string(from: userDistance))) + } else { + text = String.localizedStringWithFormat(NSLocalizedString("CONTINUE", bundle: .mapboxNavigation, value: "Continue for %@", comment: "Format for speech string after completing a maneuver and starting a new step; 1 = distance"), escapeIfNecessary(maneuverVoiceDistanceFormatter.string(from: userDistance))) + } } else if alertLevel == .high && stepDistance < minimumDistanceForHighAlert { text = String.localizedStringWithFormat(NSLocalizedString("LINKED_UTTERANCE_FORMAT", bundle: .mapboxNavigation, value: "%@, then %@", comment: "Format for speech string; 1 = current instruction; 2 = the following linked instruction"), upComingInstruction, followOnInstruction) } else if alertLevel != .high { @@ -242,25 +248,6 @@ open class RouteVoiceController: NSObject, AVSpeechSynthesizerDelegate, AVAudioP return text } - func localizeRoadDescription(_ step: RouteStep, markUpWithSSML: Bool) -> String { - var road = "" - let escapeIfNecessary = {(distance: String) -> String in - return markUpWithSSML ? distance.addingXMLEscapes : distance - } - if let name = step.names?.first { - if let code = step.codes?.first { - let markedUpName = markUpWithSSML ? "\(name.addingXMLEscapes)" : name - let markedUpCode = markUpWithSSML ? "\(code.addingXMLEscapes)" : code - road = String.localizedStringWithFormat(NSLocalizedString("NAME_AND_REF", bundle: .mapboxNavigation, value: "%@ (%@)", comment: "Format for speech string; 1 = way name; 2 = way route number"), markedUpName, markedUpCode) - } else { - road = escapeIfNecessary(name) - } - } else if let code = step.codes?.first { - road = escapeIfNecessary(code) - } - return road - } - func speak(_ text: String, error: String? = nil) { // Note why it failed if let error = error {