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 {