diff --git a/Common/FeatureFlags.swift b/Common/FeatureFlags.swift index 0c6440b564..3633733070 100644 --- a/Common/FeatureFlags.swift +++ b/Common/FeatureFlags.swift @@ -39,6 +39,7 @@ struct FeatureFlagConfiguration: Decodable { let profileExpirationSettingsViewEnabled: Bool let missedMealNotifications: Bool let allowAlgorithmExperiments: Bool + let devBranchWarningEnabled: Bool fileprivate init() { @@ -232,6 +233,13 @@ struct FeatureFlagConfiguration: Decodable { #else self.allowAlgorithmExperiments = false #endif + + // Swift compiler config is inverse, since the default state is enabled. + #if DEV_BRANCH_WARNING_DISABLED + self.devBranchWarningEnabled = false + #else + self.devBranchWarningEnabled = true + #endif } } @@ -267,6 +275,7 @@ extension FeatureFlagConfiguration : CustomDebugStringConvertible { "* profileExpirationSettingsViewEnabled: \(profileExpirationSettingsViewEnabled)", "* missedMealNotifications: \(missedMealNotifications)", "* allowAlgorithmExperiments: \(allowAlgorithmExperiments)", + "* devBranchWarningEnabled: \(devBranchWarningEnabled)", "* allowExperimentalFeatures: \(allowExperimentalFeatures)" ].joined(separator: "\n") } diff --git a/Common/hi.lproj/Intents.strings b/Common/hi.lproj/Intents.strings deleted file mode 100644 index 69202aa99c..0000000000 --- a/Common/hi.lproj/Intents.strings +++ /dev/null @@ -1,36 +0,0 @@ -/* (No Comment) */ -"9KhaIS" = "I've set the preset"; - -/* (No Comment) */ -"80eo5o" = "Add Carb Entry"; - -/* (No Comment) */ -"b085BW" = "I wasn't able to set the preset."; - -/* (No Comment) */ -"I4OZy8" = "Enable Override Preset"; - -/* (No Comment) */ -"lYMuWV" = "Override Name"; - -/* (No Comment) */ -"nDKAmn" = "What's the name of the override you'd like to set?"; - -/* (No Comment) */ -"OcNxIj" = "Add Carb Entry"; - -/* (No Comment) */ -"oLQSsJ" = "Enable '${overrideName}' Override Preset"; - -/* (No Comment) */ -"XNNmtH" = "Enable preset in Loop"; - -/* (No Comment) */ -"yBzwCL" = "Override Selection"; - -/* (No Comment) */ -"yc02Yq" = "Add a carb entry to Loop"; - -/* (No Comment) */ -"ZZ3mtM" = "Enable an override preset in Loop"; - diff --git a/Common/ce.lproj/Intents.strings b/Common/ko.lproj/Intents.strings similarity index 68% rename from Common/ce.lproj/Intents.strings rename to Common/ko.lproj/Intents.strings index 69202aa99c..853af215c0 100644 --- a/Common/ce.lproj/Intents.strings +++ b/Common/ko.lproj/Intents.strings @@ -1,36 +1,24 @@ -/* (No Comment) */ -"9KhaIS" = "I've set the preset"; - -/* (No Comment) */ "80eo5o" = "Add Carb Entry"; -/* (No Comment) */ -"b085BW" = "I wasn't able to set the preset."; +"9KhaIS" = "I've set the preset"; -/* (No Comment) */ "I4OZy8" = "Enable Override Preset"; -/* (No Comment) */ +"OcNxIj" = "Add Carb Entry"; + +"XNNmtH" = "Enable preset in Loop"; + +"ZZ3mtM" = "Enable an override preset in Loop"; + +"b085BW" = "I wasn't able to set the preset."; + "lYMuWV" = "Override Name"; -/* (No Comment) */ "nDKAmn" = "What's the name of the override you'd like to set?"; -/* (No Comment) */ -"OcNxIj" = "Add Carb Entry"; - -/* (No Comment) */ "oLQSsJ" = "Enable '${overrideName}' Override Preset"; -/* (No Comment) */ -"XNNmtH" = "Enable preset in Loop"; - -/* (No Comment) */ "yBzwCL" = "Override Selection"; -/* (No Comment) */ "yc02Yq" = "Add a carb entry to Loop"; -/* (No Comment) */ -"ZZ3mtM" = "Enable an override preset in Loop"; - diff --git a/Common/ko.lproj/Localizable.strings b/Common/ko.lproj/Localizable.strings new file mode 100644 index 0000000000..e0fb9dff1b --- /dev/null +++ b/Common/ko.lproj/Localizable.strings @@ -0,0 +1,24 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Add Carb Entry"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + diff --git a/Common/ro.lproj/Intents.strings b/Common/ro.lproj/Intents.strings index 2d7d672b4d..d6634a6a46 100644 --- a/Common/ro.lproj/Intents.strings +++ b/Common/ro.lproj/Intents.strings @@ -2,25 +2,25 @@ "9KhaIS" = "I've set the preset"; /* (No Comment) */ -"80eo5o" = "Adaugă carbohidrați"; +"80eo5o" = "Adăugați carbohidrați"; /* (No Comment) */ "b085BW" = "Nu am reușit să setez presetarea."; /* (No Comment) */ -"I4OZy8" = "Activare modificarea personalizată presetată"; +"I4OZy8" = "Activați suprascriere presetată"; /* (No Comment) */ -"lYMuWV" = "Denumirea modificării"; +"lYMuWV" = "Nume suprascriere"; /* (No Comment) */ -"nDKAmn" = "Cum se numește modificarea pe care doriți să o setați?"; +"nDKAmn" = "Cum se numește suprascrierea pe care doriți să o setați?"; /* (No Comment) */ -"OcNxIj" = "Adaugă carbohidrați"; +"OcNxIj" = "Adăugați carbohidrați"; /* (No Comment) */ -"oLQSsJ" = "Activează „$”{overrideName} Modificare Personalizată"; +"oLQSsJ" = "Activați Suprascrierea Personalizată „$”{overrideName}"; /* (No Comment) */ "XNNmtH" = "Activați presetarea în Loop"; @@ -29,8 +29,8 @@ "yBzwCL" = "Selecție modificare"; /* (No Comment) */ -"yc02Yq" = "Adaugă carbohidrați în Loop"; +"yc02Yq" = "Adăugați carbohidrați în Loop"; /* (No Comment) */ -"ZZ3mtM" = "Activați o presetare personalizată în Loop"; +"ZZ3mtM" = "Activați o suprascriere presetată în Loop"; diff --git a/Learn/ko.lproj/Localizable.strings b/Learn/ko.lproj/Localizable.strings new file mode 100644 index 0000000000..44fdc3083b --- /dev/null +++ b/Learn/ko.lproj/Localizable.strings @@ -0,0 +1,32 @@ +/* Lesson subtitle */ +"Computes the percentage of glucose measurements within a specified range" = "Computes the percentage of glucose measurements within a specified range"; + +/* Title of the button to begin lesson execution */ +"Continue" = "Continue"; + +/* Placeholder for upper range entry */ +"Maximum" = "Maximum"; + +/* Placeholder for lower range entry */ +"Minimum" = "Minimum"; + +/* Lesson title */ +"Modal Day" = "Modal Day"; + +/* Lesson result text for no data */ +"No data available" = "No data available"; + +/* Section title for glucose range */ +"Range" = "Range"; + +/* Title of config entry */ +"Start Date" = "Start Date"; + +/* Lesson title */ +"Time in Range" = "Time in Range"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "Visualizes the most frequent glucose values by time of day"; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "Weeks"; diff --git a/Learn/ko.lproj/Main.strings b/Learn/ko.lproj/Main.strings new file mode 100644 index 0000000000..6b8f04c045 --- /dev/null +++ b/Learn/ko.lproj/Main.strings @@ -0,0 +1,3 @@ + +/* Class = "UINavigationItem"; title = "Learn"; ObjectID = "8hF-Ij-B7m"; */ +"8hF-Ij-B7m.title" = "Learn"; diff --git a/Loop Status Extension/mul.lproj/MainInterface.xcstrings b/Loop Status Extension/mul.lproj/MainInterface.xcstrings index a49d7aa812..a75d5d94ff 100644 --- a/Loop Status Extension/mul.lproj/MainInterface.xcstrings +++ b/Loop Status Extension/mul.lproj/MainInterface.xcstrings @@ -91,7 +91,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Eventual 92 mg/dL" + "value" : "Estimată să ajungă la 92 mg/dL" } }, "ru" : { diff --git a/Loop Widget Extension/Bootstrap/Localizable.xcstrings b/Loop Widget Extension/Bootstrap/Localizable.xcstrings index 9ada460552..b6b231104c 100644 --- a/Loop Widget Extension/Bootstrap/Localizable.xcstrings +++ b/Loop Widget Extension/Bootstrap/Localizable.xcstrings @@ -643,7 +643,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Data" + "value" : "Dată" } } } @@ -784,7 +784,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Închidere" + "value" : "Sfârșit" } } } @@ -1099,7 +1099,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Widgetul de stare Loop" + "value" : "Widget de stare Loop" } }, "ru" : { @@ -1384,7 +1384,13 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Deschide aplicația pentru a actualiza widgetul" + "value" : "Deschideți aplicația pentru a actualiza widgetul" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开应用以更新小部件" } } } @@ -1406,7 +1412,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Suprascriere presetare" + "value" : "Suprascriere presetată" } } } @@ -1630,7 +1636,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Pornește" + "value" : "Start" } } } diff --git a/Loop Widget Extension/Live Activity/ChartView.swift b/Loop Widget Extension/Live Activity/ChartView.swift index d5a0aca0a6..241c1f4667 100644 --- a/Loop Widget Extension/Live Activity/ChartView.swift +++ b/Loop Widget Extension/Live Activity/ChartView.swift @@ -18,7 +18,21 @@ struct ChartView: View { private let preset: Preset? private let yAxisMarks: [Double] private let colorGradient: LinearGradient - + + private static let colorInRange = Color.green + private static let colorBelowRange = Color.red + private static let colorAboveRange = Color.orange + + // Infer chartable increment from yAxisMarks: mmol/L values are always below 40, mg/dL above 54. + private var chartableIncrement: Double { (yAxisMarks.max() ?? 100) < 40 ? 1.0/25.0 : 1.0 } + + // When min == max the rectangle has zero height and is invisible. Mirror the main app's + // doubleRangeWithMinimumIncrement logic by expanding by one chartable increment each side. + private func adjustedRange(min minValue: Double, max maxValue: Double) -> (min: Double, max: Double) { + guard (maxValue - minValue) < .ulpOfOne else { return (minValue, maxValue) } + return (minValue - 3 * chartableIncrement, maxValue + 3 * chartableIncrement) + } + init(glucoseSamples: [GlucoseSampleAttributes], predicatedGlucose: [Double], predicatedStartDate: Date?, predicatedInterval: TimeInterval?, useLimits: Bool, lowerLimit: Double, upperLimit: Double, glucoseRanges: [GlucoseRangeValue], preset: Preset?, yAxisMarks: [Double]) { self.glucoseSampleData = ChartValues.convert(data: glucoseSamples, useLimits: useLimits, lowerLimit: lowerLimit, upperLimit: upperLimit) self.predicatedData = ChartValues.convert( @@ -29,7 +43,7 @@ struct ChartView: View { lowerLimit: lowerLimit, upperLimit: upperLimit ) - self.colorGradient = ChartView.getGradient(useLimits: useLimits, lowerLimit: lowerLimit, upperLimit: upperLimit, highestValue: yAxisMarks.max() ?? 1) + self.colorGradient = ChartView.getGradient(useLimits: useLimits, lowerLimit: lowerLimit, upperLimit: upperLimit, lowestValue: predicatedGlucose.min() ?? 1, highestValue: predicatedGlucose.max() ?? 1) self.preset = preset self.glucoseRanges = glucoseRanges self.yAxisMarks = yAxisMarks @@ -41,22 +55,40 @@ struct ChartView: View { self.preset = preset self.glucoseRanges = glucoseRanges self.yAxisMarks = yAxisMarks - self.colorGradient = ChartView.getGradient(useLimits: useLimits, lowerLimit: lowerLimit, upperLimit: upperLimit, highestValue: yAxisMarks.max() ?? 1) + self.colorGradient = LinearGradient(colors: [], startPoint: .bottom, endPoint: .top) } - private static func getGradient(useLimits: Bool, lowerLimit: Double, upperLimit: Double, highestValue: Double) -> LinearGradient { + private static func getGradient(useLimits: Bool, lowerLimit: Double, upperLimit: Double, lowestValue: Double, highestValue: Double) -> LinearGradient { + var stops: [Gradient.Stop] = [Gradient.Stop(color: Color("glucose"), location: 0)] if useLimits { - let lowerStop = lowerLimit / highestValue - let upperStop = upperLimit / highestValue - stops = [ - Gradient.Stop(color: .red, location: 0), - Gradient.Stop(color: .red, location: lowerStop - 0.01), - Gradient.Stop(color: .green, location: lowerStop), - Gradient.Stop(color: .green, location: upperStop), - Gradient.Stop(color: .orange, location: upperStop + 0.01), - Gradient.Stop(color: .orange, location: 600), // Just use the mg/dl limit for the most upper value - ] + // For applying a color gradient to line data, the range of the plotted + // data maps to the space 0 to 1 for setting gradient stops, so normalize: + // Normalize the transition points to 0-1 space of the plotted range: + let lowerStop = (lowerLimit - lowestValue) / (highestValue - lowestValue) + let upperStop = (upperLimit - lowestValue) / (highestValue - lowestValue) + // Build up a set of stops, only using those in the 0-1 range: + stops = [] + var stopColor: Color + // Get the color for glucose at the minimum of the line: + if lowestValue < lowerLimit { + stopColor = colorBelowRange + } else if lowestValue < upperLimit { + stopColor = colorInRange + } else { + stopColor = colorAboveRange + } + stops.append(Gradient.Stop(color: stopColor, location: 0)) + // Add the transition stops if they are in the visible range: + if lowerStop > 0, lowerStop < 1 { + stops.append(Gradient.Stop(color: colorBelowRange, location: lowerStop)) + stops.append(Gradient.Stop(color: colorInRange, location: lowerStop + 0.01)) + } + if upperStop > 0, upperStop < 1 { + stops.append(Gradient.Stop(color: colorInRange, location: upperStop)) + stops.append(Gradient.Stop(color: colorAboveRange, location: upperStop + 0.01)) + } + } return LinearGradient( gradient: Gradient(stops: stops), @@ -68,26 +100,28 @@ struct ChartView: View { var body: some View { ZStack(alignment: Alignment(horizontal: .trailing, vertical: .top)){ Chart { - if let preset = self.preset, predicatedData.count > 0, preset.endDate > Date.now.addingTimeInterval(.hours(-6)) { + if let preset = self.preset, (preset.minValue > 0 || preset.maxValue > 0), predicatedData.count > 0, preset.endDate > Date.now.addingTimeInterval(.hours(-6)) { + let (presetMin, presetMax) = adjustedRange(min: preset.minValue, max: preset.maxValue) RectangleMark( xStart: .value("Start", preset.startDate), xEnd: .value("End", preset.endDate), - yStart: .value("Preset override", preset.minValue), - yEnd: .value("Preset override", preset.maxValue) + yStart: .value("Preset override", presetMin), + yEnd: .value("Preset override", presetMax) ) .foregroundStyle(.primary) .opacity(0.6) } ForEach(glucoseRanges) { item in + let (rangeMin, rangeMax) = adjustedRange(min: item.minValue, max: item.maxValue) RectangleMark( xStart: .value("Start", item.startDate), xEnd: .value("End", item.endDate), - yStart: .value("Glucose range", item.minValue), - yEnd: .value("Glucose range", item.maxValue) + yStart: .value("Glucose range", rangeMin), + yEnd: .value("Glucose range", rangeMax) ) .foregroundStyle(.primary) - .opacity(0.3) + .opacity(item.isOverride ? 0.6 : 0.3) } ForEach(glucoseSampleData) { item in @@ -107,9 +141,9 @@ struct ChartView: View { } } .chartForegroundStyleScale([ - "Good": .green, - "High": .orange, - "Low": .red, + "Good": Self.colorInRange, + "High": Self.colorAboveRange, + "Low": Self.colorBelowRange, "Default": Color("glucose") ]) .chartPlotStyle { plotContent in @@ -160,10 +194,10 @@ struct ChartValues: Identifiable { } static func convert(data: [Double], startDate: Date, interval: TimeInterval, useLimits: Bool, lowerLimit: Double, upperLimit: Double) -> [ChartValues] { - let twoHours = Date.now.addingTimeInterval(.hours(4)) - + let cutoff = adjustedChartEnd(startDate.addingTimeInterval(.hours(4))) + return data.enumerated().filter { (index, item) in - return startDate.addingTimeInterval(interval * Double(index)) < twoHours + return startDate.addingTimeInterval(interval * Double(index)) < cutoff }.map { (index, item) in return ChartValues( x: startDate.addingTimeInterval(interval * Double(index)), @@ -172,6 +206,13 @@ struct ChartValues: Identifiable { ) } } + + private static func adjustedChartEnd(_ date: Date) -> Date { + let minute = Calendar.current.component(.minute, from: date) + guard minute < 30 else { return date } + let startOfHour = Calendar.current.dateInterval(of: .hour, for: date)!.start + return startOfHour.addingTimeInterval(.minutes(30)) + } static func convert(data: [GlucoseSampleAttributes], useLimits: Bool, lowerLimit: Double, upperLimit: Double) -> [ChartValues] { return data.map { item in diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 4767ba3142..005d0e00be 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -497,6 +497,7 @@ C1F00C60285A802A006302C5 /* SwiftCharts in Frameworks */ = {isa = PBXBuildFile; productRef = C1F00C5F285A802A006302C5 /* SwiftCharts */; }; C1F00C78285A8256006302C5 /* SwiftCharts in Embed Frameworks */ = {isa = PBXBuildFile; productRef = C1F00C5F285A802A006302C5 /* SwiftCharts */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; C1F2075C26D6F9B0007AB7EB /* AppExpirationAlerter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F2075B26D6F9B0007AB7EB /* AppExpirationAlerter.swift */; }; + FADE0001000000000000DE02 /* DevelopmentBranchAlerter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FADE0001000000000000DE01 /* DevelopmentBranchAlerter.swift */; }; C1F7822627CC056900C0919A /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F7822527CC056900C0919A /* SettingsManager.swift */; }; C1F8B243223E73FD00DD66CF /* BolusProgressTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */; }; C1FB428C217806A400FAB378 /* StateColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428B217806A300FAB378 /* StateColorPalette.swift */; }; @@ -805,7 +806,6 @@ 1DC63E7325351BDF004605DA /* TrueTime.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TrueTime.framework; path = Carthage/Build/iOS/TrueTime.framework; sourceTree = ""; }; 1DE09BA824A3E23F009EE9F9 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 1DFE9E162447B6270082C280 /* UserNotificationAlertSchedulerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationAlertSchedulerTests.swift; sourceTree = ""; }; - 3D03C6DA2AACE6AC00FDE5D2 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Intents.strings; sourceTree = ""; }; 3ED319862EB659E600820BCF /* BasalViewActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasalViewActivity.swift; sourceTree = ""; }; 3ED319872EB659E600820BCF /* ChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartView.swift; sourceTree = ""; }; 3ED319882EB659E600820BCF /* GlucoseLiveActivityConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseLiveActivityConfiguration.swift; sourceTree = ""; }; @@ -1207,6 +1207,10 @@ B4E96D5C248A82A2002DABAD /* StatusBarHUDView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatusBarHUDView.xib; sourceTree = ""; }; B4F3D25024AF890C0095CE44 /* BluetoothStateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothStateManager.swift; sourceTree = ""; }; B4FEEF7C24B8A71F00A8DF9B /* DeviceDataManager+DeviceStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeviceDataManager+DeviceStatus.swift"; sourceTree = ""; }; + B6656C6C2FB3EE1300FFC8BE /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Intents.strings; sourceTree = ""; }; + B6656C6D2FB3EE1400FFC8BE /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Main.strings; sourceTree = ""; }; + B6656C6E2FB3EE1500FFC8BE /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; + B6656C6F2FB3EE1600FFC8BE /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; B66D1F202E6A5D6500471149 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; B66D1F222E6A5D6500471149 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; B66D1F242E6A5D6500471149 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; @@ -1226,7 +1230,6 @@ B66D1F3F2E6A5D6600471149 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; B66D1F412E6A5D6600471149 /* mul */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = mul; path = mul.lproj/Interface.xcstrings; sourceTree = ""; }; B66D1F422E6A5D6600471149 /* mul */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = mul; path = mul.lproj/Main.xcstrings; sourceTree = ""; }; - B6F22EF52E95A03600CCA05F /* ce */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ce; path = ce.lproj/Intents.strings; sourceTree = ""; }; B6F22EF72E95A03800CCA05F /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Intents.strings; sourceTree = ""; }; B6F22EF92E95A03C00CCA05F /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Intents.strings; sourceTree = ""; }; C1004DEF2981F5B700B8CF94 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -1314,6 +1317,7 @@ C1EE9E802A38D0FB0064784A /* BuildDetails.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = BuildDetails.plist; sourceTree = ""; }; C1EF747128D6A44A00C8C083 /* CrashRecoveryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashRecoveryManager.swift; sourceTree = ""; }; C1F2075B26D6F9B0007AB7EB /* AppExpirationAlerter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppExpirationAlerter.swift; sourceTree = ""; }; + FADE0001000000000000DE01 /* DevelopmentBranchAlerter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevelopmentBranchAlerter.swift; sourceTree = ""; }; C1F48FF62995821600C8BD69 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; C1F7822527CC056900C0919A /* SettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = ""; }; C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusProgressTableViewCell.swift; sourceTree = ""; }; @@ -2035,6 +2039,7 @@ E9B355232935906B0076AB04 /* Missed Meal Detection */, 3ED319902EB65A2D00820BCF /* Live Activity */, C1F2075B26D6F9B0007AB7EB /* AppExpirationAlerter.swift */, + FADE0001000000000000DE01 /* DevelopmentBranchAlerter.swift */, A96DAC2B2838F31200D94E38 /* SharedLogging.swift */, 7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */, 84AA81E42A4A3981000B658B /* DeeplinkManager.swift */, @@ -3033,10 +3038,9 @@ ar, sk, cs, - hi, - ce, hu, uk, + ko, ); mainGroup = 43776F831B8022E90074EA36; packageReferences = ( @@ -3390,6 +3394,7 @@ 3ED319992EB65A6900820BCF /* LiveActivityManagementView.swift in Sources */, 3ED3199A2EB65A6900820BCF /* LiveActivityBottomRowManagerView.swift in Sources */, C1F2075C26D6F9B0007AB7EB /* AppExpirationAlerter.swift in Sources */, + FADE0001000000000000DE02 /* DevelopmentBranchAlerter.swift in Sources */, B4FEEF7D24B8A71F00A8DF9B /* DeviceDataManager+DeviceStatus.swift in Sources */, 142CB7592A60BF2E0075748A /* EditMode.swift in Sources */, E95D380324EADF36005E2F50 /* CarbStoreProtocol.swift in Sources */, @@ -3952,10 +3957,9 @@ C1C3127F297E4C0400296DA4 /* ar */, C1C247882995823200371B88 /* sk */, C1C5357529C6346A00E32DF9 /* cs */, - 3D03C6DA2AACE6AC00FDE5D2 /* hi */, - B6F22EF52E95A03600CCA05F /* ce */, B6F22EF72E95A03800CCA05F /* hu */, B6F22EF92E95A03C00CCA05F /* uk */, + B6656C6C2FB3EE1300FFC8BE /* ko */, ); name = Intents.intentdefinition; sourceTree = ""; @@ -3993,6 +3997,7 @@ F5D9C01C27DABBE1002E48F6 /* tr */, F5E0BDD827E1D71E0033557E /* he */, C1C3127A297E4BFE00296DA4 /* ar */, + B6656C6D2FB3EE1400FFC8BE /* ko */, ); name = Main.storyboard; sourceTree = ""; @@ -4028,6 +4033,7 @@ 7D9BF14623370E8D005DCFD6 /* ro */, F5D9C02727DABBE4002E48F6 /* tr */, F5E0BDE327E1D7230033557E /* he */, + B6656C6F2FB3EE1600FFC8BE /* ko */, ); name = Localizable.strings; sourceTree = ""; @@ -4056,6 +4062,7 @@ F5E0BDDA27E1D71F0033557E /* he */, C1C3127C297E4BFE00296DA4 /* ar */, C1C247892995823200371B88 /* sk */, + B6656C6E2FB3EE1500FFC8BE /* ko */, ); name = Localizable.strings; sourceTree = ""; diff --git a/Loop/Localizable.xcstrings b/Loop/Localizable.xcstrings index c298b9f5d1..44d94c62a4 100644 --- a/Loop/Localizable.xcstrings +++ b/Loop/Localizable.xcstrings @@ -95,7 +95,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : " (urmează a fi administrate: %@)" + "value" : " (în așteptare: %@)" } }, "ru" : { @@ -228,12 +228,6 @@ " remaining" : { "comment" : "remaining time in setting's profile expiration section", "localizations" : { - "ce" : { - "stringUnit" : { - "state" : "translated", - "value" : "remaining" - } - }, "cs" : { "stringUnit" : { "state" : "translated", @@ -261,7 +255,7 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "restant(s)" + "value" : " restant(s)" } }, "he" : { @@ -1916,7 +1910,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "%1$@ operează cu bucla închisă în poziția OFF. Pompa și CGM-ul vor continua să funcționeze, dar aplicația nu va ajusta automat dozarea." + "value" : "%1$@ operează cu bucla închisă în poziția OFF. Pompa și senzorul CGM vor continua să funcționeze, dar aplicația nu va ajusta automat dozarea." } }, "ru" : { @@ -4189,7 +4183,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "O pompă trebuie configurată înainte ca un bolus să poată fi livrat." + "value" : "O pompă trebuie configurată înainte ca un bolus să poată fi administrat." } }, "ru" : { @@ -4350,7 +4344,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "AcceptRecommendedBolus" + "value" : "AcceptațiBolusRecomandat" } }, "ru" : { @@ -5179,7 +5173,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adaugă carbohidrați" + "value" : "Adăugați carbohidrați" } }, "ru" : { @@ -5304,7 +5298,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adaugă CGM" + "value" : "Adăugați CGM" } }, "ru" : { @@ -5375,7 +5369,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adăugați elementul la ecranul de blocare / afișajul CarPlay" + "value" : "Adăugați element la ecranul de blocare / afișajul CarPlay" } } } @@ -5464,7 +5458,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adaugă masă" + "value" : "Adăugați masă" } }, "ru" : { @@ -5524,7 +5518,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adăugați o linie predictivă" + "value" : "Adăugați linie predictivă" } } } @@ -5613,7 +5607,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adaugă pompă" + "value" : "Adăugați pompă" } }, "ru" : { @@ -5720,7 +5714,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adaugă Serviciu" + "value" : "Adăugați Serviciu" } }, "ru" : { @@ -7048,7 +7042,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "已有更新的大剂量推荐值。" + "value" : "有新的大剂量推荐值可用" } } } @@ -7243,12 +7237,6 @@ "value" : "API Secret" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "एपीआई पास्वर्ड" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -7454,7 +7442,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Măr" + "value" : "Apple" } }, "zh-Hans" : { @@ -7852,7 +7840,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Sunteţi sigur/ă că vreți să ștergeţi toate datele %@.\n(Această acţiune nu este reversibilă)" + "value" : "Sunteți sigur că vreți să ștergeți toate datele %@.\n(Această acțiune nu este reversibilă)" } }, "ru" : { @@ -7965,7 +7953,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Sunteți sigur/ă că vreți să ștergeți acest CGM?" + "value" : "Sunteți sigur că vreți să ștergeți acest CGM?" } }, "ru" : { @@ -8047,7 +8035,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Sunteți sigur/ă că vreți să ștergeți acest aliment?" + "value" : "Sunteți sigur că vreți să ștergeți acest aliment?" } }, "zh-Hans" : { @@ -8131,7 +8119,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Sunteți sigur/ă că vreți să ștergeți acest serviciu?" + "value" : "Sunteți sigur că vreți să ștergeți acest serviciu?" } }, "ru" : { @@ -10055,12 +10043,6 @@ "value" : "בטל" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "निरस्त" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -10100,7 +10082,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Renunță" + "value" : "Anulați" } }, "ru" : { @@ -10225,7 +10207,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Întrerupere bolus" + "value" : "Întrerupere Bolus" } }, "ru" : { @@ -10457,7 +10439,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Intrare carbohidrați" + "value" : "Intrare Carbohidrați" } }, "ru" : { @@ -10731,7 +10713,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "碳水化合物吸收率" + "value" : "碳水化合物系数" } } } @@ -10827,7 +10809,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adaugă carbohidrați" + "value" : "Adăugați carbohidrați" } }, "ru" : { @@ -11248,7 +11230,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Atenţie" + "value" : "Atenție" } } } @@ -12482,7 +12464,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Buclă închisă necesită o sesiune activă de senzor CGM" + "value" : "Bucla închisă necesită o sesiune activă de senzor CGM" } }, "ru" : { @@ -13289,6 +13271,12 @@ "state" : "translated", "value" : "Configurați afișajul" } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示设置" + } } } }, @@ -13349,12 +13337,6 @@ "value" : "Continue" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "जारी" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -13394,7 +13376,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Continuă" + "value" : "Continuați" } }, "ru" : { @@ -13722,7 +13704,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu s-a putut reporni %1$@" + "value" : "Eșec la reporni %1$@" } } } @@ -14274,7 +14256,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "当前葡萄糖%1$@低于校正范围" + "value" : "当前血糖%1$@低于校正范围" } } } @@ -14932,7 +14914,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Ștergeți „ %@ ”?" + "value" : "Ștergeți „%@”?" } }, "zh-Hans" : { @@ -15028,7 +15010,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Șterge cont" + "value" : "Ștergeți cont" } }, "ru" : { @@ -15147,7 +15129,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Șterge tot" + "value" : "Ștergeți tot" } }, "ru" : { @@ -15438,7 +15420,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Șterge serviciul" + "value" : "Ștergeți serviciul" } }, "ru" : { @@ -15544,7 +15526,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Ștergeți datele CGM de testare" + "value" : "Ștergeți date CGM de testare" } }, "ru" : { @@ -15732,7 +15714,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Ștergere date pompă de testare" + "value" : "Ștergeți date pompă de testare" } }, "ru" : { @@ -15953,7 +15935,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Limite de livrare" + "value" : "Limite de administrare" } }, "ru" : { @@ -16131,7 +16113,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Ați intenționat să introduceți %1$@ grame ca cantitate de carbohidrați pentru această masă?" + "value" : "Ați intenționat să introduceți %1$@ grame drept cantitate de carbohidrați pentru această masă?" } }, "ru" : { @@ -16357,7 +16339,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Renunță" + "value" : "Anulați" } }, "ru" : { @@ -16398,7 +16380,13 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Culori de afișare pentru glicemie" + "value" : "Afișați culorile pentru glicemie" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "血糖颜色显示" } } } @@ -16410,7 +16398,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Opțiuni de control al afișajului" + "value" : "Afișați opțiunile de control" } } } @@ -16423,6 +16411,12 @@ "state" : "translated", "value" : "Afișați predicția în grafic" } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在图中显示预测结果" + } } } }, @@ -16434,6 +16428,12 @@ "state" : "translated", "value" : "Afișați până la 4 elemente. Eticheta de afișare este în paranteze." } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "最多显示4个项目。括号内为显示标签。" + } } } }, @@ -16508,7 +16508,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Realizat" + "value" : "Gata" } }, "ru" : { @@ -16686,7 +16686,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Strategie de dozare" + "value" : "Strategie dozare" } }, "ru" : { @@ -16852,7 +16852,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Activează Bluetooth" + "value" : "Activați Bluetooth" } }, "ru" : { @@ -16923,7 +16923,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Activează aplicarea parțială pe bază de glicemie" + "value" : "Activați aplicarea parțială pe bază de glicemie" } } } @@ -16970,7 +16970,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Activează corecția retrospectivă integrală" + "value" : "Activați corecția retrospectivă integrală" } } } @@ -17088,7 +17088,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Activează" + "value" : "Activați" } }, "ru" : { @@ -17278,7 +17278,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Introdu bolusul" + "value" : "Introduceți bolusul" } }, "ru" : { @@ -18154,7 +18154,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Ajunge la %@" + "value" : "În cele din urmă %@" } }, "ru" : { @@ -18516,7 +18516,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Exportă jurnalul de evenimente critice" + "value" : "Exportați jurnalul de evenimente critice" } }, "ru" : { @@ -18605,7 +18605,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Export-%1$@" + "value" : "Exportați-%1$@" } }, "ru" : { @@ -18688,7 +18688,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu s-a reușit reluarea administrării de insulină" + "value" : "Eșec la reluarea administrării de insulină" } }, "ru" : { @@ -19110,7 +19110,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Remediați acum activând Notificări, Alerte critice și Notificări sensibile la timp." + "value" : "Remediați acum activând Notificări, Alerte critice și Notificări urgente." } }, "ru" : { @@ -19205,7 +19205,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Tip de alimente" + "value" : "Tip aliment" } }, "ru" : { @@ -19407,7 +19407,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "În scopuri de siguranță, trebuie să permiteți alertele critice, sensibile la timp și permisiunile de notificare (alerte necritice) pe dispozitivul dumneavoastră pentru a continua să utilizați %1$@ și nu puteți dezactiva alarmele individuale." + "value" : "În scopuri de siguranță, trebuie să permiteți alertele critice, urgente și permisiunile de notificare (alerte necritice) pe dispozitivul dumneavoastră pentru a continua să utilizați %1$@ și nu puteți dezactiva alarmele individuale." } }, "ru" : { @@ -19883,12 +19883,6 @@ "value" : "Glucose" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "शुगर" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -20652,7 +20646,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Programul intervalului țintă de glucoză" + "value" : "Programul intervalului țintă de glicemie" } }, "ru" : { @@ -20710,7 +20704,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "SUNET HARDWARE" + "value" : "SUNETE HARDWARE" } }, "ru" : { @@ -20866,7 +20860,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Cum pot dezactiva sunetul doar alertelor sensibile la timp și non-critice?" + "value" : "Cum pot dezactiva sunetul doar alertelor urgente și non-critice?" } }, "ru" : { @@ -21025,6 +21019,9 @@ } } }, + "I'm a tester" : { + "comment" : "Button that dismisses the development build warning" + }, "If iOS Focus Mode is ON and Mute Alerts is OFF, Critical Alerts will still be delivered and non-Critical Alerts will be silenced until %1$@ is added to each Focus mode as an Allowed App." : { "comment" : "Focus modes descriptive text (1: app name)", "localizations" : { @@ -21357,7 +21354,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Informaţii" + "value" : "Informații" } }, "zh-Hans" : { @@ -22012,7 +22009,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Modelul de insulină" + "value" : "Model insulină" } }, "ru" : { @@ -22328,7 +22325,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Programul Factorului de Sensibilitate la Insulină" + "value" : "Orarul Factorului de Sensibilitate la Insulină" } }, "ru" : { @@ -23228,7 +23225,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Alertele critice iOS și alertele sensibile la timp sunt tipuri de notificări Apple. Sunt folosite pentru evenimente cu prioritate ridicată. Câteva exemple includ:" + "value" : "Alertele critice iOS și alertele urgente sunt tipuri de notificări Apple. Sunt folosite pentru evenimente cu prioritate ridicată. Câteva exemple includ:" } }, "ru" : { @@ -23375,7 +23372,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Generează raport" + "value" : "Generați raport" } }, "ru" : { @@ -24088,7 +24085,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Înregistrează Doză" + "value" : "Înregistrați Doză" } }, "ru" : { @@ -24493,7 +24490,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Eșec Loop" + "value" : "Eșec Buclă" } }, "ru" : { @@ -24837,7 +24834,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "În mod normal, Loop furnizează 40% din necesarul de insulină estimat pentru fiecare ciclu de dozare. \n \nCând experimentul Aplicare parțială bazată pe glicemie este activat, Loop va varia procentul de bolus recomandat administrat pentru fiecare ciclu în funcție de nivelul glicemiei. \n \nÎn apropierea intervalului de corecție, va utiliza 20% (similar cu o bazală temporar) și va crește treptat până la maximum 80% la glicemie ridicată (200 mg/dl, 11,1 mmol/l). \n \nVă rugăm să rețineți că în timpul creșterii rapide a glicemiei, cum ar fi după o masă neanunțată, această funcție, combinată cu efectele vitezei și ale corecției retrospective, poate duce la o doză mai mare decât ar necesita ISF-ul dumneavoastră." + "value" : "În mod normal, Loop furnizează 40% din necesarul de insulină estimat pentru fiecare ciclu de dozare. \n \nCând experimentul Aplicare parțială bazată pe glicemie este activat, Loop o să varieze procentul de bolus recomandat administrat pentru fiecare ciclu în funcție de nivelul glicemiei.\n \nÎn apropierea intervalului de corecție, va utiliza 20% (similar cu o bazală temporar) și va crește treptat până la maximum 80% la glicemie ridicată (200 mg/dl, 11,1 mmol/l). \n \nVă rugăm să rețineți că în timpul creșterii rapide a glicemiei, cum ar fi după o masă neanunțată, această funcție, combinată cu efectele vitezei și ale corecției retrospective, poate duce la o doză mai mare decât ar necesita ISF-ul dumneavoastră." } }, "zh-Hans" : { @@ -25080,7 +25077,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Loop va stabili ratele bazale temporare pentru a crește și reduce cantitatea de insulina livrată." + "value" : "Loop va stabili ratele bazale temporare pentru a crește și reduce cantitatea de insulina administrată." } }, "ru" : { @@ -25756,7 +25753,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Bolus prandial" + "value" : "Bolus pentru masă" } }, "ru" : { @@ -25964,7 +25961,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Notificări privind mesele pierdute" + "value" : "Notificări Mese Neanunțate" } }, "ru" : { @@ -26756,12 +26753,6 @@ "value" : "שם" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "नाम" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -27236,7 +27227,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nicio alertă nu va suna când este dezactivat sunetul. Odată ce această perioadă se încheie, alertele și alarmele dvs. vor relua normal." + "value" : "Nicio alertă nu va suna când este dezactivat sunetul. Odată ce această perioadă se încheie, alertele și alarmele dumneavoastră vor relua normal." } } } @@ -27711,7 +27702,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu există date recente despre glicemie" + "value" : "Lipsă glicemie recentă" } }, "ru" : { @@ -27806,7 +27797,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu există date recente despre glicemie" + "value" : "Lipsă date recente glicemie" } }, "ru" : { @@ -27901,7 +27892,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu există date recente despre pompă" + "value" : "Lipsă date recente pompă" } }, "ru" : { @@ -28287,7 +28278,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Livrarea notificărilor este setată la Rezumat programat în setările telefonului dvs. \n\nPentru a evita întârzierea primirii notificărilor de la %1$@ , vă recomandăm ca livrarea notificărilor să fie setată la Livrare imediată." + "value" : "Livrarea notificărilor este setată la Rezumat programat în setările telefonului dumneavoastră. \n\nPentru a evita întârzierea primirii notificărilor de la %1$@, vă recomandăm ca livrarea notificărilor să fie setată la Livrare imediată." } }, "ru" : { @@ -28447,7 +28438,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Notificări întârziate" + "value" : "Notificări Întârziate" } }, "ru" : { @@ -28607,7 +28598,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Notificările vă oferă informații importante despre aplicația %1$@ fără a fi necesar să deschideți aplicația. \n\nPăstrați-le activate în setările telefonului pentru a vă asigura că primiți notificări %1$@ , alerte critice și notificări sensibile la timp." + "value" : "Notificările vă oferă informații importante despre aplicația %1$@ fără a fi necesar să deschideți aplicația. \n\nPăstrați-le activate în setările telefonului pentru a vă asigura că primiți notificări %1$@, alerte critice și notificări urgente." } }, "ru" : { @@ -28791,7 +28782,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Oh nu! Loop s-a blocat în timpul administrării, iar ajustările insulinei au fost întrerupte până la închiderea acestui dialog. Istoricul administrării poate să nu fie exact. Vă rugăm să consultați graficele de livrare a insulinei și să vă monitorizați cu atenție glicemia." + "value" : "Oh nu! Loop s-a blocat în timpul administrării, iar ajustările insulinei au fost întrerupte până la închiderea acestui dialog. Istoricul administrării poate să nu fie exact. Vă rugăm să consultați graficele de administrare a insulinei și să vă monitorizați cu atenție glicemia." } }, "ru" : { @@ -32655,7 +32646,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Introducere de la distanță a bolusului: %@ U" + "value" : "Intrare de la distanță a bolusului: %@ U" } }, "ru" : { @@ -32738,7 +32729,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Introducere de la distanță a carbohidraților: %d grame" + "value" : "Intrare de la distanță a carbohidraților: %d grame" } }, "ru" : { @@ -33141,7 +33132,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Reîncearcă" + "value" : "Reîncercați" } }, "ru" : { @@ -33223,7 +33214,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Salvează" + "value" : "Salvați" } } } @@ -33317,7 +33308,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Salvați carbohidrații și livrați" + "value" : "Salvați carbohidrații și administrați" } }, "zh-Hans" : { @@ -33394,7 +33385,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Salvează fără bolusare" + "value" : "Salvați fără bolusare" } }, "ru" : { @@ -33559,7 +33550,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Selectarea unui aliment preferat în ecranul de introducere a carbohidraților completează automat câmpurile pentru cantitatea de carbohidrați, tipul de aliment și timpul de absorbție! Apăsați butonul de adăugare de mai jos pentru a crea primul dvs. aliment preferat!" + "value" : "Selectarea unui aliment preferat în ecranul de introducere a carbohidraților completează automat câmpurile pentru cantitatea de carbohidrați, tipul de aliment și timpul de absorbție! Apăsați butonul de adăugare de mai jos pentru a crea primul dumneavoastră aliment preferat!" } }, "zh-Hans" : { @@ -34027,7 +34018,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Afișează ultima eroare de loop" + "value" : "Afișează ultima eroare a buclei" } }, "ru" : { @@ -34128,7 +34119,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Calculator simplu de bolus" + "value" : "Calculator simplu bolus" } }, "ru" : { @@ -34217,7 +34208,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Calculator simplu al mesei" + "value" : "Calculator simplu masă" } }, "ru" : { @@ -34949,7 +34940,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Asistenţă" + "value" : "Asistență" } }, "ru" : { @@ -35151,6 +35142,9 @@ } } }, + "Switch to main" : { + "comment" : "Button on the development build warning that opens documentation about switching branches" + }, "Tap here to set up a CGM" : { "comment" : "Descriptive text for button to add CGM device", "localizations" : { @@ -35217,7 +35211,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Apăsați aici pentru a configura un CGM" + "value" : "Atingeți aici pentru a configura un CGM" } }, "ru" : { @@ -35312,7 +35306,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Apăsați aici pentru a configura o pompă de insulină" + "value" : "Atingeți aici pentru a configura o pompă de insulină" } }, "ru" : { @@ -35407,7 +35401,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Apăsați aici pentru a configura un Serviciu" + "value" : "Atingeți aici pentru a configura un Serviciu" } }, "ru" : { @@ -35502,7 +35496,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Atinge pentru a adăuga" + "value" : "Atingeți pentru a adăuga" } }, "ru" : { @@ -35615,7 +35609,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Apăsați pentru a relua" + "value" : "Atingeți pentru a relua" } }, "ru" : { @@ -36212,7 +36206,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Algoritmul de dozare al bolusurilor folosește o estimare mai conservatoare a glicemiei prognozate decât cea utilizată pentru a ajusta rata bazală. \n\n Ca rezultat, glicemia estimată după un bolus poate fi în continuare mai mare decât intervalul țintă." + "value" : "Algoritmul de dozare al bolusurilor folosește o estimare mai conservatoare a glicemiei prognozate decât cea utilizată pentru a ajusta rata bazală. \n\nCa rezultat, glicemia estimată după un bolus poate fi în continuare mai mare decât intervalul țintă." } }, "ru" : { @@ -36295,7 +36289,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Recomandarea pentru bolus a fost actualizată. Vă rugăm să reconfirmaţi valoarea bolusului." + "value" : "Recomandarea pentru bolus a fost actualizată. Vă rugăm să reconfirmați valoarea bolusului." } }, "ru" : { @@ -36903,7 +36897,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Trebuie sa configurați o valoare maximă pentru bolus înainte ca acesta să poată fi livrat." + "value" : "Trebuie sa configurați o valoare maximă pentru bolus înainte ca acesta să poată fi administrat." } }, "ru" : { @@ -37027,6 +37021,9 @@ } } }, + "This is the development version of Loop, built from the dev branch. Any updates on this branch may contain new, untested features, and may be unsafe. If you are not a tester, please do not use this branch, and switch to main." : { + "comment" : "Body of the warning shown at launch on development builds" + }, "This option only applies when Loop's Dosing Strategy is set to Automatic Bolus." : { "comment" : "String shown when glucose based partial application cannot be enabled because dosing strategy is not set to Automatic Bolus", "localizations" : { @@ -37127,7 +37124,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Alerte sensibile la timp" + "value" : "Alerte Urgente" } }, "ru" : { @@ -37198,7 +37195,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Notificări urgente" + "value" : "Notificări Urgente" } }, "ru" : { @@ -37410,7 +37407,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Dezactivați volumul pe dispozitivul iOS sau adăugați %1$@ ca aplicație permisă pentru fiecare Mod de concentrare. Alertele sensibile la timp și cele critice vor suna în continuare, dar alertele non-critice vor fi dezactivate." + "value" : "Dezactivați volumul pe dispozitivul iOS sau adăugați %1$@ ca aplicație permisă pentru fiecare Mod de concentrare. Alertele urgente și cele critice vor suna în continuare, dar alertele non-critice vor fi dezactivate." } }, "ru" : { @@ -37778,7 +37775,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu se poate conecta la pompă" + "value" : "Nu se poate accesa pompa" } }, "ru" : { @@ -37867,7 +37864,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu se pot salva Carbohidrații" + "value" : "Nu se pot salva carbohidrații" } }, "ru" : { @@ -37962,7 +37959,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu se poate salva glicemia manuala" + "value" : "Nu se poate salva glicemia manuală" } }, "ru" : { @@ -38051,7 +38048,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu s-a putut opri livrarea bolusului. Mutați iPhone-ul mai aproape de pompă și încercați din nou. Verificați istoricul administrării insulinei pentru detalii și monitorizați îndeaproape glicemia." + "value" : "Eșec la opri administrarea bolusului. Mutați telefonul mai aproape de pompă și încercați din nou. Verificați istoricul administrării insulinei pentru detalii și monitorizați îndeaproape glicemia." } }, "ru" : { @@ -38496,7 +38493,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Activează sunetul" + "value" : "Activați sunetul" } }, "ru" : { @@ -39218,7 +39215,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Folosește funcția Dezactivare sunet alerte. Aceasta îți permite să dezactivezi temporar toate alertele și alarmele prin intermediul aplicației %1$@, inclusiv alertele critice și alertele sensibile la timp." + "value" : "Folosește funcția Dezactivare sunet alerte. Aceasta îți permite să dezactivezi temporar toate alertele și alarmele prin intermediul aplicației %1$@, inclusiv alertele critice și alertele urgente." } }, "ru" : { @@ -39570,6 +39567,9 @@ } } }, + "Warning" : { + "comment" : "Title of the warning shown at launch on development builds" + }, "Warning! Safety notifications are turned OFF" : { "comment" : "Alert Permissions Need Attention alert title", "localizations" : { @@ -39688,7 +39688,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Care sunt exemple de alerte critice și alerte sensibile la timp?" + "value" : "Care sunt exemple de alerte critice și alerte urgente?" } }, "ru" : { @@ -40127,7 +40127,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "În timp ce alertele dezactivate sunt activate, toate alertele de la aplicația %1$@ inclusiv alertele critice și cele sensibile la timp, se vor afișa temporar fără sunete și vor vibra doar." + "value" : "În timp ce alertele dezactivate sunt activate, toate alertele de la aplicația %1$@ inclusiv alertele critice și cele urgente, se vor afișa temporar fără sunete și vor vibra doar." } }, "ru" : { @@ -40238,7 +40238,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "În timp ce încercam să repornesc %1$@ a apărut o eroare. \n \n %2$@" + "value" : "În timp încercării repornirii %1$@ a apărut o eroare. \n \n %2$@" } }, "ru" : { @@ -40695,7 +40695,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Este posibil să nu primiți alerte sonore, vizuale sau cu vibrații referitoare la informații critice de siguranță. \n\nPentru a remedia problema, atingeți „Setări” și asigurați-vă că notificările, alertele critice și notificările sensibile la timp sunt activate." + "value" : "Este posibil să nu primiți alerte sonore, vizuale sau cu vibrații referitoare la informații critice de siguranță. \n\nPentru a remedia problema, atingeți „Setări” și asigurați-vă că notificările, alertele critice și notificările urgente sunt activate." } }, "ru" : { @@ -41039,7 +41039,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Glicemia ta este sub limita de siguranță, %1$@." + "value" : "Glicemia dumneavoastră este sub limita de siguranță, %1$@." } }, "ru" : { diff --git a/Loop/Managers/DevelopmentBranchAlerter.swift b/Loop/Managers/DevelopmentBranchAlerter.swift new file mode 100644 index 0000000000..c6795f1d05 --- /dev/null +++ b/Loop/Managers/DevelopmentBranchAlerter.swift @@ -0,0 +1,50 @@ +// +// DevelopmentBranchAlerter.swift +// Loop +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import UIKit + +enum DevelopmentBranchAlerter { + + // The LoopWorkspace superproject branch this warning applies to. + private static let developmentBranchName = "dev" + + private static let switchToMainURL = URL(string: "https://loopkit.github.io/loopdocs/faqs/loop-faqs/#how-do-i-return-to-the-released-version")! + + /// Presents a blocking warning when this is a build from the development branch. + /// Shown on every launch; the alert can only be dismissed by an explicit choice. + static func alertIfNeeded(viewControllerToPresentFrom: UIViewController) { + guard FeatureFlags.devBranchWarningEnabled else { + return + } + + guard BuildDetails.default.workspaceGitBranch == developmentBranchName else { + return + } + + let alert = UIAlertController( + title: NSLocalizedString("Warning", comment: "Title of the warning shown at launch on development builds"), + message: NSLocalizedString("This is the development version of Loop, built from the dev branch. Any updates on this branch may contain new, untested features, and may be unsafe. If you are not a tester, please do not use this branch, and switch to main.", comment: "Body of the warning shown at launch on development builds"), + preferredStyle: .alert + ) + + alert.addAction(UIAlertAction( + title: NSLocalizedString("I'm a tester", comment: "Button that dismisses the development build warning"), + style: .default, + handler: nil + )) + + alert.addAction(UIAlertAction( + title: NSLocalizedString("Switch to main", comment: "Button on the development build warning that opens documentation about switching branches"), + style: .default, + handler: { _ in + UIApplication.shared.open(switchToMainURL) + } + )) + + viewControllerToPresentFrom.present(alert, animated: true) + } +} diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index e928c5e2d0..ff678a8824 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -535,7 +535,18 @@ final class DeviceDataManager { return nil } - return pumpManagerTypeByIdentifier(managerIdentifier) + if let pumpManager = pumpManagerTypeByIdentifier(managerIdentifier) { + return pumpManager + } + + /// The pumpManager was not found for managerIdentifier. If this was for an "Omnipod" (OmniKit) or + /// "Omnipod-DASH" (OmniBLE), have the universal "Omni" pumpManager (OmnipodKit) handle instead. + let OmniStr = "Omni" + if managerIdentifier.hasPrefix(OmniStr) { + return pumpManagerTypeByIdentifier(OmniStr) + } + + return nil } func pumpManagerFromRawValue(_ rawValue: [String: Any]) -> PumpManagerUI? { diff --git a/Loop/Managers/Live Activity/GlucoseActivityAttributes.swift b/Loop/Managers/Live Activity/GlucoseActivityAttributes.swift index 173a46cb86..1b3328d65b 100644 --- a/Loop/Managers/Live Activity/GlucoseActivityAttributes.swift +++ b/Loop/Managers/Live Activity/GlucoseActivityAttributes.swift @@ -64,6 +64,16 @@ public struct GlucoseRangeValue: Identifiable, Codable, Hashable { public let maxValue: Double public let startDate: Date public let endDate: Date + public let isOverride: Bool + + public init(id: UUID, minValue: Double, maxValue: Double, startDate: Date, endDate: Date, isOverride: Bool = false) { + self.id = id + self.minValue = minValue + self.maxValue = maxValue + self.startDate = startDate + self.endDate = endDate + self.isOverride = isOverride + } } public struct BottomRowItem: Codable, Hashable { diff --git a/Loop/Managers/Live Activity/LiveActivityManager.swift b/Loop/Managers/Live Activity/LiveActivityManager.swift index da1d4fdfa7..f7a4723f14 100644 --- a/Loop/Managers/Live Activity/LiveActivityManager.swift +++ b/Loop/Managers/Live Activity/LiveActivityManager.swift @@ -143,13 +143,21 @@ class LiveActivityManager : LiveActivityManagerProxy { var presetContext: Preset? = nil if let override = self.loopSettings.preMealOverride ?? self.loopSettings.scheduleOverride, let start = glucoseSamples.first?.startDate { - presetContext = Preset( - title: override.getTitle(), - startDate: max(override.startDate, start), - endDate: override.duration.isInfinite ? endDateChart : min(override.actualEndDate, endDateChart), - minValue: override.settings.targetRange?.lowerBound.doubleValue(for: unit) ?? 0, - maxValue: override.settings.targetRange?.upperBound.doubleValue(for: unit) ?? 0 - ) + let presetStart = max(override.startDate, start) + let presetEnd = override.duration.isInfinite ? endDateChart : min(override.actualEndDate, endDateChart) + // Only create a preset if it overlaps the chart window. If the override ended + // before the chart window starts (e.g. spacious mode only shows 2h of history), + // presetEnd < presetStart and drawing a RectangleMark with those backwards dates + // forces SwiftUI Charts to expand the x-axis far into the past. + if presetStart <= presetEnd { + presetContext = Preset( + title: override.getTitle(), + startDate: presetStart, + endDate: presetEnd, + minValue: override.settings.targetRange?.lowerBound.doubleValue(for: unit) ?? 0, + maxValue: override.settings.targetRange?.upperBound.doubleValue(for: unit) ?? 0 + ) + } } var glucoseRanges: [GlucoseRangeValue] = [] @@ -157,8 +165,8 @@ class LiveActivityManager : LiveActivityManagerProxy { glucoseRanges = getGlucoseRanges( glucoseRangeSchedule: glucoseRangeSchedule, presetContext: presetContext, - start: start, - end: endDateChart, + start: adjustedChartStart(start), + end: adjustedChartEnd(endDateChart), unit: unit ) } @@ -331,7 +339,7 @@ class LiveActivityManager : LiveActivityManagerProxy { // In compact mode, we only want to show the history let timeInterval: TimeInterval = self.settings.addPredictiveLine ? .hours(-2) : .hours(-6) self.glucoseStore.getGlucoseSamples( - start: Date.now.addingTimeInterval(timeInterval), + start: adjustedChartStart(Date.now.addingTimeInterval(timeInterval)), end: Date.now ) { result in switch (result) { @@ -349,6 +357,26 @@ class LiveActivityManager : LiveActivityManagerProxy { return samples } + // If the chart start falls past the half-hour mark (HH:31–HH:59), pull it back to HH:30 + // so that the nearest hour label is never truncated at the left edge. + private func adjustedChartStart(_ date: Date) -> Date { + let calendar = Calendar.current + let minute = calendar.component(.minute, from: date) + guard minute > 30 else { return date } + let startOfHour = calendar.dateInterval(of: .hour, for: date)!.start + return startOfHour.addingTimeInterval(.minutes(30)) + } + + // If the chart end falls before the half-hour mark (HH:00–HH:29), push it forward to HH:30 + // so that the nearest hour label is never truncated at the right edge. + private func adjustedChartEnd(_ date: Date) -> Date { + let calendar = Calendar.current + let minute = calendar.component(.minute, from: date) + guard minute < 30 else { return date } + let startOfHour = calendar.dateInterval(of: .hour, for: date)!.start + return startOfHour.addingTimeInterval(.minutes(30)) + } + private func getGlucoseRanges(glucoseRangeSchedule: GlucoseRangeSchedule, presetContext: Preset?, start: Date, end: Date, unit: HKUnit) -> [GlucoseRangeValue] { var glucoseRanges: [GlucoseRangeValue] = [] for item in glucoseRangeSchedule.quantityBetween(start: start, end: end) { @@ -358,8 +386,9 @@ class LiveActivityManager : LiveActivityManagerProxy { let endDate = min(item.endDate, end) if let presetContext = presetContext { + let noTargetRange = presetContext.minValue == 0 && presetContext.maxValue == 0 if presetContext.startDate > startDate, presetContext.endDate < endDate { - // A preset is active during this schedule + // Override entirely within this schedule segment glucoseRanges.append(GlucoseRangeValue( id: UUID(), minValue: minValue, @@ -367,6 +396,16 @@ class LiveActivityManager : LiveActivityManagerProxy { startDate: startDate, endDate: presetContext.startDate )) + if noTargetRange { + glucoseRanges.append(GlucoseRangeValue( + id: UUID(), + minValue: minValue, + maxValue: maxValue, + startDate: presetContext.startDate, + endDate: presetContext.endDate, + isOverride: true + )) + } glucoseRanges.append(GlucoseRangeValue( id: UUID(), minValue: minValue, @@ -375,7 +414,17 @@ class LiveActivityManager : LiveActivityManagerProxy { endDate: endDate )) } else if presetContext.endDate > startDate, presetContext.endDate < endDate { - // Cut off the start of the glucose target + // Override ends within this segment (started before) + if noTargetRange { + glucoseRanges.append(GlucoseRangeValue( + id: UUID(), + minValue: minValue, + maxValue: maxValue, + startDate: startDate, + endDate: presetContext.endDate, + isOverride: true + )) + } glucoseRanges.append(GlucoseRangeValue( id: UUID(), minValue: minValue, @@ -384,7 +433,7 @@ class LiveActivityManager : LiveActivityManagerProxy { endDate: endDate )) } else if presetContext.startDate < endDate, presetContext.startDate > startDate { - // Cut off the end of the glucose target + // Override starts within this segment (ends after) glucoseRanges.append(GlucoseRangeValue( id: UUID(), minValue: minValue, @@ -392,9 +441,31 @@ class LiveActivityManager : LiveActivityManagerProxy { startDate: startDate, endDate: presetContext.startDate )) + if noTargetRange { + glucoseRanges.append(GlucoseRangeValue( + id: UUID(), + minValue: minValue, + maxValue: maxValue, + startDate: presetContext.startDate, + endDate: endDate, + isOverride: true + )) + } if presetContext.endDate == end { break } + } else if presetContext.startDate <= startDate, presetContext.endDate >= endDate { + // Override completely covers this segment + if noTargetRange { + glucoseRanges.append(GlucoseRangeValue( + id: UUID(), + minValue: minValue, + maxValue: maxValue, + startDate: startDate, + endDate: endDate, + isOverride: true + )) + } } else { // No overlap with target and override glucoseRanges.append(GlucoseRangeValue( diff --git a/Loop/Managers/LoopAppManager.swift b/Loop/Managers/LoopAppManager.swift index b8e23d0bba..3f00104c5a 100644 --- a/Loop/Managers/LoopAppManager.swift +++ b/Loop/Managers/LoopAppManager.swift @@ -315,6 +315,10 @@ class LoopAppManager: NSObject { self.state = state.next alertManager.playbackAlertsFromPersistence() + + if let rootViewController = rootViewController { + DevelopmentBranchAlerter.alertIfNeeded(viewControllerToPresentFrom: rootViewController) + } } // MARK: - Life Cycle diff --git a/Loop/mul.lproj/Main.xcstrings b/Loop/mul.lproj/Main.xcstrings index 9dddf366e0..4316a6468f 100644 --- a/Loop/mul.lproj/Main.xcstrings +++ b/Loop/mul.lproj/Main.xcstrings @@ -1016,7 +1016,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Glicemia este estimată prin combinarea unui număr de date sursă. Folosiți acest instrument pentru a controla diverse surse de date și a felului în care influențează estimarea." + "value" : "Nivelul viitor al glicemiei este prezis prin combinarea efectelor mai multor factori de intrare. Utilizați acest instrument pentru a comuta diversele date de intrare și pentru a vedea cum se compară acestea cu predicția finală." } }, "ru" : { @@ -2444,7 +2444,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Glicemie estimată" + "value" : "Glicemie prognozată" } }, "ru" : { @@ -2806,12 +2806,6 @@ "value" : "Glucose" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "शुगर" - } - }, "it" : { "stringUnit" : { "state" : "translated", diff --git a/LoopCore/Localizable.xcstrings b/LoopCore/Localizable.xcstrings index e4062e82e0..9c2e507b20 100644 --- a/LoopCore/Localizable.xcstrings +++ b/LoopCore/Localizable.xcstrings @@ -165,6 +165,12 @@ "state" : "translated", "value" : "Carbohidrați activi (COB)" } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "活性碳水 (COB)" + } } } }, @@ -238,6 +244,12 @@ "state" : "translated", "value" : "Insulină activă (IOB)" } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "活性胰岛素(IOB)" + } } } }, @@ -449,6 +461,12 @@ "state" : "translated", "value" : "Glicemia curentă (Valoare și tendință)" } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "当前血糖(数值和箭头)" + } } } }, @@ -637,6 +655,12 @@ "state" : "translated", "value" : "Parcelă și rând" } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "图表和数据行" + } } } }, @@ -659,6 +683,12 @@ "state" : "translated", "value" : "Doar rând" } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "仅数据行" + } } } }, @@ -804,17 +834,35 @@ "state" : "translated", "value" : "Actualizat" } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新" + } } } }, "Updated (at)" : { "comment" : "Description for the Updated time selection for the Live Activity configuration", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktualisiert (am)" + } + }, "ro" : { "stringUnit" : { "state" : "translated", "value" : "Actualizat (la)" } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新(更新于)" + } } } }, diff --git a/LoopUI/Localizable.xcstrings b/LoopUI/Localizable.xcstrings index 19df68c2c1..a21b1ce012 100644 --- a/LoopUI/Localizable.xcstrings +++ b/LoopUI/Localizable.xcstrings @@ -132,7 +132,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "\n%1$@\n\nAtingeți pictogramele de stare ale CGM și ale pompei de insulină pentru mai multe informații. %2$@ va continua să încerce să finalizeze o buclă, dar verificați eventualele probleme de comunicare cu pompa și CGM-ul dvs." + "value" : "\n%1$@\n\nAtingeți pictogramele de stare ale CGM și ale pompei de insulină pentru mai multe informații. %2$@ va continua să încerce să finalizeze o buclă, dar verificați eventualele probleme de comunicare cu pompa și CGM-ul dumneavoastră." } }, "ru" : { @@ -360,12 +360,6 @@ "value" : "---" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "– – –" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -1500,7 +1494,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Loop automat" + "value" : "Buclă închisă" } }, "ru" : { @@ -2002,12 +1996,6 @@ "value" : "גבוה" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "HIGH" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -2362,7 +2350,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Loop a rulat acum %@" + "value" : "Bucla a rulat acum %@" } }, "ru" : { @@ -2451,7 +2439,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Avertizare Loop" + "value" : "Avertizare buclă" } }, "ru" : { @@ -2513,12 +2501,6 @@ "value" : "נמוך" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "LOW" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -3046,7 +3028,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Loop manual" + "value" : "Buclă deschisă" } }, "ru" : { diff --git a/WatchApp Extension/Localizable.xcstrings b/WatchApp Extension/Localizable.xcstrings index 9505a5db9d..2ed6d420bf 100644 --- a/WatchApp Extension/Localizable.xcstrings +++ b/WatchApp Extension/Localizable.xcstrings @@ -135,12 +135,6 @@ "value" : "---" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "– – –" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -762,7 +756,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adaugă carbohidrați" + "value" : "Adăugați carbohidrați" } }, "ru" : { @@ -1181,12 +1175,6 @@ "value" : "Continue" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "जारी" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -1226,7 +1214,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Continuă" + "value" : "Continuați" } }, "ru" : { @@ -1470,7 +1458,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Renunță" + "value" : "Renunțați" } }, "ru" : { @@ -1714,7 +1702,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Asigurați-vă că iPhone-ul este în apropiere, după care încercați din nou" + "value" : "Asigurați-vă că telefonul este în apropiere, după care încercați din nou" } }, "ru" : { @@ -1809,7 +1797,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Asigurați-vă că iPhone-ul este în apropiere, după care încercați din nou." + "value" : "Asigurați-vă că telefonul este în apropiere, după care încercați din nou." } }, "ru" : { @@ -3601,7 +3589,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Salvează" + "value" : "Salvați" } }, "ru" : { @@ -3702,7 +3690,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Salvează & Bolus" + "value" : "Salvați & bolusați" } }, "ru" : { @@ -4237,7 +4225,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu se poate accesa iPhone-ul" + "value" : "Nu se poate accesa iPhone" } }, "ru" : { diff --git a/WatchApp/Info.plist b/WatchApp/Info.plist index 70d55802d0..642853115d 100644 --- a/WatchApp/Info.plist +++ b/WatchApp/Info.plist @@ -33,6 +33,10 @@ WKCompanionAppBundleIdentifier $(MAIN_APP_BUNDLE_IDENTIFIER) + WKSupportsLiveActivityLaunchAttributeTypes + + GlucoseActivityAttributes + WKWatchKitApp diff --git a/WatchApp/mul.lproj/Interface.xcstrings b/WatchApp/mul.lproj/Interface.xcstrings index 82d219187a..75cecdb708 100644 --- a/WatchApp/mul.lproj/Interface.xcstrings +++ b/WatchApp/mul.lproj/Interface.xcstrings @@ -309,12 +309,6 @@ "value" : "---" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "– – –" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -2364,12 +2358,6 @@ "value" : "---" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "– – –" - } - }, "it" : { "stringUnit" : { "state" : "translated",