Skip to content

Commit 32df9ac

Browse files
authored
Localize loop error messages (#781)
* Localize loop error messages * Refactor error strings into enums to separate out presentation concerns and avoid duplication
1 parent ac9c150 commit 32df9ac

File tree

3 files changed

+83
-24
lines changed

3 files changed

+83
-24
lines changed

Loop/Managers/DeviceDataManager.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ extension DeviceDataManager: DoseStoreDelegate {
311311
extension DeviceDataManager {
312312
func enactBolus(units: Double, at startDate: Date = Date(), completion: @escaping (_ error: Error?) -> Void) {
313313
guard let pumpManager = pumpManager else {
314-
completion(LoopError.configurationError("Pump Manager"))
314+
completion(LoopError.configurationError(.pumpManager))
315315
return
316316
}
317317

@@ -338,7 +338,7 @@ extension DeviceDataManager: LoopDataManagerDelegate {
338338
completion: @escaping (_ result: Result<DoseEntry>) -> Void
339339
) {
340340
guard let pumpManager = pumpManager else {
341-
completion(.failure(LoopError.configurationError("Pump Manager")))
341+
completion(.failure(LoopError.configurationError(.pumpManager)))
342342
return
343343
}
344344

Loop/Managers/LoopDataManager.swift

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ extension LoopDataManager {
612612
_ = updateGroup.wait(timeout: .distantFuture)
613613

614614
guard let lastGlucoseDate = latestGlucoseDate else {
615-
throw LoopError.missingDataError(details: "Glucose data not available", recovery: "Check your CGM data source")
615+
throw LoopError.missingDataError(.glucose)
616616
}
617617

618618
let retrospectiveStart = lastGlucoseDate.addingTimeInterval(-settings.retrospectiveCorrectionInterval)
@@ -738,7 +738,7 @@ extension LoopDataManager {
738738
dispatchPrecondition(condition: .onQueue(dataAccessQueue))
739739

740740
guard let basalRates = basalRateSchedule else {
741-
throw LoopError.configurationError("Basal Rate Schedule")
741+
throw LoopError.configurationError(.basalRateSchedule)
742742
}
743743

744744
let pendingTempBasalInsulin: Double
@@ -765,11 +765,11 @@ extension LoopDataManager {
765765
dispatchPrecondition(condition: .onQueue(dataAccessQueue))
766766

767767
guard let model = insulinModelSettings?.model else {
768-
throw LoopError.configurationError("Check settings")
768+
throw LoopError.configurationError(.insulinModel)
769769
}
770770

771771
guard let glucose = self.glucoseStore.latestGlucose else {
772-
throw LoopError.missingDataError(details: "Cannot predict glucose due to missing input data", recovery: "Check your CGM data source")
772+
throw LoopError.missingDataError(.glucose)
773773
}
774774

775775
var momentum: [GlucoseEffect] = []
@@ -810,13 +810,15 @@ extension LoopDataManager {
810810
*/
811811
private func updateRetrospectiveGlucoseEffect(effectDuration: TimeInterval = TimeInterval(minutes: 60)) throws {
812812
dispatchPrecondition(condition: .onQueue(dataAccessQueue))
813+
814+
guard let carbEffect = self.carbEffect else {
815+
self.retrospectivePredictedGlucose = nil
816+
throw LoopError.missingDataError(.carbEffect)
817+
}
813818

814-
guard
815-
let carbEffect = self.carbEffect,
816-
let insulinEffect = self.insulinEffect
817-
else {
819+
guard let insulinEffect = self.insulinEffect else {
818820
self.retrospectivePredictedGlucose = nil
819-
throw LoopError.missingDataError(details: "Cannot retrospect glucose due to missing input data", recovery: nil)
821+
throw LoopError.missingDataError(.insulinEffect)
820822
}
821823

822824
guard let change = retrospectiveGlucoseChange else {
@@ -858,12 +860,12 @@ extension LoopDataManager {
858860

859861
guard let glucose = glucoseStore.latestGlucose else {
860862
self.predictedGlucose = nil
861-
throw LoopError.missingDataError(details: "Glucose", recovery: "Check your CGM data source")
863+
throw LoopError.missingDataError(.glucose)
862864
}
863865

864866
guard let pumpStatusDate = doseStore.lastReservoirValue?.startDate else {
865867
self.predictedGlucose = nil
866-
throw LoopError.missingDataError(details: "Reservoir", recovery: "Check that your pump is in range")
868+
throw LoopError.missingDataError(.reservoir)
867869
}
868870

869871
let startDate = Date()
@@ -880,17 +882,17 @@ extension LoopDataManager {
880882

881883
guard glucoseMomentumEffect != nil else {
882884
self.predictedGlucose = nil
883-
throw LoopError.missingDataError(details: "Momentum effects", recovery: nil)
885+
throw LoopError.missingDataError(.momentumEffect)
884886
}
885887

886888
guard carbEffect != nil else {
887889
self.predictedGlucose = nil
888-
throw LoopError.missingDataError(details: "Carb effects", recovery: nil)
890+
throw LoopError.missingDataError(.carbEffect)
889891
}
890892

891893
guard insulinEffect != nil else {
892894
self.predictedGlucose = nil
893-
throw LoopError.missingDataError(details: "Insulin effects", recovery: nil)
895+
throw LoopError.missingDataError(.insulinEffect)
894896
}
895897

896898
let predictedGlucose = try predictGlucose(using: settings.enabledEffects)
@@ -904,7 +906,7 @@ extension LoopDataManager {
904906
let maxBolus = settings.maximumBolus,
905907
let model = insulinModelSettings?.model
906908
else {
907-
throw LoopError.configurationError("Check settings")
909+
throw LoopError.configurationError(.generalSettings)
908910
}
909911

910912
guard lastRequestedBolus == nil

Loop/Models/LoopError.swift

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,76 @@
99
import Foundation
1010
import LoopKit
1111

12+
enum ConfigurationErrorDetail {
13+
case pumpManager
14+
case basalRateSchedule
15+
case insulinModel
16+
case generalSettings
17+
18+
func localized() -> String {
19+
switch self {
20+
case .pumpManager:
21+
return NSLocalizedString("Pump Manager", comment: "Details for configuration error when pump manager is missing")
22+
case .basalRateSchedule:
23+
return NSLocalizedString("Basal Rate Schedule", comment: "Details for configuration error when basal rate schedule is missing")
24+
case .insulinModel:
25+
return NSLocalizedString("Insulin Model", comment: "Details for configuration error when insulin model is missing")
26+
case .generalSettings:
27+
return NSLocalizedString("Check settings", comment: "Details for configuration error when one or more loop settings are missing")
28+
}
29+
}
30+
}
31+
32+
enum MissingDataErrorDetail {
33+
case glucose
34+
case reservoir
35+
case momentumEffect
36+
case carbEffect
37+
case insulinEffect
38+
39+
var localizedDetail: String {
40+
switch self {
41+
case .glucose:
42+
return NSLocalizedString("Glucose data not available", comment: "Description of error when glucose data is missing")
43+
case .reservoir:
44+
return NSLocalizedString("Reservoir", comment: "Details for missing data error when reservoir data is missing")
45+
case .momentumEffect:
46+
return NSLocalizedString("Momentum effects", comment: "Details for missing data error when momentum effects are missing")
47+
case .carbEffect:
48+
return NSLocalizedString("Carb effects", comment: "Details for missing data error when carb effects are missing")
49+
case .insulinEffect:
50+
return NSLocalizedString("Insulin effects", comment: "Details for missing data error when insulin effects are missing")
51+
}
52+
}
53+
54+
var localizedRecovery: String? {
55+
switch self {
56+
case .glucose:
57+
return NSLocalizedString("Check your CGM data source", comment: "Recovery suggestion when glucose data is missing")
58+
case .reservoir:
59+
return NSLocalizedString("Check that your pump is in range", comment: "Recovery suggestion when reservoir data is missing")
60+
case .momentumEffect:
61+
return nil
62+
case .carbEffect:
63+
return nil
64+
case .insulinEffect:
65+
return nil
66+
}
67+
}
68+
}
1269

1370
enum LoopError: Error {
1471
// A bolus failed to start
1572
case bolusCommand(SetBolusError)
1673

1774
// Missing or unexpected configuration values
18-
case configurationError(String)
75+
case configurationError(ConfigurationErrorDetail)
1976

2077
// No connected devices, or failure during device connection
2178
case connectionError
2279

2380
// Missing data required to perform an action
24-
case missingDataError(details: String, recovery: String?)
81+
case missingDataError(MissingDataErrorDetail)
2582

2683
// Glucose data is too old to perform action
2784
case glucoseTooOld(date: Date)
@@ -41,8 +98,8 @@ extension LoopError: LocalizedError {
4198

4299
public var recoverySuggestion: String? {
43100
switch self {
44-
case .missingDataError(_, let recovery):
45-
return recovery;
101+
case .missingDataError(let detail):
102+
return detail.localizedRecovery;
46103
default:
47104
return nil;
48105
}
@@ -57,11 +114,11 @@ extension LoopError: LocalizedError {
57114
case .bolusCommand(let error):
58115
return error.errorDescription
59116
case .configurationError(let details):
60-
return String(format: NSLocalizedString("Configuration Error: %1$@", comment: "The error message displayed for configuration errors. (1: configuration error details)"), details)
117+
return String(format: NSLocalizedString("Configuration Error: %1$@", comment: "The error message displayed for configuration errors. (1: configuration error details)"), details.localized())
61118
case .connectionError:
62119
return NSLocalizedString("No connected devices, or failure during device connection", comment: "The error message displayed for device connection errors.")
63-
case .missingDataError(let details, _):
64-
return String(format: NSLocalizedString("Missing data: %1$@", comment: "The error message for missing data. (1: missing data details)"), details)
120+
case .missingDataError(let details):
121+
return String(format: NSLocalizedString("Missing data: %1$@", comment: "The error message for missing data. (1: missing data details)"), details.localizedDetail)
65122
case .glucoseTooOld(let date):
66123
let minutes = formatter.string(from: -date.timeIntervalSinceNow) ?? ""
67124
return String(format: NSLocalizedString("Glucose data is %1$@ old", comment: "The error message when glucose data is too old to be used. (1: glucose data age in minutes)"), minutes)

0 commit comments

Comments
 (0)