From f003030e60ad5605afaf788505e213b78073c69c Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 22 Oct 2023 12:02:39 -0500 Subject: [PATCH 01/17] Updated translations from Lokalise on Sun Oct 22 11:55:50 CDT 2023 --- LoopSupport.xcodeproj/project.pbxproj | 2 +- LoopSupportKitUI/it.lproj/Localizable.strings | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/LoopSupport.xcodeproj/project.pbxproj b/LoopSupport.xcodeproj/project.pbxproj index e2a35bc..e6ddaec 100644 --- a/LoopSupport.xcodeproj/project.pbxproj +++ b/LoopSupport.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ diff --git a/LoopSupportKitUI/it.lproj/Localizable.strings b/LoopSupportKitUI/it.lproj/Localizable.strings index 15a963f..a0c190a 100644 --- a/LoopSupportKitUI/it.lproj/Localizable.strings +++ b/LoopSupportKitUI/it.lproj/Localizable.strings @@ -25,6 +25,12 @@ /* Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ "Share Version Only" = "Condividi solo la versione"; +/* Navigation link title for Submit Bug Report */ +"Submit Bug Report" = "Invia Reporto Bug"; + +/* Navigation Link Title for Usage Data Sharing */ +"Usage Data Sharing" = "Condivisione dei dati di utilizzo"; + /* Main summary text for UsageDataPrivacyPreferenceView */ "You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." = "È possibile scegliere di condividere i dati di utilizzo con gli sviluppatori di Loop per migliorare Loop. La condivisione non è obbligatoria e Loop funzionerà pienamente indipendentemente dall'opzione scelta. I dati di utilizzo non saranno condivisi con terze parti."; From 1d2f0b495932e5fcc105937c03f03166351b2a41 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Sun, 24 Aug 2025 17:01:23 -0700 Subject: [PATCH 02/17] Updated translations from Lokalise on Sun Aug 24 12:32:21 PDT 2025 --- LoopSupportKitUI/fr.lproj/Localizable.strings | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/LoopSupportKitUI/fr.lproj/Localizable.strings b/LoopSupportKitUI/fr.lproj/Localizable.strings index 9e5325b..161421b 100644 --- a/LoopSupportKitUI/fr.lproj/Localizable.strings +++ b/LoopSupportKitUI/fr.lproj/Localizable.strings @@ -25,6 +25,12 @@ /* Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ "Share Version Only" = "Partager la version uniquement"; +/* Navigation link title for Submit Bug Report */ +"Submit Bug Report" = "Soumettre un rapport de bug"; + +/* Navigation Link Title for Usage Data Sharing */ +"Usage Data Sharing" = "Partage des données d'utilisation"; + /* Main summary text for UsageDataPrivacyPreferenceView */ "You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." = "Vous pouvez éventuellement choisir de partager les données d'utilisation avec les développeurs de Loop pour améliorer Loop. Le partage n'est pas nécessaire et Loop fonctionnera pleinement, quelle que soit l'option choisie. Les données d'utilisation ne seront pas partagées avec des tiers."; From f65bc974e3af57f464b538058eeedf1b952290b7 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Sat, 30 Aug 2025 12:37:00 -0700 Subject: [PATCH 03/17] Updated translations from Lokalise on Sat Aug 30 10:22:12 PDT 2025 --- LoopSupportKitUI/it.lproj/Localizable.strings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LoopSupportKitUI/it.lproj/Localizable.strings b/LoopSupportKitUI/it.lproj/Localizable.strings index a0c190a..aa1eb09 100644 --- a/LoopSupportKitUI/it.lproj/Localizable.strings +++ b/LoopSupportKitUI/it.lproj/Localizable.strings @@ -8,7 +8,7 @@ "Do not share any data." = "Non condividere alcun dato."; /* Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." = "Oltre alle informazioni sulla versione, condividi in modo anonimo i dati su come Loop viene utilizzato sul tuo telefono. I dati di utilizzo includono eventi come l'apertura del ciclo, la somministrazione di boli, l'aggiunta di carboidrati e la navigazione tra le schermate. Non include dati sanitari come valori di glucosio o quantità di dosaggio."; +"In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." = "Oltre alle informazioni sulla versione, condividi in modo anonimo i dati su come Loop viene utilizzato sul tuo telefono. I dati di utilizzo includono eventi come l'apertura del ciclo, l'erogazione di boli, l'aggiunta di carboidrati e la navigazione tra le schermate. Non include dati sanitari come valori di glicemia o quantità dosate."; /* Title for LoopKit Analytics */ "LoopKit Analytics" = "Analisi LoopKit"; @@ -26,7 +26,7 @@ "Share Version Only" = "Condividi solo la versione"; /* Navigation link title for Submit Bug Report */ -"Submit Bug Report" = "Invia Reporto Bug"; +"Submit Bug Report" = "Invia segnalazione bug"; /* Navigation Link Title for Usage Data Sharing */ "Usage Data Sharing" = "Condivisione dei dati di utilizzo"; From e3e8849405225486cd8d4db202ebee78a5c57dfc Mon Sep 17 00:00:00 2001 From: marionbarker Date: Thu, 4 Sep 2025 18:33:41 -0700 Subject: [PATCH 04/17] Convert to using xcstrings --- LoopSupport.xcodeproj/project.pbxproj | 65 +- LoopSupportKitUI/Localizable.xcstrings | 996 ++++++++++++++++++ LoopSupportKitUI/cs.lproj/Localizable.strings | 9 - LoopSupportKitUI/da.lproj/Localizable.strings | 36 - LoopSupportKitUI/de.lproj/Localizable.strings | 36 - LoopSupportKitUI/en.lproj/Localizable.strings | 36 - LoopSupportKitUI/es.lproj/Localizable.strings | 36 - LoopSupportKitUI/fi.lproj/Localizable.strings | 3 - LoopSupportKitUI/fr.lproj/Localizable.strings | 36 - LoopSupportKitUI/he.lproj/Localizable.strings | 3 - LoopSupportKitUI/hi.lproj/Localizable.strings | 3 - LoopSupportKitUI/it.lproj/Localizable.strings | 36 - LoopSupportKitUI/ja.lproj/Localizable.strings | 3 - LoopSupportKitUI/nb.lproj/Localizable.strings | 36 - LoopSupportKitUI/nl.lproj/Localizable.strings | 36 - LoopSupportKitUI/pl.lproj/Localizable.strings | 36 - .../pt-BR.lproj/Localizable.strings | 3 - LoopSupportKitUI/ro.lproj/Localizable.strings | 36 - LoopSupportKitUI/ru.lproj/Localizable.strings | 36 - LoopSupportKitUI/sk.lproj/Localizable.strings | 3 - LoopSupportKitUI/sv.lproj/Localizable.strings | 3 - LoopSupportKitUI/tr.lproj/Localizable.strings | 36 - LoopSupportKitUI/vi.lproj/Localizable.strings | 3 - .../zh-Hans.lproj/Localizable.strings | 36 - 24 files changed, 1004 insertions(+), 558 deletions(-) create mode 100644 LoopSupportKitUI/Localizable.xcstrings delete mode 100644 LoopSupportKitUI/cs.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/da.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/de.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/en.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/es.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/fi.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/fr.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/he.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/hi.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/it.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/ja.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/nb.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/nl.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/pl.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/pt-BR.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/ro.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/ru.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/sk.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/sv.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/tr.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/vi.lproj/Localizable.strings delete mode 100644 LoopSupportKitUI/zh-Hans.lproj/Localizable.strings diff --git a/LoopSupport.xcodeproj/project.pbxproj b/LoopSupport.xcodeproj/project.pbxproj index e6ddaec..0cac0e0 100644 --- a/LoopSupport.xcodeproj/project.pbxproj +++ b/LoopSupport.xcodeproj/project.pbxproj @@ -18,11 +18,11 @@ A9CE800325BCAC8700C04F9B /* LoopSupportUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CE800225BCAC8700C04F9B /* LoopSupportUI.swift */; }; A9CE800B25BCACAE00C04F9B /* LoopSupportPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CE800A25BCACAE00C04F9B /* LoopSupportPlugin.swift */; }; A9CE801325BCAE4000C04F9B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A9CE801225BCAE4000C04F9B /* Assets.xcassets */; }; + B66D1F552E6A752300471149 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B66D1F542E6A752300471149 /* Localizable.xcstrings */; }; C12ECD05289560B90089EB07 /* LoopSupportKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A936A95425BC9A190085119C /* LoopSupportKitUI.framework */; platformFilter = ios; }; C12ECD06289560B90089EB07 /* LoopSupportKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A936A95425BC9A190085119C /* LoopSupportKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C14A7E7C295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14A7E7B295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift */; }; C15349062960AA9000B47074 /* UsageDataPrivacyPreferenceMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15349052960AA9000B47074 /* UsageDataPrivacyPreferenceMenuItem.swift */; }; - C1821690297E55A7001EB097 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C182168E297E55A7001EB097 /* Localizable.strings */; }; C1BE9741295E3800008BA25D /* LoopKitAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BE9740295E3800008BA25D /* LoopKitAnalytics.swift */; }; C1BE9744295E40B1008BA25D /* Amplitude in Frameworks */ = {isa = PBXBuildFile; productRef = C1BE9743295E40B1008BA25D /* Amplitude */; }; /* End PBXBuildFile section */ @@ -83,31 +83,10 @@ A9CE800225BCAC8700C04F9B /* LoopSupportUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopSupportUI.swift; sourceTree = ""; }; A9CE800A25BCACAE00C04F9B /* LoopSupportPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopSupportPlugin.swift; sourceTree = ""; }; A9CE801225BCAE4000C04F9B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - C116134F2983096D00777E7C /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; - C11A2BD129830A3100AC5135 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; - C122DF0629BBFAAE00321F8D /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; - C12BCCFD29BBFA480066A158 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; + B66D1F542E6A752300471149 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; C14A7E7B295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsageDataPrivacyPreferenceView.swift; sourceTree = ""; }; C15349052960AA9000B47074 /* UsageDataPrivacyPreferenceMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsageDataPrivacyPreferenceMenuItem.swift; sourceTree = ""; }; - C155A8F62986396E009BD257 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; - C160FF6C29BF7D6800D4DFD4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; - C160FF6D29BF7D6900D4DFD4 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; - C16EF1FB298639B800A944BB /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; - C182168F297E55A7001EB097 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; - C18886EA29830A5E004C982D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; - C18B7262299581C600F138D3 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; - C192C60729C78711001EFEA6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; - C1AD630429BBFAA80002685D /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; - C1B0CFDD29C786BF0045B04D /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; - C1B267A12995824000BCB7C1 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; - C1BCB5BE298309C4001C50FF /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; C1BE9740295E3800008BA25D /* LoopKitAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopKitAnalytics.swift; sourceTree = ""; }; - C1E5A6E129C7870100703C90 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; - C1E693D329C786E200410918 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; - C1F4FD5C29C7869800D7ACBC /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; - C1FAB5C529C786B000D25073 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Localizable.strings; sourceTree = ""; }; - C1FDCC0D29C786F90056E652 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; }; - C1FF3D5029C786A900BDC1EC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -159,7 +138,7 @@ C14A7E7A295F2EA700CD87B4 /* Views */, A936A95625BC9A190085119C /* LoopSupportKitUI.h */, A936A95725BC9A190085119C /* Info.plist */, - C182168E297E55A7001EB097 /* Localizable.strings */, + B66D1F542E6A752300471149 /* Localizable.xcstrings */, A9CE800225BCAC8700C04F9B /* LoopSupportUI.swift */, A9CE801225BCAE4000C04F9B /* Assets.xcassets */, C1BE9740295E3800008BA25D /* LoopKitAnalytics.swift */, @@ -346,7 +325,7 @@ buildActionMask = 2147483647; files = ( A9CE801325BCAE4000C04F9B /* Assets.xcassets in Resources */, - C1821690297E55A7001EB097 /* Localizable.strings in Resources */, + B66D1F552E6A752300471149 /* Localizable.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -404,38 +383,6 @@ }; /* End PBXTargetDependency section */ -/* Begin PBXVariantGroup section */ - C182168E297E55A7001EB097 /* Localizable.strings */ = { - isa = PBXVariantGroup; - children = ( - C182168F297E55A7001EB097 /* es */, - C116134F2983096D00777E7C /* nb */, - C1BCB5BE298309C4001C50FF /* it */, - C11A2BD129830A3100AC5135 /* fr */, - C18886EA29830A5E004C982D /* nl */, - C155A8F62986396E009BD257 /* de */, - C16EF1FB298639B800A944BB /* pl */, - C18B7262299581C600F138D3 /* da */, - C1B267A12995824000BCB7C1 /* tr */, - C12BCCFD29BBFA480066A158 /* cs */, - C1AD630429BBFAA80002685D /* ro */, - C122DF0629BBFAAE00321F8D /* ru */, - C160FF6C29BF7D6800D4DFD4 /* en */, - C160FF6D29BF7D6900D4DFD4 /* zh-Hans */, - C1F4FD5C29C7869800D7ACBC /* fi */, - C1FF3D5029C786A900BDC1EC /* he */, - C1FAB5C529C786B000D25073 /* hi */, - C1B0CFDD29C786BF0045B04D /* ja */, - C1E693D329C786E200410918 /* pt-BR */, - C1FDCC0D29C786F90056E652 /* sk */, - C1E5A6E129C7870100703C90 /* sv */, - C192C60729C78711001EFEA6 /* vi */, - ); - name = Localizable.strings; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - /* Begin XCBuildConfiguration section */ A936A92025BC99CB0085119C /* Debug */ = { isa = XCBuildConfiguration; @@ -528,6 +475,7 @@ GCC_WARN_UNUSED_PARAMETER = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFLocalizedString, @@ -538,6 +486,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -631,6 +580,7 @@ GCC_WARN_UNUSED_PARAMETER = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFLocalizedString, @@ -640,6 +590,7 @@ MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; diff --git a/LoopSupportKitUI/Localizable.xcstrings b/LoopSupportKitUI/Localizable.xcstrings new file mode 100644 index 0000000..cb7d45f --- /dev/null +++ b/LoopSupportKitUI/Localizable.xcstrings @@ -0,0 +1,996 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." : { + "comment" : "Description in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Del anonymt minimale data om denne Loop-version for at hjælpe udviklere med at vide, hvor mange mennesker der bruger Loop. Hvilken enhed og operativsystemversion vil også blive delt." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Teile anonym minimale Daten über diese Loop-Version, damit die Entwickler wissen, wie viele Personen Loop verwenden. Welches Gerät und welche Betriebssystemversion genutzt werden, wird ebenfalls geteilt." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comparta de forma anónima datos mínimos sobre esta versión de Loop, para ayudar a los desarrolladores a saber cuántas personas están usando Loop. Su dispositivo y versión del sistema operativo también se compartirán." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partager anonymement des données minimales sur cette version de Loop, pour aider les développeurs à comprendre combien de personnes utilisent Loop. L'appareil et la version du système d'exploitation seront également partagés." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Condividi in modo anonimo dati minimi su questa versione di Loop, per aiutare gli sviluppatori a sapere quante persone stanno utilizzando Loop. Verranno condivisi anche il dispositivo e la versione del sistema operativo." + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Del anonymt minimalt med data om denne Loop-versjonen, for å hjelpe utviklere å vite hvor mange som bruker Loop. Hvilken enhet og operativsystemversjon vil også bli delt." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deel anoniem een minimale hoeveelheid gegevens over deze versie van Loop, zodat ontwikkelaars weten hoeveel mensen Loop gebruiken. Het type apparaat en de versie van het besturingssysteem wordt ook gedeeld." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anonimowo udostępniaj ograniczone dane o tej wersji pętli, aby pomóc programistom dowiedzieć się, ile osób z niej korzysta. Udostępnione zostaną również informacje dotyczące telefonu i systemu operacyjnego." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partajați în mod anonim date minime despre această versiune Loop, pentru a ajuta dezvoltatorii să știe câți oameni folosesc Loop. Ce dispozitiv și versiunea sistemului de operare vor fi, de asemenea, partajate." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Анонимно делиться минимальными данными об этой версии Loop, чтобы помочь разработчикам узнать, сколько людей используют Loop. Также будут переданы данные об устройстве и версии операционной системы." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geliştiricilerin Loop'u kaç kişinin kullandığını bilmelerine yardımcı olmak için bu Loop sürümüyle ilgili minimum verileri anonim olarak paylaşın. Hangi cihaz olduğu ve işletim sistemi sürümü de paylaşılacaktır." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." + } + } + } + }, + "Continue" : { + "comment" : "Button title for choosing onboarding without nightscout", + "localizations" : { + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pokračovat" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fortsæt" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Weiter" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Continuar" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jatka" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Continuer" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Continue" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "जारी" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Continua" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "次へ" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fortsett" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ga Verder" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kontynuuj" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Continuar" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Continuă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Продолжить" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pokračovať" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fortsätt" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Devam et" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tiếp tục" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "继续" + } + } + } + }, + "Do not share any data." : { + "comment" : "Description in UsageDataPrivacyPreferenceView for no sharing", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Del ikke data." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Daten teilen." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No compartir ningún dato." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ne partager aucune donnée." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Non condividere alcun dato." + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ikke del noen data." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deel geen gegevens" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nie udostępniaj żadnych danych." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nu partajați niciun fel de date." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не делиться никакими данными." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Herhangi bir veri paylaşmayın." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Do not share any data." + } + } + } + }, + "In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." : { + "comment" : "Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ud over versionsoplysninger kan du anonymt dele data om, hvordan Loop bruges på din telefon. Brugsdata omfatter begivenheder som at åbne Loop, bolus, tilføje kulhydrater og navigere mellem skærme. De omfatter ikke sundhedsdata som glukoseværdier eller doseringsmængder." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zusätzlich zu den Versionsinformationen kannst Du anonym Daten darüber weitergeben, wie Loop auf Deinem Telefon verwendet wird. Zu den Nutzungsdaten gehören Ereignisse wie das Öffnen von Loop, Bolusgaben, Hinzufügen von Kohlenhydraten und das Navigieren zwischen Bildschirmen. Sie umfassen keine Gesundheitsdaten wie Glukosewerte oder Bolusmengen." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Además de la información sobre la versión, comparte de forma anónima datos sobre cómo se utiliza Loop en tu teléfono. Los datos de uso incluyen eventos como la apertura de Loop, la administración de bolos, la adición de carbohidratos y la navegación entre pantallas. No incluye datos de salud como valores de glucosa o cantidades de dosis." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "En plus des informations de version, partagez anonymement des données sur la façon dont Loop est utilisé sur votre téléphone. Les données d’utilisation comprennent des événements tels que l’ouverture de boucle, les bolus, l’ajout de glucides et la navigation entre les écrans. Il n’inclut pas les données de santé telles que les valeurs de glucose ou les quantités de dosage." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oltre alle informazioni sulla versione, condividi in modo anonimo i dati su come Loop viene utilizzato sul tuo telefono. I dati di utilizzo includono eventi come l'apertura del ciclo, l'erogazione di boli, l'aggiunta di carboidrati e la navigazione tra le schermate. Non include dati sanitari come valori di glicemia o quantità dosate." + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "I tillegg til versjonsinformasjon, kan du anonymt dele data om hvordan Loop brukes på telefonen din. Bruksdata inkluderer hendelser som åpning av loop, bolusering, tilsetning av karbohydrater og navigering mellom skjermer. Den inkluderer ikke helsedata som blodsukkerverdier eller doseringsmengder." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Naast versiegegevens kun je anoniem gegevens delen over hoe Loop op je telefoon wordt gebruikt. Gebruiksgegevens omvatten gebeurtenissen zoals het openen van Loop, bolussen, het toevoegen koolhydraten en het navigeren tussen schermen. Het bevat geen gezondheidsgegevens zoals glucosewaarden of doseringhoeveelheden." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oprócz informacji statystycznych możesz anonimowo udostępniać dane dotyczące wykorzystania pętli w Twoim telefonie. Dane dotyczące użytkowania obejmują m.in.: uruchomienie pętli, podawanie bolusa, wprowadzenie węglowodanów i nawigację między ekranami. Nie obejmują danych zdrowotnych, takich jak wartości glukozy czy dawki insuliny." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pe lângă informațiile despre versiune, partajați în mod anonim date despre modul în care Loop este utilizat pe telefonul dvs. Datele de utilizare includ evenimente precum deschiderea buclei, bolusarea, adăugarea de carbohidrați și navigarea între ecrane. Nu include date de sănătate, cum ar fi valorile glucozei sau cantitățile de dozare." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "В дополнение к информации о версии, анонимно делиться данными о том, как Loop используется на вашем телефоне. Данные об использовании включают такие события, как открытие петли, введение болюса, добавление углеводов и перемещение между экранами. Они не включают данные о состоянии здоровья, такие как значения глюкозы или количество доз." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sürüm bilgilerine ek olarak, Loop'un telefonunuzda nasıl kullanıldığına ilişkin verileri anonim olarak paylaşın. Kullanım verileri, Loop'u açma, bolus gönderme, karbonhidrat ekleme ve ekranlar arasında gezinme gibi olayları içerir. KŞ değerleri veya dozaj miktarları gibi sağlık verilerini içermez." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." + } + } + } + }, + "LoopKit Analytics" : { + "comment" : "Title for LoopKit Analytics", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoopKit Analytics" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoopKit Analytics" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Análisis LoopKit" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoopKit Analytics" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Analisi LoopKit" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoopKit Analytics" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoopKit Analytics" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Analityka LoopKit" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Analitice LoopKit" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoopKit Analytics" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoopKit Analizi" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoopKit Analytics" + } + } + } + }, + "No Sharing" : { + "comment" : "Title in UsageDataPrivacyPreferenceView for no sharing", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen deling" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Daten teilen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No compartir" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pas de partage" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessuna condivisione" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen deling" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Niet Delen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak udostępniania" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fără partajare" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не делиться" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Paylaşım Yok" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "No Sharing" + } + } + } + }, + "Share Data" : { + "comment" : "Title on UsageDataPrivacyPreferenceView", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Del data" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nutzungsdaten teilen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compartir datos" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partager les données" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Condividi dati" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Del data" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deel Gegevens" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Udostępnij dane" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partajați datele" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поделиться данными" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Veri Paylaşımı" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Share Data" + } + } + } + }, + "Share Usage Data" : { + "comment" : "Title in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Del brugsdata" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nutzungsdaten teilen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compartir datos de uso" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partager les données d'utilisation" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Condividi dati di utilizzo" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Del bruksdata" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deel Gebruiksgegevens" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Udostępnij więcej danych" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partajați datele de utilizare" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Делиться данными об использовании" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kullanım Verilerini Paylaşın" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Share Usage Data" + } + } + } + }, + "Share Version Only" : { + "comment" : "Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Del kun version" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nur Version teilen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sólo versión para compartir" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partager la version uniquement" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Condividi solo la versione" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Del kun versjon" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deel Alleen Versie" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Udostępnij statystyki" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partajați numai versiunea" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поделиться только версией" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yalnızca Sürümü Paylaş" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Share Version Only" + } + } + } + }, + "Submit Bug Report" : { + "comment" : "Navigation link title for Submit Bug Report", + "localizations" : { + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odeslat hlášení o chybě" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Indsend fejlrapport" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fehlerbericht senden" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enviar informe de error" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Soumettre un rapport de bug" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invia segnalazione bug" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Send inn feilrapport" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bugrapport Indienen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prześlij raport o błędzie" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Raportare probleme" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отправить отчет об ошибке" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hata Raporu Gönder" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Submit Bug Report" + } + } + } + }, + "Usage Data Sharing" : { + "comment" : "Navigation Link Title for Usage Data Sharing", + "localizations" : { + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sdílení dat o používání" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deling af brugerdata" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nutzungsdaten teilen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uso compartido de datos" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partage des données d'utilisation" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Condivisione dei dati di utilizzo" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deling av bruksdata" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gebruiksgegevens Delen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Udostępnianie danych użytkowania" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partajarea datelor de utilizare" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обмен данными об использовании" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kullanım Verisi Paylaşımı" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Usage Data Sharing" + } + } + } + }, + "You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." : { + "comment" : "Main summary text for UsageDataPrivacyPreferenceView", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Du kan eventuelt vælge at dele brugsdata med Loop-udviklere for at forbedre Loop. Deling er ikke påkrævet, og Loop vil fungere fuldt ud, uanset hvilken mulighed du vælger. Brugsdata vil ikke blive delt med tredjeparter." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Du kannst optional wählen, ob Du Nutzungsdaten mit Loop-Entwicklern teilen möchtest, um Loop zu verbessern. Die Weitergabe ist nicht erforderlich und Loop wird unabhängig davon vollständig funktionieren. Die Nutzungsdaten werden nicht an Dritte weitergegeben." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Puedes optar por compartir los datos de uso con los desarrolladores del Loop para mejorarlo. No es obligatorio compartirlos y Loop funcionará plenamente independientemente de la opción que elijas. Los datos de uso no se compartirán con terceros." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vous pouvez éventuellement choisir de partager les données d'utilisation avec les développeurs de Loop pour améliorer Loop. Le partage n'est pas nécessaire et Loop fonctionnera pleinement, quelle que soit l'option choisie. Les données d'utilisation ne seront pas partagées avec des tiers." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "È possibile scegliere di condividere i dati di utilizzo con gli sviluppatori di Loop per migliorare Loop. La condivisione non è obbligatoria e Loop funzionerà pienamente indipendentemente dall'opzione scelta. I dati di utilizzo non saranno condivisi con terze parti." + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Du kan valgfritt velge å dele bruksdata med Loop-utviklere for å forbedre Loop. Deling er ikke nødvendig, og Loop vil fungere fullt ut uansett hvilket alternativ du velger. Bruksdata vil ikke bli delt med tredjeparter." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je kunt ervoor kiezen om gebruiksgegevens te delen met de ontwikkelaars van Loop om Loop te verbeteren. Het delen is niet vereist en Loop zal volledig functioneren, ongeacht welke optie je kiest. Gebruiksgegevens worden niet gedeeld met derden." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opcjonalnie możesz udostępnić dane użytkowania programistom, aby ulepszyć pętlę. Udostępnianie nie jest wymagane, a pętla będzie w pełni funkcjonalna bez względu na wybraną opcję. Dane dotyczące użytkowania nie będą udostępniane innym osobom." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Puteți alege opțional să partajați datele de utilizare cu dezvoltatorii Loop pentru a îmbunătăți Loop. Partajarea nu este necesară, iar Loop va funcționa pe deplin, indiferent de opțiunea pe care o alegeți. Datele de utilizare nu vor fi partajate cu terțe părți." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "По желанию вы можете поделиться данными об использовании с разработчиками Loop, чтобы улучшить Loop. Передача данных не является обязательной, и Loop будет функционировать в полном объеме независимо от того, какой вариант вы выберете. Данные об использовании не будут передаваться третьим лицам." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop'u iyileştirmek için isteğe bağlı olarak Loop geliştiricileri ile kullanım verilerini paylaşmayı seçebilirsiniz. Paylaşım gerekli değildir ve hangi seçeneği seçerseniz seçin Loop tam olarak çalışacaktır. Kullanım verileri üçüncü şahıslarla paylaşılmayacaktır." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." + } + } + } + } + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/LoopSupportKitUI/cs.lproj/Localizable.strings b/LoopSupportKitUI/cs.lproj/Localizable.strings deleted file mode 100644 index 394a2f8..0000000 --- a/LoopSupportKitUI/cs.lproj/Localizable.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Pokračovat"; - -/* Navigation link title for Submit Bug Report */ -"Submit Bug Report" = "Odeslat hlášení o chybě"; - -/* Navigation Link Title for Usage Data Sharing */ -"Usage Data Sharing" = "Sdílení dat o používání"; - diff --git a/LoopSupportKitUI/da.lproj/Localizable.strings b/LoopSupportKitUI/da.lproj/Localizable.strings deleted file mode 100644 index 7339718..0000000 --- a/LoopSupportKitUI/da.lproj/Localizable.strings +++ /dev/null @@ -1,36 +0,0 @@ -/* Description in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." = "Del anonymt minimale data om denne Loop-version for at hjælpe udviklere med at vide, hvor mange mennesker der bruger Loop. Hvilken enhed og operativsystemversion vil også blive delt."; - -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Fortsæt"; - -/* Description in UsageDataPrivacyPreferenceView for no sharing */ -"Do not share any data." = "Del ikke data."; - -/* Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." = "Ud over versionsoplysninger kan du anonymt dele data om, hvordan Loop bruges på din telefon. Brugsdata omfatter begivenheder som at åbne Loop, bolus, tilføje kulhydrater og navigere mellem skærme. De omfatter ikke sundhedsdata som glukoseværdier eller doseringsmængder."; - -/* Title for LoopKit Analytics */ -"LoopKit Analytics" = "LoopKit Analytics"; - -/* Title in UsageDataPrivacyPreferenceView for no sharing */ -"No Sharing" = "Ingen deling"; - -/* Title on UsageDataPrivacyPreferenceView */ -"Share Data" = "Del data"; - -/* Title in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"Share Usage Data" = "Del brugsdata"; - -/* Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Share Version Only" = "Del kun version"; - -/* Navigation link title for Submit Bug Report */ -"Submit Bug Report" = "Indsend fejlrapport"; - -/* Navigation Link Title for Usage Data Sharing */ -"Usage Data Sharing" = "Deling af brugerdata"; - -/* Main summary text for UsageDataPrivacyPreferenceView */ -"You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." = "Du kan eventuelt vælge at dele brugsdata med Loop-udviklere for at forbedre Loop. Deling er ikke påkrævet, og Loop vil fungere fuldt ud, uanset hvilken mulighed du vælger. Brugsdata vil ikke blive delt med tredjeparter."; - diff --git a/LoopSupportKitUI/de.lproj/Localizable.strings b/LoopSupportKitUI/de.lproj/Localizable.strings deleted file mode 100644 index 18159ea..0000000 --- a/LoopSupportKitUI/de.lproj/Localizable.strings +++ /dev/null @@ -1,36 +0,0 @@ -/* Description in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." = "Teile anonym minimale Daten über diese Loop-Version, damit die Entwickler wissen, wie viele Personen Loop verwenden. Welches Gerät und welche Betriebssystemversion genutzt werden, wird ebenfalls geteilt."; - -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Weiter"; - -/* Description in UsageDataPrivacyPreferenceView for no sharing */ -"Do not share any data." = "Keine Daten teilen."; - -/* Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." = "Zusätzlich zu den Versionsinformationen kannst Du anonym Daten darüber weitergeben, wie Loop auf Deinem Telefon verwendet wird. Zu den Nutzungsdaten gehören Ereignisse wie das Öffnen von Loop, Bolusgaben, Hinzufügen von Kohlenhydraten und das Navigieren zwischen Bildschirmen. Sie umfassen keine Gesundheitsdaten wie Glukosewerte oder Bolusmengen."; - -/* Title for LoopKit Analytics */ -"LoopKit Analytics" = "LoopKit Analytics"; - -/* Title in UsageDataPrivacyPreferenceView for no sharing */ -"No Sharing" = "Keine Daten teilen"; - -/* Title on UsageDataPrivacyPreferenceView */ -"Share Data" = "Nutzungsdaten teilen"; - -/* Title in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"Share Usage Data" = "Nutzungsdaten teilen"; - -/* Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Share Version Only" = "Nur Version teilen"; - -/* Navigation link title for Submit Bug Report */ -"Submit Bug Report" = "Fehlerbericht senden"; - -/* Navigation Link Title for Usage Data Sharing */ -"Usage Data Sharing" = "Nutzungsdaten teilen"; - -/* Main summary text for UsageDataPrivacyPreferenceView */ -"You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." = "Du kannst optional wählen, ob Du Nutzungsdaten mit Loop-Entwicklern teilen möchtest, um Loop zu verbessern. Die Weitergabe ist nicht erforderlich und Loop wird unabhängig davon vollständig funktionieren. Die Nutzungsdaten werden nicht an Dritte weitergegeben."; - diff --git a/LoopSupportKitUI/en.lproj/Localizable.strings b/LoopSupportKitUI/en.lproj/Localizable.strings deleted file mode 100644 index 330a31e..0000000 --- a/LoopSupportKitUI/en.lproj/Localizable.strings +++ /dev/null @@ -1,36 +0,0 @@ -/* Description in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." = "Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared."; - -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Continue"; - -/* Description in UsageDataPrivacyPreferenceView for no sharing */ -"Do not share any data." = "Do not share any data."; - -/* Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." = "In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts."; - -/* Title for LoopKit Analytics */ -"LoopKit Analytics" = "LoopKit Analytics"; - -/* Title in UsageDataPrivacyPreferenceView for no sharing */ -"No Sharing" = "No Sharing"; - -/* Title on UsageDataPrivacyPreferenceView */ -"Share Data" = "Share Data"; - -/* Title in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"Share Usage Data" = "Share Usage Data"; - -/* Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Share Version Only" = "Share Version Only"; - -/* Navigation link title for Submit Bug Report */ -"Submit Bug Report" = "Submit Bug Report"; - -/* Navigation Link Title for Usage Data Sharing */ -"Usage Data Sharing" = "Usage Data Sharing"; - -/* Main summary text for UsageDataPrivacyPreferenceView */ -"You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." = "You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties."; - diff --git a/LoopSupportKitUI/es.lproj/Localizable.strings b/LoopSupportKitUI/es.lproj/Localizable.strings deleted file mode 100644 index d360fd5..0000000 --- a/LoopSupportKitUI/es.lproj/Localizable.strings +++ /dev/null @@ -1,36 +0,0 @@ -/* Description in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." = "Comparta de forma anónima datos mínimos sobre esta versión de Loop, para ayudar a los desarrolladores a saber cuántas personas están usando Loop. Su dispositivo y versión del sistema operativo también se compartirán."; - -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Continuar"; - -/* Description in UsageDataPrivacyPreferenceView for no sharing */ -"Do not share any data." = "No compartir ningún dato."; - -/* Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." = "Además de la información sobre la versión, comparte de forma anónima datos sobre cómo se utiliza Loop en tu teléfono. Los datos de uso incluyen eventos como la apertura de Loop, la administración de bolos, la adición de carbohidratos y la navegación entre pantallas. No incluye datos de salud como valores de glucosa o cantidades de dosis."; - -/* Title for LoopKit Analytics */ -"LoopKit Analytics" = "Análisis LoopKit"; - -/* Title in UsageDataPrivacyPreferenceView for no sharing */ -"No Sharing" = "No compartir"; - -/* Title on UsageDataPrivacyPreferenceView */ -"Share Data" = "Compartir datos"; - -/* Title in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"Share Usage Data" = "Compartir datos de uso"; - -/* Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Share Version Only" = "Sólo versión para compartir"; - -/* Navigation link title for Submit Bug Report */ -"Submit Bug Report" = "Enviar informe de error"; - -/* Navigation Link Title for Usage Data Sharing */ -"Usage Data Sharing" = "Uso compartido de datos"; - -/* Main summary text for UsageDataPrivacyPreferenceView */ -"You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." = "Puedes optar por compartir los datos de uso con los desarrolladores del Loop para mejorarlo. No es obligatorio compartirlos y Loop funcionará plenamente independientemente de la opción que elijas. Los datos de uso no se compartirán con terceros."; - diff --git a/LoopSupportKitUI/fi.lproj/Localizable.strings b/LoopSupportKitUI/fi.lproj/Localizable.strings deleted file mode 100644 index 8aa5032..0000000 --- a/LoopSupportKitUI/fi.lproj/Localizable.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Jatka"; - diff --git a/LoopSupportKitUI/fr.lproj/Localizable.strings b/LoopSupportKitUI/fr.lproj/Localizable.strings deleted file mode 100644 index 161421b..0000000 --- a/LoopSupportKitUI/fr.lproj/Localizable.strings +++ /dev/null @@ -1,36 +0,0 @@ -/* Description in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." = "Partager anonymement des données minimales sur cette version de Loop, pour aider les développeurs à comprendre combien de personnes utilisent Loop. L'appareil et la version du système d'exploitation seront également partagés."; - -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Continuer"; - -/* Description in UsageDataPrivacyPreferenceView for no sharing */ -"Do not share any data." = "Ne partager aucune donnée."; - -/* Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." = "En plus des informations de version, partagez anonymement des données sur la façon dont Loop est utilisé sur votre téléphone. Les données d’utilisation comprennent des événements tels que l’ouverture de boucle, les bolus, l’ajout de glucides et la navigation entre les écrans. Il n’inclut pas les données de santé telles que les valeurs de glucose ou les quantités de dosage."; - -/* Title for LoopKit Analytics */ -"LoopKit Analytics" = "LoopKit Analytics"; - -/* Title in UsageDataPrivacyPreferenceView for no sharing */ -"No Sharing" = "Pas de partage"; - -/* Title on UsageDataPrivacyPreferenceView */ -"Share Data" = "Partager les données"; - -/* Title in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"Share Usage Data" = "Partager les données d'utilisation"; - -/* Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Share Version Only" = "Partager la version uniquement"; - -/* Navigation link title for Submit Bug Report */ -"Submit Bug Report" = "Soumettre un rapport de bug"; - -/* Navigation Link Title for Usage Data Sharing */ -"Usage Data Sharing" = "Partage des données d'utilisation"; - -/* Main summary text for UsageDataPrivacyPreferenceView */ -"You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." = "Vous pouvez éventuellement choisir de partager les données d'utilisation avec les développeurs de Loop pour améliorer Loop. Le partage n'est pas nécessaire et Loop fonctionnera pleinement, quelle que soit l'option choisie. Les données d'utilisation ne seront pas partagées avec des tiers."; - diff --git a/LoopSupportKitUI/he.lproj/Localizable.strings b/LoopSupportKitUI/he.lproj/Localizable.strings deleted file mode 100644 index 03a3189..0000000 --- a/LoopSupportKitUI/he.lproj/Localizable.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Continue"; - diff --git a/LoopSupportKitUI/hi.lproj/Localizable.strings b/LoopSupportKitUI/hi.lproj/Localizable.strings deleted file mode 100644 index f4b15b8..0000000 --- a/LoopSupportKitUI/hi.lproj/Localizable.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Button title for choosing onboarding without nightscout */ -"Continue" = "जारी"; - diff --git a/LoopSupportKitUI/it.lproj/Localizable.strings b/LoopSupportKitUI/it.lproj/Localizable.strings deleted file mode 100644 index aa1eb09..0000000 --- a/LoopSupportKitUI/it.lproj/Localizable.strings +++ /dev/null @@ -1,36 +0,0 @@ -/* Description in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." = "Condividi in modo anonimo dati minimi su questa versione di Loop, per aiutare gli sviluppatori a sapere quante persone stanno utilizzando Loop. Verranno condivisi anche il dispositivo e la versione del sistema operativo."; - -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Continua"; - -/* Description in UsageDataPrivacyPreferenceView for no sharing */ -"Do not share any data." = "Non condividere alcun dato."; - -/* Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." = "Oltre alle informazioni sulla versione, condividi in modo anonimo i dati su come Loop viene utilizzato sul tuo telefono. I dati di utilizzo includono eventi come l'apertura del ciclo, l'erogazione di boli, l'aggiunta di carboidrati e la navigazione tra le schermate. Non include dati sanitari come valori di glicemia o quantità dosate."; - -/* Title for LoopKit Analytics */ -"LoopKit Analytics" = "Analisi LoopKit"; - -/* Title in UsageDataPrivacyPreferenceView for no sharing */ -"No Sharing" = "Nessuna condivisione"; - -/* Title on UsageDataPrivacyPreferenceView */ -"Share Data" = "Condividi dati"; - -/* Title in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"Share Usage Data" = "Condividi dati di utilizzo"; - -/* Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Share Version Only" = "Condividi solo la versione"; - -/* Navigation link title for Submit Bug Report */ -"Submit Bug Report" = "Invia segnalazione bug"; - -/* Navigation Link Title for Usage Data Sharing */ -"Usage Data Sharing" = "Condivisione dei dati di utilizzo"; - -/* Main summary text for UsageDataPrivacyPreferenceView */ -"You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." = "È possibile scegliere di condividere i dati di utilizzo con gli sviluppatori di Loop per migliorare Loop. La condivisione non è obbligatoria e Loop funzionerà pienamente indipendentemente dall'opzione scelta. I dati di utilizzo non saranno condivisi con terze parti."; - diff --git a/LoopSupportKitUI/ja.lproj/Localizable.strings b/LoopSupportKitUI/ja.lproj/Localizable.strings deleted file mode 100644 index a6d0ea7..0000000 --- a/LoopSupportKitUI/ja.lproj/Localizable.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Button title for choosing onboarding without nightscout */ -"Continue" = "次へ"; - diff --git a/LoopSupportKitUI/nb.lproj/Localizable.strings b/LoopSupportKitUI/nb.lproj/Localizable.strings deleted file mode 100644 index eb61eb2..0000000 --- a/LoopSupportKitUI/nb.lproj/Localizable.strings +++ /dev/null @@ -1,36 +0,0 @@ -/* Description in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." = "Del anonymt minimalt med data om denne Loop-versjonen, for å hjelpe utviklere å vite hvor mange som bruker Loop. Hvilken enhet og operativsystemversjon vil også bli delt."; - -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Fortsett"; - -/* Description in UsageDataPrivacyPreferenceView for no sharing */ -"Do not share any data." = "Ikke del noen data."; - -/* Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." = "I tillegg til versjonsinformasjon, kan du anonymt dele data om hvordan Loop brukes på telefonen din. Bruksdata inkluderer hendelser som åpning av loop, bolusering, tilsetning av karbohydrater og navigering mellom skjermer. Den inkluderer ikke helsedata som blodsukkerverdier eller doseringsmengder."; - -/* Title for LoopKit Analytics */ -"LoopKit Analytics" = "LoopKit Analytics"; - -/* Title in UsageDataPrivacyPreferenceView for no sharing */ -"No Sharing" = "Ingen deling"; - -/* Title on UsageDataPrivacyPreferenceView */ -"Share Data" = "Del data"; - -/* Title in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"Share Usage Data" = "Del bruksdata"; - -/* Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Share Version Only" = "Del kun versjon"; - -/* Navigation link title for Submit Bug Report */ -"Submit Bug Report" = "Send inn feilrapport"; - -/* Navigation Link Title for Usage Data Sharing */ -"Usage Data Sharing" = "Deling av bruksdata"; - -/* Main summary text for UsageDataPrivacyPreferenceView */ -"You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." = "Du kan valgfritt velge å dele bruksdata med Loop-utviklere for å forbedre Loop. Deling er ikke nødvendig, og Loop vil fungere fullt ut uansett hvilket alternativ du velger. Bruksdata vil ikke bli delt med tredjeparter."; - diff --git a/LoopSupportKitUI/nl.lproj/Localizable.strings b/LoopSupportKitUI/nl.lproj/Localizable.strings deleted file mode 100644 index 2e316ba..0000000 --- a/LoopSupportKitUI/nl.lproj/Localizable.strings +++ /dev/null @@ -1,36 +0,0 @@ -/* Description in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." = "Deel anoniem een minimale hoeveelheid gegevens over deze versie van Loop, zodat ontwikkelaars weten hoeveel mensen Loop gebruiken. Het type apparaat en de versie van het besturingssysteem wordt ook gedeeld."; - -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Ga Verder"; - -/* Description in UsageDataPrivacyPreferenceView for no sharing */ -"Do not share any data." = "Deel geen gegevens"; - -/* Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." = "Naast versiegegevens kun je anoniem gegevens delen over hoe Loop op je telefoon wordt gebruikt. Gebruiksgegevens omvatten gebeurtenissen zoals het openen van Loop, bolussen, het toevoegen koolhydraten en het navigeren tussen schermen. Het bevat geen gezondheidsgegevens zoals glucosewaarden of doseringhoeveelheden."; - -/* Title for LoopKit Analytics */ -"LoopKit Analytics" = "LoopKit Analytics"; - -/* Title in UsageDataPrivacyPreferenceView for no sharing */ -"No Sharing" = "Niet Delen"; - -/* Title on UsageDataPrivacyPreferenceView */ -"Share Data" = "Deel Gegevens"; - -/* Title in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"Share Usage Data" = "Deel Gebruiksgegevens"; - -/* Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Share Version Only" = "Deel Alleen Versie"; - -/* Navigation link title for Submit Bug Report */ -"Submit Bug Report" = "Bugrapport Indienen"; - -/* Navigation Link Title for Usage Data Sharing */ -"Usage Data Sharing" = "Gebruiksgegevens Delen"; - -/* Main summary text for UsageDataPrivacyPreferenceView */ -"You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." = "Je kunt ervoor kiezen om gebruiksgegevens te delen met de ontwikkelaars van Loop om Loop te verbeteren. Het delen is niet vereist en Loop zal volledig functioneren, ongeacht welke optie je kiest. Gebruiksgegevens worden niet gedeeld met derden."; - diff --git a/LoopSupportKitUI/pl.lproj/Localizable.strings b/LoopSupportKitUI/pl.lproj/Localizable.strings deleted file mode 100644 index da545ff..0000000 --- a/LoopSupportKitUI/pl.lproj/Localizable.strings +++ /dev/null @@ -1,36 +0,0 @@ -/* Description in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." = "Anonimowo udostępniaj ograniczone dane o tej wersji pętli, aby pomóc programistom dowiedzieć się, ile osób z niej korzysta. Udostępnione zostaną również informacje dotyczące telefonu i systemu operacyjnego."; - -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Kontynuuj"; - -/* Description in UsageDataPrivacyPreferenceView for no sharing */ -"Do not share any data." = "Nie udostępniaj żadnych danych."; - -/* Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." = "Oprócz informacji statystycznych możesz anonimowo udostępniać dane dotyczące wykorzystania pętli w Twoim telefonie. Dane dotyczące użytkowania obejmują m.in.: uruchomienie pętli, podawanie bolusa, wprowadzenie węglowodanów i nawigację między ekranami. Nie obejmują danych zdrowotnych, takich jak wartości glukozy czy dawki insuliny."; - -/* Title for LoopKit Analytics */ -"LoopKit Analytics" = "Analityka LoopKit"; - -/* Title in UsageDataPrivacyPreferenceView for no sharing */ -"No Sharing" = "Brak udostępniania"; - -/* Title on UsageDataPrivacyPreferenceView */ -"Share Data" = "Udostępnij dane"; - -/* Title in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"Share Usage Data" = "Udostępnij więcej danych"; - -/* Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Share Version Only" = "Udostępnij statystyki"; - -/* Navigation link title for Submit Bug Report */ -"Submit Bug Report" = "Prześlij raport o błędzie"; - -/* Navigation Link Title for Usage Data Sharing */ -"Usage Data Sharing" = "Udostępnianie danych użytkowania"; - -/* Main summary text for UsageDataPrivacyPreferenceView */ -"You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." = "Opcjonalnie możesz udostępnić dane użytkowania programistom, aby ulepszyć pętlę. Udostępnianie nie jest wymagane, a pętla będzie w pełni funkcjonalna bez względu na wybraną opcję. Dane dotyczące użytkowania nie będą udostępniane innym osobom."; - diff --git a/LoopSupportKitUI/pt-BR.lproj/Localizable.strings b/LoopSupportKitUI/pt-BR.lproj/Localizable.strings deleted file mode 100644 index 4802aab..0000000 --- a/LoopSupportKitUI/pt-BR.lproj/Localizable.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Continuar"; - diff --git a/LoopSupportKitUI/ro.lproj/Localizable.strings b/LoopSupportKitUI/ro.lproj/Localizable.strings deleted file mode 100644 index fdd5080..0000000 --- a/LoopSupportKitUI/ro.lproj/Localizable.strings +++ /dev/null @@ -1,36 +0,0 @@ -/* Description in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." = "Partajați în mod anonim date minime despre această versiune Loop, pentru a ajuta dezvoltatorii să știe câți oameni folosesc Loop. Ce dispozitiv și versiunea sistemului de operare vor fi, de asemenea, partajate."; - -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Continuă"; - -/* Description in UsageDataPrivacyPreferenceView for no sharing */ -"Do not share any data." = "Nu partajați niciun fel de date."; - -/* Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." = "Pe lângă informațiile despre versiune, partajați în mod anonim date despre modul în care Loop este utilizat pe telefonul dvs. Datele de utilizare includ evenimente precum deschiderea buclei, bolusarea, adăugarea de carbohidrați și navigarea între ecrane. Nu include date de sănătate, cum ar fi valorile glucozei sau cantitățile de dozare."; - -/* Title for LoopKit Analytics */ -"LoopKit Analytics" = "Analitice LoopKit"; - -/* Title in UsageDataPrivacyPreferenceView for no sharing */ -"No Sharing" = "Fără partajare"; - -/* Title on UsageDataPrivacyPreferenceView */ -"Share Data" = "Partajați datele"; - -/* Title in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"Share Usage Data" = "Partajați datele de utilizare"; - -/* Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Share Version Only" = "Partajați numai versiunea"; - -/* Navigation link title for Submit Bug Report */ -"Submit Bug Report" = "Raportare probleme"; - -/* Navigation Link Title for Usage Data Sharing */ -"Usage Data Sharing" = "Partajarea datelor de utilizare"; - -/* Main summary text for UsageDataPrivacyPreferenceView */ -"You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." = "Puteți alege opțional să partajați datele de utilizare cu dezvoltatorii Loop pentru a îmbunătăți Loop. Partajarea nu este necesară, iar Loop va funcționa pe deplin, indiferent de opțiunea pe care o alegeți. Datele de utilizare nu vor fi partajate cu terțe părți."; - diff --git a/LoopSupportKitUI/ru.lproj/Localizable.strings b/LoopSupportKitUI/ru.lproj/Localizable.strings deleted file mode 100644 index e88fdff..0000000 --- a/LoopSupportKitUI/ru.lproj/Localizable.strings +++ /dev/null @@ -1,36 +0,0 @@ -/* Description in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." = "Анонимно делиться минимальными данными об этой версии Loop, чтобы помочь разработчикам узнать, сколько людей используют Loop. Также будут переданы данные об устройстве и версии операционной системы."; - -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Продолжить"; - -/* Description in UsageDataPrivacyPreferenceView for no sharing */ -"Do not share any data." = "Не делиться никакими данными."; - -/* Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." = "В дополнение к информации о версии, анонимно делиться данными о том, как Loop используется на вашем телефоне. Данные об использовании включают такие события, как открытие петли, введение болюса, добавление углеводов и перемещение между экранами. Они не включают данные о состоянии здоровья, такие как значения глюкозы или количество доз."; - -/* Title for LoopKit Analytics */ -"LoopKit Analytics" = "LoopKit Analytics"; - -/* Title in UsageDataPrivacyPreferenceView for no sharing */ -"No Sharing" = "Не делиться"; - -/* Title on UsageDataPrivacyPreferenceView */ -"Share Data" = "Поделиться данными"; - -/* Title in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"Share Usage Data" = "Делиться данными об использовании"; - -/* Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Share Version Only" = "Поделиться только версией"; - -/* Navigation link title for Submit Bug Report */ -"Submit Bug Report" = "Отправить отчет об ошибке"; - -/* Navigation Link Title for Usage Data Sharing */ -"Usage Data Sharing" = "Обмен данными об использовании"; - -/* Main summary text for UsageDataPrivacyPreferenceView */ -"You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." = "По желанию вы можете поделиться данными об использовании с разработчиками Loop, чтобы улучшить Loop. Передача данных не является обязательной, и Loop будет функционировать в полном объеме независимо от того, какой вариант вы выберете. Данные об использовании не будут передаваться третьим лицам."; - diff --git a/LoopSupportKitUI/sk.lproj/Localizable.strings b/LoopSupportKitUI/sk.lproj/Localizable.strings deleted file mode 100644 index 07ba582..0000000 --- a/LoopSupportKitUI/sk.lproj/Localizable.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Pokračovať"; - diff --git a/LoopSupportKitUI/sv.lproj/Localizable.strings b/LoopSupportKitUI/sv.lproj/Localizable.strings deleted file mode 100644 index f0784a3..0000000 --- a/LoopSupportKitUI/sv.lproj/Localizable.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Fortsätt"; - diff --git a/LoopSupportKitUI/tr.lproj/Localizable.strings b/LoopSupportKitUI/tr.lproj/Localizable.strings deleted file mode 100644 index ca132c9..0000000 --- a/LoopSupportKitUI/tr.lproj/Localizable.strings +++ /dev/null @@ -1,36 +0,0 @@ -/* Description in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." = "Geliştiricilerin Loop'u kaç kişinin kullandığını bilmelerine yardımcı olmak için bu Loop sürümüyle ilgili minimum verileri anonim olarak paylaşın. Hangi cihaz olduğu ve işletim sistemi sürümü de paylaşılacaktır."; - -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Devam et"; - -/* Description in UsageDataPrivacyPreferenceView for no sharing */ -"Do not share any data." = "Herhangi bir veri paylaşmayın."; - -/* Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." = "Sürüm bilgilerine ek olarak, Loop'un telefonunuzda nasıl kullanıldığına ilişkin verileri anonim olarak paylaşın. Kullanım verileri, Loop'u açma, bolus gönderme, karbonhidrat ekleme ve ekranlar arasında gezinme gibi olayları içerir. KŞ değerleri veya dozaj miktarları gibi sağlık verilerini içermez."; - -/* Title for LoopKit Analytics */ -"LoopKit Analytics" = "LoopKit Analizi"; - -/* Title in UsageDataPrivacyPreferenceView for no sharing */ -"No Sharing" = "Paylaşım Yok"; - -/* Title on UsageDataPrivacyPreferenceView */ -"Share Data" = "Veri Paylaşımı"; - -/* Title in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"Share Usage Data" = "Kullanım Verilerini Paylaşın"; - -/* Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Share Version Only" = "Yalnızca Sürümü Paylaş"; - -/* Navigation link title for Submit Bug Report */ -"Submit Bug Report" = "Hata Raporu Gönder"; - -/* Navigation Link Title for Usage Data Sharing */ -"Usage Data Sharing" = "Kullanım Verisi Paylaşımı"; - -/* Main summary text for UsageDataPrivacyPreferenceView */ -"You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." = "Loop'u iyileştirmek için isteğe bağlı olarak Loop geliştiricileri ile kullanım verilerini paylaşmayı seçebilirsiniz. Paylaşım gerekli değildir ve hangi seçeneği seçerseniz seçin Loop tam olarak çalışacaktır. Kullanım verileri üçüncü şahıslarla paylaşılmayacaktır."; - diff --git a/LoopSupportKitUI/vi.lproj/Localizable.strings b/LoopSupportKitUI/vi.lproj/Localizable.strings deleted file mode 100644 index 5221154..0000000 --- a/LoopSupportKitUI/vi.lproj/Localizable.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Button title for choosing onboarding without nightscout */ -"Continue" = "Tiếp tục"; - diff --git a/LoopSupportKitUI/zh-Hans.lproj/Localizable.strings b/LoopSupportKitUI/zh-Hans.lproj/Localizable.strings deleted file mode 100644 index 2208cd9..0000000 --- a/LoopSupportKitUI/zh-Hans.lproj/Localizable.strings +++ /dev/null @@ -1,36 +0,0 @@ -/* Description in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." = "Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared."; - -/* Button title for choosing onboarding without nightscout */ -"Continue" = "继续"; - -/* Description in UsageDataPrivacyPreferenceView for no sharing */ -"Do not share any data." = "Do not share any data."; - -/* Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." = "In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts."; - -/* Title for LoopKit Analytics */ -"LoopKit Analytics" = "LoopKit Analytics"; - -/* Title in UsageDataPrivacyPreferenceView for no sharing */ -"No Sharing" = "No Sharing"; - -/* Title on UsageDataPrivacyPreferenceView */ -"Share Data" = "Share Data"; - -/* Title in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers */ -"Share Usage Data" = "Share Usage Data"; - -/* Title in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly */ -"Share Version Only" = "Share Version Only"; - -/* Navigation link title for Submit Bug Report */ -"Submit Bug Report" = "Submit Bug Report"; - -/* Navigation Link Title for Usage Data Sharing */ -"Usage Data Sharing" = "Usage Data Sharing"; - -/* Main summary text for UsageDataPrivacyPreferenceView */ -"You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." = "You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties."; - From 9239e1c19b6ae17c727291393c0c4aaf8fd41552 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Tue, 7 Oct 2025 14:09:01 -0700 Subject: [PATCH 05/17] add ce, hu, uk languages --- LoopSupport.xcodeproj/project.pbxproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LoopSupport.xcodeproj/project.pbxproj b/LoopSupport.xcodeproj/project.pbxproj index 0cac0e0..7d1cc0b 100644 --- a/LoopSupport.xcodeproj/project.pbxproj +++ b/LoopSupport.xcodeproj/project.pbxproj @@ -304,6 +304,9 @@ sk, sv, vi, + ce, + hu, + uk, ); mainGroup = A936A90425BC99CB0085119C; packageReferences = ( From b7252aa4f93c75c383e366004ca13028aaa056f0 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Wed, 19 Nov 2025 10:00:05 -0800 Subject: [PATCH 06/17] Updated translations from lokalise on Wed Nov 19 09:07:32 PST 2025 --- LoopSupportKitUI/Localizable.xcstrings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LoopSupportKitUI/Localizable.xcstrings b/LoopSupportKitUI/Localizable.xcstrings index cb7d45f..143ff2f 100644 --- a/LoopSupportKitUI/Localizable.xcstrings +++ b/LoopSupportKitUI/Localizable.xcstrings @@ -808,7 +808,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Raportare probleme" + "value" : "Trimiteți raportul de eroare" } }, "ru" : { From 2a64a2a5bc29e5fc6b16733d53e0d8572d011ed0 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Sun, 1 Feb 2026 10:12:25 -0800 Subject: [PATCH 07/17] Updated translations from lokalise on Sun Feb 1 09:46:29 PST 2026 --- LoopSupportKitUI/Localizable.xcstrings | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/LoopSupportKitUI/Localizable.xcstrings b/LoopSupportKitUI/Localizable.xcstrings index 143ff2f..02d52d4 100644 --- a/LoopSupportKitUI/Localizable.xcstrings +++ b/LoopSupportKitUI/Localizable.xcstrings @@ -73,7 +73,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." + "value" : "将匿名共享与当前 Loop 版本相关的最少数据,帮助开发者统计 Loop 的使用人数。共享内容还包括设备类型和操作系统版本。" } } } @@ -281,7 +281,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Do not share any data." + "value" : "不共享任何数据。" } } } @@ -358,7 +358,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." + "value" : "除了版本信息外,Loop 还会匿名收集您在本机上的使用情况,例如打开应用、进行大剂量给药、记录碳水,以及在各个界面之间切换。这些数据不包含血糖数值或任何给药剂量等健康信息。" } } } @@ -512,7 +512,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "No Sharing" + "value" : "不共享" } } } @@ -589,7 +589,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Share Data" + "value" : "数据共享" } } } @@ -666,7 +666,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Share Usage Data" + "value" : "共享使用数据" } } } @@ -743,7 +743,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Share Version Only" + "value" : "仅共享版本信息" } } } @@ -826,7 +826,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Submit Bug Report" + "value" : "提交 Bug 报告" } } } @@ -909,7 +909,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Usage Data Sharing" + "value" : "使用数据共享" } } } @@ -986,7 +986,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." + "value" : "您可以选择是否将使用数据共享给 Loop 开发者,用于改进产品。是否共享完全由您决定,且不会影响 Loop 的任何功能。使用数据不会提供给第三方。" } } } From a1ae8418df53edf28f0ec919d5b1db5863bd4d10 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Fri, 27 Mar 2026 16:39:13 -0700 Subject: [PATCH 08/17] Updated translations from lokalise on Fri Mar 27 14:20:33 PDT 2026 --- LoopSupportKitUI/Localizable.xcstrings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LoopSupportKitUI/Localizable.xcstrings b/LoopSupportKitUI/Localizable.xcstrings index 02d52d4..dc7e2c4 100644 --- a/LoopSupportKitUI/Localizable.xcstrings +++ b/LoopSupportKitUI/Localizable.xcstrings @@ -168,7 +168,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Continuă" + "value" : "Continuați" } }, "ru" : { @@ -340,7 +340,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Pe lângă informațiile despre versiune, partajați în mod anonim date despre modul în care Loop este utilizat pe telefonul dvs. Datele de utilizare includ evenimente precum deschiderea buclei, bolusarea, adăugarea de carbohidrați și navigarea între ecrane. Nu include date de sănătate, cum ar fi valorile glucozei sau cantitățile de dozare." + "value" : "Pe lângă informațiile despre versiune, partajați în mod anonim date despre modul în care Loop este utilizat pe telefonul dumneavoastră. Datele de utilizare includ evenimente precum deschiderea buclei, bolusarea, adăugarea de carbohidrați și navigarea între ecrane. Nu include date de sănătate, cum ar fi valorile glicemiei sau insulina administrată." } }, "ru" : { From b783278343abb86f88b5d57bdf31611bb1cbc938 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Tue, 12 May 2026 17:19:24 -0700 Subject: [PATCH 09/17] language modification, add ar, ko; delete ci, hi --- LoopSupport.xcodeproj/project.pbxproj | 4 ++-- LoopSupportKitUI/Localizable.xcstrings | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/LoopSupport.xcodeproj/project.pbxproj b/LoopSupport.xcodeproj/project.pbxproj index 7d1cc0b..e87b962 100644 --- a/LoopSupport.xcodeproj/project.pbxproj +++ b/LoopSupport.xcodeproj/project.pbxproj @@ -298,15 +298,15 @@ Base, fi, he, - hi, ja, "pt-BR", sk, sv, vi, - ce, hu, uk, + ko, + ar, ); mainGroup = A936A90425BC99CB0085119C; packageReferences = ( diff --git a/LoopSupportKitUI/Localizable.xcstrings b/LoopSupportKitUI/Localizable.xcstrings index dc7e2c4..69c0c08 100644 --- a/LoopSupportKitUI/Localizable.xcstrings +++ b/LoopSupportKitUI/Localizable.xcstrings @@ -123,12 +123,6 @@ "value" : "Continue" } }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "जारी" - } - }, "it" : { "stringUnit" : { "state" : "translated", From 21957dfec01d401edf8c7f9574a2975b60e7592b Mon Sep 17 00:00:00 2001 From: Marion Barker <19607791+marionbarker@users.noreply.github.com> Date: Sat, 26 Sep 2026 06:09:36 -0700 Subject: [PATCH 10/17] Updated translations from lokalise on Fri Sep 25 13:06:28 PDT 2026 (#31) --- LoopSupportKitUI/Localizable.xcstrings | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/LoopSupportKitUI/Localizable.xcstrings b/LoopSupportKitUI/Localizable.xcstrings index 69c0c08..c9d9828 100644 --- a/LoopSupportKitUI/Localizable.xcstrings +++ b/LoopSupportKitUI/Localizable.xcstrings @@ -144,7 +144,7 @@ "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ga Verder" + "value" : "Ga verder" } }, "pl" : { @@ -476,7 +476,7 @@ "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Niet Delen" + "value" : "Niet delen" } }, "pl" : { @@ -553,7 +553,7 @@ "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Deel Gegevens" + "value" : "Deel gegevens" } }, "pl" : { @@ -630,7 +630,7 @@ "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Deel Gebruiksgegevens" + "value" : "Deel gebruiksgegevens" } }, "pl" : { @@ -707,7 +707,7 @@ "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Deel Alleen Versie" + "value" : "Deel alleen versie" } }, "pl" : { @@ -790,7 +790,7 @@ "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bugrapport Indienen" + "value" : "Bugrapport indienen" } }, "pl" : { @@ -873,7 +873,7 @@ "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Gebruiksgegevens Delen" + "value" : "Gebruiksgegevens delen" } }, "pl" : { From cfc6de8addba9e2e56270b77261864b3a2b712a0 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 13 Oct 2026 17:05:54 -0500 Subject: [PATCH 11/17] Send analytics to ingest.loopkit.org Usage data now goes to the LoopKit ingest endpoint instead of Amplitude's default API host. --- LoopSupportKitUI/LoopKitAnalytics.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/LoopSupportKitUI/LoopKitAnalytics.swift b/LoopSupportKitUI/LoopKitAnalytics.swift index 73f39af..6dbc69a 100644 --- a/LoopSupportKitUI/LoopKitAnalytics.swift +++ b/LoopSupportKitUI/LoopKitAnalytics.swift @@ -59,6 +59,7 @@ public class LoopKitAnalytics { let amplitude = Amplitude() amplitude.setTrackingOptions(AMPTrackingOptions().disableCity().disableCarrier().disableIDFA().disableLatLng().disableIPAddress().disableRegion()) amplitude.initializeApiKey("7dd7414785560c0dd1ef802ac10c00b4") + amplitude.setServerUrl("https://ingest.loopkit.org/v1/events") client = amplitude client?.logEvent("LoopKitAnalytics instantiation") #endif From 4983fd6b65bcee7bb09b2d54e0bb499807d0c251 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 13 Oct 2026 19:35:40 -0500 Subject: [PATCH 12/17] Include build details on the instantiation event Adds the build number and the LoopWorkspace and Loop git branch and revision from BuildDetails.plist, so builds sharing a version string can be told apart. This event is sent at the version-only sharing level. --- LoopSupportKitUI/LoopKitAnalytics.swift | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/LoopSupportKitUI/LoopKitAnalytics.swift b/LoopSupportKitUI/LoopKitAnalytics.swift index 6dbc69a..0c74f10 100644 --- a/LoopSupportKitUI/LoopKitAnalytics.swift +++ b/LoopSupportKitUI/LoopKitAnalytics.swift @@ -61,10 +61,26 @@ public class LoopKitAnalytics { amplitude.initializeApiKey("7dd7414785560c0dd1ef802ac10c00b4") amplitude.setServerUrl("https://ingest.loopkit.org/v1/events") client = amplitude - client?.logEvent("LoopKitAnalytics instantiation") + client?.logEvent("LoopKitAnalytics instantiation", withEventProperties: buildProperties) #endif } + private var buildProperties: [String: Any] { + var properties: [String: Any] = [:] + properties["buildNumber"] = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") + guard let url = Bundle.main.url(forResource: "BuildDetails", withExtension: "plist"), + let data = try? Data(contentsOf: url), + let details = try? PropertyListSerialization.propertyList(from: data, format: nil) as? [String: Any] + else { + return properties + } + properties["workspaceBranch"] = details["com-loopkit-LoopWorkspace-git-branch"] + properties["workspaceRevision"] = details["com-loopkit-LoopWorkspace-git-revision"] + properties["loopBranch"] = details["com-loopkit-Loop-git-branch"] + properties["loopRevision"] = details["com-loopkit-Loop-git-revision"] + return properties + } + public func recordAnalyticsEvent(_ name: String, withProperties properties: [AnyHashable : Any]?, outOfSession: Bool) { if usageDataPrivacyPreference == .shareUsageDetailsWithDevelopers { client?.logEvent(name, withEventProperties: properties, outOfSession: outOfSession) From cedd5cb408077c20b4b38fcafaa4cf9a61e4a68c Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 13 Oct 2026 20:10:46 -0500 Subject: [PATCH 13/17] Include build customizations on the instantiation event Sends the identifiers Loop's build-details script collected from .loopcustomization marker files. --- LoopSupportKitUI/LoopKitAnalytics.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/LoopSupportKitUI/LoopKitAnalytics.swift b/LoopSupportKitUI/LoopKitAnalytics.swift index 0c74f10..6828407 100644 --- a/LoopSupportKitUI/LoopKitAnalytics.swift +++ b/LoopSupportKitUI/LoopKitAnalytics.swift @@ -78,6 +78,7 @@ public class LoopKitAnalytics { properties["workspaceRevision"] = details["com-loopkit-LoopWorkspace-git-revision"] properties["loopBranch"] = details["com-loopkit-Loop-git-branch"] properties["loopRevision"] = details["com-loopkit-Loop-git-revision"] + properties["customizations"] = details["com-loopkit-Loop-customizations"] return properties } From 95a1ba84a6a2695db896fd28d5e2dc94176b4328 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 21 Oct 2026 07:04:09 -0500 Subject: [PATCH 14/17] Provide Report a Problem from LoopSupport (#40) * Require iOS 17.6 * Provide Report a Problem from LoopSupport * Leave the GitHub issues link to Loop's Report a Problem screen Loop now lists every way to report a problem on one screen, including GitHub issues, so LoopSupport no longer adds its own Submit Bug Report row to Support. Also drops supportMenuItem, which nothing calls. * Provide Report a Problem from LoopSupport The Report a Problem screen, the developer report form and its submitter move here from Loop, so each support plugin decides what its users can report and where it goes. LoopSupport adds one Report a Problem row to Support with all four options: send to the Loop developers, share a diagnostic report, open a GitHub issue, and export critical event logs through the app. The report context is built from the app's bundle and BuildDetails plus the pump and CGM status the delegate provides. * Generate the diagnostic report through dev's completion handler * update string catalog --------- Co-authored-by: marionbarker --- LoopSupport.xcodeproj/project.pbxproj | 28 +- .../IssueReport/IssueReportContext.swift | 45 ++++ .../IssueReport/IssueReportSubmitter.swift | 139 ++++++++++ .../IssueReport/ReportProblemView.swift | 161 +++++++++++ .../IssueReport/SendIssueReportView.swift | 249 ++++++++++++++++++ LoopSupportKitUI/Localizable.xcstrings | 121 +++++++++ LoopSupportKitUI/LoopSupportUI.swift | 17 +- 7 files changed, 746 insertions(+), 14 deletions(-) create mode 100644 LoopSupportKitUI/IssueReport/IssueReportContext.swift create mode 100644 LoopSupportKitUI/IssueReport/IssueReportSubmitter.swift create mode 100644 LoopSupportKitUI/IssueReport/ReportProblemView.swift create mode 100644 LoopSupportKitUI/IssueReport/SendIssueReportView.swift diff --git a/LoopSupport.xcodeproj/project.pbxproj b/LoopSupport.xcodeproj/project.pbxproj index e87b962..728533e 100644 --- a/LoopSupport.xcodeproj/project.pbxproj +++ b/LoopSupport.xcodeproj/project.pbxproj @@ -21,6 +21,10 @@ B66D1F552E6A752300471149 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B66D1F542E6A752300471149 /* Localizable.xcstrings */; }; C12ECD05289560B90089EB07 /* LoopSupportKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A936A95425BC9A190085119C /* LoopSupportKitUI.framework */; platformFilter = ios; }; C12ECD06289560B90089EB07 /* LoopSupportKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A936A95425BC9A190085119C /* LoopSupportKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C1A55E022E9F000000B10002 /* IssueReportContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A55E022E9F000000B10001 /* IssueReportContext.swift */; }; + C1A55E022E9F000000B10004 /* IssueReportSubmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A55E022E9F000000B10003 /* IssueReportSubmitter.swift */; }; + C1A55E022E9F000000B10006 /* ReportProblemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A55E022E9F000000B10005 /* ReportProblemView.swift */; }; + C1A55E022E9F000000B10008 /* SendIssueReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A55E022E9F000000B10007 /* SendIssueReportView.swift */; }; C14A7E7C295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14A7E7B295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift */; }; C15349062960AA9000B47074 /* UsageDataPrivacyPreferenceMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15349052960AA9000B47074 /* UsageDataPrivacyPreferenceMenuItem.swift */; }; C1BE9741295E3800008BA25D /* LoopKitAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BE9740295E3800008BA25D /* LoopKitAnalytics.swift */; }; @@ -84,6 +88,10 @@ A9CE800A25BCACAE00C04F9B /* LoopSupportPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopSupportPlugin.swift; sourceTree = ""; }; A9CE801225BCAE4000C04F9B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B66D1F542E6A752300471149 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; + C1A55E022E9F000000B10001 /* IssueReportContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueReportContext.swift; sourceTree = ""; }; + C1A55E022E9F000000B10003 /* IssueReportSubmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueReportSubmitter.swift; sourceTree = ""; }; + C1A55E022E9F000000B10005 /* ReportProblemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportProblemView.swift; sourceTree = ""; }; + C1A55E022E9F000000B10007 /* SendIssueReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendIssueReportView.swift; sourceTree = ""; }; C14A7E7B295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsageDataPrivacyPreferenceView.swift; sourceTree = ""; }; C15349052960AA9000B47074 /* UsageDataPrivacyPreferenceMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsageDataPrivacyPreferenceMenuItem.swift; sourceTree = ""; }; C1BE9740295E3800008BA25D /* LoopKitAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopKitAnalytics.swift; sourceTree = ""; }; @@ -136,6 +144,7 @@ isa = PBXGroup; children = ( C14A7E7A295F2EA700CD87B4 /* Views */, + C1A55E022E9F000000B10000 /* IssueReport */, A936A95625BC9A190085119C /* LoopSupportKitUI.h */, A936A95725BC9A190085119C /* Info.plist */, B66D1F542E6A752300471149 /* Localizable.xcstrings */, @@ -185,6 +194,17 @@ name = Frameworks; sourceTree = ""; }; + C1A55E022E9F000000B10000 /* IssueReport */ = { + isa = PBXGroup; + children = ( + C1A55E022E9F000000B10001 /* IssueReportContext.swift */, + C1A55E022E9F000000B10003 /* IssueReportSubmitter.swift */, + C1A55E022E9F000000B10005 /* ReportProblemView.swift */, + C1A55E022E9F000000B10007 /* SendIssueReportView.swift */, + ); + path = IssueReport; + sourceTree = ""; + }; C14A7E7A295F2EA700CD87B4 /* Views */ = { isa = PBXGroup; children = ( @@ -349,6 +369,10 @@ A9CE7DA125BC9C0200C04F9B /* OSLog.swift in Sources */, C15349062960AA9000B47074 /* UsageDataPrivacyPreferenceMenuItem.swift in Sources */, C14A7E7C295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift in Sources */, + C1A55E022E9F000000B10002 /* IssueReportContext.swift in Sources */, + C1A55E022E9F000000B10004 /* IssueReportSubmitter.swift in Sources */, + C1A55E022E9F000000B10006 /* ReportProblemView.swift in Sources */, + C1A55E022E9F000000B10008 /* SendIssueReportView.swift in Sources */, A9CE7D8425BC9B3700C04F9B /* LocalizedString.swift in Sources */, A9CE800325BCAC8700C04F9B /* LoopSupportUI.swift in Sources */, C1BE9741295E3800008BA25D /* LoopKitAnalytics.swift in Sources */, @@ -477,7 +501,7 @@ GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_PARAMETER = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, @@ -582,7 +606,7 @@ GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_PARAMETER = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, diff --git a/LoopSupportKitUI/IssueReport/IssueReportContext.swift b/LoopSupportKitUI/IssueReport/IssueReportContext.swift new file mode 100644 index 0000000..c9f9a19 --- /dev/null +++ b/LoopSupportKitUI/IssueReport/IssueReportContext.swift @@ -0,0 +1,45 @@ +// +// IssueReportContext.swift +// LoopSupportKitUI +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import UIKit +import LoopKit +import LoopKitUI + +enum IssueReportContext { + /// Build, device and pump/CGM details sent with every issue report. + @MainActor + static func current(supportInfo: SupportInfoProvider?) -> [String: IssueReportContextValue] { + let info = Bundle.main.infoDictionary ?? [:] + let buildDetails = Bundle.main.url(forResource: "BuildDetails", withExtension: "plist") + .flatMap { NSDictionary(contentsOf: $0) as? [String: Any] } ?? [:] + + var context: [String: IssueReportContextValue] = [ + "appVersion": .string(info["CFBundleShortVersionString"] as? String ?? ""), + "buildNumber": .string(info["CFBundleVersion"] as? String ?? ""), + "customizations": .strings(buildDetails["com-loopkit-Loop-customizations"] as? [String] ?? []), + "deviceModel": .string(modelIdentifier), + "osVersion": .string(UIDevice.current.systemVersion), + ] + let branch = buildDetails["com-loopkit-LoopWorkspace-git-branch"] as? String ?? buildDetails["com-loopkit-Loop-git-branch"] as? String + context["branch"] = branch.map { .string($0) } + context["workspaceRevision"] = (buildDetails["com-loopkit-LoopWorkspace-git-revision"] as? String).map { .string($0) } + context["loopRevision"] = (buildDetails["com-loopkit-Loop-git-revision"] as? String).map { .string($0) } + context["pumpModel"] = supportInfo?.pumpStatus.map { .string($0.device.model ?? "Unknown") } + context["cgmModel"] = supportInfo?.cgmStatus.map { .string($0.device?.model ?? "Unknown") } + return context + } + + private static var modelIdentifier: String { + #if targetEnvironment(simulator) + return "\(UIDevice.current.model)Simulator" + #else + var info = utsname() + uname(&info) + return withUnsafePointer(to: &info.machine) { $0.withMemoryRebound(to: CChar.self, capacity: 1) { String(validatingUTF8: $0) } } ?? "unknown" + #endif + } +} diff --git a/LoopSupportKitUI/IssueReport/IssueReportSubmitter.swift b/LoopSupportKitUI/IssueReport/IssueReportSubmitter.swift new file mode 100644 index 0000000..13b4f3c --- /dev/null +++ b/LoopSupportKitUI/IssueReport/IssueReportSubmitter.swift @@ -0,0 +1,139 @@ +// +// IssueReportSubmitter.swift +// LoopSupportKitUI +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import Foundation + +struct IssueReport { + struct Contact: Encodable { + var name: String? + var email: String? + var zulip: String? + var github: String? + } + + struct Attachment { + var filename: String + var contentType: String + var data: Data + } + + var title: String? + var description: String + var contact: Contact? + var context: [String: IssueReportContextValue] + var attachments: [Attachment] +} + +enum IssueReportContextValue: Encodable, Equatable { + case string(String) + case strings([String]) + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .string(let value): try container.encode(value) + case .strings(let value): try container.encode(value) + } + } +} + +enum IssueReportSubmitterError: LocalizedError { + case server(status: Int, reason: String?) + case invalidResponse + case network(Error) + + var isRetryable: Bool { + switch self { + case .server(let status, _): return status >= 500 + case .invalidResponse, .network: return true + } + } + + var errorDescription: String? { + switch self { + case .server(413, _): + return LocalizedString("The report is too large. Try fewer or smaller screenshots.", comment: "Issue report error when the upload is too large") + case .server(415, _): + return LocalizedString("One of the attachments isn't a supported image type.", comment: "Issue report error when an attachment type is refused") + case .server(429, _): + return LocalizedString("Too many reports have been sent from this network recently. Please try again later.", comment: "Issue report error when rate limited") + case .server(let status, let reason): + return String(format: LocalizedString("The server couldn't accept the report (%1$d %2$@).", comment: "Issue report error with HTTP status (1) and reason (2)"), status, reason ?? "") + case .invalidResponse: + return LocalizedString("The server sent an unexpected response.", comment: "Issue report error for an unreadable response") + case .network(let error): + return error.localizedDescription + } + } +} + +struct IssueReportSubmitter { + static let endpoint = URL(string: "https://ingest.loopkit.org/v1/issues")! + static let apiKey = "7dd7414785560c0dd1ef802ac10c00b4" + + var session: URLSession = .shared + + func submit(_ report: IssueReport) async throws -> String { + let boundary = "Boundary-\(UUID().uuidString)" + var request = URLRequest(url: Self.endpoint) + request.httpMethod = "POST" + request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + + let body = try Self.body(for: report, boundary: boundary) + let data: Data + let response: URLResponse + do { + (data, response) = try await session.upload(for: request, from: body) + } catch { + throw IssueReportSubmitterError.network(error) + } + guard let response = response as? HTTPURLResponse else { + throw IssueReportSubmitterError.invalidResponse + } + let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] + guard response.statusCode == 200 else { + throw IssueReportSubmitterError.server(status: response.statusCode, reason: json?["error"] as? String) + } + guard let id = json?["id"] as? String else { + throw IssueReportSubmitterError.invalidResponse + } + return id + } + + static func body(for report: IssueReport, boundary: String) throws -> Data { + struct Payload: Encodable { + var title: String? + var description: String + var contact: IssueReport.Contact? + var context: [String: IssueReportContextValue] + } + let payload = try JSONEncoder().encode(Payload(title: report.title, description: report.description, contact: report.contact, context: report.context)) + + var body = Data() + func appendPart(name: String, filename: String? = nil, contentType: String? = nil, data: Data) { + var header = "--\(boundary)\r\nContent-Disposition: form-data; name=\"\(name)\"" + if let filename { + header += "; filename=\"\(filename.replacingOccurrences(of: "\"", with: ""))\"" + } + header += "\r\n" + if let contentType { + header += "Content-Type: \(contentType)\r\n" + } + body.append(Data((header + "\r\n").utf8)) + body.append(data) + body.append(Data("\r\n".utf8)) + } + + appendPart(name: "client", data: Data(apiKey.utf8)) + appendPart(name: "report", contentType: "application/json", data: payload) + for attachment in report.attachments { + appendPart(name: "attachment", filename: attachment.filename, contentType: attachment.contentType, data: attachment.data) + } + body.append(Data("--\(boundary)--\r\n".utf8)) + return body + } +} diff --git a/LoopSupportKitUI/IssueReport/ReportProblemView.swift b/LoopSupportKitUI/IssueReport/ReportProblemView.swift new file mode 100644 index 0000000..bf30c93 --- /dev/null +++ b/LoopSupportKitUI/IssueReport/ReportProblemView.swift @@ -0,0 +1,161 @@ +// +// ReportProblemView.swift +// LoopSupportKitUI +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import SwiftUI +import LoopKitUI + +struct ReportProblemView: View { + let delegate: SupportUIDelegate? + + @State private var isPreparingReport = false + @State private var reportFile: ReportFile? + @State private var writtenReportURL: URL? + + private struct ReportFile: Identifiable { + let url: URL + var id: URL { url } + } + + var body: some View { + List { + Section { + NavigationLink(destination: SendIssueReportView( + context: IssueReportContext.current(supportInfo: delegate), + generateDiagnosticReport: delegate.map { delegate in { await delegate.generateIssueReport() } } + )) { + option( + LocalizedString("Send to the Loop Developers", comment: "Report a problem option: submit an issue report to the developers"), + detail: LocalizedString("Private. Describe the problem and attach screenshots and a diagnostic report. Only the LoopKit development team can see it.", comment: "Report a problem option detail: submit an issue report to the developers") + ) + } + + if delegate != nil { + Button(action: shareDiagnosticReport) { + HStack { + option( + LocalizedString("Share a Diagnostic Report", comment: "Report a problem option: share the diagnostic report yourself"), + detail: LocalizedString("Save the report or send it to someone helping you, for example on Zulip or Facebook. It contains health data such as glucose, insulin, carbs and your therapy settings.", comment: "Report a problem option detail: share the diagnostic report yourself") + ) + if isPreparingReport { + Spacer() + ProgressView() + } + } + } + .disabled(isPreparingReport) + } + + Button(action: { delegate?.openURL(url: URL(string: "https://github.com/LoopKit/Loop/issues")!) }) { + option( + LocalizedString("Open a GitHub Issue", comment: "Report a problem option: open the public GitHub issues page"), + detail: LocalizedString("Public. For bugs you can describe without any personal or health data.", comment: "Report a problem option detail: open the public GitHub issues page") + ) + } + + if let delegate { + NavigationLink(destination: delegate.criticalEventLogExportView()) { + option( + LocalizedString("Export Critical Event Logs", comment: "Report a problem option: export the critical event logs"), + detail: LocalizedString("Detailed logs as files. Only needed when a developer asks for them.", comment: "Report a problem option detail: export the critical event logs") + ) + } + } + } + } + .listStyle(.insetGrouped) + .navigationTitle(LocalizedString("Report a Problem", comment: "Title of the report a problem screen")) + .navigationBarTitleDisplayMode(.inline) + .sheet(item: $reportFile, onDismiss: removeReportFile) { file in + ActivityView(activityItems: [file.url]) + } + } + + private func option(_ title: String, detail: String) -> some View { + VStack(alignment: .leading, spacing: 4) { + Text(title) + .foregroundColor(.primary) + Text(detail) + .font(.footnote) + .foregroundColor(.secondary) + } + .padding(.vertical, 4) + } + + private func shareDiagnosticReport() { + guard let delegate else { + return + } + isPreparingReport = true + Task { @MainActor in + let date = Date() + let report = "Generated: \(date)\n\n" + (await delegate.generateIssueReport()) + let name = "Loop Report \(ISO8601DateFormatter.string(from: date, timeZone: .current, formatOptions: [.withSpaceBetweenDateAndTime, .withInternetDateTime])).md" + let url = FileManager.default.temporaryDirectory.appendingPathComponent(name.replacingOccurrences(of: ":", with: "-")) + do { + try report.write(to: url, atomically: true, encoding: .utf8) + writtenReportURL = url + reportFile = ReportFile(url: url) + } catch { + reportFile = nil + } + isPreparingReport = false + } + } + + private func removeReportFile() { + if let url = writtenReportURL { + try? FileManager.default.removeItem(at: url) + } + writtenReportURL = nil + } +} + +struct ReportProblemMenuItem: View { + let support: LoopSupportUI + + var body: some View { + NavigationLink(destination: ReportProblemView(delegate: support.delegate)) { + Text(LocalizedString("Report a Problem", comment: "The title text for the report a problem menu item")) + } + } +} + +/// Report a Problem shown on its own, outside settings. +struct ReportProblemModal: View { + let delegate: SupportUIDelegate? + + @Environment(\.dismissAction) private var dismiss + + var body: some View { + NavigationView { + ReportProblemView(delegate: delegate) + .toolbar { + ToolbarItem(placement: .confirmationAction) { + Button(LocalizedString("Done", comment: "Button to close the report a problem screen"), action: dismiss) + } + } + } + } +} + +private struct ActivityView: UIViewControllerRepresentable { + let activityItems: [Any] + + func makeUIViewController(context: Context) -> UIActivityViewController { + UIActivityViewController(activityItems: activityItems, applicationActivities: nil) + } + + func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} +} + +private extension SupportInfoProvider { + func generateIssueReport() async -> String { + await withCheckedContinuation { continuation in + generateIssueReport { continuation.resume(returning: $0) } + } + } +} diff --git a/LoopSupportKitUI/IssueReport/SendIssueReportView.swift b/LoopSupportKitUI/IssueReport/SendIssueReportView.swift new file mode 100644 index 0000000..9c32a6f --- /dev/null +++ b/LoopSupportKitUI/IssueReport/SendIssueReportView.swift @@ -0,0 +1,249 @@ +// +// SendIssueReportView.swift +// LoopSupportKitUI +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import PhotosUI +import SwiftUI + +struct SendIssueReportView: View { + let context: [String: IssueReportContextValue] + let generateDiagnosticReport: (() async -> String)? + + @Environment(\.dismiss) private var dismiss + + @State private var title = "" + @State private var description = "" + @State private var isAnonymous = true + @State private var contact = IssueReport.Contact() + @State private var includeDiagnosticReport = true + @State private var photoSelection: [PhotosPickerItem] = [] + @State private var screenshots: [Screenshot] = [] + @State private var isSending = false + @State private var sentReportID: String? + @State private var sendError: IssueReportSubmitterError? + @State private var showingError = false + + private static let maxDescriptionLength = 20_000 + private static let maxScreenshots = 8 + private static let maxAttachmentBytes = 10_000_000 + + private struct Screenshot: Identifiable { + let id = UUID() + let image: UIImage + let attachment: IssueReport.Attachment + } + + var body: some View { + Form { + Section(footer: Text(String(format: LocalizedString("%1$d of %2$d characters", comment: "Issue report description length (1: used)(2: limit)"), description.count, Self.maxDescriptionLength))) { + TextField(LocalizedString("Title (optional)", comment: "Placeholder for the issue report title"), text: $title) + ZStack(alignment: .topLeading) { + if description.isEmpty { + Text(LocalizedString("Describe what you saw and what you expected to happen", comment: "Placeholder for the issue report description")) + .foregroundColor(Color(.placeholderText)) + .padding(.top, 8) + .padding(.leading, 4) + } + TextEditor(text: $description) + .frame(minHeight: 160) + } + } + + screenshotsSection + + Section(footer: Text(isAnonymous ? + LocalizedString("The report won't include your name, contact details or anything else that identifies you.", comment: "Footer for an anonymous issue report") : + LocalizedString("Fill in any of these so developers can follow up with you.", comment: "Footer for issue report contact details")) + ) { + Toggle(LocalizedString("Submit Anonymously", comment: "Toggle for submitting an issue report without contact details"), isOn: $isAnonymous) + if !isAnonymous { + TextField(LocalizedString("Name", comment: "Issue report contact name placeholder"), text: binding(\.name)) + .textContentType(.name) + TextField(LocalizedString("Email", comment: "Issue report contact email placeholder"), text: binding(\.email)) + .textContentType(.emailAddress) + .keyboardType(.emailAddress) + .textInputAutocapitalization(.never) + TextField(LocalizedString("Zulip Name", comment: "Issue report contact Zulip placeholder"), text: binding(\.zulip)) + .textInputAutocapitalization(.never) + TextField(LocalizedString("GitHub Username", comment: "Issue report contact GitHub placeholder"), text: binding(\.github)) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + } + } + + if generateDiagnosticReport != nil { + Section(footer: Text(LocalizedString("Attaches Loop's issue report as a text file. It contains health data such as recent glucose, insulin, carbs and your therapy settings.", comment: "Footer for the include diagnostic report toggle"))) { + Toggle(LocalizedString("Include Diagnostic Report", comment: "Toggle for attaching the diagnostic report to an issue report"), isOn: $includeDiagnosticReport) + } + } + + Section(header: Text(LocalizedString("Also Sent", comment: "Header for the build and device details sent with an issue report")), + footer: Text(LocalizedString("Reports go to the LoopKit development team and are visible only to them. Your IP address is used only to limit how often reports can be sent and is never stored.", comment: "Privacy footer for issue reports"))) + { + ForEach(context.keys.sorted(), id: \.self) { key in + HStack { + Text(key) + Spacer() + Text(Self.displayValue(context[key])) + .foregroundColor(.secondary) + .multilineTextAlignment(.trailing) + } + .font(.footnote) + } + } + } + .navigationTitle(LocalizedString("Send to Developers", comment: "Title of the issue report screen")) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .confirmationAction) { + if isSending { + ProgressView() + } else { + Button(LocalizedString("Submit", comment: "Button to submit an issue report to the developers")) { + send() + } + .disabled(!canSend) + } + } + } + .disabled(isSending) + .onChange(of: photoSelection) { _, items in + loadScreenshots(items) + } + .alert(LocalizedString("Report Submitted", comment: "Title of the alert after an issue report was submitted"), isPresented: Binding(get: { sentReportID != nil }, set: { if !$0 { sentReportID = nil } })) { + Button(LocalizedString("Copy ID", comment: "Button to copy the issue report ID")) { + UIPasteboard.general.string = sentReportID + dismiss() + } + Button(LocalizedString("Done", comment: "Button to close the issue report sent alert"), role: .cancel) { + dismiss() + } + } message: { + Text(String(format: LocalizedString("Thank you. Your report ID is %1$@. Mention it if you discuss the problem on Zulip or GitHub.", comment: "Message after an issue report was sent (1: report ID)"), sentReportID ?? "")) + } + .alert(LocalizedString("Unable to Submit Report", comment: "Title of the alert when an issue report fails"), isPresented: $showingError, presenting: sendError) { error in + if error.isRetryable { + Button(LocalizedString("Try Again", comment: "Button to retry sending an issue report")) { + send() + } + } + Button(LocalizedString("OK", comment: "Button to close the issue report error alert"), role: .cancel) {} + } message: { error in + Text(error.localizedDescription) + } + } + + private var screenshotsSection: some View { + Section(footer: Text(String(format: LocalizedString("Up to %1$d screenshots or photos.", comment: "Footer for issue report screenshots (1: limit)"), Self.maxScreenshots))) { + if !screenshots.isEmpty { + ScrollView(.horizontal) { + HStack { + ForEach(screenshots) { screenshot in + Image(uiImage: screenshot.image) + .resizable() + .scaledToFit() + .frame(height: 120) + .clipShape(RoundedRectangle(cornerRadius: 6)) + } + } + } + } + PhotosPicker(selection: $photoSelection, maxSelectionCount: Self.maxScreenshots, matching: .images) { + Text(screenshots.isEmpty ? + LocalizedString("Add Screenshots", comment: "Button to choose screenshots for an issue report") : + LocalizedString("Change Screenshots", comment: "Button to change the screenshots on an issue report")) + } + } + } + + private var trimmedDescription: String { + description.trimmingCharacters(in: .whitespacesAndNewlines) + } + + private var canSend: Bool { + !trimmedDescription.isEmpty && description.count <= Self.maxDescriptionLength + } + + private func binding(_ keyPath: WritableKeyPath) -> Binding { + Binding(get: { contact[keyPath: keyPath] ?? "" }, set: { contact[keyPath: keyPath] = $0 }) + } + + private func loadScreenshots(_ items: [PhotosPickerItem]) { + Task { + var loaded: [Screenshot] = [] + for (index, item) in items.enumerated() { + guard let data = try? await item.loadTransferable(type: Data.self), let screenshot = Self.screenshot(from: data, index: index + 1) else { + continue + } + loaded.append(screenshot) + } + screenshots = loaded + } + } + + // The server accepts only PNG and JPEG, so anything else (HEIC from the camera) is converted + private static func screenshot(from data: Data, index: Int) -> Screenshot? { + guard let image = UIImage(data: data) else { + return nil + } + if data.starts(with: [0x89, 0x50, 0x4E, 0x47]), data.count <= maxAttachmentBytes { + return Screenshot(image: image, attachment: .init(filename: "screenshot-\(index).png", contentType: "image/png", data: data)) + } + for quality in [0.8, 0.5, 0.3] { + if let jpeg = image.jpegData(compressionQuality: quality), jpeg.count <= maxAttachmentBytes { + return Screenshot(image: image, attachment: .init(filename: "screenshot-\(index).jpg", contentType: "image/jpeg", data: jpeg)) + } + } + return nil + } + + private static func displayValue(_ value: IssueReportContextValue?) -> String { + switch value { + case .string(let string): return string + case .strings(let strings): return strings.isEmpty ? "–" : strings.joined(separator: ", ") + case nil: return "" + } + } + + private func send() { + isSending = true + Task { + var attachments = screenshots.map(\.attachment) + if includeDiagnosticReport, let generateDiagnosticReport { + let report = await generateDiagnosticReport() + // Stay under the server's per-file limit; decoding repairs a character cut in half + let text = String(decoding: report.utf8.prefix(Self.maxAttachmentBytes - 4), as: UTF8.self) + attachments.append(.init(filename: "diagnostic-report.txt", contentType: "text/plain; charset=utf-8", data: Data(text.utf8))) + } + let report = IssueReport( + title: title.isEmpty ? nil : String(title.prefix(200)), + description: trimmedDescription, + contact: isAnonymous ? nil : Self.trimmed(contact), + context: context, + attachments: attachments + ) + do { + sentReportID = try await IssueReportSubmitter().submit(report) + } catch let error as IssueReportSubmitterError { + sendError = error + showingError = true + } catch { + sendError = .network(error) + showingError = true + } + isSending = false + } + } + + private static func trimmed(_ contact: IssueReport.Contact) -> IssueReport.Contact? { + func clean(_ value: String?) -> String? { + let value = value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + return value.isEmpty ? nil : value + } + let result = IssueReport.Contact(name: clean(contact.name), email: clean(contact.email), zulip: clean(contact.zulip), github: clean(contact.github)) + return [result.name, result.email, result.zulip, result.github].allSatisfy({ $0 == nil }) ? nil : result + } +} diff --git a/LoopSupportKitUI/Localizable.xcstrings b/LoopSupportKitUI/Localizable.xcstrings index c9d9828..3ca37b4 100644 --- a/LoopSupportKitUI/Localizable.xcstrings +++ b/LoopSupportKitUI/Localizable.xcstrings @@ -1,6 +1,15 @@ { "sourceLanguage" : "en", "strings" : { + "%1$d of %2$d characters" : { + "comment" : "Issue report description length (1: used)(2: limit)" + }, + "Add Screenshots" : { + "comment" : "Button to choose screenshots for an issue report" + }, + "Also Sent" : { + "comment" : "Header for the build and device details sent with an issue report" + }, "Anonymously share minimal data about this Loop version, to help developers know how many people are using Loop. Which device, and operating system version will also be shared." : { "comment" : "Description in UsageDataPrivacyPreferenceView for shareInstallationStatsOnly", "localizations" : { @@ -78,6 +87,12 @@ } } }, + "Attaches Loop's issue report as a text file. It contains health data such as recent glucose, insulin, carbs and your therapy settings." : { + "comment" : "Footer for the include diagnostic report toggle" + }, + "Change Screenshots" : { + "comment" : "Button to change the screenshots on an issue report" + }, "Continue" : { "comment" : "Button title for choosing onboarding without nightscout", "localizations" : { @@ -203,6 +218,15 @@ } } }, + "Copy ID" : { + "comment" : "Button to copy the issue report ID" + }, + "Describe what you saw and what you expected to happen" : { + "comment" : "Placeholder for the issue report description" + }, + "Detailed logs as files. Only needed when a developer asks for them." : { + "comment" : "Report a problem option detail: export the critical event logs" + }, "Do not share any data." : { "comment" : "Description in UsageDataPrivacyPreferenceView for no sharing", "localizations" : { @@ -280,6 +304,21 @@ } } }, + "Done" : { + "comment" : "Button to close the issue report sent alert\nButton to close the report a problem screen" + }, + "Email" : { + "comment" : "Issue report contact email placeholder" + }, + "Export Critical Event Logs" : { + "comment" : "Report a problem option: export the critical event logs" + }, + "Fill in any of these so developers can follow up with you." : { + "comment" : "Footer for issue report contact details" + }, + "GitHub Username" : { + "comment" : "Issue report contact GitHub placeholder" + }, "In addition to version information, anonymously share data about how Loop is being used on your phone. Usage data includes events like opening loop, bolusing, adding carbs, and navigating between screens. It does not include health data like glucose values or dosing amounts." : { "comment" : "Description in UsageDataPrivacyPreferenceView for shareUsageDetailsWithDevelopers", "localizations" : { @@ -357,6 +396,9 @@ } } }, + "Include Diagnostic Report" : { + "comment" : "Toggle for attaching the diagnostic report to an issue report" + }, "LoopKit Analytics" : { "comment" : "Title for LoopKit Analytics", "localizations" : { @@ -434,6 +476,9 @@ } } }, + "Name" : { + "comment" : "Issue report contact name placeholder" + }, "No Sharing" : { "comment" : "Title in UsageDataPrivacyPreferenceView for no sharing", "localizations" : { @@ -511,6 +556,42 @@ } } }, + "OK" : { + "comment" : "Button to close the issue report error alert" + }, + "One of the attachments isn't a supported image type." : { + "comment" : "Issue report error when an attachment type is refused" + }, + "Open a GitHub Issue" : { + "comment" : "Report a problem option: open the public GitHub issues page" + }, + "Private. Describe the problem and attach screenshots and a diagnostic report. Only the LoopKit development team can see it." : { + "comment" : "Report a problem option detail: submit an issue report to the developers" + }, + "Public. For bugs you can describe without any personal or health data." : { + "comment" : "Report a problem option detail: open the public GitHub issues page" + }, + "Report a Problem" : { + "comment" : "The title text for the report a problem menu item\nTitle of the report a problem screen" + }, + "Report Submitted" : { + "comment" : "Title of the alert after an issue report was submitted" + }, + "Reports go to the LoopKit development team and are visible only to them. Your IP address is used only to limit how often reports can be sent and is never stored." : { + "comment" : "Privacy footer for issue reports" + }, + "Save the report or send it to someone helping you, for example on Zulip or Facebook. It contains health data such as glucose, insulin, carbs and your therapy settings." : { + "comment" : "Report a problem option detail: share the diagnostic report yourself" + }, + "Send to Developers" : { + "comment" : "Title of the issue report screen" + }, + "Send to the Loop Developers" : { + "comment" : "Report a problem option: submit an issue report to the developers" + }, + "Share a Diagnostic Report" : { + "comment" : "Report a problem option: share the diagnostic report yourself" + }, "Share Data" : { "comment" : "Title on UsageDataPrivacyPreferenceView", "localizations" : { @@ -742,8 +823,15 @@ } } }, + "Submit" : { + "comment" : "Button to submit an issue report to the developers" + }, + "Submit Anonymously" : { + "comment" : "Toggle for submitting an issue report without contact details" + }, "Submit Bug Report" : { "comment" : "Navigation link title for Submit Bug Report", + "extractionState" : "stale", "localizations" : { "cs" : { "stringUnit" : { @@ -825,6 +913,36 @@ } } }, + "Thank you. Your report ID is %1$@. Mention it if you discuss the problem on Zulip or GitHub." : { + "comment" : "Message after an issue report was sent (1: report ID)" + }, + "The report is too large. Try fewer or smaller screenshots." : { + "comment" : "Issue report error when the upload is too large" + }, + "The report won't include your name, contact details or anything else that identifies you." : { + "comment" : "Footer for an anonymous issue report" + }, + "The server couldn't accept the report (%1$d %2$@)." : { + "comment" : "Issue report error with HTTP status (1) and reason (2)" + }, + "The server sent an unexpected response." : { + "comment" : "Issue report error for an unreadable response" + }, + "Title (optional)" : { + "comment" : "Placeholder for the issue report title" + }, + "Too many reports have been sent from this network recently. Please try again later." : { + "comment" : "Issue report error when rate limited" + }, + "Try Again" : { + "comment" : "Button to retry sending an issue report" + }, + "Unable to Submit Report" : { + "comment" : "Title of the alert when an issue report fails" + }, + "Up to %1$d screenshots or photos." : { + "comment" : "Footer for issue report screenshots (1: limit)" + }, "Usage Data Sharing" : { "comment" : "Navigation Link Title for Usage Data Sharing", "localizations" : { @@ -984,6 +1102,9 @@ } } } + }, + "Zulip Name" : { + "comment" : "Issue report contact Zulip placeholder" } }, "version" : "1.0" diff --git a/LoopSupportKitUI/LoopSupportUI.swift b/LoopSupportKitUI/LoopSupportUI.swift index 45f6790..02ea6f2 100644 --- a/LoopSupportKitUI/LoopSupportUI.swift +++ b/LoopSupportKitUI/LoopSupportUI.swift @@ -35,28 +35,21 @@ public final class LoopSupportUI: SupportUI { public func getScenarios(from scenarioURLs: [URL]) -> [LoopScenario] { [] } - public func supportMenuItem(supportInfoProvider: SupportInfoProvider, urlHandler: @escaping (URL) -> Void) -> AnyView? { - return AnyView(Button(LocalizedString("Submit Bug Report", comment: "Navigation link title for Submit Bug Report"), action: { - let url = URL(string: "https://github.com/LoopKit/Loop/issues")! - urlHandler(url) - })) - } - public weak var delegate: SupportUIDelegate? public func initializationComplete(for services: [LoopKit.Service]) { } public func configurationMenuItems() -> [LoopKitUI.CustomMenuItem] { - let view = Button(LocalizedString("Submit Bug Report", comment: "Navigation link title for Submit Bug Report"), action: { - let url = URL(string: "https://github.com/LoopKit/Loop/issues")! - self.delegate?.openURL(url: url) - }) return [ - CustomMenuItem(section: .support, view: AnyView(view)), + CustomMenuItem(section: .support, view: AnyView(ReportProblemMenuItem(support: self))), CustomMenuItem(section: .configuration, view: AnyView(UsageDataPrivacyPreferenceMenuItem())), ] } + public func reportProblemView() -> AnyView? { + AnyView(ReportProblemModal(delegate: delegate)) + } + public func loopWillReset() { } public func loopDidReset() { } From a647bced24118717a842d53bea83ac7823dc6f32 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 21 Oct 2026 23:23:09 -0500 Subject: [PATCH 15/17] View the diagnostic report before sharing or sending it (#41) Share a Diagnostic Report now opens a viewer showing the report, with a Share button for saving or sending it as a file. The developer report form gains View Diagnostic Report under Include Diagnostic Report, so the attachment can be checked before submitting. --- LoopSupport.xcodeproj/project.pbxproj | 4 + .../IssueReport/DiagnosticReportView.swift | 100 ++++++++++++++++++ .../IssueReport/ReportProblemView.swift | 69 ++---------- .../IssueReport/SendIssueReportView.swift | 7 +- LoopSupportKitUI/Localizable.xcstrings | 18 +++- 5 files changed, 131 insertions(+), 67 deletions(-) create mode 100644 LoopSupportKitUI/IssueReport/DiagnosticReportView.swift diff --git a/LoopSupport.xcodeproj/project.pbxproj b/LoopSupport.xcodeproj/project.pbxproj index 728533e..8792a70 100644 --- a/LoopSupport.xcodeproj/project.pbxproj +++ b/LoopSupport.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ C12ECD06289560B90089EB07 /* LoopSupportKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A936A95425BC9A190085119C /* LoopSupportKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C1A55E022E9F000000B10002 /* IssueReportContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A55E022E9F000000B10001 /* IssueReportContext.swift */; }; C1A55E022E9F000000B10004 /* IssueReportSubmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A55E022E9F000000B10003 /* IssueReportSubmitter.swift */; }; + C1A55E022E9F000000B1000A /* DiagnosticReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A55E022E9F000000B10009 /* DiagnosticReportView.swift */; }; C1A55E022E9F000000B10006 /* ReportProblemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A55E022E9F000000B10005 /* ReportProblemView.swift */; }; C1A55E022E9F000000B10008 /* SendIssueReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A55E022E9F000000B10007 /* SendIssueReportView.swift */; }; C14A7E7C295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14A7E7B295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift */; }; @@ -90,6 +91,7 @@ B66D1F542E6A752300471149 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; C1A55E022E9F000000B10001 /* IssueReportContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueReportContext.swift; sourceTree = ""; }; C1A55E022E9F000000B10003 /* IssueReportSubmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueReportSubmitter.swift; sourceTree = ""; }; + C1A55E022E9F000000B10009 /* DiagnosticReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticReportView.swift; sourceTree = ""; }; C1A55E022E9F000000B10005 /* ReportProblemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportProblemView.swift; sourceTree = ""; }; C1A55E022E9F000000B10007 /* SendIssueReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendIssueReportView.swift; sourceTree = ""; }; C14A7E7B295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsageDataPrivacyPreferenceView.swift; sourceTree = ""; }; @@ -197,6 +199,7 @@ C1A55E022E9F000000B10000 /* IssueReport */ = { isa = PBXGroup; children = ( + C1A55E022E9F000000B10009 /* DiagnosticReportView.swift */, C1A55E022E9F000000B10001 /* IssueReportContext.swift */, C1A55E022E9F000000B10003 /* IssueReportSubmitter.swift */, C1A55E022E9F000000B10005 /* ReportProblemView.swift */, @@ -371,6 +374,7 @@ C14A7E7C295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift in Sources */, C1A55E022E9F000000B10002 /* IssueReportContext.swift in Sources */, C1A55E022E9F000000B10004 /* IssueReportSubmitter.swift in Sources */, + C1A55E022E9F000000B1000A /* DiagnosticReportView.swift in Sources */, C1A55E022E9F000000B10006 /* ReportProblemView.swift in Sources */, C1A55E022E9F000000B10008 /* SendIssueReportView.swift in Sources */, A9CE7D8425BC9B3700C04F9B /* LocalizedString.swift in Sources */, diff --git a/LoopSupportKitUI/IssueReport/DiagnosticReportView.swift b/LoopSupportKitUI/IssueReport/DiagnosticReportView.swift new file mode 100644 index 0000000..705cf97 --- /dev/null +++ b/LoopSupportKitUI/IssueReport/DiagnosticReportView.swift @@ -0,0 +1,100 @@ +// +// DiagnosticReportView.swift +// LoopSupportKitUI +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import SwiftUI + +/// Shows the diagnostic report as text, with a button to share it as a file. +struct DiagnosticReportView: View { + let generateReport: () async -> String + + @State private var report: String? + @State private var reportFile: ReportFile? + + private struct ReportFile: Identifiable { + let url: URL + var id: URL { url } + } + + var body: some View { + Group { + if let report { + ReportTextView(text: report) + .ignoresSafeArea(edges: .bottom) + } else { + ProgressView(LocalizedString("Preparing Report…", comment: "Shown while the diagnostic report is generated")) + } + } + .navigationTitle(LocalizedString("Diagnostic Report", comment: "Title of the diagnostic report viewer")) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .primaryAction) { + Button(action: share) { + Image(systemName: "square.and.arrow.up") + } + .accessibilityLabel(LocalizedString("Share", comment: "Button to share the diagnostic report")) + .disabled(report == nil) + } + } + .sheet(item: $reportFile, onDismiss: removeReportFile) { file in + ActivityView(activityItems: [file.url]) + } + .task { + if report == nil { + report = "Generated: \(Date())\n\n" + (await generateReport()) + } + } + } + + private func share() { + guard let report else { + return + } + let name = "Loop Report \(ISO8601DateFormatter.string(from: Date(), timeZone: .current, formatOptions: [.withSpaceBetweenDateAndTime, .withInternetDateTime])).md" + let url = FileManager.default.temporaryDirectory.appendingPathComponent(name.replacingOccurrences(of: ":", with: "-")) + if (try? report.write(to: url, atomically: true, encoding: .utf8)) != nil { + reportFile = ReportFile(url: url) + } + } + + private func removeReportFile() { + if let url = reportFile?.url { + try? FileManager.default.removeItem(at: url) + } + } +} + +// A UITextView keeps scrolling smooth for reports of several hundred kilobytes +private struct ReportTextView: UIViewRepresentable { + let text: String + + func makeUIView(context: Context) -> UITextView { + let textView = UITextView() + textView.isEditable = false + textView.dataDetectorTypes = [] + textView.adjustsFontForContentSizeCategory = true + textView.font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .monospacedSystemFont(ofSize: 12, weight: .regular)) + textView.textContainerInset = UIEdgeInsets(top: 12, left: 8, bottom: 12, right: 8) + textView.text = text + return textView + } + + func updateUIView(_ textView: UITextView, context: Context) { + if textView.text != text { + textView.text = text + } + } +} + +private struct ActivityView: UIViewControllerRepresentable { + let activityItems: [Any] + + func makeUIViewController(context: Context) -> UIActivityViewController { + UIActivityViewController(activityItems: activityItems, applicationActivities: nil) + } + + func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} +} diff --git a/LoopSupportKitUI/IssueReport/ReportProblemView.swift b/LoopSupportKitUI/IssueReport/ReportProblemView.swift index bf30c93..6de93be 100644 --- a/LoopSupportKitUI/IssueReport/ReportProblemView.swift +++ b/LoopSupportKitUI/IssueReport/ReportProblemView.swift @@ -11,15 +11,6 @@ import LoopKitUI struct ReportProblemView: View { let delegate: SupportUIDelegate? - @State private var isPreparingReport = false - @State private var reportFile: ReportFile? - @State private var writtenReportURL: URL? - - private struct ReportFile: Identifiable { - let url: URL - var id: URL { url } - } - var body: some View { List { Section { @@ -33,20 +24,13 @@ struct ReportProblemView: View { ) } - if delegate != nil { - Button(action: shareDiagnosticReport) { - HStack { - option( - LocalizedString("Share a Diagnostic Report", comment: "Report a problem option: share the diagnostic report yourself"), - detail: LocalizedString("Save the report or send it to someone helping you, for example on Zulip or Facebook. It contains health data such as glucose, insulin, carbs and your therapy settings.", comment: "Report a problem option detail: share the diagnostic report yourself") - ) - if isPreparingReport { - Spacer() - ProgressView() - } - } + if let delegate { + NavigationLink(destination: DiagnosticReportView(generateReport: { await delegate.generateIssueReport() })) { + option( + LocalizedString("Share a Diagnostic Report", comment: "Report a problem option: share the diagnostic report yourself"), + detail: LocalizedString("View the report, then save it or send it to someone helping you, for example on Zulip or Facebook. It contains health data such as glucose, insulin, carbs and your therapy settings.", comment: "Report a problem option detail: share the diagnostic report yourself") + ) } - .disabled(isPreparingReport) } Button(action: { delegate?.openURL(url: URL(string: "https://github.com/LoopKit/Loop/issues")!) }) { @@ -69,9 +53,6 @@ struct ReportProblemView: View { .listStyle(.insetGrouped) .navigationTitle(LocalizedString("Report a Problem", comment: "Title of the report a problem screen")) .navigationBarTitleDisplayMode(.inline) - .sheet(item: $reportFile, onDismiss: removeReportFile) { file in - ActivityView(activityItems: [file.url]) - } } private func option(_ title: String, detail: String) -> some View { @@ -84,34 +65,6 @@ struct ReportProblemView: View { } .padding(.vertical, 4) } - - private func shareDiagnosticReport() { - guard let delegate else { - return - } - isPreparingReport = true - Task { @MainActor in - let date = Date() - let report = "Generated: \(date)\n\n" + (await delegate.generateIssueReport()) - let name = "Loop Report \(ISO8601DateFormatter.string(from: date, timeZone: .current, formatOptions: [.withSpaceBetweenDateAndTime, .withInternetDateTime])).md" - let url = FileManager.default.temporaryDirectory.appendingPathComponent(name.replacingOccurrences(of: ":", with: "-")) - do { - try report.write(to: url, atomically: true, encoding: .utf8) - writtenReportURL = url - reportFile = ReportFile(url: url) - } catch { - reportFile = nil - } - isPreparingReport = false - } - } - - private func removeReportFile() { - if let url = writtenReportURL { - try? FileManager.default.removeItem(at: url) - } - writtenReportURL = nil - } } struct ReportProblemMenuItem: View { @@ -142,16 +95,6 @@ struct ReportProblemModal: View { } } -private struct ActivityView: UIViewControllerRepresentable { - let activityItems: [Any] - - func makeUIViewController(context: Context) -> UIActivityViewController { - UIActivityViewController(activityItems: activityItems, applicationActivities: nil) - } - - func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} -} - private extension SupportInfoProvider { func generateIssueReport() async -> String { await withCheckedContinuation { continuation in diff --git a/LoopSupportKitUI/IssueReport/SendIssueReportView.swift b/LoopSupportKitUI/IssueReport/SendIssueReportView.swift index 9c32a6f..80fa484 100644 --- a/LoopSupportKitUI/IssueReport/SendIssueReportView.swift +++ b/LoopSupportKitUI/IssueReport/SendIssueReportView.swift @@ -74,9 +74,14 @@ struct SendIssueReportView: View { } } - if generateDiagnosticReport != nil { + if let generateDiagnosticReport { Section(footer: Text(LocalizedString("Attaches Loop's issue report as a text file. It contains health data such as recent glucose, insulin, carbs and your therapy settings.", comment: "Footer for the include diagnostic report toggle"))) { Toggle(LocalizedString("Include Diagnostic Report", comment: "Toggle for attaching the diagnostic report to an issue report"), isOn: $includeDiagnosticReport) + if includeDiagnosticReport { + NavigationLink(destination: DiagnosticReportView(generateReport: generateDiagnosticReport)) { + Text(LocalizedString("View Diagnostic Report", comment: "Button to view the diagnostic report attached to an issue report")) + } + } } } diff --git a/LoopSupportKitUI/Localizable.xcstrings b/LoopSupportKitUI/Localizable.xcstrings index 3ca37b4..2d57bb2 100644 --- a/LoopSupportKitUI/Localizable.xcstrings +++ b/LoopSupportKitUI/Localizable.xcstrings @@ -227,6 +227,9 @@ "Detailed logs as files. Only needed when a developer asks for them." : { "comment" : "Report a problem option detail: export the critical event logs" }, + "Diagnostic Report" : { + "comment" : "Title of the diagnostic report viewer" + }, "Do not share any data." : { "comment" : "Description in UsageDataPrivacyPreferenceView for no sharing", "localizations" : { @@ -565,6 +568,9 @@ "Open a GitHub Issue" : { "comment" : "Report a problem option: open the public GitHub issues page" }, + "Preparing Report…" : { + "comment" : "Shown while the diagnostic report is generated" + }, "Private. Describe the problem and attach screenshots and a diagnostic report. Only the LoopKit development team can see it." : { "comment" : "Report a problem option detail: submit an issue report to the developers" }, @@ -580,15 +586,15 @@ "Reports go to the LoopKit development team and are visible only to them. Your IP address is used only to limit how often reports can be sent and is never stored." : { "comment" : "Privacy footer for issue reports" }, - "Save the report or send it to someone helping you, for example on Zulip or Facebook. It contains health data such as glucose, insulin, carbs and your therapy settings." : { - "comment" : "Report a problem option detail: share the diagnostic report yourself" - }, "Send to Developers" : { "comment" : "Title of the issue report screen" }, "Send to the Loop Developers" : { "comment" : "Report a problem option: submit an issue report to the developers" }, + "Share" : { + "comment" : "Button to share the diagnostic report" + }, "Share a Diagnostic Report" : { "comment" : "Report a problem option: share the diagnostic report yourself" }, @@ -1026,6 +1032,12 @@ } } }, + "View Diagnostic Report" : { + "comment" : "Button to view the diagnostic report attached to an issue report" + }, + "View the report, then save it or send it to someone helping you, for example on Zulip or Facebook. It contains health data such as glucose, insulin, carbs and your therapy settings." : { + "comment" : "Report a problem option detail: share the diagnostic report yourself" + }, "You can optionally choose to share usage data with Loop developers to improve Loop. Sharing is not required, and Loop will function fully no matter which option you choose. Usage data will not be shared with third parties." : { "comment" : "Main summary text for UsageDataPrivacyPreferenceView", "localizations" : { From 776b4754592ca930d0759b6ff057d878dc0ae57b Mon Sep 17 00:00:00 2001 From: Marion Barker <19607791+marionbarker@users.noreply.github.com> Date: Thu, 22 Oct 2026 14:18:15 -0700 Subject: [PATCH 16/17] Updated translations from lokalise on Thu Oct 22 11:50:14 PDT 2026 (#43) --- LoopSupport.xcodeproj/project.pbxproj | 30 +++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/LoopSupport.xcodeproj/project.pbxproj b/LoopSupport.xcodeproj/project.pbxproj index 8792a70..4fe0549 100644 --- a/LoopSupport.xcodeproj/project.pbxproj +++ b/LoopSupport.xcodeproj/project.pbxproj @@ -21,13 +21,13 @@ B66D1F552E6A752300471149 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B66D1F542E6A752300471149 /* Localizable.xcstrings */; }; C12ECD05289560B90089EB07 /* LoopSupportKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A936A95425BC9A190085119C /* LoopSupportKitUI.framework */; platformFilter = ios; }; C12ECD06289560B90089EB07 /* LoopSupportKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A936A95425BC9A190085119C /* LoopSupportKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C14A7E7C295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14A7E7B295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift */; }; + C15349062960AA9000B47074 /* UsageDataPrivacyPreferenceMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15349052960AA9000B47074 /* UsageDataPrivacyPreferenceMenuItem.swift */; }; C1A55E022E9F000000B10002 /* IssueReportContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A55E022E9F000000B10001 /* IssueReportContext.swift */; }; C1A55E022E9F000000B10004 /* IssueReportSubmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A55E022E9F000000B10003 /* IssueReportSubmitter.swift */; }; - C1A55E022E9F000000B1000A /* DiagnosticReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A55E022E9F000000B10009 /* DiagnosticReportView.swift */; }; C1A55E022E9F000000B10006 /* ReportProblemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A55E022E9F000000B10005 /* ReportProblemView.swift */; }; C1A55E022E9F000000B10008 /* SendIssueReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A55E022E9F000000B10007 /* SendIssueReportView.swift */; }; - C14A7E7C295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14A7E7B295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift */; }; - C15349062960AA9000B47074 /* UsageDataPrivacyPreferenceMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15349052960AA9000B47074 /* UsageDataPrivacyPreferenceMenuItem.swift */; }; + C1A55E022E9F000000B1000A /* DiagnosticReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A55E022E9F000000B10009 /* DiagnosticReportView.swift */; }; C1BE9741295E3800008BA25D /* LoopKitAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BE9740295E3800008BA25D /* LoopKitAnalytics.swift */; }; C1BE9744295E40B1008BA25D /* Amplitude in Frameworks */ = {isa = PBXBuildFile; productRef = C1BE9743295E40B1008BA25D /* Amplitude */; }; /* End PBXBuildFile section */ @@ -89,13 +89,13 @@ A9CE800A25BCACAE00C04F9B /* LoopSupportPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopSupportPlugin.swift; sourceTree = ""; }; A9CE801225BCAE4000C04F9B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B66D1F542E6A752300471149 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; + C14A7E7B295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsageDataPrivacyPreferenceView.swift; sourceTree = ""; }; + C15349052960AA9000B47074 /* UsageDataPrivacyPreferenceMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsageDataPrivacyPreferenceMenuItem.swift; sourceTree = ""; }; C1A55E022E9F000000B10001 /* IssueReportContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueReportContext.swift; sourceTree = ""; }; C1A55E022E9F000000B10003 /* IssueReportSubmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueReportSubmitter.swift; sourceTree = ""; }; - C1A55E022E9F000000B10009 /* DiagnosticReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticReportView.swift; sourceTree = ""; }; C1A55E022E9F000000B10005 /* ReportProblemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportProblemView.swift; sourceTree = ""; }; C1A55E022E9F000000B10007 /* SendIssueReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendIssueReportView.swift; sourceTree = ""; }; - C14A7E7B295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsageDataPrivacyPreferenceView.swift; sourceTree = ""; }; - C15349052960AA9000B47074 /* UsageDataPrivacyPreferenceMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsageDataPrivacyPreferenceMenuItem.swift; sourceTree = ""; }; + C1A55E022E9F000000B10009 /* DiagnosticReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticReportView.swift; sourceTree = ""; }; C1BE9740295E3800008BA25D /* LoopKitAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopKitAnalytics.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -196,6 +196,15 @@ name = Frameworks; sourceTree = ""; }; + C14A7E7A295F2EA700CD87B4 /* Views */ = { + isa = PBXGroup; + children = ( + C14A7E7B295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift */, + C15349052960AA9000B47074 /* UsageDataPrivacyPreferenceMenuItem.swift */, + ); + path = Views; + sourceTree = ""; + }; C1A55E022E9F000000B10000 /* IssueReport */ = { isa = PBXGroup; children = ( @@ -208,15 +217,6 @@ path = IssueReport; sourceTree = ""; }; - C14A7E7A295F2EA700CD87B4 /* Views */ = { - isa = PBXGroup; - children = ( - C14A7E7B295F2EC100CD87B4 /* UsageDataPrivacyPreferenceView.swift */, - C15349052960AA9000B47074 /* UsageDataPrivacyPreferenceMenuItem.swift */, - ); - path = Views; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ From 929bee8bd484cc915fa6c303e7fdade61db34c0e Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 24 Oct 2026 09:50:35 -0500 Subject: [PATCH 17/17] Put the report name in the subject when sharing the diagnostic report (#45) Sharing passed only the file URL, so Mail left the subject blank. The old issue report shared through an activity item source that supplied the report name as the subject; do the same here. --- .../IssueReport/DiagnosticReportView.swift | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/LoopSupportKitUI/IssueReport/DiagnosticReportView.swift b/LoopSupportKitUI/IssueReport/DiagnosticReportView.swift index 705cf97..f88395d 100644 --- a/LoopSupportKitUI/IssueReport/DiagnosticReportView.swift +++ b/LoopSupportKitUI/IssueReport/DiagnosticReportView.swift @@ -16,6 +16,7 @@ struct DiagnosticReportView: View { private struct ReportFile: Identifiable { let url: URL + let name: String var id: URL { url } } @@ -40,7 +41,7 @@ struct DiagnosticReportView: View { } } .sheet(item: $reportFile, onDismiss: removeReportFile) { file in - ActivityView(activityItems: [file.url]) + ActivityView(activityItems: [ReportItem(url: file.url, subject: file.name)]) } .task { if report == nil { @@ -56,7 +57,7 @@ struct DiagnosticReportView: View { let name = "Loop Report \(ISO8601DateFormatter.string(from: Date(), timeZone: .current, formatOptions: [.withSpaceBetweenDateAndTime, .withInternetDateTime])).md" let url = FileManager.default.temporaryDirectory.appendingPathComponent(name.replacingOccurrences(of: ":", with: "-")) if (try? report.write(to: url, atomically: true, encoding: .utf8)) != nil { - reportFile = ReportFile(url: url) + reportFile = ReportFile(url: url, name: name) } } @@ -98,3 +99,26 @@ private struct ActivityView: UIViewControllerRepresentable { func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} } + +// Supplies the report name as the subject, e.g. for Mail +private final class ReportItem: NSObject, UIActivityItemSource { + let url: URL + let subject: String + + init(url: URL, subject: String) { + self.url = url + self.subject = subject + } + + func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { + url + } + + func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { + url + } + + func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String { + subject + } +}