From 433ae210f45a999e2f5e4738a67f551704449abc Mon Sep 17 00:00:00 2001 From: marionbarker Date: Fri, 27 Mar 2026 16:39:02 -0700 Subject: [PATCH 1/7] Updated translations from lokalise on Fri Mar 27 14:20:33 PDT 2026 --- Common/ro.lproj/Intents.strings | 2 +- .../mul.lproj/MainInterface.xcstrings | 2 +- .../Bootstrap/Localizable.xcstrings | 2 +- Loop/Localizable.xcstrings | 88 +++++++++---------- LoopCore/Localizable.xcstrings | 6 ++ LoopUI/Localizable.xcstrings | 10 +-- WatchApp Extension/Localizable.xcstrings | 4 +- 7 files changed, 60 insertions(+), 54 deletions(-) diff --git a/Common/ro.lproj/Intents.strings b/Common/ro.lproj/Intents.strings index 2d7d672b4d..cf8b48e51f 100644 --- a/Common/ro.lproj/Intents.strings +++ b/Common/ro.lproj/Intents.strings @@ -20,7 +20,7 @@ "OcNxIj" = "Adaugă carbohidrați"; /* (No Comment) */ -"oLQSsJ" = "Activează „$”{overrideName} Modificare Personalizată"; +"oLQSsJ" = "Activează Modificare Personalizată „$”{overrideName}"; /* (No Comment) */ "XNNmtH" = "Activați presetarea în Loop"; 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..8670913f2b 100644 --- a/Loop Widget Extension/Bootstrap/Localizable.xcstrings +++ b/Loop Widget Extension/Bootstrap/Localizable.xcstrings @@ -1384,7 +1384,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Deschide aplicația pentru a actualiza widgetul" + "value" : "Deschideți aplicația pentru a actualiza widget-ul" } } } diff --git a/Loop/Localizable.xcstrings b/Loop/Localizable.xcstrings index c298b9f5d1..45296a7c25 100644 --- a/Loop/Localizable.xcstrings +++ b/Loop/Localizable.xcstrings @@ -261,7 +261,7 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "restant(s)" + "value" : " restant(s)" } }, "he" : { @@ -7454,7 +7454,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Măr" + "value" : "Apple" } }, "zh-Hans" : { @@ -7852,7 +7852,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" : { @@ -10225,7 +10225,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Întrerupere bolus" + "value" : "Întrerupere Bolus" } }, "ru" : { @@ -10457,7 +10457,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Intrare carbohidrați" + "value" : "Intrare Carbohidrați" } }, "ru" : { @@ -11248,7 +11248,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Atenţie" + "value" : "Atenție" } } } @@ -12482,7 +12482,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" : { @@ -13394,7 +13394,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Continuă" + "value" : "Continuați" } }, "ru" : { @@ -14932,7 +14932,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Ștergeți „ %@ ”?" + "value" : "Ștergeți „%@”?" } }, "zh-Hans" : { @@ -15544,7 +15544,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Ștergeți datele CGM de testare" + "value" : "Ștergeți date CGM de testare" } }, "ru" : { @@ -15732,7 +15732,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Ștergere date pompă de testare" + "value" : "Ștergeți date pompă de testare" } }, "ru" : { @@ -16131,7 +16131,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" : { @@ -16508,7 +16508,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Realizat" + "value" : "Gata" } }, "ru" : { @@ -17278,7 +17278,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Introdu bolusul" + "value" : "Introduceți bolusul" } }, "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" : { @@ -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" : { @@ -20652,7 +20652,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Programul intervalului țintă de glucoză" + "value" : "Programul intervalului țintă de glicemie" } }, "ru" : { @@ -20710,7 +20710,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "SUNET HARDWARE" + "value" : "SUNETE HARDWARE" } }, "ru" : { @@ -20866,7 +20866,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" : { @@ -21357,7 +21357,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Informaţii" + "value" : "Informații" } }, "zh-Hans" : { @@ -23228,7 +23228,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" : { @@ -24493,7 +24493,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Eșec Loop" + "value" : "Eșec Buclă" } }, "ru" : { @@ -25756,7 +25756,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Bolus prandial" + "value" : "Bolus pentru masă" } }, "ru" : { @@ -25964,7 +25964,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Notificări privind mesele pierdute" + "value" : "Notificări Mese Neanunțate" } }, "ru" : { @@ -27236,7 +27236,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." } } } @@ -28287,7 +28287,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 +28447,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Notificări întârziate" + "value" : "Notificări Întârziate" } }, "ru" : { @@ -28607,7 +28607,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" : { @@ -33559,7 +33559,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 +34027,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Afișează ultima eroare de loop" + "value" : "Afișează ultima eroare a buclei" } }, "ru" : { @@ -34128,7 +34128,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Calculator simplu de bolus" + "value" : "Calculator simplu bolus" } }, "ru" : { @@ -34217,7 +34217,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Calculator simplu al mesei" + "value" : "Calculator simplu masă" } }, "ru" : { @@ -34949,7 +34949,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Asistenţă" + "value" : "Asistență" } }, "ru" : { @@ -37127,7 +37127,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Alerte sensibile la timp" + "value" : "Alerte Urgente" } }, "ru" : { @@ -37198,7 +37198,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Notificări urgente" + "value" : "Notificări Urgente" } }, "ru" : { @@ -37410,7 +37410,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 +37778,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu se poate conecta la pompă" + "value" : "Nu se poate accesa pompa" } }, "ru" : { @@ -37867,7 +37867,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu se pot salva Carbohidrații" + "value" : "Nu se pot salva carbohidrații" } }, "ru" : { @@ -37962,7 +37962,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu se poate salva glicemia manuala" + "value" : "Nu se poate salva glicemia manuală" } }, "ru" : { @@ -39218,7 +39218,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" : { @@ -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" : { diff --git a/LoopCore/Localizable.xcstrings b/LoopCore/Localizable.xcstrings index e4062e82e0..4f6da1e7d0 100644 --- a/LoopCore/Localizable.xcstrings +++ b/LoopCore/Localizable.xcstrings @@ -810,6 +810,12 @@ "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", diff --git a/LoopUI/Localizable.xcstrings b/LoopUI/Localizable.xcstrings index 19df68c2c1..1a2495374e 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" : { @@ -1500,7 +1500,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Loop automat" + "value" : "Buclă închisă" } }, "ru" : { @@ -2362,7 +2362,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Loop a rulat acum %@" + "value" : "Bucla a rulat acum %@" } }, "ru" : { @@ -2451,7 +2451,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Avertizare Loop" + "value" : "Avertizare buclă" } }, "ru" : { @@ -3046,7 +3046,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..62bbf23998 100644 --- a/WatchApp Extension/Localizable.xcstrings +++ b/WatchApp Extension/Localizable.xcstrings @@ -1226,7 +1226,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Continuă" + "value" : "Continuați" } }, "ru" : { @@ -3702,7 +3702,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Salvează & Bolus" + "value" : "Salvează & Bolusează" } }, "ru" : { From 493f59677ec0c2514df6e2abe87fa8719e3c9777 Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Sun, 10 May 2026 13:07:18 -0400 Subject: [PATCH 2/7] Fix Live activity plot: colors in prediction, override target, time-range and labels (#2410) * Fix gradient definition for colors on predicted BG in LIve Activity * Adjust zero-height overrides and target ranges to be visible on plot * Fix color definitions * Make override bars a little wider * Correctly handle override with no glucose range set * Change null-range override handling to mirror main app approach This will now display the override bar in a darker color, even if the glucose range is unset and thus stays the same as before. * Only display overrides that overlap Live Activity chart window * Dynamically adjust graph start/end times to avoid label truncation --- .../Live Activity/ChartView.swift | 93 +++++++++++++----- .../GlucoseActivityAttributes.swift | 10 ++ .../Live Activity/LiveActivityManager.swift | 97 ++++++++++++++++--- 3 files changed, 161 insertions(+), 39 deletions(-) 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/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( From f066d40f90e6b81de2bd8f25d86e56e3544793f0 Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Tue, 12 May 2026 11:11:25 -0400 Subject: [PATCH 3/7] Add plist value to open Watch app on Smart Stack Live Activity tap (#2432) --- WatchApp/Info.plist | 4 ++++ 1 file changed, 4 insertions(+) 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 From 22a4ddc1cc34fae6b96d00cd08004f24c7e80b8d Mon Sep 17 00:00:00 2001 From: marionbarker Date: Tue, 12 May 2026 16:14:19 -0700 Subject: [PATCH 4/7] Updated translations from lokalise on Tue May 12 15:47:31 PDT 2026 --- Common/ro.lproj/Intents.strings | 16 +- .../Bootstrap/Localizable.xcstrings | 18 +- Loop/Localizable.xcstrings | 154 ++++++++++-------- Loop/mul.lproj/Main.xcstrings | 4 +- LoopCore/Localizable.xcstrings | 42 +++++ WatchApp Extension/Localizable.xcstrings | 14 +- 6 files changed, 160 insertions(+), 88 deletions(-) diff --git a/Common/ro.lproj/Intents.strings b/Common/ro.lproj/Intents.strings index cf8b48e51f..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ă Modificare Personalizată „$”{overrideName}"; +"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/Loop Widget Extension/Bootstrap/Localizable.xcstrings b/Loop Widget Extension/Bootstrap/Localizable.xcstrings index 8670913f2b..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ți aplicația pentru a actualiza widget-ul" + "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/Localizable.xcstrings b/Loop/Localizable.xcstrings index 45296a7c25..9708e1bb2e 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" : { @@ -1916,7 +1916,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 +4189,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 +4350,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "AcceptRecommendedBolus" + "value" : "AcceptațiBolusRecomandat" } }, "ru" : { @@ -5179,7 +5179,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adaugă carbohidrați" + "value" : "Adăugați carbohidrați" } }, "ru" : { @@ -5304,7 +5304,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adaugă CGM" + "value" : "Adăugați CGM" } }, "ru" : { @@ -5375,7 +5375,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 +5464,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adaugă masă" + "value" : "Adăugați masă" } }, "ru" : { @@ -5524,7 +5524,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adăugați o linie predictivă" + "value" : "Adăugați linie predictivă" } } } @@ -5613,7 +5613,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adaugă pompă" + "value" : "Adăugați pompă" } }, "ru" : { @@ -5720,7 +5720,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adaugă Serviciu" + "value" : "Adăugați Serviciu" } }, "ru" : { @@ -7048,7 +7048,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "已有更新的大剂量推荐值。" + "value" : "有新的大剂量推荐值可用" } } } @@ -7852,7 +7852,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 +7965,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 +8047,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 +8131,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" : { @@ -10100,7 +10100,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Renunță" + "value" : "Anulați" } }, "ru" : { @@ -10731,7 +10731,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "碳水化合物吸收率" + "value" : "碳水化合物系数" } } } @@ -10827,7 +10827,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adaugă carbohidrați" + "value" : "Adăugați carbohidrați" } }, "ru" : { @@ -13289,6 +13289,12 @@ "state" : "translated", "value" : "Configurați afișajul" } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示设置" + } } } }, @@ -13722,7 +13728,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu s-a putut reporni %1$@" + "value" : "Eșec la reporni %1$@" } } } @@ -14274,7 +14280,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "当前葡萄糖%1$@低于校正范围" + "value" : "当前血糖%1$@低于校正范围" } } } @@ -15028,7 +15034,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Șterge cont" + "value" : "Ștergeți cont" } }, "ru" : { @@ -15147,7 +15153,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Șterge tot" + "value" : "Ștergeți tot" } }, "ru" : { @@ -15438,7 +15444,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Șterge serviciul" + "value" : "Ștergeți serviciul" } }, "ru" : { @@ -15953,7 +15959,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Limite de livrare" + "value" : "Limite de administrare" } }, "ru" : { @@ -16357,7 +16363,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Renunță" + "value" : "Anulați" } }, "ru" : { @@ -16398,7 +16404,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 +16422,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Opțiuni de control al afișajului" + "value" : "Afișați opțiunile de control" } } } @@ -16423,6 +16435,12 @@ "state" : "translated", "value" : "Afișați predicția în grafic" } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在图中显示预测结果" + } } } }, @@ -16434,6 +16452,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个项目。括号内为显示标签。" + } } } }, @@ -16686,7 +16710,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Strategie de dozare" + "value" : "Strategie dozare" } }, "ru" : { @@ -16852,7 +16876,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Activează Bluetooth" + "value" : "Activați Bluetooth" } }, "ru" : { @@ -16923,7 +16947,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 +16994,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Activează corecția retrospectivă integrală" + "value" : "Activați corecția retrospectivă integrală" } } } @@ -17088,7 +17112,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Activează" + "value" : "Activați" } }, "ru" : { @@ -18154,7 +18178,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Ajunge la %@" + "value" : "În cele din urmă %@" } }, "ru" : { @@ -18516,7 +18540,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Exportă jurnalul de evenimente critice" + "value" : "Exportați jurnalul de evenimente critice" } }, "ru" : { @@ -18605,7 +18629,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Export-%1$@" + "value" : "Exportați-%1$@" } }, "ru" : { @@ -18688,7 +18712,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" : { @@ -19205,7 +19229,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Tip de alimente" + "value" : "Tip aliment" } }, "ru" : { @@ -22012,7 +22036,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Modelul de insulină" + "value" : "Model insulină" } }, "ru" : { @@ -22328,7 +22352,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Programul Factorului de Sensibilitate la Insulină" + "value" : "Orarul Factorului de Sensibilitate la Insulină" } }, "ru" : { @@ -23375,7 +23399,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Generează raport" + "value" : "Generați raport" } }, "ru" : { @@ -24088,7 +24112,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Înregistrează Doză" + "value" : "Înregistrați Doză" } }, "ru" : { @@ -24837,7 +24861,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 +25104,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" : { @@ -27711,7 +27735,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu există date recente despre glicemie" + "value" : "Lipsă glicemie recentă" } }, "ru" : { @@ -27806,7 +27830,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu există date recente despre glicemie" + "value" : "Lipsă date recente glicemie" } }, "ru" : { @@ -27901,7 +27925,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu există date recente despre pompă" + "value" : "Lipsă date recente pompă" } }, "ru" : { @@ -28791,7 +28815,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 +32679,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Introducere de la distanță a bolusului: %@ U" + "value" : "Intrare de la distanță a bolusului: %@ U" } }, "ru" : { @@ -32738,7 +32762,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 +33165,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Reîncearcă" + "value" : "Reîncercați" } }, "ru" : { @@ -33223,7 +33247,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Salvează" + "value" : "Salvați" } } } @@ -33317,7 +33341,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 +33418,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Salvează fără bolusare" + "value" : "Salvați fără bolusare" } }, "ru" : { @@ -35217,7 +35241,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 +35336,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 +35431,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 +35526,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Atinge pentru a adăuga" + "value" : "Atingeți pentru a adăuga" } }, "ru" : { @@ -35615,7 +35639,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Apăsați pentru a relua" + "value" : "Atingeți pentru a relua" } }, "ru" : { @@ -36212,7 +36236,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 +36319,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 +36927,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" : { @@ -38051,7 +38075,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 +38520,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Activează sunetul" + "value" : "Activați sunetul" } }, "ru" : { @@ -41039,7 +41063,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/mul.lproj/Main.xcstrings b/Loop/mul.lproj/Main.xcstrings index 9dddf366e0..a2c7e1e121 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" : { diff --git a/LoopCore/Localizable.xcstrings b/LoopCore/Localizable.xcstrings index 4f6da1e7d0..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,6 +834,12 @@ "state" : "translated", "value" : "Actualizat" } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新" + } } } }, @@ -821,6 +857,12 @@ "state" : "translated", "value" : "Actualizat (la)" } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新(更新于)" + } } } }, diff --git a/WatchApp Extension/Localizable.xcstrings b/WatchApp Extension/Localizable.xcstrings index 62bbf23998..8cdbd9cf46 100644 --- a/WatchApp Extension/Localizable.xcstrings +++ b/WatchApp Extension/Localizable.xcstrings @@ -762,7 +762,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Adaugă carbohidrați" + "value" : "Adăugați carbohidrați" } }, "ru" : { @@ -1470,7 +1470,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Renunță" + "value" : "Renunțați" } }, "ru" : { @@ -1714,7 +1714,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 +1809,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 +3601,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Salvează" + "value" : "Salvați" } }, "ru" : { @@ -3702,7 +3702,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Salvează & Bolusează" + "value" : "Salvați & bolusați" } }, "ru" : { @@ -4237,7 +4237,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nu se poate accesa iPhone-ul" + "value" : "Nu se poate accesa iPhone" } }, "ru" : { From a383da970a78be2012c26380a0268400a103ccee Mon Sep 17 00:00:00 2001 From: marionbarker Date: Tue, 12 May 2026 16:29:10 -0700 Subject: [PATCH 5/7] language modification: add ko, delete ce, hi --- Common/hi.lproj/Intents.strings | 36 ------------------- Common/{ce.lproj => ko.lproj}/Intents.strings | 30 +++++----------- Common/ko.lproj/Localizable.strings | 24 +++++++++++++ Learn/ko.lproj/Localizable.strings | 32 +++++++++++++++++ Learn/ko.lproj/Main.strings | 3 ++ Loop.xcodeproj/project.pbxproj | 15 ++++---- Loop/Localizable.xcstrings | 36 ------------------- Loop/mul.lproj/Main.xcstrings | 6 ---- LoopUI/Localizable.xcstrings | 18 ---------- WatchApp Extension/Localizable.xcstrings | 12 ------- WatchApp/mul.lproj/Interface.xcstrings | 12 ------- 11 files changed, 77 insertions(+), 147 deletions(-) delete mode 100644 Common/hi.lproj/Intents.strings rename Common/{ce.lproj => ko.lproj}/Intents.strings (68%) create mode 100644 Common/ko.lproj/Localizable.strings create mode 100644 Learn/ko.lproj/Localizable.strings create mode 100644 Learn/ko.lproj/Main.strings 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/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.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 4767ba3142..069edf83ea 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -805,7 +805,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 +1206,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 +1229,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 = ""; }; @@ -3033,10 +3035,9 @@ ar, sk, cs, - hi, - ce, hu, uk, + ko, ); mainGroup = 43776F831B8022E90074EA36; packageReferences = ( @@ -3952,10 +3953,9 @@ C1C3127F297E4C0400296DA4 /* ar */, C1C247882995823200371B88 /* sk */, C1C5357529C6346A00E32DF9 /* cs */, - 3D03C6DA2AACE6AC00FDE5D2 /* hi */, - B6F22EF52E95A03600CCA05F /* ce */, B6F22EF72E95A03800CCA05F /* hu */, B6F22EF92E95A03C00CCA05F /* uk */, + B6656C6C2FB3EE1300FFC8BE /* ko */, ); name = Intents.intentdefinition; sourceTree = ""; @@ -3993,6 +3993,7 @@ F5D9C01C27DABBE1002E48F6 /* tr */, F5E0BDD827E1D71E0033557E /* he */, C1C3127A297E4BFE00296DA4 /* ar */, + B6656C6D2FB3EE1400FFC8BE /* ko */, ); name = Main.storyboard; sourceTree = ""; @@ -4028,6 +4029,7 @@ 7D9BF14623370E8D005DCFD6 /* ro */, F5D9C02727DABBE4002E48F6 /* tr */, F5E0BDE327E1D7230033557E /* he */, + B6656C6F2FB3EE1600FFC8BE /* ko */, ); name = Localizable.strings; sourceTree = ""; @@ -4056,6 +4058,7 @@ F5E0BDDA27E1D71F0033557E /* he */, C1C3127C297E4BFE00296DA4 /* ar */, C1C247892995823200371B88 /* sk */, + B6656C6E2FB3EE1500FFC8BE /* ko */, ); name = Localizable.strings; sourceTree = ""; diff --git a/Loop/Localizable.xcstrings b/Loop/Localizable.xcstrings index 9708e1bb2e..d6044376d1 100644 --- a/Loop/Localizable.xcstrings +++ b/Loop/Localizable.xcstrings @@ -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", @@ -7243,12 +7237,6 @@ "value" : "API Secret" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "एपीआई पास्वर्ड" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -10055,12 +10043,6 @@ "value" : "בטל" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "निरस्त" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -13355,12 +13337,6 @@ "value" : "Continue" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "जारी" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -19907,12 +19883,6 @@ "value" : "Glucose" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "शुगर" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -26780,12 +26750,6 @@ "value" : "שם" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "नाम" - } - }, "it" : { "stringUnit" : { "state" : "translated", diff --git a/Loop/mul.lproj/Main.xcstrings b/Loop/mul.lproj/Main.xcstrings index a2c7e1e121..4316a6468f 100644 --- a/Loop/mul.lproj/Main.xcstrings +++ b/Loop/mul.lproj/Main.xcstrings @@ -2806,12 +2806,6 @@ "value" : "Glucose" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "शुगर" - } - }, "it" : { "stringUnit" : { "state" : "translated", diff --git a/LoopUI/Localizable.xcstrings b/LoopUI/Localizable.xcstrings index 1a2495374e..a21b1ce012 100644 --- a/LoopUI/Localizable.xcstrings +++ b/LoopUI/Localizable.xcstrings @@ -360,12 +360,6 @@ "value" : "---" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "– – –" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -2002,12 +1996,6 @@ "value" : "גבוה" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "HIGH" - } - }, "it" : { "stringUnit" : { "state" : "translated", @@ -2513,12 +2501,6 @@ "value" : "נמוך" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "LOW" - } - }, "it" : { "stringUnit" : { "state" : "translated", diff --git a/WatchApp Extension/Localizable.xcstrings b/WatchApp Extension/Localizable.xcstrings index 8cdbd9cf46..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", @@ -1181,12 +1175,6 @@ "value" : "Continue" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "जारी" - } - }, "it" : { "stringUnit" : { "state" : "translated", 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", From e3229270a2704a601ef69ed3f407195717d5b09e Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 22 May 2026 19:37:59 -0500 Subject: [PATCH 6/7] Warn at launch on development (dev branch) builds (#2439) * Warn at launch on development (dev branch) builds Present a blocking alert after the status screen appears on each launch when the app was built from the LoopWorkspace dev branch, advising non-testers to switch to main. Detected via BuildDetails.workspaceGitBranch. Gated by a new devBranchWarningEnabled feature flag (on by default, disabled with DEV_BRANCH_WARNING_DISABLED in SWIFT_ACTIVE_COMPILATION_CONDITIONS). The alert offers "I'm a tester" (dismiss) and "Switch to main" (opens the LoopDocs branches page). * Point Switch to main at the dev browser-build docs * Update DevelopmentBranchAlerter.swift Update url * chore/update localization strings --------- Co-authored-by: marionbarker --- Common/FeatureFlags.swift | 9 ++++ Loop.xcodeproj/project.pbxproj | 4 ++ Loop/Localizable.xcstrings | 12 +++++ Loop/Managers/DevelopmentBranchAlerter.swift | 50 ++++++++++++++++++++ Loop/Managers/LoopAppManager.swift | 4 ++ 5 files changed, 79 insertions(+) create mode 100644 Loop/Managers/DevelopmentBranchAlerter.swift 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/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 069edf83ea..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 */; }; @@ -1316,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 = ""; }; @@ -2037,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 */, @@ -3391,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 */, diff --git a/Loop/Localizable.xcstrings b/Loop/Localizable.xcstrings index d6044376d1..44d94c62a4 100644 --- a/Loop/Localizable.xcstrings +++ b/Loop/Localizable.xcstrings @@ -21019,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" : { @@ -35139,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" : { @@ -37015,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" : { @@ -39558,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" : { 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/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 From 1f71ec4fa94941abdbd72fd5bd914770faa2e90b Mon Sep 17 00:00:00 2001 From: Marion Barker <19607791+marionbarker@users.noreply.github.com> Date: Thu, 28 May 2026 12:24:38 -0700 Subject: [PATCH 7/7] Support use of OmnipodKit (#2426) * continue OmniBLE, OmniKit pump managers if present, otherwise automatically transfer pod to OmnipodKit --- Loop/Managers/DeviceDataManager.swift | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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? {