diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index b147fc93e2..74f9aa3ca5 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -726,6 +726,19 @@ 8DC1CE32F39301122B550325 /* LoopKitTypeAliases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FAEFD820F50BFF211AC6D9 /* LoopKitTypeAliases.swift */; }; ABD9218EB98C9216FBB04382 /* LoopKitTypeAliases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FAEFD820F50BFF211AC6D9 /* LoopKitTypeAliases.swift */; }; AF79DFAB0F55143D66022668 /* LoopKitTypeAliases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FAEFD820F50BFF211AC6D9 /* LoopKitTypeAliases.swift */; }; + 89C0C34F420190E8CC7D03B1 /* DayCharts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F40DE2F3FC02CBA3BE7AF2F /* DayCharts.swift */; }; + 332CC9FC011E78CE5BEA583C /* LoopDesign.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BD3DDF41B598A8C4B5CEB92 /* LoopDesign.swift */; }; + 0C70F4D862F6D3FDB3D91D87 /* LoopHistoryMath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21622B2355AED8F6A12FE17C /* LoopHistoryMath.swift */; }; + 2ADBB92A8ABB74360D18EEFA /* LoopHistoryModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471FFD525A4EB897745608BE /* LoopHistoryModels.swift */; }; + A743DC26D066EE5AC88C2F45 /* LoopHistoryProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09448CF97996C40BEDDE6588 /* LoopHistoryProvider.swift */; }; + 6A6C1E1C7A26EC88AFB4FAB1 /* DayDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3A83D311A9ECAE563F74A51 /* DayDetailView.swift */; }; + 23B98F0A53A093412229364D /* LogbookScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEDC42672A7A8C57546EA0C /* LogbookScreen.swift */; }; + C4B5E0A1D2F3A4B5C6D7E8F9 /* CarbEntryScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5E0A1D2F3A4B5C6D7E8FA /* CarbEntryScreen.swift */; }; + 3ED693ACBF8F9E416055BD56 /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844BECBB6357021979B9443F /* MainTabBarController.swift */; }; + 7705483CFFCF0B89F8F1A08E /* NowScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0874B7B679DB9CE661CCE152 /* NowScreen.swift */; }; + 591F13AF435B37B4818E2F2B /* TrendsComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFDE56977CC73CBDA1170C5 /* TrendsComponents.swift */; }; + 5CD0888FF33D6F844A81E638 /* TrendsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF0FD64B2B3E9C9AA8764684 /* TrendsScreen.swift */; }; + 97CF13197936D2794766C84A /* LoopHistoryMathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42B08644C3031D9E815F9892 /* LoopHistoryMathTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1596,6 +1609,19 @@ 35FAEFD820F50BFF211AC6D9 /* LoopKitTypeAliases.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LoopKitTypeAliases.swift; sourceTree = ""; }; 36421A4B9676EF68CDDC4ECD /* AlgorithmBridge.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AlgorithmBridge.swift; sourceTree = ""; }; 6C372374D3D3D9C5F8E3AD93 /* TemporaryScheduleOverrideAlgorithmTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemporaryScheduleOverrideAlgorithmTests.swift; sourceTree = ""; }; + 6F40DE2F3FC02CBA3BE7AF2F /* DayCharts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayCharts.swift; sourceTree = ""; }; + 0BD3DDF41B598A8C4B5CEB92 /* LoopDesign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopDesign.swift; sourceTree = ""; }; + 21622B2355AED8F6A12FE17C /* LoopHistoryMath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopHistoryMath.swift; sourceTree = ""; }; + 471FFD525A4EB897745608BE /* LoopHistoryModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopHistoryModels.swift; sourceTree = ""; }; + 09448CF97996C40BEDDE6588 /* LoopHistoryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopHistoryProvider.swift; sourceTree = ""; }; + E3A83D311A9ECAE563F74A51 /* DayDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayDetailView.swift; sourceTree = ""; }; + 7AEDC42672A7A8C57546EA0C /* LogbookScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogbookScreen.swift; sourceTree = ""; }; + C4B5E0A1D2F3A4B5C6D7E8FA /* CarbEntryScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbEntryScreen.swift; sourceTree = ""; }; + 844BECBB6357021979B9443F /* MainTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = ""; }; + 0874B7B679DB9CE661CCE152 /* NowScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowScreen.swift; sourceTree = ""; }; + 4BFDE56977CC73CBDA1170C5 /* TrendsComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendsComponents.swift; sourceTree = ""; }; + AF0FD64B2B3E9C9AA8764684 /* TrendsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendsScreen.swift; sourceTree = ""; }; + 42B08644C3031D9E815F9892 /* LoopHistoryMathTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopHistoryMathTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1918,6 +1944,7 @@ 43776F8E1B8022E90074EA36 /* Loop */ = { isa = PBXGroup; children = ( + 8574687EF267DC59043C0242 /* Redesign */, C16DA84022E8E104008624C2 /* Plugins */, B66D1F322E6A5D6600471149 /* Localizable.xcstrings */, B66D1F382E6A5D6600471149 /* InfoPlist.xcstrings */, @@ -2816,6 +2843,7 @@ 6C1F8E3BA2D45079B3E2F512 /* OutcomesPackageTests.swift */, BE36167100E59D7413C50F63 /* SetupComparisonTests.swift */, FEEDF0000000000000000005 /* GlucoseStatisticsTests.swift */, + 42B08644C3031D9E815F9892 /* LoopHistoryMathTests.swift */, A9DFAFB224F0415E00950D1E /* CarbBackfillRequestUserInfoTests.swift */, A963B279252CEBAE0062AA12 /* SetBolusUserInfoTests.swift */, C19008FF252271BB00721625 /* SimpleBolusCalculatorTests.swift */, @@ -2992,6 +3020,25 @@ path = Fixtures; sourceTree = ""; }; + 8574687EF267DC59043C0242 /* Redesign */ = { + isa = PBXGroup; + children = ( + AF0FD64B2B3E9C9AA8764684 /* TrendsScreen.swift */, + 4BFDE56977CC73CBDA1170C5 /* TrendsComponents.swift */, + 0874B7B679DB9CE661CCE152 /* NowScreen.swift */, + 844BECBB6357021979B9443F /* MainTabBarController.swift */, + C4B5E0A1D2F3A4B5C6D7E8FA /* CarbEntryScreen.swift */, + 7AEDC42672A7A8C57546EA0C /* LogbookScreen.swift */, + E3A83D311A9ECAE563F74A51 /* DayDetailView.swift */, + 09448CF97996C40BEDDE6588 /* LoopHistoryProvider.swift */, + 471FFD525A4EB897745608BE /* LoopHistoryModels.swift */, + 21622B2355AED8F6A12FE17C /* LoopHistoryMath.swift */, + 0BD3DDF41B598A8C4B5CEB92 /* LoopDesign.swift */, + 6F40DE2F3FC02CBA3BE7AF2F /* DayCharts.swift */, + ); + path = Redesign; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -3650,6 +3697,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5CD0888FF33D6F844A81E638 /* TrendsScreen.swift in Sources */, + 591F13AF435B37B4818E2F2B /* TrendsComponents.swift in Sources */, + 7705483CFFCF0B89F8F1A08E /* NowScreen.swift in Sources */, + 3ED693ACBF8F9E416055BD56 /* MainTabBarController.swift in Sources */, + 23B98F0A53A093412229364D /* LogbookScreen.swift in Sources */, + C4B5E0A1D2F3A4B5C6D7E8F9 /* CarbEntryScreen.swift in Sources */, + 6A6C1E1C7A26EC88AFB4FAB1 /* DayDetailView.swift in Sources */, + A743DC26D066EE5AC88C2F45 /* LoopHistoryProvider.swift in Sources */, + 2ADBB92A8ABB74360D18EEFA /* LoopHistoryModels.swift in Sources */, + 0C70F4D862F6D3FDB3D91D87 /* LoopHistoryMath.swift in Sources */, + 332CC9FC011E78CE5BEA583C /* LoopDesign.swift in Sources */, + 89C0C34F420190E8CC7D03B1 /* DayCharts.swift in Sources */, C17824A51E1AD4D100D9D25C /* ManualBolusRecommendation.swift in Sources */, 897A5A9624C2175B00C4E71D /* BolusEntryView.swift in Sources */, 4F70C2131DE90339006380B7 /* StatusExtensionContext.swift in Sources */, @@ -4101,6 +4160,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 97CF13197936D2794766C84A /* LoopHistoryMathTests.swift in Sources */, A9DF02CB24F72B9E00B7C988 /* CriticalEventLogTests.swift in Sources */, 1D80313D24746274002810DF /* AlertStoreTests.swift in Sources */, C1777A6625A125F100595963 /* ManualEntryDoseViewModelTests.swift in Sources */, diff --git a/Loop/DerivedAssetsBase.xcassets/accent-ink.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/accent-ink.colorset/Contents.json new file mode 100644 index 0000000000..af109ce19a --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/accent-ink.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.000", + "green": "0.384", + "blue": "0.800", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0.039", + "green": "0.518", + "blue": "1.000", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/agp-inner.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/agp-inner.colorset/Contents.json new file mode 100644 index 0000000000..2ddc0f5f90 --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/agp-inner.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.000", + "green": "0.690", + "blue": "1.000", + "alpha": "0.340" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0.388", + "green": "0.729", + "blue": "1.000", + "alpha": "0.380" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/agp-outer.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/agp-outer.colorset/Contents.json new file mode 100644 index 0000000000..270771d475 --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/agp-outer.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.000", + "green": "0.690", + "blue": "1.000", + "alpha": "0.160" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0.388", + "green": "0.729", + "blue": "1.000", + "alpha": "0.180" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/basal-edge.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/basal-edge.colorset/Contents.json new file mode 100644 index 0000000000..fa72f9b84c --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/basal-edge.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.890", + "green": "0.725", + "blue": "0.514", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0.431", + "green": "0.322", + "blue": "0.196", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/basal.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/basal.colorset/Contents.json new file mode 100644 index 0000000000..76f27a3287 --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/basal.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.988", + "green": "0.890", + "blue": "0.761", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0.263", + "green": "0.188", + "blue": "0.106", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/bolus.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/bolus.colorset/Contents.json new file mode 100644 index 0000000000..30b11e52a8 --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/bolus.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.663", + "green": "0.282", + "blue": "0.039", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "1.000", + "green": "0.639", + "blue": "0.278", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/carbs-ink.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/carbs-ink.colorset/Contents.json new file mode 100644 index 0000000000..df21da3860 --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/carbs-ink.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.184", + "green": "0.490", + "blue": "0.086", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0.349", + "green": "0.894", + "blue": "0.200", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/control-thumb.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/control-thumb.colorset/Contents.json new file mode 100644 index 0000000000..34b15edebe --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/control-thumb.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "1.000", + "green": "1.000", + "blue": "1.000", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0.388", + "green": "0.388", + "blue": "0.400", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/critical-ink.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/critical-ink.colorset/Contents.json new file mode 100644 index 0000000000..2f18c32cc1 --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/critical-ink.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.769", + "green": "0.188", + "blue": "0.169", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "1.000", + "green": "0.412", + "blue": "0.380", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/glucose-ink.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/glucose-ink.colorset/Contents.json new file mode 100644 index 0000000000..b704d8af81 --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/glucose-ink.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.000", + "green": "0.439", + "blue": "0.722", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0.388", + "green": "0.729", + "blue": "1.000", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/ink-muted.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/ink-muted.colorset/Contents.json new file mode 100644 index 0000000000..6eda3190f3 --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/ink-muted.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.424", + "green": "0.424", + "blue": "0.439", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0.682", + "green": "0.682", + "blue": "0.698", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/insulin-ink.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/insulin-ink.colorset/Contents.json new file mode 100644 index 0000000000..1acea580ea --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/insulin-ink.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.541", + "green": "0.227", + "blue": "0.024", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "1.000", + "green": "0.639", + "blue": "0.278", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/range-high.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/range-high.colorset/Contents.json new file mode 100644 index 0000000000..28f578bf71 --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/range-high.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.949", + "green": "0.718", + "blue": "0.020", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "1.000", + "green": "0.839", + "blue": "0.039", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/range-in.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/range-in.colorset/Contents.json new file mode 100644 index 0000000000..3d0f452a75 --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/range-in.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.184", + "green": "0.659", + "blue": "0.310", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0.196", + "green": "0.843", + "blue": "0.294", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/range-low.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/range-low.colorset/Contents.json new file mode 100644 index 0000000000..1349d6a59e --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/range-low.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.898", + "green": "0.282", + "blue": "0.302", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "1.000", + "green": "0.624", + "blue": "0.604", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/range-very-high.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/range-very-high.colorset/Contents.json new file mode 100644 index 0000000000..b2851438d1 --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/range-very-high.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.541", + "green": "0.380", + "blue": "0.000", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0.788", + "green": "0.541", + "blue": "0.000", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/range-very-low.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/range-very-low.colorset/Contents.json new file mode 100644 index 0000000000..46e9e70555 --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/range-very-low.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.608", + "green": "0.110", + "blue": "0.122", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "1.000", + "green": "0.271", + "blue": "0.227", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/status-critical.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/status-critical.colorset/Contents.json new file mode 100644 index 0000000000..9c67fd2db6 --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/status-critical.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "1.000", + "green": "0.231", + "blue": "0.188", + "alpha": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "1.000", + "green": "0.271", + "blue": "0.227", + "alpha": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/DerivedAssetsBase.xcassets/target-band.colorset/Contents.json b/Loop/DerivedAssetsBase.xcassets/target-band.colorset/Contents.json new file mode 100644 index 0000000000..6ff45863c5 --- /dev/null +++ b/Loop/DerivedAssetsBase.xcassets/target-band.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "red": "0.000", + "green": "0.690", + "blue": "1.000", + "alpha": "0.140" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "red": "0.388", + "green": "0.729", + "blue": "1.000", + "alpha": "0.180" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/Loop/Localizable.xcstrings b/Loop/Localizable.xcstrings index e8be2e8501..6808025436 100644 --- a/Loop/Localizable.xcstrings +++ b/Loop/Localizable.xcstrings @@ -12,6 +12,33279 @@ }, " -" : { + }, + "%1$@ ago" : { + "comment" : "Carb entry time choice: eaten this long before now. (1: duration, e.g. 15m or 1h)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "قبل %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "před %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ siden" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "vor %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "hace %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ sitten" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "il y a %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לפני %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ ezelőtt" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ fa" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@前" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ siden" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ geleden" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ temu" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "há %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "acum %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ назад" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "pred %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "för %1$@ sedan" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ önce" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ тому" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ trước" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@前" + } + } + } + }, + "%1$@ d" : { + "comment" : "Trends period picker, short label for a period of several days, e.g. 7 d. (1: number of days)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ يوم" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ d" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ d" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ T" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ d" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ pv" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ j" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ ימ׳" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ nap" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@日" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ d" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ d" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ d" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ d" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ z" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ д" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ d" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ d" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ д" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ ngày" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@天" + } + } + } + }, + "%1$@ entries" : { + "comment" : "Trends Carbs per day tile detail: average number of carb entries per day, a fractional value such as 2.5, so not pluralised. (1: number with one decimal)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ إدخالات" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "záznamů: %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ registreringer" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ Einträge" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ entradas" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ kirjausta" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ entrées" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ רשומות" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ bejegyzés" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ inserimenti" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@件" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ registreringer" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ registraties" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "wpisów: %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ registros" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ intrări" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "записей: %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "záznamov: %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ registreringar" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ giriş" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "записів: %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ lần nhập" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@条" + } + } + } + }, + "%1$@ for %2$@" : { + "comment" : "A temp basal: its rate and how long it ran. (1: rate, e.g. 0.50 U/h; 2: duration, e.g. 30m)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ لمدة %2$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ po dobu %2$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ i %2$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ für %2$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ durante %2$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %2$@ ajan" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ pendant %2$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ למשך %2$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ ideig" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ per %2$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@(%2$@間)" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ i %2$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ gedurende %2$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ przez %2$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ por %2$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ timp de %2$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ на %2$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ počas %2$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ i %2$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%2$@ boyunca %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ на %2$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ trong %2$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@,持续%2$@" + } + } + } + }, + "%1$@ for %2$@ (scheduled %3$@)" : { + "comment" : "A temp basal: its rate, how long it ran and the scheduled rate it replaced. (1: rate, e.g. 0.50 U/h; 2: duration, e.g. 30m; 3: scheduled rate number, e.g. 0.80)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ لمدة %2$@ (المجدول %3$@)" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ po dobu %2$@ (dle plánu %3$@)" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ i %2$@ (planlagt %3$@)" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ für %2$@ (geplant %3$@)" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ durante %2$@ (programada %3$@)" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %2$@ ajan (ohjelmoitu %3$@)" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ pendant %2$@ (programmé %3$@)" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ למשך %2$@ (מתוזמן %3$@)" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ ideig (ütemezett: %3$@)" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ per %2$@ (programmata %3$@)" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@(%2$@間、予定 %3$@)" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ i %2$@ (planlagt %3$@)" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ gedurende %2$@ (gepland %3$@)" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ przez %2$@ (wg planu %3$@)" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ por %2$@ (programada %3$@)" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ timp de %2$@ (programat %3$@)" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ на %2$@ (по плану %3$@)" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ počas %2$@ (podľa plánu %3$@)" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ i %2$@ (schemalagd %3$@)" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%2$@ boyunca %1$@ (planlanan %3$@)" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ на %2$@ (за планом %3$@)" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ trong %2$@ (theo lịch %3$@)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@,持续%2$@(预设 %3$@)" + } + } + } + }, + "%1$@ g" : { + "comment" : "Format string for an amount of carbohydrate in grams. (1: grams)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ г" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ gr" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ г" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ g" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ 克" + } + } + } + }, + "%1$@ h" : { + "comment" : "Time in range row: average hours a day spent in the band, abbreviated. (1: hours with one decimal)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ س" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ h" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ t" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ Std" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ h" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ h" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ h" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ שע׳" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ óra" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ h" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@時間" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ t" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ u" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ godz." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ h" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ h" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ ч" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ h" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ h" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ sa" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ год" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ giờ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ 小时" + } + } + } + }, + "%1$@ manual, %2$@ automatic" : { + "comment" : "VoiceOver: how the bolus divides. (1: manual units, 2: automatic units)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ يدوي، %2$@ تلقائي" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ ručně, %2$@ automaticky" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ manuel, %2$@ automatisk" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ manuell, %2$@ automatisch" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ manual, %2$@ automático" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ manuaalinen, %2$@ automaattinen" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ manuel, %2$@ automatique" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ ידני, %2$@ אוטומטי" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ kézi, %2$@ automatikus" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ manuale, %2$@ automatico" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "手動 %1$@、自動 %2$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ manuell, %2$@ automatisk" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ handmatig, %2$@ automatisch" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ ręcznie, %2$@ automatycznie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ manual, %2$@ automático" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ manual, %2$@ automat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ вручную, %2$@ автоматически" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ ručne, %2$@ automaticky" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ manuell, %2$@ automatisk" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ manuel, %2$@ otomatik" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ вручну, %2$@ автоматично" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ thủ công, %2$@ tự động" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "手动 %1$@,自动 %2$@" + } + } + } + }, + "%1$@ mmol/mol" : { + "comment" : "Trends GMI tile detail: the GMI in IFCC units. (1: value)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mmol/mol" + } + } + } + }, + "%1$@ of %2$@" : { + "comment" : "Trends Loop active tile detail: time in closed loop out of the time Loop status is known. (1: duration in closed loop, e.g. 6d 4h, 2: duration covered, e.g. 7d) Trends: how many days of the period are averaged. (1: days averaged, a number; 2: days in the period, e.g. 14 days)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ من %2$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ z %2$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ af %2$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ von %2$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ de %2$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ / %2$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ sur %2$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ מתוך %2$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ / %2$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ su %2$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ / %2$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ av %2$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ van %2$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ z %2$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ de %2$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ din %2$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ из %2$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ z %2$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ av %2$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ / %2$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ з %2$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ / %2$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ / %2$@" + } + } + } + }, + "%1$@ U/h" : { + "comment" : "Format string for an insulin rate in units per hour. (1: rate)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ وحدة لكل ساعة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U/hod" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ E/t" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ IE/Std" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U/h" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U/h" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U/h" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "U/hr %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U/óra" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U/ora" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ 単位/時" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ E/t" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ E/uur" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ J/h" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U/h" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U/oră" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ Ед/ч" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ J/hod" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ IE/h" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ Ü/Sa" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U/год" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U/giờ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U/小时" + } + } + } + }, + "%1$@ vs plan" : { + "comment" : "Basal tile on the Now screen: how the current basal rate differs from the scheduled rate. (1: signed difference in U/h, e.g. +0.25)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ مقارنة بالمجدول" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ oproti plánu" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ ift. plan" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ ggü. Plan" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ vs. lo programado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ vs. ohjelmoitu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ vs prévu" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ לעומת התוכנית" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ a tervhez képest" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ vs programmata" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "予定比 %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mot plan" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ t.o.v. gepland" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ wzgl. planu" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ vs. programado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ față de program" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ к плану" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ oproti plánu" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ mot schema" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "plana göre %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ до плану" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ so với lịch" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "较预设 %1$@" + } + } + } + }, + "%1$@ · %2$@ in 5 min" : { + "comment" : "Under the glucose value on the Now screen: the glucose unit and how much glucose changed over 5 minutes. (1: glucose unit, e.g. mmol/L; 2: signed change, e.g. +0.3)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ خلال ٥ دقائق" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ za 5 min" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ på 5 min" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ in 5 Min" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ en 5 min" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ / 5 min" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ en 5 min" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ ב-5 דק׳" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ 5 perc alatt" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ in 5 min" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · 5分で%2$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ på 5 min" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ in 5 min" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ w 5 min" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ em 5 min" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ în 5 min" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ за 5 мин" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ za 5 min" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ på 5 min" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · 5 dk'da %2$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ за 5 хв" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · %2$@ trong 5 phút" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · 5分钟内%2$@" + } + } + } + }, + "%1$@ · ending soon" : { + "comment" : "Status pill on the Now screen when a pump pod or CGM sensor is near the end of its life. (1: device name from the pump or CGM plugin, e.g. Omnipod 5)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · ينتهي قريبًا" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · brzy výměna" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · udløber snart" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · läuft bald ab" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · termina pronto" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · päättyy pian" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · fin proche" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · מסתיים בקרוב" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · hamarosan lejár" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · in scadenza" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · まもなく終了" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · utløper snart" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · verloopt binnenkort" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · wkrótce wymiana" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · terminando em breve" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · expiră curând" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · скоро замена" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · čoskoro výmena" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · går snart ut" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · yakında bitiyor" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · незабаром заміна" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · sắp hết hạn" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ · 即将到期" + } + } + } + }, + "%1$@%%" : { + "comment" : "A percentage. (1: number)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %%" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %%" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %%" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %%" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %%" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %%" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%%%1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%%" + } + } + } + }, + "%1$@%% basal" : { + "comment" : "Trends Insulin per day tile detail: share of insulin delivered as basal. (1: percentage number)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% القاعدية" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "bazál %1$@%%" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% basal" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% Basal" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% basal" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% basaalia" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% basal" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% Basal" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% basal" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% basale" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ベーサル %1$@%%" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% basal" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% basaal" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "baza %1$@%%" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% basal" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% bazală" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "базал %1$@%%" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "bazál %1$@%%" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% basal" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%%%1$@ bazal" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "базал %1$@%%" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% Basal" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "基础 %1$@%%" + } + } + } + }, + "%1$@%% basal · %2$@" : { + "comment" : "Trends Insulin per day tile detail when only some days are averaged. (1: basal share as a percentage number, 2: days averaged, e.g. 5 of 14 days)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% القاعدية · %2$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "bazál %1$@%% · %2$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% basal · %2$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% Basal · %2$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% basal · %2$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% basaalia · %2$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% basal · %2$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% Basal · %2$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% basal · %2$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% basale · %2$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ベーサル %1$@%% · %2$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% basal · %2$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% basaal · %2$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "baza %1$@%% · %2$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% basal · %2$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% bazală · %2$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "базал %1$@%% · %2$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "bazál %1$@%% · %2$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% basal · %2$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%%%1$@ bazal · %2$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "базал %1$@%% · %2$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% Basal · %2$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "基础 %1$@%% · %2$@" + } + } + } + }, + "%1$@%% CGM active" : { + "comment" : "Trends Time in range card: share of the period with CGM readings. (1: percentage number)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% CGM نشط" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM aktivní %1$@%%" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% CGM aktiv" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% CGM aktiv" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM activo %1$@%%" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% CGM aktiivinen" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM actif %1$@ %%" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% CGM פעיל" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% CGM aktív" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM attivo %1$@%%" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM稼働 %1$@%%" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% CGM aktiv" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% CGM actief" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM aktywny %1$@%%" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM ativo %1$@%%" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@%% CGM activ" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM активен %1$@%%" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM aktívny %1$@%%" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %% CGM aktiv" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%%%1$@ CGM aktif" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM активний %1$@%%" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM hoạt động %1$@%%" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM 在线 %1$@%%" + } + } + } + }, + "%1$@, %2$@ %3$@: %4$@ percent, %5$@ hours a day" : { + "comment" : "VoiceOver label for a Time in range row. (1: band name, e.g. In range, 2: glucose thresholds, e.g. 3.9–10.0, 3: glucose unit, 4: percentage number, 5: average hours a day with one decimal)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@، %2$@ %3$@: %4$@ بالمئة، %5$@ ساعة يوميًا" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: procenta — %4$@, hodin denně — %5$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: %4$@ procent, %5$@ timer om dagen" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: %4$@ Prozent, %5$@ Stunden pro Tag" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: %4$@ por ciento, %5$@ horas al día" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: %4$@ prosenttia, %5$@ tuntia päivässä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@ : %4$@ pour cent, %5$@ heures par jour" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: %4$@ אחוז, %5$@ שעות ביום" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: %4$@ százalék, napi %5$@ óra" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: %4$@ percento, %5$@ ore al giorno" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@、%2$@ %3$@:%4$@パーセント、1日%5$@時間" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: %4$@ prosent, %5$@ timer i døgnet" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: %4$@ procent, %5$@ uur per dag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: procent — %4$@, godzin dziennie — %5$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: %4$@ por cento, %5$@ horas por dia" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: %4$@ procente, %5$@ ore pe zi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: процент — %4$@, часов в день — %5$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: percentá — %4$@, hodín denne — %5$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: %4$@ procent, %5$@ timmar per dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: yüzde %4$@, günde %5$@ saat" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: відсоток — %4$@, годин на день — %5$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@ %3$@: %4$@ phần trăm, %5$@ giờ mỗi ngày" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@,%2$@ %3$@:百分之%4$@,每天%5$@小时" + } + } + } + }, + "%lld days" : { + "comment" : "A number of days, e.g. a Trends period or how many days are averaged. (1: number of days) Trends period picker, VoiceOver name for a period of several days. (1: number of days)", + "localizations" : { + "en" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld day" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld days" + } + } + } + } + }, + "ar" : { + "variations" : { + "plural" : { + "zero" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld يوم" + } + }, + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld يوم" + } + }, + "two" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld يوم" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld أيام" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld يومًا" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld يوم" + } + } + } + } + }, + "cs" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld den" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dny" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dne" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dní" + } + } + } + } + }, + "da" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dag" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dage" + } + } + } + } + }, + "de" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Tag" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Tage" + } + } + } + } + }, + "es" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld día" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld días" + } + } + } + } + }, + "fi" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld päivä" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld päivää" + } + } + } + } + }, + "fr" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld jour" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld jours" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld jours" + } + } + } + } + }, + "he" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld יום" + } + }, + "two" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld ימים" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld ימים" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld ימים" + } + } + } + } + }, + "hu" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld nap" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld nap" + } + } + } + } + }, + "it" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld giorno" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld giorni" + } + } + } + } + }, + "ja" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld日" + } + } + } + } + }, + "nb" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dag" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dager" + } + } + } + } + }, + "nl" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dag" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dagen" + } + } + } + } + }, + "pl" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dzień" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dni" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dni" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dnia" + } + } + } + } + }, + "pt-BR" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dia" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dias" + } + } + } + } + }, + "ro" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld zi" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld zile" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld de zile" + } + } + } + } + }, + "ru" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld день" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld дня" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld дней" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld дня" + } + } + } + } + }, + "sk" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld deň" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dni" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dňa" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dní" + } + } + } + } + }, + "sv" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dag" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dagar" + } + } + } + } + }, + "tr" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld gün" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld gün" + } + } + } + } + }, + "uk" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld день" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld дні" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld днів" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld дня" + } + } + } + } + }, + "vi" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld ngày" + } + } + } + } + }, + "zh-Hans" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld天" + } + } + } + } + } + } + }, + "%lld doses" : { + "comment" : "Logbook folded automatic boluses row: how many automatic boluses it holds. (1: number)", + "localizations" : { + "ar" : { + "variations" : { + "plural" : { + "zero" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld جرعة" + } + }, + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "جرعة واحدة" + } + }, + "two" : { + "stringUnit" : { + "state" : "translated", + "value" : "جرعتان" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld جرعات" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld جرعة" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld جرعة" + } + } + } + } + }, + "cs" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dávka" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dávky" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dávky" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dávek" + } + } + } + } + }, + "da" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dosis" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld doser" + } + } + } + } + }, + "de" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Dosis" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Dosen" + } + } + } + } + }, + "en" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dose" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld doses" + } + } + } + } + }, + "es" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dosis" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dosis" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dosis" + } + } + } + } + }, + "fi" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld annos" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld annosta" + } + } + } + } + }, + "fr" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dose" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld doses" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld doses" + } + } + } + } + }, + "he" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "מנה %lld" + } + }, + "two" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld מנות" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld מנות" + } + } + } + } + }, + "hu" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld adag" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld adag" + } + } + } + } + }, + "it" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dose" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dosi" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dosi" + } + } + } + } + }, + "ja" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld回" + } + } + } + } + }, + "nb" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dose" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld doser" + } + } + } + } + }, + "nl" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dosis" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld doses" + } + } + } + } + }, + "pl" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dawka" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dawki" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dawek" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dawki" + } + } + } + } + }, + "pt-BR" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dose" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld doses" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld doses" + } + } + } + } + }, + "ro" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld doză" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld doze" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld de doze" + } + } + } + } + }, + "ru" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld доза" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld дозы" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld доз" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld дозы" + } + } + } + } + }, + "sk" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dávka" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dávky" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dávky" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dávok" + } + } + } + } + }, + "sv" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dos" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld doser" + } + } + } + } + }, + "tr" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld doz" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld doz" + } + } + } + } + }, + "uk" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld доза" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld дози" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld доз" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld дози" + } + } + } + } + }, + "vi" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld liều" + } + } + } + } + }, + "zh-Hans" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 次" + } + } + } + } + } + } + }, + "%lld entries" : { + "comment" : "Trends Carbs tile detail for a one-day period: how many carb entries were made that day. (1: number)", + "localizations" : { + "ar" : { + "variations" : { + "plural" : { + "zero" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld إدخالات" + } + }, + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "إدخال واحد" + } + }, + "two" : { + "stringUnit" : { + "state" : "translated", + "value" : "إدخالان" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld إدخالات" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld إدخالًا" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld إدخال" + } + } + } + } + }, + "cs" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld záznam" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld záznamy" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld záznamu" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld záznamů" + } + } + } + } + }, + "da" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld registrering" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld registreringer" + } + } + } + } + }, + "de" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Eintrag" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Einträge" + } + } + } + } + }, + "en" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld entry" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld entries" + } + } + } + } + }, + "es" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld entrada" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld entradas" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld entradas" + } + } + } + } + }, + "fi" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kirjaus" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kirjausta" + } + } + } + } + }, + "fr" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld entrée" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld entrées" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld entrées" + } + } + } + } + }, + "he" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "רשומה אחת" + } + }, + "two" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld רשומות" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld רשומות" + } + } + } + } + }, + "hu" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld bejegyzés" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld bejegyzés" + } + } + } + } + }, + "it" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld inserimento" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld inserimenti" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld inserimenti" + } + } + } + } + }, + "ja" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld件" + } + } + } + } + }, + "nb" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld registrering" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld registreringer" + } + } + } + } + }, + "nl" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld registratie" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld registraties" + } + } + } + } + }, + "pl" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld wpis" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld wpisy" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld wpisów" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld wpisu" + } + } + } + } + }, + "pt-BR" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld registro" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld registros" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld registros" + } + } + } + } + }, + "ro" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld intrare" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld intrări" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld de intrări" + } + } + } + } + }, + "ru" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld запись" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld записи" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld записей" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld записи" + } + } + } + } + }, + "sk" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld záznam" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld záznamy" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld záznamu" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld záznamov" + } + } + } + } + }, + "sv" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld registrering" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld registreringar" + } + } + } + } + }, + "tr" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld giriş" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld giriş" + } + } + } + } + }, + "uk" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld запис" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld записи" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld записів" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld запису" + } + } + } + } + }, + "vi" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld lần nhập" + } + } + } + } + }, + "zh-Hans" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld条" + } + } + } + } + } + } + }, + "%lld shorter gaps" : { + "comment" : "Chart note: how many other, shorter gaps in glucose readings there were; follows the longest gap. (1: number of gaps)", + "localizations" : { + "en" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld shorter gap" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld shorter gaps" + } + } + } + } + }, + "ar" : { + "variations" : { + "plural" : { + "zero" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld فجوة أقصر" + } + }, + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld فجوة أقصر" + } + }, + "two" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld فجوة أقصر" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld فجوات أقصر" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld فجوة أقصر" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld فجوة أقصر" + } + } + } + } + }, + "cs" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kratší výpadek" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kratší výpadky" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kratšího výpadku" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kratších výpadků" + } + } + } + } + }, + "da" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kortere hul" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kortere huller" + } + } + } + } + }, + "de" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kürzere Lücke" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kürzere Lücken" + } + } + } + } + }, + "es" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld interrupción más corta" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld interrupciones más cortas" + } + } + } + } + }, + "fi" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld lyhyempi katko" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld lyhyempää katkoa" + } + } + } + } + }, + "fr" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld interruption plus courte" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld interruptions plus courtes" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld interruptions plus courtes" + } + } + } + } + }, + "he" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld פער קצר יותר" + } + }, + "two" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld פערים קצרים יותר" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld פערים קצרים יותר" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld פערים קצרים יותר" + } + } + } + } + }, + "hu" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld rövidebb kimaradás" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld rövidebb kimaradás" + } + } + } + } + }, + "it" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld interruzione più breve" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld interruzioni più brevi" + } + } + } + } + }, + "ja" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "ほか短い欠損%lld件" + } + } + } + } + }, + "nb" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kortere hull" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kortere hull" + } + } + } + } + }, + "nl" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kortere onderbreking" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kortere onderbrekingen" + } + } + } + } + }, + "pl" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld krótsza przerwa" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld krótsze przerwy" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld krótszych przerw" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld krótszej przerwy" + } + } + } + } + }, + "pt-BR" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld interrupção mais curta" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld interrupções mais curtas" + } + } + } + } + }, + "ro" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld întrerupere mai scurtă" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld întreruperi mai scurte" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld de întreruperi mai scurte" + } + } + } + } + }, + "ru" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld короткий пропуск" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld коротких пропуска" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld коротких пропусков" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld коротких пропуска" + } + } + } + } + }, + "sk" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kratší výpadok" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kratšie výpadky" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kratšieho výpadku" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kratších výpadkov" + } + } + } + } + }, + "sv" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kortare lucka" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld kortare luckor" + } + } + } + } + }, + "tr" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld daha kısa boşluk" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld daha kısa boşluk" + } + } + } + } + }, + "uk" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld коротший пропуск" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld коротші пропуски" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld коротших пропусків" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld коротшого пропуску" + } + } + } + } + }, + "vi" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld khoảng trống ngắn hơn" + } + } + } + } + }, + "zh-Hans" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "另有%lld段较短中断" + } + } + } + } + } + } + }, + "+ %1$@ automatic" : { + "comment" : "Trends Boluses per day tile detail: average number of automatic boluses per day on top of the manual ones, a fractional value such as 4.2. (1: number with one decimal)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ تلقائي" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ automat." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ automatiske" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ automatisch" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ automáticos" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ automaattista" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ automatiques" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ אוטומטיים" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ automatikus" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ automatici" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ 自動 %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ automatiske" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ automatisch" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ automat." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ automáticos" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ automate" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ авто" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ automat." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ automatiska" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ otomatik" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ авто" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ %1$@ tự động" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "+ 自动 %1$@" + } + } + } + }, + "25–75%" : { + "comment" : "Trends Daily pattern legend: band between the 25th and 75th percentiles of glucose", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75 %" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75 %" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75 %" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75 %" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%25–75" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "25–75%" + } + } + } + }, + "5–95%" : { + "comment" : "Trends Daily pattern legend: band between the 5th and 95th percentiles of glucose", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95 %" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95 %" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95 %" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95 %" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%5–95" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "5–95%" + } + } + } + }, + "Absorbed by %1$@" : { + "comment" : "Under the end of the carb absorption curve: when the carbs will have absorbed (1: time)" + }, + "absorbs over %1$@ h" : { + "comment" : "Logbook carb row detail: carb absorption time in hours. (1: hours, e.g. 3 or 2.5)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يُمتص خلال %1$@ س" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "vstřebání za %1$@ h" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "optages over %1$@ t" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aufnahme über %1$@ Std" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "se absorbe en %1$@ h" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "imeytyy %1$@ h:ssa" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "absorption sur %1$@ h" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "נספג במשך %1$@ שע׳" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ óra alatt szívódik fel" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "assorbimento in %1$@ h" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@時間で吸収" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "absorberes over %1$@ t" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "opname in %1$@ u" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "wchłanianie przez %1$@ godz." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "absorve em %1$@ h" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "se absoarbe în %1$@ h" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "усваивается за %1$@ ч" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "vstrebanie za %1$@ h" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "absorberas under %1$@ h" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ sa içinde emilir" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "засвоюється за %1$@ год" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "hấp thu trong %1$@ giờ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@小时内吸收" + } + } + } + }, + "Active insulin %1$@, active carbs %2$@" : { + "comment" : "VoiceOver summary of the active insulin and carbs chart. (1: insulin on board, e.g. 1.25 U, or unknown; 2: carbs on board, e.g. 20 g, or unknown)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الأنسولين النشط %1$@، الكارب النشط %2$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktivní inzulín %1$@, aktivní sacharidy %2$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktivt insulin %1$@, aktive kulhydrater %2$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktives Insulin %1$@, aktive KH %2$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina activa %1$@, carbohidratos activos %2$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Akt. insuliini %1$@, akt. hiilari %2$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insuline active %1$@, glucides actifs %2$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אינסולין פעיל %1$@, פחמימות פעילות %2$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktív inzulin %1$@, aktív szénhidrát %2$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina attiva %1$@, carboidrati attivi %2$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "残存インスリン %1$@、残存糖質 %2$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktivt insulin %1$@, aktive karbohydrater %2$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actieve insuline %1$@, actieve koolhydraten %2$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktywna insulina %1$@, aktywne węglowodany %2$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina ativa %1$@, carboidratos ativos %2$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulină activă %1$@, carbohidrați activi %2$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Активный инсулин %1$@, активные углеводы %2$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktívny inzulín %1$@, aktívne sacharidy %2$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktivt insulin %1$@, aktiva kolhydrater %2$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktif insülin %1$@, aktif karb %2$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Активний інсулін %1$@, активні вуглеводи %2$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin còn hoạt động %1$@, Carbs còn hoạt động %2$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "活性胰岛素 %1$@,活性碳水 %2$@" + } + } + } + }, + "Active insulin and carbs" : { + "comment" : "Title of the day page card charting insulin and carbs on board", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الأنسولين والكارب النشطان" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktivní inzulín a sacharidy" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktivt insulin og kulhydrater" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktives Insulin und KH" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina y carbohidratos activos" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktiivinen insuliini ja hiilihydraatit" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insuline et glucides actifs" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אינסולין ופחמימות פעילים" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktív inzulin és szénhidrát" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina e carboidrati attivi" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "残存インスリンと残存糖質" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktivt insulin og karbohydrater" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actieve insuline en koolhydraten" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktywna insulina i węglowodany" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina e carboidratos ativos" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulină activă și carbohidrați activi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Активный инсулин и углеводы" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktívny inzulín a sacharidy" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktivt insulin och kolhydrater" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktif insülin ve karb" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Активний інсулін і вуглеводи" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin và Carbs còn hoạt động" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "活性胰岛素和碳水" + } + } + } + }, + "Add carbs" : { + "comment" : "Button on the Now screen that opens carb entry", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "إضافة كارب" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Přidat sacharidy" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tilføj kulhydrater" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "KH hinzufügen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agregar carbohidratos" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lisää hiilihydraatteja" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajouter des glucides" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "הוספת פחמימות" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Szénhidrát hozzáadása" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aggiungi carboidrati" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "カーボを追加" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Legg til karbohydrater" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Koolhydraten toevoegen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dodaj węglowodany" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicionar carbs" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adăugați carbohidrați" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Добавить углеводы" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pridať sacharidy" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lägg till kolhydrater" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Karb ekle" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Додати вуглеводи" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thêm Carbs" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加碳水" + } + } + } + }, + "Add pump" : { + "comment" : "Status pill on the Now screen when no pump is set up; tapping it starts pump setup", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "إضافة مضخة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Přidat pumpu" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tilføj pumpe" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpe hinzufügen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agregar microinfusadora" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lisää pumppu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajouter une pompe" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "הוספת משאבה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpa hozzáadása" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aggiungi pompa" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ポンプを追加" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Legg til pumpe" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pomp toevoegen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dodaj pompę" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicionar bomba" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adăugați pompă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Добавить помпу" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pridať pumpu" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lägg till pump" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompa ekle" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Додати помпу" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thêm bơm" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加胰岛素泵" + } + } + } + }, + "Aim: average under %1$@ %2$@, GMI under 7%%, CV 36%% or less." : { + "comment" : "Trends Summary footnote: the consensus targets for average glucose, GMI and glucose variability (CV). (1: glucose value, 2: glucose unit)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الهدف: متوسط أقل من %1$@ %2$@، وGMI أقل من 7%%، وCV ‏36%% أو أقل." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cíl: průměr pod %1$@ %2$@, GMI pod 7%%, CV nejvýše 36%%." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mål: gennemsnit under %1$@ %2$@, GMI under 7 %%, CV højst 36 %%." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ziel: Durchschnitt unter %1$@ %2$@, GMI unter 7 %%, VK höchstens 36 %%." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Objetivo: promedio por debajo de %1$@ %2$@, GMI por debajo del 7%%, CV del 36%% o menos." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tavoite: keskiarvo alle %1$@ %2$@, GMI alle 7 %%, CV enintään 36 %%." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Objectif : moyenne sous %1$@ %2$@, GMI sous 7 %%, CV de 36 %% au plus." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "יעד: ממוצע מתחת ל-%1$@ %2$@, GMI מתחת ל-7%%, CV ‏36%% או פחות." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cél: átlag %1$@ %2$@ alatt, GMI 7%% alatt, CV legfeljebb 36%%." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obiettivo: media sotto %1$@ %2$@, GMI sotto il 7%%, CV al massimo 36%%." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "目標:平均%1$@ %2$@未満、GMI 7%%未満、CV 36%%以下。" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mål: gjennomsnitt under %1$@ %2$@, GMI under 7 %%, CV høyst 36 %%." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Doel: gemiddelde onder %1$@ %2$@, GMI onder 7%%, CV hoogstens 36%%." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cel: średnia poniżej %1$@ %2$@, GMI poniżej 7%%, CV najwyżej 36%%." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meta: média abaixo de %1$@ %2$@, GMI abaixo de 7%%, CV de no máximo 36%%." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Țintă: medie sub %1$@ %2$@, GMI sub 7%%, CV de cel mult 36%%." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Цель: среднее ниже %1$@ %2$@, GMI ниже 7%%, CV не более 36%%." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cieľ: priemer pod %1$@ %2$@, GMI pod 7%%, CV najviac 36%%." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mål: genomsnitt under %1$@ %2$@, GMI under 7 %%, CV högst 36 %%." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hedef: ortalama %1$@ %2$@ altı, GMI %%7 altı, CV en fazla %%36." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Мета: середнє нижче %1$@ %2$@, GMI нижче 7%%, CV не більше 36%%." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mục tiêu: trung bình dưới %1$@ %2$@, GMI dưới 7%%, CV từ 36%% trở xuống." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "目标:平均值低于 %1$@ %2$@,GMI 低于 7%%,CV 不超过 36%%。" + } + } + } + }, + "Aim: over 70%% in range; under 25%% above and 4%% below; under 5%% very high and 1%% very low; CGM active over 70%%." : { + "comment" : "Trends Time in range card footnote: the consensus targets for time in range, above and below range, very high, very low and CGM wear", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الهدف: أكثر من 70%% ضمن النطاق؛ أقل من 25%% فوق و4%% تحت؛ أقل من 5%% مرتفع جدًا و1%% منخفض جدًا؛ وCGM نشط أكثر من 70%%." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cíl: přes 70%% v rozsahu; pod 25%% nad a 4%% pod; pod 5%% velmi vysoko a 1%% velmi nízko; CGM aktivní přes 70%%." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mål: over 70 %% i området; under 25 %% over og 4 %% under; under 5 %% meget højt og 1 %% meget lavt; CGM aktiv over 70 %%." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ziel: über 70 %% im Zielbereich; unter 25 %% darüber und 4 %% darunter; unter 5 %% sehr hoch und 1 %% sehr niedrig; CGM über 70 %% aktiv." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Objetivo: más del 70%% en rango; menos del 25%% por encima y del 4%% por debajo; menos del 5%% muy alto y del 1%% muy bajo; CGM activo más del 70%%." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tavoite: yli 70 %% tavoitealueella; alle 25 %% yläpuolella ja 4 %% alapuolella; alle 5 %% erittäin korkealla ja 1 %% erittäin matalalla; CGM aktiivinen yli 70 %%." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Objectif : plus de 70 %% dans la cible ; moins de 25 %% au-dessus et 4 %% en dessous ; moins de 5 %% très haut et 1 %% très bas ; CGM actif plus de 70 %%." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "יעד: מעל 70%% בטווח; מתחת ל-25%% מעל ו-4%% מתחת; מתחת ל-5%% גבוה מאוד ו-1%% נמוך מאוד; CGM פעיל מעל 70%%." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cél: több mint 70%% tartományban; kevesebb mint 25%% felette és 4%% alatta; kevesebb mint 5%% nagyon magas és 1%% nagyon alacsony; CGM aktív több mint 70%%." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obiettivo: oltre il 70%% nell'intervallo; meno del 25%% sopra e del 4%% sotto; meno del 5%% molto alta e dell'1%% molto bassa; CGM attivo oltre il 70%%." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "目標:範囲内70%%超、範囲上25%%未満・範囲下4%%未満、非常に高い5%%未満・非常に低い1%%未満、CGM稼働70%%超。" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mål: over 70 %% i målområdet; under 25 %% over og 4 %% under; under 5 %% svært høyt og 1 %% svært lavt; CGM aktiv over 70 %%." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Doel: meer dan 70%% binnen bereik; minder dan 25%% erboven en 4%% eronder; minder dan 5%% zeer hoog en 1%% zeer laag; CGM meer dan 70%% actief." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cel: ponad 70%% w zakresie; poniżej 25%% powyżej i 4%% poniżej; poniżej 5%% bardzo wysokich i 1%% bardzo niskich; CGM aktywny ponad 70%%." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meta: mais de 70%% no alvo; menos de 25%% acima e 4%% abaixo; menos de 5%% muito alto e 1%% muito baixo; CGM ativo mais de 70%%." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Țintă: peste 70%% în interval; sub 25%% peste și 4%% sub; sub 5%% foarte ridicat și 1%% foarte scăzut; CGM activ peste 70%%." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Цель: в диапазоне более 70%%; выше — менее 25%%, ниже — менее 4%%; очень высокая — менее 5%%, очень низкая — менее 1%%; CGM активен более 70%%." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cieľ: nad 70%% v rozsahu; pod 25%% nad a 4%% pod; pod 5%% veľmi vysoko a 1%% veľmi nízko; CGM aktívny nad 70%%." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mål: över 70 %% inom målområdet; under 25 %% över och 4 %% under; under 5 %% mycket högt och 1 %% mycket lågt; CGM aktiv över 70 %%." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hedef: aralıkta %%70'in üzeri; üstünde %%25'in, altında %%4'ün altı; çok yüksekte %%5'in, çok düşükte %%1'in altı; CGM aktif %%70'in üzeri." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Мета: у діапазоні понад 70%%; вище — менше 25%%, нижче — менше 4%%; дуже висока — менше 5%%, дуже низька — менше 1%%; CGM активний понад 70%%." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mục tiêu: trên 70%% trong khoảng; dưới 25%% trên khoảng và 4%% dưới khoảng; dưới 5%% rất cao và 1%% rất thấp; CGM hoạt động trên 70%%." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "目标:范围内超过70%%;高于范围低于25%%,低于范围低于4%%;极高低于5%%,极低低于1%%;CGM 在线超过70%%。" + } + } + } + }, + "All" : { + "comment" : "Logbook filter chip: show every entry", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الكل" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vše" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Todo" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kaikki" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tout" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "הכל" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mind" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tutto" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "すべて" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alles" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wszystko" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tudo" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toate" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Все" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Všetko" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alla" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tümü" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Усі" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tất cả" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "全部" + } + } + } + }, + "Automatic" : { + "comment" : "Chart legend: hatched bars are automatic boluses given by Loop", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تلقائي" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatické" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisk" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisch" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automático" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automaattinen" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatique" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אוטומטי" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatikus" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatico" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "自動" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisk" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisch" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatyczne" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automático" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматические" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatické" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisk" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otomatik" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматичні" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tự động" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自动" + } + } + } + }, + "Automatic %1$@" : { + "comment" : "Basal and bolus: bolus Loop gave automatically. (1: units, e.g. 5.4 U)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تلقائي %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automaticky %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisk %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisch %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automático %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automaattinen %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatique %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אוטומטי %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatikus %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatico %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "自動 %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisk %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisch %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatycznie %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automático %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automat %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматически %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automaticky %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisk %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otomatik %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматично %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tự động %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自动 %1$@" + } + } + } + }, + "Automatic bolus" : { + "comment" : "Chart callout label for a bolus Loop delivered automatically near the touched time Logbook row title: a bolus Loop delivered automatically", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus تلقائي" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatický bolus" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisk bolus" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatischer Bolus" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolo automático" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automaattinen bolus" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus automatique" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus אוטומטי" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatikus bólus" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolo automatico" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "自動ボーラス" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisk bolus" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatische bolus" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus automatyczny" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus automático" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus automat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматический болюс" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatický bolus" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisk bolus" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otomatik bolus" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматичний болюс" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus tự động" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自动大剂量" + } + } + } + }, + "Automatic boluses" : { + "comment" : "Logbook row title: several automatic boluses in a row, folded into one row that lists them", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "جرعات تلقائية" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatické bolusy" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatiske boluser" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatische Boli" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolos automáticos" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automaattiset boluset" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus automatiques" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "בולוסים אוטומטיים" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatikus bóluszok" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boli automatici" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "自動ボーラス" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatiske boluser" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatische bolussen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusy automatyczne" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus automáticos" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusuri automate" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматические болюсы" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatické bolusy" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatiska bolusdoser" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otomatik boluslar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматичні болюси" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus tự động" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自动大剂量" + } + } + } + }, + "Average %1$@ U" : { + "comment" : "Trends Insulin per day legend: the dashed line showing average total insulin per day. (1: units)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "المتوسط %1$@ وحدة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Průměr %1$@ U" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gennemsnit %1$@ E" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Durchschnitt %1$@ IE" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Promedio %1$@ U" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keskiarvo %1$@ U" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Moyenne %1$@ U" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "ממוצע U %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Átlag %1$@ U" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Media %1$@ U" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "平均 %1$@ 単位" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gjennomsnitt %1$@ E" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gemiddeld %1$@ E" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Średnio %1$@ J" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Média %1$@ U" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Medie %1$@ U" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Среднее %1$@ Ед." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Priemer %1$@ j" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Medel %1$@ E" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ortalama %1$@ Ü" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Середнє %1$@ U" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trung bình %1$@ U" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "平均 %1$@ U" + } + } + } + }, + "average per day" : { + "comment" : "Trends Basal and bolus card subtitle when the figures are averaged per day over the period", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "المتوسط اليومي" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "průměr za den" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "gennemsnit pr. dag" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Durchschnitt pro Tag" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "promedio por día" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "keskiarvo päivässä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "moyenne par jour" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "ממוצע ליום" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "napi átlag" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "media al giorno" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "1日あたりの平均" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "gjennomsnitt per dag" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "gemiddeld per dag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "średnio dziennie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "média por dia" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "medie pe zi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "в среднем за день" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "priemer za deň" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "medel per dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "günlük ortalama" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "у середньому за день" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "trung bình mỗi ngày" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "每日平均" + } + } + } + }, + "Basal" : { + "comment" : "Chart callout label for the basal rate at the touched time Label of a tile showing the current basal rate (Now screen) or the day's basal insulin total (Today so far totals) Logbook filter chip: show only basal changes", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "القاعدية" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazál" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basaalinen" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basale" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ベーサル" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basaal" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Podstawowy" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazală" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Базал" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazálna" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazal" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Базал" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "基础" + } + } + } + }, + "Basal %1$@, %2$@ percent" : { + "comment" : "VoiceOver: basal units and share. (1: units, 2: percentage number)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "القاعدية %1$@، %2$@ بالمئة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazál %1$@, %2$@ procent" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal %1$@, %2$@ procent" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal %1$@, %2$@ Prozent" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal %1$@, %2$@ por ciento" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basaali %1$@, %2$@ prosenttia" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal %1$@, %2$@ pour cent" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal %1$@, %2$@ אחוז" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal %1$@, %2$@ százalék" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basale %1$@, %2$@ percento" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ベーサル %1$@、%2$@ パーセント" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal %1$@, %2$@ prosent" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basaal %1$@, %2$@ procent" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Podstawowy %1$@, %2$@ procent" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal %1$@, %2$@ por cento" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazală %1$@, %2$@ la sută" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Базал %1$@, %2$@ процентов" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazál %1$@, %2$@ percent" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal %1$@, %2$@ procent" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazal %1$@, yüzde %2$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Базал %1$@, %2$@ відсотків" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal %1$@, %2$@ phần trăm" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "基础 %1$@,百分之 %2$@" + } + } + } + }, + "Basal adjustments" : { + "comment" : "Logbook row title summarising the temp basals Loop set in a day Title of the list of every temp basal Loop set in a day", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تعديلات القاعدية" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Úpravy bazálu" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basaljusteringer" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basalanpassungen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajustes de basal" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basaalisäädöt" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajustements du basal" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "התאמות Basal" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal-módosítások" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Regolazioni basale" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ベーサル調整" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basaljusteringer" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basaalaanpassingen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zmiany bazy" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajustes de basal" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajustări bazală" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Изменения базала" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Úpravy bazálu" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basaljusteringar" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazal ayarlamaları" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Зміни базалу" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Điều chỉnh Basal" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "基础率调整" + } + } + } + }, + "Basal and bolus" : { + "comment" : "Trends and day page card title: insulin split into basal and bolus", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "القاعدية والبلعة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazál a bolus" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal og bolus" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal und Bolus" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal y bolo" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basaali ja bolus" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal et bolus" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal ו-Bolus" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal és bólus" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basale e bolo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ベーサルとボーラス" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal og bolus" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basaal en bolus" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina podstawowa i bolus" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal e bolus" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazală și bolus" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Базал и болюс" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazál a bolus" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal och bolus" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazal ve bolus" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Базал і болюс" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal và bolus" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "基础与大剂量" + } + } + } + }, + "basal and bolus" : { + "comment" : "Trends Insulin per day card subtitle when each bar is one day's basal and bolus", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "القاعدية و Bolus" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "bazál a bolus" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "basal og bolus" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal und Bolus" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "basal y bolo" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "basaali ja bolus" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "basal et bolus" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal ו-Bolus" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "basal és bólus" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "basale e bolo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ベーサルとボーラス" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "basal og bolus" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "basaal en bolus" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "baza i bolus" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "basal e bolus" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "bazală și bolus" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "базал и болюс" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "bazál a bolus" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "basal och bolus" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "bazal ve bolus" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "базал і болюс" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal và Bolus" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "基础和大剂量" + } + } + } + }, + "Basal given" : { + "comment" : "Chart legend: filled area is basal insulin actually delivered", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "القاعدية المُعطاة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Podaný bazál" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Givet basal" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abgegebene Basalrate" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal administrada" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Annettu basaali" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal administré" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal שניתן" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Leadott basal" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basale erogata" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "注入済みベーサル" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gitt basal" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toegediend basaal" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Podana baza" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal entregue" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazală administrată" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поданный базал" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Podaný bazál" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Given basal" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verilen bazal" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поданий базал" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal đã tiêm" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已输注基础" + } + } + } + }, + "Basal resumed" : { + "comment" : "Logbook row title: insulin delivery resumed after a suspend", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "استئناف القاعدية" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazál obnoven" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal genoptaget" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal fortgesetzt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal reanudada" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basaali jatkuu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal repris" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal חודש" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal folytatva" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basale ripresa" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ベーサル再開" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal gjenopptatt" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basaal hervat" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Baza wznowiona" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal retomada" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazală reluată" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Базал возобновлён" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazál obnovený" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal återupptagen" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazal devam ettirildi" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Базал відновлено" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Đã tiếp tục Basal" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "基础已恢复" + } + } + } + }, + "Basal suspended" : { + "comment" : "Logbook row title: insulin delivery was suspended", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "إيقاف القاعدية مؤقتًا" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazál pozastaven" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal suspenderet" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal unterbrochen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal suspendida" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basaali pysäytetty" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal suspendu" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal הושהה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal felfüggesztve" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basale sospesa" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ベーサル一時停止" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal stanset" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basaal onderbroken" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Baza wstrzymana" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal suspensa" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazală suspendată" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Базал приостановлен" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazál pozastavený" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal pausad" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazal askıya alındı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Базал призупинено" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Đã tạm ngưng Basal" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "基础已暂停" + } + } + } + }, + "Bolus %1$@, %2$@ percent" : { + "comment" : "VoiceOver: bolus units and share. (1: units, 2: percentage number)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "البلعة %1$@، %2$@ بالمئة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus %1$@, %2$@ procent" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus %1$@, %2$@ procent" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus %1$@, %2$@ Prozent" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolo %1$@, %2$@ por ciento" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus %1$@, %2$@ prosenttia" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus %1$@, %2$@ pour cent" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus %1$@, %2$@ אחוז" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bólus %1$@, %2$@ százalék" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolo %1$@, %2$@ percento" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ボーラス %1$@、%2$@ パーセント" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus %1$@, %2$@ prosent" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus %1$@, %2$@ procent" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus %1$@, %2$@ procent" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus %1$@, %2$@ por cento" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus %1$@, %2$@ la sută" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Болюс %1$@, %2$@ процентов" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus %1$@, %2$@ percent" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus %1$@, %2$@ procent" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus %1$@, yüzde %2$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Болюс %1$@, %2$@ відсотків" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus %1$@, %2$@ phần trăm" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "大剂量 %1$@,百分之 %2$@" + } + } + } + }, + "Boluses" : { + "comment" : "Trends summary tile for a one-day period: how many manual boluses were given that day", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الجرعات" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusy" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boluser" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boli" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolos" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boluksia" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "בולוסים" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bóluszok" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boli" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ボーラス" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boluser" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolussen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusy" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusuri" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Болюсы" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusy" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusdoser" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Болюси" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Liều bolus" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "大剂量次数" + } + } + } + }, + "Boluses per day" : { + "comment" : "Trends summary tile: average number of manual boluses per day", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "جرعات Bolus يوميًا" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusů denně" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boluser pr. dag" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boli pro Tag" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolos por día" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boluksia päivässä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus par jour" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus ליום" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bólusok naponta" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boli al giorno" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "1日のボーラス回数" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boluser per dag" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolussen per dag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusy dziennie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus por dia" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusuri pe zi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Болюсов в день" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusy za deň" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusar per dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Günlük bolus sayısı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Болюсів на день" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Số lần Bolus mỗi ngày" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "每日大剂量次数" + } + } + } + }, + "Boluses, basal changes, carbs and pump events from the last 7 days" : { + "comment" : "Logbook settings descriptive text", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "جرعات Bolus وتغييرات القاعدية والكارب وأحداث المضخة خلال آخر 7 أيام" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusy, změny bazálu, sacharidy a události pumpy za posledních 7 dní" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusser, basalændringer, kulhydrater og pumpehændelser fra de seneste 7 dage" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boli, Basaländerungen, KH und Pumpenereignisse der letzten 7 Tage" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolos, cambios de basal, carbohidratos y eventos de la bomba de los últimos 7 días" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolukset, basaalimuutokset, hiilihydraatit ja pumpun tapahtumat viimeisiltä 7 päivältä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus, changements de basal, glucides et événements de la pompe des 7 derniers jours" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "בולוסים, שינויי בזאלי, פחמימות ואירועי משאבה מ-7 הימים האחרונים" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bólusok, bázisváltozások, szénhidrátok és pumpaesemények az utolsó 7 napból" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boli, modifiche della basale, carboidrati ed eventi del microinfusore degli ultimi 7 giorni" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "直近7日間のボーラス、ベーサル変更、カーボ、ポンプのイベント" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boluser, basalendringer, karbohydrater og pumpehendelser fra de siste 7 dagene" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolussen, basaalwijzigingen, koolhydraten en pompgebeurtenissen van de laatste 7 dagen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusy, zmiany bazy, węglowodany i zdarzenia pompy z ostatnich 7 dni" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus, mudanças de basal, carbs e eventos da bomba dos últimos 7 dias" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusuri, modificări ale bazalei, carbohidrați și evenimente ale pompei din ultimele 7 zile" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Болюсы, изменения базала, углеводы и события помпы за последние 7 дней" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusy, zmeny bazálu, sacharidy a udalosti pumpy za posledných 7 dní" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolusar, basaländringar, kolhydrater och pumphändelser från de senaste 7 dagarna" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Son 7 günün bolusları, bazal değişiklikleri, karbonhidratları ve pompa olayları" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Болюси, зміни базалу, вуглеводи та події помпи за останні 7 днів" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus, thay đổi basal, Carbs và sự kiện của bơm trong 7 ngày gần nhất" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "最近 7 天的大剂量、基础率变化、碳水和泵事件" + } + } + } + }, + "Carbs" : { + "comment" : "Label of a tile showing the day's total carbohydrate Logbook filter chip: show only carb entries Logbook row title: a carb entry", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "Carbs" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sacharidy" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kulhydrater" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "KH" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Carbohidratos" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hiilihydraatit" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glucides" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Carbs" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Szénhidrát" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Carb." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "カーボ" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Karbohydrater" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Koolhydraten" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Węglowodany" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Carbs" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Carbohidrați" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Углеводы" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sacharidy" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kolhydrater" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Karb" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вуглеводи" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Carbs" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "碳水化合物" + } + } + } + }, + "Carbs per day" : { + "comment" : "Trends summary tile: average grams of carbohydrate entered per day", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الكارب يوميًا" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sacharidy denně" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kulhydrater pr. dag" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "KH pro Tag" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Carbohidratos por día" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hiilihydraatit päivässä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glucides par jour" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "פחמימות ליום" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Szénhidrát naponta" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Carboidrati al giorno" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "1日のカーボ" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Karbohydrater per dag" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Koolhydraten per dag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Węglowodany dziennie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Carbs por dia" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Carbohidrați pe zi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Углеводов в день" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sacharidy za deň" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kolhydrater per dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Günlük karb" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вуглеводів на день" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Carbs mỗi ngày" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "每日碳水" + } + } + } + }, + "CGM data covers %1$@." : { + "comment" : "Trends Summary note when glucose readings start partway through the period. (1: days covered, e.g. 5 of 14 days)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "بيانات CGM تغطي %1$@." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Data CGM pokrývají %1$@." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM-data dækker %1$@." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM-Daten decken %1$@ ab." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Los datos de CGM cubren %1$@." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM-data kattaa %1$@." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Les données CGM couvrent %1$@." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "נתוני CGM מכסים %1$@." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM-adat: %1$@." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "I dati CGM coprono %1$@." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGMデータ:%1$@。" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM-data dekker %1$@." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM-gegevens beslaan %1$@." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dane CGM obejmują %1$@." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Os dados de CGM cobrem %1$@." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Datele CGM acoperă %1$@." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Данные CGM: %1$@." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Údaje CGM pokrývajú %1$@." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM-data täcker %1$@." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM verisi: %1$@." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дані CGM: %1$@." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dữ liệu CGM bao phủ %1$@." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM 数据覆盖 %1$@。" + } + } + } + }, + "Closed loop" : { + "comment" : "Trends Loop status: time Loop was running in closed loop", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "حلقة مغلقة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uzavřená smyčka" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lukket Loop" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Closed Loop" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Circuito cerrado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suljettu säätö" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boucle fermée" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Closed Loop" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zárt hurok" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop chiuso" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "クローズドループ" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lukket Loop" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actieve loop" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pętla zamknięta" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ciclo fechado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buclă închisă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Замкнутый цикл" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uzavretá slučka" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sluten loop" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kapalı döngü" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Замкнутий цикл" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vòng lặp kín" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "闭环" + } + } + } + }, + "Closed loop off" : { + "comment" : "Reason shown for a stretch of time when Loop was in open loop because closed loop was turned off", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الحلقة المغلقة متوقفة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uzavřená smyčka vypnuta" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lukket Loop slået fra" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Closed Loop aus" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Circuito cerrado desactivado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suljettu säätö pois" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boucle fermée désactivée" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Closed Loop כבוי" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zárt hurok kikapcsolva" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop chiuso disattivato" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "クローズドループ オフ" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lukket Loop av" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actieve loop uit" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pętla zamknięta wyłączona" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ciclo fechado desligado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buclă închisă oprită" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Замкнутый цикл выключен" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uzavretá slučka vypnutá" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sluten loop av" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kapalı döngü kapalı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Замкнутий цикл вимкнено" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vòng lặp kín tắt" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "闭环已关闭" + } + } + } + }, + "Closed loop paused · %1$@" : { + "comment" : "Status pill on the Now screen: closed loop is switched on but paused (e.g. no CGM data), and Loop last ran this long ago. (1: duration, e.g. 3m)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الحلقة المغلقة متوقفة مؤقتًا · %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uzavřená smyčka pozastavena · %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lukket Loop sat på pause · %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Closed Loop pausiert · %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Circuito cerrado en pausa · %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suljettu säätö tauolla · %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boucle fermée en pause · %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Closed Loop מושהה · %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zárt hurok szüneteltetve · %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop chiuso in pausa · %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "クローズドループ一時停止中 · %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lukket Loop satt på pause · %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actieve loop gepauzeerd · %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pętla zamknięta wstrzymana · %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ciclo fechado pausado · %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buclă închisă în pauză · %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Замкнутый цикл на паузе · %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uzavretá slučka pozastavená · %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sluten loop pausad · %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kapalı döngü duraklatıldı · %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Замкнутий цикл на паузі · %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vòng lặp kín tạm dừng · %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "闭环已暂停 · %1$@" + } + } + } + }, + "Closed loop paused · not run yet" : { + "comment" : "Status pill on the Now screen: closed loop is switched on but paused (e.g. no CGM data), and Loop has never completed", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الحلقة المغلقة متوقفة مؤقتًا · لم يعمل بعد" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uzavřená smyčka pozastavena · zatím neproběhla" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lukket Loop sat på pause · ikke kørt endnu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Closed Loop pausiert · noch nicht gelaufen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Circuito cerrado en pausa · aún no se ha ejecutado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suljettu säätö tauolla · ei vielä ajettu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boucle fermée en pause · pas encore exécuté" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Closed Loop מושהה · טרם רץ" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zárt hurok szüneteltetve · még nem futott" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop chiuso in pausa · non ancora eseguito" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "クローズドループ一時停止中 · 未実行" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lukket Loop satt på pause · ikke kjørt ennå" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actieve loop gepauzeerd · nog niet gedraaid" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pętla zamknięta wstrzymana · jeszcze nie działała" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ciclo fechado pausado · ainda não executou" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buclă închisă în pauză · nu a rulat încă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Замкнутый цикл на паузе · ещё не запускался" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uzavretá slučka pozastavená · ešte neprebehla" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sluten loop pausad · inte körd än" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kapalı döngü duraklatıldı · henüz çalışmadı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Замкнутий цикл на паузі · ще не запускався" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vòng lặp kín tạm dừng · chưa chạy" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "闭环已暂停 · 尚未运行" + } + } + } + }, + "Closed loop · %1$@" : { + "comment" : "Status pill on the Now screen: closed loop is on and Loop last ran this long ago. (1: duration, e.g. 3m)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "حلقة مغلقة · %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uzavřená smyčka · %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lukket Loop · %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Closed Loop · %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Circuito cerrado · %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suljettu säätö · %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boucle fermée · %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Closed Loop · %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zárt hurok · %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop chiuso · %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "クローズドループ · %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lukket Loop · %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actieve loop · %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pętla zamknięta · %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ciclo fechado · %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buclă închisă · %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Замкнутый цикл · %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uzavretá slučka · %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sluten loop · %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kapalı döngü · %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Замкнутий цикл · %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vòng lặp kín · %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "闭环 · %1$@" + } + } + } + }, + "Closed loop · not run yet" : { + "comment" : "Status pill on the Now screen: closed loop is on but Loop has never completed", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "حلقة مغلقة · لم يعمل بعد" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uzavřená smyčka · zatím neproběhla" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lukket Loop · ikke kørt endnu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Closed Loop · noch nicht gelaufen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Circuito cerrado · aún no se ha ejecutado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suljettu säätö · ei vielä ajettu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boucle fermée · pas encore exécuté" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Closed Loop · טרם רץ" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zárt hurok · még nem futott" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop chiuso · non ancora eseguito" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "クローズドループ · 未実行" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lukket Loop · ikke kjørt ennå" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actieve loop · nog niet gedraaid" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pętla zamknięta · jeszcze nie działała" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ciclo fechado · ainda não executou" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buclă închisă · nu a rulat încă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Замкнутый цикл · ещё не запускался" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uzavretá slučka · ešte neprebehla" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sluten loop · inte körd än" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kapalı döngü · henüz çalışmadı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Замкнутий цикл · ще не запускався" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vòng lặp kín · chưa chạy" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "闭环 · 尚未运行" + } + } + } + }, + "COB" : { + "comment" : "Abbreviation for carbs on board (active carbohydrate) Abbreviation for carbs on board; label of a tile on the Now screen", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GA" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB" + } + } + } + }, + "COB (g, right)" : { + "comment" : "Chart legend: carbs on board in grams, on the right axis", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g، يمين)" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, vpravo)" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, højre)" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, rechts)" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, derecha)" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, oikea)" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GA (g, droite)" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, ימין)" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, jobb)" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, destra)" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB(g、右)" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, høyre)" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, rechts)" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, prawa oś)" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, direita)" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, dreapta)" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (г, справа)" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, vpravo)" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, höger)" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (gr, sağ)" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (г, праворуч)" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB (g, bên phải)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "COB(克,右轴)" + } + } + } + }, + "Configuration error" : { + "comment" : "Reason Loop could not run: its settings are incomplete", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "خطأ في الإعدادات" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chyba konfigurace" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konfigurationsfejl" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konfigurationsfehler" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error de configuración" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Määritysvirhe" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erreur de configuration" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "שגיאת תצורה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konfigurációs hiba" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Errore di configurazione" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "設定エラー" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konfigurasjonsfeil" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuratiefout" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Błąd konfiguracji" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erro de configuração" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eroare de configurare" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ошибка настроек" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chyba konfigurácie" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konfigurationsfel" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yapılandırma hatası" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Помилка налаштувань" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lỗi cấu hình" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置错误" + } + } + } + }, + "Connection error" : { + "comment" : "Reason Loop could not run: no connected devices, or a device connection failed", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "خطأ في الاتصال" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chyba připojení" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forbindelsesfejl" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verbindungsfehler" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error de conexión" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yhteysvirhe" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erreur de connexion" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "שגיאת חיבור" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kapcsolódási hiba" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Errore di connessione" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "接続エラー" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tilkoblingsfeil" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verbindingsfout" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Błąd połączenia" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erro de conexão" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eroare de conexiune" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ошибка подключения" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chyba pripojenia" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anslutningsfel" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bağlantı hatası" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Помилка підключення" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lỗi kết nối" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "连接错误" + } + } + } + }, + "Couldn't delete" : { + "comment" : "Alert title when deleting a Logbook entry fails", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تعذّر الحذف" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nelze smazat" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunne ikke slette" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Löschen fehlgeschlagen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se pudo eliminar" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Poisto epäonnistui" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suppression impossible" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לא ניתן היה למחוק" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nem sikerült törölni" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossibile eliminare" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "削除できませんでした" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunne ikke slette" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verwijderen mislukt" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nie udało się usunąć" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Não foi possível apagar" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nu s-a putut șterge" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не удалось удалить" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nedá sa odstrániť" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunde inte ta bort" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Silinemedi" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не вдалося видалити" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không thể xóa" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法删除" + } + } + } + }, + "Couldn't export" : { + "comment" : "Alert title when exporting the Trends PDF or CSV fails", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تعذّر التصدير" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nelze exportovat" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunne ikke eksportere" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Export fehlgeschlagen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se pudo exportar" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vienti epäonnistui" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportation impossible" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לא ניתן היה לייצא" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nem sikerült exportálni" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossibile esportare" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "書き出せませんでした" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunne ikke eksportere" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exporteren mislukt" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nie udało się wyeksportować" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Não foi possível exportar" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nu s-a putut exporta" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не удалось экспортировать" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nedá sa exportovať" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunde inte exportera" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dışa aktarılamadı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не вдалося експортувати" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không thể xuất" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法导出" + } + } + } + }, + "Couldn't load Logbook: %1$@" : { + "comment" : "Logbook error when its history can't be read. (1: system error description)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تعذّر تحميل السجل: %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nelze načíst deník: %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunne ikke indlæse logbogen: %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Logbuch konnte nicht geladen werden: %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se pudo cargar el registro: %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lokikirjaa ei voitu ladata: %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossible de charger le journal : %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לא ניתן היה לטעון את היומן: %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nem sikerült betölteni a naplót: %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossibile caricare il diario: %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ログブックを読み込めませんでした:%1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunne ikke laste inn loggboken: %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Logboek kan niet worden geladen: %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nie udało się wczytać dziennika: %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Não foi possível carregar o diário: %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nu s-a putut încărca jurnalul: %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не удалось загрузить журнал: %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Denník sa nedá načítať: %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunde inte läsa in loggboken: %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kayıt Defteri yüklenemedi: %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не вдалося завантажити журнал: %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không thể tải Nhật ký: %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法载入日志:%1$@" + } + } + } + }, + "Couldn't load recent CGM readings" : { + "comment" : "Prediction chart on the Now screen when recent glucose couldn't be read", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تعذّر تحميل قراءات CGM الأخيرة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nelze načíst nedávné hodnoty CGM" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunne ikke indlæse de seneste CGM-målinger" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktuelle CGM-Werte konnten nicht geladen werden" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se pudieron cargar las lecturas recientes del CGM" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Viimeisimpiä CGM-lukemia ei voitu ladata" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossible de charger les mesures CGM récentes" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לא ניתן היה לטעון קריאות CGM אחרונות" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nem sikerült betölteni a legutóbbi CGM-értékeket" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossibile caricare le letture CGM recenti" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "最近のCGM値を読み込めませんでした" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunne ikke laste inn nylige CGM-målinger" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recente CGM-waarden kunnen niet worden geladen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nie udało się wczytać ostatnich odczytów CGM" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Não foi possível carregar as leituras recentes do CGM" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nu s-au putut încărca citirile CGM recente" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не удалось загрузить последние показания CGM" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nedajú sa načítať nedávne hodnoty CGM" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunde inte läsa in senaste CGM-värden" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Son CGM okumaları yüklenemedi" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не вдалося завантажити останні показники CGM" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không thể tải các chỉ số CGM gần đây" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法载入最近的 CGM 读数" + } + } + } + }, + "Couldn't load this day: %1$@" : { + "comment" : "Day page error when the day's history can't be read. (1: system error description)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تعذّر تحميل هذا اليوم: %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nelze načíst tento den: %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunne ikke indlæse denne dag: %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dieser Tag konnte nicht geladen werden: %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se pudo cargar este día: %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Päivää ei voitu ladata: %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossible de charger cette journée : %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לא ניתן היה לטעון יום זה: %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nem sikerült betölteni ezt a napot: %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossibile caricare questo giorno: %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "この日を読み込めませんでした:%1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunne ikke laste inn denne dagen: %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deze dag kan niet worden geladen: %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nie udało się wczytać tego dnia: %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Não foi possível carregar este dia: %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nu s-a putut încărca această zi: %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не удалось загрузить этот день: %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tento deň sa nedá načítať: %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunde inte läsa in dagen: %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu gün yüklenemedi: %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не вдалося завантажити цей день: %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không thể tải ngày này: %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法载入这一天:%1$@" + } + } + } + }, + "Couldn't load today's history: %1$@" : { + "comment" : "Shown on the Now screen's Today so far card when history couldn't be read. (1: system error description)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تعذّر تحميل سجل اليوم: %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nelze načíst dnešní historii: %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunne ikke indlæse dagens historik: %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Der heutige Verlauf konnte nicht geladen werden: %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se pudo cargar el historial de hoy: %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tämän päivän historiaa ei voitu ladata: %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossible de charger l'historique du jour : %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לא ניתן היה לטעון את היסטוריית היום: %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nem sikerült betölteni a mai előzményeket: %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossibile caricare la cronologia di oggi: %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "今日の履歴を読み込めませんでした:%1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunne ikke laste inn dagens historikk: %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "De geschiedenis van vandaag kan niet worden geladen: %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nie udało się wczytać dzisiejszej historii: %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Não foi possível carregar o histórico de hoje: %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nu s-a putut încărca istoricul de azi: %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не удалось загрузить историю за сегодня: %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dnešná história sa nedá načítať: %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunde inte läsa in dagens historik: %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bugünün geçmişi yüklenemedi: %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не вдалося завантажити історію за сьогодні: %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không thể tải lịch sử hôm nay: %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法载入今天的历史记录:%1$@" + } + } + } + }, + "Couldn't load Trends: %1$@" : { + "comment" : "Trends error message when the history can't be read. (1: error description)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تعذّر تحميل الاتجاهات: %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nelze načíst trendy: %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunne ikke indlæse Tendenser: %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trends konnten nicht geladen werden: %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se pudieron cargar las tendencias: %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trendejä ei voitu ladata: %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossible de charger les tendances : %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לא ניתן היה לטעון מגמות: %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nem sikerült betölteni a trendeket: %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossibile caricare le tendenze: %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "トレンドを読み込めませんでした:%1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunne ikke laste inn Trender: %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trends kunnen niet worden geladen: %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nie udało się wczytać trendów: %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Não foi possível carregar as tendências: %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nu s-au putut încărca tendințele: %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не удалось загрузить тренды: %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trendy sa nedajú načítať: %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kunde inte läsa in Trender: %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eğilimler yüklenemedi: %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не вдалося завантажити тренди: %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không thể tải Xu hướng: %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法载入趋势:%1$@" + } + } + } + }, + "Covers %1$@: Loop keeps 7 days of runs, and closed or open loop is recorded from when this version of Loop first ran." : { + "comment" : "Trends Loop status footnote when the status covers only part of the period. (1: date range covered)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يغطي %1$@: يحتفظ Loop بسجل تشغيلات 7 أيام، وتُسجَّل الحلقة المغلقة أو المفتوحة منذ أول تشغيل لهذا الإصدار من Loop." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pokrývá %1$@: Loop uchovává běhy za 7 dní a uzavřená či otevřená smyčka se zaznamenává od prvního spuštění této verze Loop." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dækker %1$@: Loop gemmer kørsler i 7 dage, og lukket eller åbent Loop registreres fra første gang denne version af Loop kørte." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Umfasst %1$@: Loop speichert Durchläufe 7 Tage lang, und Closed oder Open Loop wird erst ab dem ersten Start dieser Loop-Version aufgezeichnet." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cubre %1$@: Loop guarda 7 días de ejecuciones, y el circuito cerrado o abierto se registra desde la primera vez que se ejecutó esta versión de Loop." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kattaa %1$@: Loop säilyttää 7 päivän ajot, ja suljettu tai avoin säätö tallennetaan siitä lähtien, kun tämä Loopin versio käynnistettiin ensimmäisen kerran." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Couvre %1$@ : Loop conserve 7 jours d'exécutions, et la boucle fermée ou ouverte est enregistrée depuis le premier lancement de cette version de Loop." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מכסה %1$@: Loop שומר 7 ימים של הרצות, ו-Closed Loop או Open Loop נרשמים מאז ההפעלה הראשונה של גרסה זו של Loop." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lefedett időszak: %1$@. A Loop 7 napnyi futást őriz meg, a zárt vagy nyitott hurok pedig a Loop ezen verziójának első futtatásától kerül rögzítésre." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copre %1$@: Loop conserva 7 giorni di esecuzioni e il loop chiuso o aperto è registrato dal primo avvio di questa versione di Loop." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "対象期間 %1$@:Loopは実行記録を7日間保持し、クローズド/オープンループはこのバージョンのLoopを初めて起動した時点から記録されます。" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dekker %1$@: Loop lagrer kjøringer i 7 dager, og lukket eller åpen Loop registreres fra første gang denne versjonen av Loop kjørte." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Omvat %1$@: Loop bewaart 7 dagen aan loop-runs, en actieve of open loop wordt vastgelegd vanaf de eerste keer dat deze versie van Loop draaide." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obejmuje %1$@: Loop przechowuje cykle z 7 dni, a pętla zamknięta lub otwarta jest zapisywana od pierwszego uruchomienia tej wersji Loop." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cobre %1$@: o Loop guarda 7 dias de execuções, e o ciclo fechado ou aberto é registrado desde a primeira vez que esta versão do Loop foi executada." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Acoperă %1$@: Loop păstrează 7 zile de rulări, iar bucla închisă sau deschisă este înregistrată de la prima rulare a acestei versiuni de Loop." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Охвачено: %1$@. Loop хранит запуски за 7 дней, а замкнутый или открытый цикл записывается с первого запуска этой версии Loop." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pokrýva %1$@: Loop uchováva behy za 7 dní a uzavretá či otvorená slučka sa zaznamenáva od prvého spustenia tejto verzie Loop." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Omfattar %1$@: Loop sparar 7 dagars körningar, och sluten eller öppen loop registreras från när den här versionen av Loop först kördes." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kapsanan dönem %1$@: Loop 7 günlük çalışma kaydı tutar; kapalı veya açık döngü, Loop'un bu sürümünün ilk çalıştığı andan itibaren kaydedilir." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Охоплено: %1$@. Loop зберігає запуски за 7 днів, а замкнутий чи відкритий цикл записується з першого запуску цієї версії Loop." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bao gồm %1$@: Loop lưu các lần chạy trong 7 ngày, và trạng thái vòng lặp kín hoặc mở được ghi lại từ khi phiên bản Loop này chạy lần đầu." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "覆盖 %1$@:Loop 保留 7 天的运行记录,闭环或开环状态从此版本 Loop 首次运行时开始记录。" + } + } + } + }, + "CSV, one row per day" : { + "comment" : "Trends export menu item: share a CSV spreadsheet file with one row for each day", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV، صف واحد لكل يوم" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, jeden řádek na den" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, én række pr. dag" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, eine Zeile pro Tag" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, una fila por día" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, yksi rivi päivää kohden" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, une ligne par jour" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, שורה אחת לכל יום" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, naponta egy sor" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, una riga al giorno" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV(1日1行)" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, én rad per dag" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, één rij per dag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, jeden wiersz na dzień" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, uma linha por dia" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, un rând pe zi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, одна строка на день" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, jeden riadok na deň" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, en rad per dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, gün başına bir satır" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, один рядок на день" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV, mỗi ngày một dòng" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "CSV,每天一行" + } + } + } + }, + "Custom" : { + "comment" : "Carb entry absorption choice Other, before it is chosen: you set the food and absorption time yourself", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "مخصص" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vlastní" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brugerdefineret" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eigene" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personalizado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oma" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personnalisé" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מותאם אישית" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Egyéni" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personalizzato" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "カスタム" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Egendefinert" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aangepast" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Własne" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personalizado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personalizat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Своё" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vlastné" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anpassad" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Özel" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Власне" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tùy chỉnh" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自定义" + } + } + } + }, + "Daily pattern" : { + "comment" : "Trends card title: the ambulatory glucose profile, glucose by time of day across the period", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "النمط اليومي" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Denní profil" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Døgnmønster" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tagesprofil" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Patrón diario" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vuorokausiprofiili" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil journalier" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "דפוס יומי" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Napi mintázat" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Andamento giornaliero" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "日内パターン" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Døgnmønster" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dagpatroon" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil dobowy" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Padrão diário" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tipar zilnic" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Суточный профиль" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Denný profil" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dygnsmönster" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Günlük örüntü" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Добовий профіль" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mẫu hằng ngày" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "每日模式" + } + } + } + }, + "Daily pattern over %1$@. Median highest %2$@ at %3$@, lowest %4$@ at %5$@" : { + "comment" : "VoiceOver label for the Trends Daily pattern chart. (1: days with readings, e.g. 14 days; 2: highest median glucose; 3: time of day; 4: lowest median glucose; 5: time of day)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "النمط اليومي خلال %1$@. أعلى وسيط %2$@ عند %3$@، وأدنى وسيط %4$@ عند %5$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Denní profil za %1$@. Nejvyšší medián %2$@ v %3$@, nejnižší %4$@ v %5$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Døgnmønster over %1$@. Median højest %2$@ kl. %3$@, lavest %4$@ kl. %5$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tagesprofil über %1$@. Median am höchsten %2$@ um %3$@, am niedrigsten %4$@ um %5$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Patrón diario de %1$@. Mediana más alta %2$@ a las %3$@, más baja %4$@ a las %5$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vuorokausiprofiili %1$@ ajalta. Korkein mediaani %2$@ klo %3$@, matalin %4$@ klo %5$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil journalier sur %1$@. Médiane la plus haute %2$@ à %3$@, la plus basse %4$@ à %5$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "דפוס יומי במשך %1$@. חציון גבוה ביותר %2$@ ב-%3$@, נמוך ביותר %4$@ ב-%5$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Napi mintázat, %1$@. Legmagasabb medián %2$@ ekkor: %3$@, legalacsonyabb %4$@ ekkor: %5$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Andamento giornaliero su %1$@. Mediana più alta %2$@ alle %3$@, più bassa %4$@ alle %5$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@の日内パターン。中央値の最高は%3$@の%2$@、最低は%5$@の%4$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Døgnmønster over %1$@. Median høyest %2$@ kl. %3$@, lavest %4$@ kl. %5$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dagpatroon over %1$@. Mediaan hoogst %2$@ om %3$@, laagst %4$@ om %5$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil dobowy z %1$@. Najwyższa mediana %2$@ o %3$@, najniższa %4$@ o %5$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Padrão diário em %1$@. Mediana mais alta %2$@ às %3$@, mais baixa %4$@ às %5$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tipar zilnic pe %1$@. Mediana cea mai mare %2$@ la %3$@, cea mai mică %4$@ la %5$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Суточный профиль за %1$@. Наибольшая медиана %2$@ в %3$@, наименьшая %4$@ в %5$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Denný profil za %1$@. Najvyšší medián %2$@ o %3$@, najnižší %4$@ o %5$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dygnsmönster över %1$@. Högsta median %2$@ kl. %3$@, lägsta %4$@ kl. %5$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ boyunca günlük örüntü. En yüksek medyan %3$@ saatinde %2$@, en düşük %5$@ saatinde %4$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Добовий профіль за %1$@. Найвища медіана %2$@ о %3$@, найнижча %4$@ о %5$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mẫu hằng ngày trong %1$@. Trung vị cao nhất %2$@ lúc %3$@, thấp nhất %4$@ lúc %5$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@的每日模式。中位数最高 %2$@(%3$@),最低 %4$@(%5$@)" + } + } + } + }, + "Daily pattern: not enough days" : { + "comment" : "VoiceOver label for the Trends Daily pattern chart when fewer than 5 days have readings", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "النمط اليومي: لا توجد أيام كافية" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Denní profil: málo dní" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Døgnmønster: ikke nok dage" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tagesprofil: nicht genug Tage" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Patrón diario: no hay suficientes días" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vuorokausiprofiili: liian vähän päiviä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil journalier : pas assez de jours" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "דפוס יומי: אין מספיק ימים" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Napi mintázat: nincs elég nap" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Andamento giornaliero: giorni insufficienti" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "日内パターン:日数が不足しています" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Døgnmønster: ikke nok dager" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dagpatroon: niet genoeg dagen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil dobowy: za mało dni" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Padrão diário: dias insuficientes" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tipar zilnic: prea puține zile" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Суточный профиль: недостаточно дней" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Denný profil: málo dní" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dygnsmönster: för få dagar" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Günlük örüntü: yeterli gün yok" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Добовий профіль: замало днів" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mẫu hằng ngày: không đủ số ngày" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "每日模式:天数不足" + } + } + } + }, + "Day" : { + "comment" : "Appearance option and app icon name: the light (daytime) version", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "نهار" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Den" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dag" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tag" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Día" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Päivä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jour" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "יום" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nap" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Giorno" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "日" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dag" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dzień" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dia" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "День" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deň" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gündüz" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "День" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ngày" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "日" + } + } + } + }, + "Day (Trends period)" : { + "comment" : "Trends period picker: a period of one calendar day (not daytime)", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Day" + } + }, + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يوم" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Den" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dag" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tag" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Día" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Päivä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jour" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "יום" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nap" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Giorno" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "日" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dag" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dzień" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dia" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "День" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deň" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gün" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "День" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ngày" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "日" + } + } + } + }, + "Eaten" : { + "comment" : "Carb entry section title: when the food was eaten", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "وقت الأكل" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Snězeno" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spist" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gegessen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comido" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Syöty" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mangé" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "נאכל" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Elfogyasztva" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mangiato" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "食べた時刻" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spist" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gegeten" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zjedzono" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Consumido" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Consumat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Съедено" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zjedené" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ätit" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yenme zamanı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "З'їдено" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Đã ăn" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "进食时间" + } + } + } + }, + "Eaten at %1$@, absorbed by %2$@" : { + "comment" : "Accessibility summary of the carb entry preview chart (1: time eaten, 2: time absorbed)" + }, + "Edit" : { + "comment" : "Swipe action to edit a Logbook carb entry", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تعديل" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Upravit" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rediger" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bearbeiten" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Muokkaa" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modifier" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "ערוך" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Szerkesztés" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modifica" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "編集" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rediger" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wijzig" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Edytuj" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editați" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Изменить" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Upraviť" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Redigera" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Düzenle" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Змінити" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sửa" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑" + } + } + } + }, + "Edit Carb Entry" : { + "comment" : "Title of the screen for editing a carb entry from the Logbook", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تعديل إدخال الكارب" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Upravit záznam sacharidů" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rediger kulhydrater" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "KH-Eintrag bearbeiten" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar entrada de carbohidratos" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Muokkaa hiilihydraatteja" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modifier les glucides" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "עריכת פחמימות" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Szénhidrát szerkesztése" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modifica carboidrati inseriti" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "糖質の記入を編集" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rediger karbohydrater" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Koolhydraatinvoer wijzigen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Edytuj wprowadzone węglowodany" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar carbs" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editați intrarea de carbohidrați" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Редактировать запись углеводов" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Upraviť záznam sacharidov" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Redigera kolhydrater" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Karb Girişini Düzenle" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Редагувати запис вуглеводів" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sửa khai báo Carb" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑碳水记录" + } + } + } + }, + "Eventually %1$@ %2$@" : { + "comment" : "Prediction card on the Now screen: Loop's eventual predicted glucose. (1: glucose value; 2: glucose unit, e.g. mmol/L)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "في النهاية %1$@ %2$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nakonec %1$@ %2$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Med tiden %1$@ %2$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voraussichtlich %1$@ %2$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Al final %1$@ %2$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ennuste %1$@ %2$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Finalement %1$@ %2$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eventually %1$@ %2$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Várható %1$@ %2$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Infine %1$@ %2$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "予想 %1$@ %2$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Omsider %1$@ %2$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uiteindelijk %1$@ %2$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Docelowo %1$@ %2$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "No final %1$@ %2$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "În cele din urmă %1$@ %2$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "В конечном итоге %1$@ %2$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nakoniec %1$@ %2$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Förväntat %1$@ %2$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nihai KŞ %1$@ %2$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Зрештою %1$@ %2$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kết quả %1$@ %2$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "最终 %1$@ %2$@" + } + } + } + }, + "Export" : { + "comment" : "Trends menu title: export the period as a PDF report or CSV file", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تصدير" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportovat" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eksporter" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportieren" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportar" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vie" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exporter" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "ייצוא" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportálás" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esporta" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "書き出し" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eksporter" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exporteer" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eksportuj" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportar" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportați" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Экспорт" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportovať" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportera" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dışa Aktar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Експорт" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Xuất" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导出" + } + } + } + }, + "Fast" : { + "comment" : "Carb entry absorption choice: food that absorbs quickly, such as sweets", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "سريع" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rychle" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hurtig" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schnell" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rápido" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nopea" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rapide" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מהיר" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gyors" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Veloce" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "速い" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rask" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Snel" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Szybko" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rápido" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rapid" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Быстро" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rýchlo" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Snabb" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hızlı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Швидко" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nhanh" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "快" + } + } + } + }, + "From Apple Health: Loop keeps 7 days, so this day has no temp basal or schedule detail." : { + "comment" : "Day page note when the day is older than Loop's 7-day history and was read from Apple Health", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "من Apple Health: يحتفظ Loop بـ 7 أيام فقط، لذا لا توجد تفاصيل Temp Basal أو الجدول لهذا اليوم." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Z aplikace Zdraví: Loop uchovává 7 dní, takže pro tento den chybí podrobnosti o dočasném bazálu a plánu." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fra Apple Sundhed: Loop gemmer 7 dage, så denne dag har ingen detaljer om midlertidig basal eller tidsplan." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aus Apple Health: Loop speichert 7 Tage, daher gibt es für diesen Tag keine Details zu temporären Basalraten oder zum Zeitplan." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "De Apple Salud: Loop guarda 7 días, así que este día no tiene detalle de basal temporal ni de programación." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apple Terveydestä: Loop säilyttää 7 päivää, joten tältä päivältä ei ole tilapäisen basaalin tai ohjelman tietoja." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Depuis Apple Santé : Loop conserve 7 jours, donc ce jour n'a pas de détail de basal temp. ni de programmation." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מ-Apple Health: Loop שומר 7 ימים, ולכן אין ליום זה פרטי Temp Basal או תוכנית." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Az Apple Egészség appból: a Loop 7 napot őriz meg, így ehhez a naphoz nincsenek ideiglenes basal- és ütemezési részletek." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Da Apple Salute: Loop conserva 7 giorni, quindi questo giorno non ha dettagli su basale temporanea o programmazione." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Appleヘルスケアから取得:Loopは7日間のみ保持するため、この日には一時ベーサルやスケジュールの詳細がありません。" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fra Apple Helse: Loop lagrer 7 dager, så denne dagen har ingen detaljer om midlertidig basal eller tidsplan." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uit Apple Health: Loop bewaart 7 dagen, dus deze dag heeft geen details over tijdelijk basaal of het schema." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Z aplikacji Zdrowie: Loop przechowuje 7 dni, więc dla tego dnia brak szczegółów bazy tymczasowej i harmonogramu." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Do app Saúde: o Loop guarda 7 dias, então este dia não tem detalhes de basal temporária nem da programação." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Din Apple Sănătate: Loop păstrează 7 zile, așa că această zi nu are detalii despre bazala temporară sau program." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Из приложения «Здоровье»: Loop хранит 7 дней, поэтому для этого дня нет данных о временном базале и расписании." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Z aplikácie Zdravie: Loop uchováva 7 dní, preto pre tento deň chýbajú podrobnosti o dočasnom bazáli a pláne." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Från Apple Hälsa: Loop sparar 7 dagar, så den här dagen saknar detaljer om temporär basal och schema." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apple Health'ten: Loop 7 gün saklar, bu nedenle bu gün için geçici bazal veya program ayrıntısı yok." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "З програми «Здоровʼя»: Loop зберігає 7 днів, тому для цього дня немає даних про тимчасовий базал і розклад." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Từ Apple Health: Loop chỉ lưu 7 ngày, nên ngày này không có chi tiết Temp Basal hoặc lịch biểu." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "来自 Apple 健康:Loop 只保留 7 天,因此这一天没有临时基础率或预设详情。" + } + } + } + }, + "Gaps" : { + "comment" : "Trends Loop status button and screen title: list of times Loop wasn't in closed loop", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الفجوات" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Výpadky" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Huller" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lücken" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Interrupciones" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Katkot" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Interruptions" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "פערים" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kimaradások" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Interruzioni" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "中断" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hull" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Onderbrekingen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Przerwy" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Interrupções" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Întreruperi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пропуски" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Výpadky" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Luckor" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boşluklar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пропуски" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gián đoạn" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "中断" + } + } + } + }, + "glucose %1$@" : { + "comment" : "Logbook row detail: glucose when the dose was given. (1: glucose value, e.g. 7.5)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "السكر %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "glukóza %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "glukose %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Blutzucker %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "glucosa %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "glukoosi %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "glycémie %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "סוכר %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "glükóz %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "glicemia %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "血糖値 %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "blodsukker %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "glucose %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "glukoza %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "glicose %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "glicemie %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "глюкоза %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "glykémia %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "glukos %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "KŞ %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "глюкоза %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "đường huyết %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "血糖 %1$@" + } + } + } + }, + "glucose %1$@ falling" : { + "comment" : "Logbook row detail: glucose when the dose was given, and falling. (1: glucose value, e.g. 11.0)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "السكر %1$@ ينخفض" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "glukóza %1$@, klesá" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "glukose %1$@ faldende" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Blutzucker %1$@ fallend" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "glucosa %1$@ bajando" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "glukoosi %1$@ laskeva" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "glycémie %1$@ en baisse" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "סוכר %1$@ יורד" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "glükóz %1$@, csökkenő" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "glicemia %1$@ in discesa" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "血糖値 %1$@ 下降中" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "blodsukker %1$@ fallende" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "glucose %1$@ dalend" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "glukoza %1$@, spada" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "glicose %1$@ caindo" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "glicemie %1$@ în scădere" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "глюкоза %1$@, падает" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "glykémia %1$@, klesá" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "glukos %1$@ sjunkande" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "KŞ %1$@ düşüyor" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "глюкоза %1$@, падає" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "đường huyết %1$@ đang giảm" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "血糖 %1$@ 下降中" + } + } + } + }, + "glucose %1$@ rising" : { + "comment" : "Logbook row detail: glucose when the dose was given, and rising. (1: glucose value, e.g. 11.0)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "السكر %1$@ يرتفع" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "glukóza %1$@, stoupá" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "glukose %1$@ stigende" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Blutzucker %1$@ steigend" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "glucosa %1$@ subiendo" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "glukoosi %1$@ nouseva" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "glycémie %1$@ en hausse" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "סוכר %1$@ עולה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "glükóz %1$@, emelkedő" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "glicemia %1$@ in salita" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "血糖値 %1$@ 上昇中" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "blodsukker %1$@ stigende" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "glucose %1$@ stijgend" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "glukoza %1$@, rośnie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "glicose %1$@ subindo" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "glicemie %1$@ în creștere" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "глюкоза %1$@, растёт" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "glykémia %1$@, stúpa" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "glukos %1$@ stigande" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "KŞ %1$@ yükseliyor" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "глюкоза %1$@, зростає" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "đường huyết %1$@ đang tăng" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "血糖 %1$@ 上升中" + } + } + } + }, + "Glucose prediction unavailable" : { + "comment" : "VoiceOver label for the prediction chart when Loop has no prediction", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "توقع السكر غير متاح" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predikce glukózy není k dispozici" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glukoseforudsigelse ikke tilgængelig" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Blutzuckervorhersage nicht verfügbar" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predicción de glucosa no disponible" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glukoosiennuste ei saatavilla" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prédiction de glycémie indisponible" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "תחזית סוכר אינה זמינה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glükózelőrejelzés nem érhető el" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Previsione glicemica non disponibile" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "血糖値の予測はありません" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Blodsukkerprognose ikke tilgjengelig" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glucosevoorspelling niet beschikbaar" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prognoza glukozy niedostępna" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Previsão de glicose indisponível" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predicția glicemiei indisponibilă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прогноз глюкозы недоступен" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predikcia glykémie nie je k dispozícii" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glukosprognos ej tillgänglig" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kan şekeri tahmini yok" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прогноз глюкози недоступний" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không có dự đoán đường huyết" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "血糖预测不可用" + } + } + } + }, + "Glucose too old" : { + "comment" : "Reason Loop could not run: the latest glucose reading was too old", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "قراءة السكر قديمة جدًا" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glukóza je příliš stará" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glukosemåling for gammel" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Blutzuckerwert zu alt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glucosa demasiado antigua" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glukoosi liian vanha" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glycémie trop ancienne" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "קריאת הסוכר ישנה מדי" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Túl régi glükózérték" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glicemia troppo vecchia" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "血糖値が古すぎます" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Blodsukkermåling for gammel" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glucosewaarde te oud" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glukoza zbyt stara" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glicose muito antiga" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glicemie prea veche" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Данные глюкозы устарели" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glykémia je príliš stará" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glukosvärdet är för gammalt" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kan şekeri verisi çok eski" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дані глюкози застаріли" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dữ liệu đường huyết quá cũ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "血糖数据过旧" + } + } + } + }, + "Health" : { + "comment" : "Logbook carb row detail: the entry came from Apple Health", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "Health" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zdraví" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sundhed" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Health" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Salud" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terveys" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Santé" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Health" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Egészség" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Salute" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ヘルスケア" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Helse" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gezondheid" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zdrowie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Saúde" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sănătate" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Здоровье" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zdravie" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hälsa" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Health" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Здоровʼя" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apple Health" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "健康" + } + } + } + }, + "How fast it absorbs" : { + "comment" : "Carb entry section title: choose how quickly the food's carbs absorb", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "سرعة الامتصاص" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jak rychle se vstřebá" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hvor hurtigt det optages" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wie schnell es aufgenommen wird" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Qué tan rápido se absorbe" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kuinka nopeasti imeytyy" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vitesse d'absorption" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "באיזו מהירות נספג" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Milyen gyorsan szívódik fel" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Velocità di assorbimento" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "吸収の速さ" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hvor raskt det tas opp" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hoe snel het wordt opgenomen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jak szybko się wchłania" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Velocidade de absorção" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cât de repede se absoarbe" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Скорость усвоения" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ako rýchlo sa vstrebe" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hur snabbt det tas upp" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ne kadar hızlı emilir" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Швидкість засвоєння" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tốc độ hấp thu" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "吸收速度" + } + } + } + }, + "In range" : { + "comment" : "Time in range band: glucose 70–180 mg/dL (3.9–10.0 mmol/L) Time-in-range band name: glucose 70–180 mg/dL (3.9–10.0 mmol/L)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "ضمن النطاق" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "V rozsahu" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "I området" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Im Zielbereich" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "En rango" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tavoitealueella" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dans la cible" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "בטווח" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tartományban" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nell'intervallo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "範囲内" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "I målområdet" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Binnen bereik" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "W zakresie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "No alvo" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "În interval" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "В диапазоне" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "V rozsahu" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inom målområdet" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aralıkta" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "У діапазоні" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trong khoảng" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "范围内" + } + } + } + }, + "Infusion set changed" : { + "comment" : "Pump event in the history: the infusion set was replaced", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تم تغيير مجموعة التسريب" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Infuzní set vyměněn" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Infusionssæt skiftet" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Infusionsset gewechselt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Equipo de infusión cambiado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Infuusiosetti vaihdettu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cathéter changé" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "סט העירוי הוחלף" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Infúziós szerelék cserélve" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Set di infusione cambiato" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "インフュージョンセット交換" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Infusjonssett byttet" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Infuusset vervangen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wymieniono zestaw infuzyjny" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Conjunto de infusão trocado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Set de infuzie schimbat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Инфузионный набор заменён" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Infúzny set vymenený" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Infusionsset bytt" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İnfüzyon seti değiştirildi" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Інфузійний набір замінено" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Đã thay bộ truyền" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已更换输注管路" + } + } + } + }, + "Insulin isn't being adjusted. Tap for details." : { + "comment" : "Notice detail on the Now screen when Loop hasn't completed recently", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا يتم تعديل الأنسولين. اضغط للتفاصيل." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulín se neupravuje. Klepněte pro podrobnosti." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin bliver ikke justeret. Tryk for detaljer." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin wird nicht angepasst. Für Details tippen." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "La insulina no se está ajustando. Toca para ver detalles." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insuliinia ei säädetä. Napauta nähdäksesi lisätiedot." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "L'insuline n'est pas ajustée. Touchez pour en savoir plus." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "האינסולין אינו מותאם. הקש לפרטים." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Az inzulin nem kerül módosításra. Koppints a részletekért." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "L'insulina non viene regolata. Tocca per i dettagli." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "インスリンが調整されていません。タップして詳細を表示。" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin blir ikke justert. Trykk for detaljer." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insuline wordt niet aangepast. Tik voor details." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina nie jest dostosowywana. Stuknij, aby zobaczyć szczegóły." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "A insulina não está sendo ajustada. Toque para ver detalhes." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina nu este ajustată. Atingeți pentru detalii." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Инсулин не регулируется. Нажмите для подробностей." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulín sa neupravuje. Klepnite pre podrobnosti." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulinet justeras inte. Tryck för detaljer." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İnsülin ayarlanmıyor. Ayrıntılar için dokunun." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Інсулін не регулюється. Торкніться, щоб дізнатися більше." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin không được điều chỉnh. Chạm để xem chi tiết." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "胰岛素未在调整。轻点查看详情。" + } + } + } + }, + "Insulin per day" : { + "comment" : "Trends tile and card title: average total insulin per day", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الأنسولين يوميًا" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulín za den" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin pr. dag" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin pro Tag" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina por día" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insuliini päivässä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insuline par jour" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אינסולין ליום" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulin naponta" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina al giorno" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "1日のインスリン量" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin per dag" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insuline per dag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina dziennie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina por dia" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulină pe zi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Инсулин в день" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulín za deň" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin per dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Günlük insülin" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Інсулін на день" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin mỗi ngày" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "每日胰岛素" + } + } + } + }, + "Insulin per day, average %1$@" : { + "comment" : "VoiceOver label for the Trends Insulin per day chart. (1: average insulin per day, e.g. 32.1 U)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الأنسولين يوميًا، المتوسط %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulín za den, průměr %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin pr. dag, gennemsnit %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin pro Tag, Durchschnitt %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina por día, promedio %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insuliini päivässä, keskiarvo %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insuline par jour, moyenne %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אינסולין ליום, ממוצע %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulin naponta, átlag %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina al giorno, media %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "1日のインスリン量、平均 %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin per dag, gjennomsnitt %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insuline per dag, gemiddeld %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina dziennie, średnio %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina por dia, média %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulină pe zi, medie %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Инсулин в день, в среднем %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulín za deň, priemer %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin per dag, medel %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Günlük insülin, ortalama %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Інсулін на день, у середньому %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin mỗi ngày, trung bình %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "每日胰岛素,平均 %1$@" + } + } + } + }, + "Insulin per day, weekly average, average %1$@" : { + "comment" : "VoiceOver label for the Trends Insulin per day chart when bars are weekly averages. (1: average insulin per day, e.g. 32.1 U)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الأنسولين يوميًا، متوسط أسبوعي، المتوسط %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulín za den, týdenní průměr, průměr %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin pr. dag, ugegennemsnit, gennemsnit %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin pro Tag, Wochendurchschnitt, Durchschnitt %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina por día, promedio semanal, promedio %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insuliini päivässä, viikkokeskiarvo, keskiarvo %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insuline par jour, moyenne hebdomadaire, moyenne %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אינסולין ליום, ממוצע שבועי, ממוצע %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulin naponta, heti átlag, átlag %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina al giorno, media settimanale, media %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "1日のインスリン量、週平均、平均 %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin per dag, ukesgjennomsnitt, gjennomsnitt %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insuline per dag, weekgemiddelde, gemiddeld %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina dziennie, średnia tygodniowa, średnio %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulina por dia, média semanal, média %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulină pe zi, medie săptămânală, medie %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Инсулин в день, среднее за неделю, в среднем %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulín za deň, týždenný priemer, priemer %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin per dag, veckomedel, medel %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Günlük insülin, haftalık ortalama, ortalama %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Інсулін на день, середнє за тиждень, у середньому %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin mỗi ngày, trung bình theo tuần, trung bình %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "每日胰岛素,按周平均,平均 %1$@" + } + } + } + }, + "Insulin timeline, %1$@, bolus %2$@, basal %3$@, carbs %4$@" : { + "comment" : "VoiceOver summary of the day's insulin timeline chart with no glucose readings. (1: weekday and date; 2: bolus total, e.g. 12.5 U; 3: basal total; 4: carbs total, e.g. 140 g)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "مخطط الأنسولين، %1$@، Bolus %2$@، القاعدية %3$@، الكارب %4$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Časová osa inzulínu, %1$@, bolus %2$@, bazál %3$@, sacharidy %4$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulintidslinje, %1$@, bolus %2$@, basal %3$@, kulhydrater %4$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin-Zeitleiste, %1$@, Bolus %2$@, Basal %3$@, KH %4$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cronología de insulina, %1$@, bolo %2$@, basal %3$@, carbohidratos %4$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insuliinin aikajana, %1$@, bolus %2$@, basaali %3$@, hiilihydraatit %4$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chronologie de l'insuline, %1$@, bolus %2$@, basal %3$@, glucides %4$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "ציר זמן אינסולין, %1$@, Bolus %2$@, Basal %3$@, פחמימות %4$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulin idővonal, %1$@, bólus %2$@, basal %3$@, szénhidrát %4$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cronologia insulina, %1$@, bolo %2$@, basale %3$@, carboidrati %4$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "インスリンのタイムライン、%1$@、ボーラス %2$@、ベーサル %3$@、カーボ %4$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulintidslinje, %1$@, bolus %2$@, basal %3$@, karbohydrater %4$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulinetijdlijn, %1$@, bolus %2$@, basaal %3$@, koolhydraten %4$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oś czasu insuliny, %1$@, bolus %2$@, baza %3$@, węglowodany %4$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Linha do tempo de insulina, %1$@, bolus %2$@, basal %3$@, carbs %4$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cronologie insulină, %1$@, bolus %2$@, bazală %3$@, carbohidrați %4$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Хронология инсулина, %1$@, болюс %2$@, базал %3$@, углеводы %4$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Časová os inzulínu, %1$@, bolus %2$@, bazál %3$@, sacharidy %4$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulintidslinje, %1$@, bolus %2$@, basal %3$@, kolhydrater %4$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İnsülin zaman çizelgesi, %1$@, bolus %2$@, bazal %3$@, karb %4$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Хронологія інсуліну, %1$@, болюс %2$@, базал %3$@, вуглеводи %4$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dòng thời gian insulin, %1$@, Bolus %2$@, Basal %3$@, Carbs %4$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "胰岛素时间线,%1$@,大剂量 %2$@,基础 %3$@,碳水 %4$@" + } + } + } + }, + "Insulin timeline, %1$@, bolus %2$@, basal %3$@, carbs %4$@, latest glucose %5$@ %6$@" : { + "comment" : "VoiceOver summary of the day's insulin timeline chart. (1: weekday and date; 2: bolus total, e.g. 12.5 U; 3: basal total; 4: carbs total, e.g. 140 g; 5: latest glucose value; 6: glucose unit, mmol/L or mg/dL)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "مخطط الأنسولين، %1$@، Bolus %2$@، القاعدية %3$@، الكارب %4$@، آخر قراءة سكر %5$@ %6$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Časová osa inzulínu, %1$@, bolus %2$@, bazál %3$@, sacharidy %4$@, poslední glukóza %5$@ %6$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulintidslinje, %1$@, bolus %2$@, basal %3$@, kulhydrater %4$@, seneste glukose %5$@ %6$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulin-Zeitleiste, %1$@, Bolus %2$@, Basal %3$@, KH %4$@, letzter Blutzucker %5$@ %6$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cronología de insulina, %1$@, bolo %2$@, basal %3$@, carbohidratos %4$@, última glucosa %5$@ %6$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insuliinin aikajana, %1$@, bolus %2$@, basaali %3$@, hiilihydraatit %4$@, viimeisin glukoosi %5$@ %6$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chronologie de l'insuline, %1$@, bolus %2$@, basal %3$@, glucides %4$@, dernière glycémie %5$@ %6$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "ציר זמן אינסולין, %1$@, Bolus %2$@, Basal %3$@, פחמימות %4$@, סוכר אחרון %5$@ %6$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulin idővonal, %1$@, bólus %2$@, basal %3$@, szénhidrát %4$@, legutóbbi glükóz %5$@ %6$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cronologia insulina, %1$@, bolo %2$@, basale %3$@, carboidrati %4$@, ultima glicemia %5$@ %6$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "インスリンのタイムライン、%1$@、ボーラス %2$@、ベーサル %3$@、カーボ %4$@、最新の血糖値 %5$@ %6$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulintidslinje, %1$@, bolus %2$@, basal %3$@, karbohydrater %4$@, siste blodsukker %5$@ %6$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulinetijdlijn, %1$@, bolus %2$@, basaal %3$@, koolhydraten %4$@, laatste glucose %5$@ %6$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oś czasu insuliny, %1$@, bolus %2$@, baza %3$@, węglowodany %4$@, ostatnia glukoza %5$@ %6$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Linha do tempo de insulina, %1$@, bolus %2$@, basal %3$@, carbs %4$@, última glicose %5$@ %6$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cronologie insulină, %1$@, bolus %2$@, bazală %3$@, carbohidrați %4$@, ultima glicemie %5$@ %6$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Хронология инсулина, %1$@, болюс %2$@, базал %3$@, углеводы %4$@, последняя глюкоза %5$@ %6$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Časová os inzulínu, %1$@, bolus %2$@, bazál %3$@, sacharidy %4$@, posledná glykémia %5$@ %6$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insulintidslinje, %1$@, bolus %2$@, basal %3$@, kolhydrater %4$@, senaste glukos %5$@ %6$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İnsülin zaman çizelgesi, %1$@, bolus %2$@, bazal %3$@, karb %4$@, son KŞ %5$@ %6$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Хронологія інсуліну, %1$@, болюс %2$@, базал %3$@, вуглеводи %4$@, остання глюкоза %5$@ %6$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dòng thời gian insulin, %1$@, Bolus %2$@, Basal %3$@, Carbs %4$@, đường huyết mới nhất %5$@ %6$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "胰岛素时间线,%1$@,大剂量 %2$@,基础 %3$@,碳水 %4$@,最新血糖 %5$@ %6$@" + } + } + } + }, + "Invalid future glucose" : { + "comment" : "Reason Loop could not run: a glucose reading had a timestamp in the future", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "قراءة سكر مستقبلية غير صالحة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glukóza s budoucím časem" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ugyldig glukosemåling i fremtiden" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ungültiger Blutzuckerwert in der Zukunft" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glucosa futura no válida" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Virheellinen tulevaisuuden glukoosiarvo" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glycémie future non valide" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "קריאת סוכר עתידית לא תקינה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Érvénytelen, jövőbeli glükózérték" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glicemia futura non valida" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "未来時刻の無効な血糖値" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ugyldig blodsukkermåling i fremtiden" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ongeldige glucosewaarde in de toekomst" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glukoza z przyszłą datą" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glicose futura inválida" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glicemie invalidă, din viitor" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Глюкоза с временем в будущем" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glykémia s budúcim časom" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ogiltigt glukosvärde i framtiden" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geçersiz, ileri tarihli KŞ" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Глюкоза з часом у майбутньому" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Đường huyết có thời gian trong tương lai không hợp lệ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无效的未来血糖" + } + } + } + }, + "IOB" : { + "comment" : "Abbreviation for insulin on board (active insulin) Abbreviation for insulin on board; label of a tile on the Now screen", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "IA" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB" + } + } + } + }, + "IOB (U, left)" : { + "comment" : "Chart legend: insulin on board in units, on the left axis", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (وحدة، يسار)" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (U, vlevo)" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (E, venstre)" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (IE, links)" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (U, izquierda)" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (U, vasen)" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "IA (U, gauche)" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (U, שמאל)" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (U, bal)" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (U, sinistra)" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB(U、左)" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (E, venstre)" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (E, links)" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (J, lewa oś)" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (U, esquerda)" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (U, stânga)" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (Ед, слева)" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (j, vľavo)" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (E, vänster)" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (Ü, sol)" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (U, ліворуч)" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB (U, bên trái)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "IOB(U,左轴)" + } + } + } + }, + "Last reading %1$@ ago" : { + "comment" : "Shown on the Now screen under No reading when the latest CGM reading is too old. (1: duration, e.g. 25m)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "آخر قراءة قبل %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Poslední hodnota před %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seneste måling for %1$@ siden" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Letzter Wert vor %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Última lectura hace %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Viimeisin lukema %1$@ sitten" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dernière mesure il y a %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "קריאה אחרונה לפני %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Utolsó érték %1$@ ezelőtt" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ultima lettura %1$@ fa" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "最終測定 %1$@前" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Siste måling for %1$@ siden" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Laatste waarde %1$@ geleden" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ostatni odczyt %1$@ temu" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Última leitura há %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ultima citire acum %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Последнее показание %1$@ назад" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Posledná hodnota pred %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Senaste värde för %1$@ sedan" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Son okuma %1$@ önce" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Останній показник %1$@ тому" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chỉ số cuối cách đây %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "最后读数在 %1$@前" + } + } + } + }, + "Limited" : { + "comment" : "Trends Loop status: time closed loop was on but Loop wasn't completing runs", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "محدود" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Omezeno" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Begrænset" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eingeschränkt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limitado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rajoitettu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limitée" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מוגבל" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Korlátozott" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limitato" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "制限あり" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Begrenset" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Beperkt" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ograniczona" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limitado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limitat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ограничен" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obmedzená" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Begränsad" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sınırlı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обмежено" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hạn chế" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "受限" + } + } + } + }, + "Logbook" : { + "comment" : "Title of the Logbook tab Title of the Logbook tab in the redesigned interface, listing past events", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "السجل" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deník" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Logbog" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Logbuch" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Registro" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lokikirja" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Journal" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "יומן" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Napló" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diario" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ログブック" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loggbok" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Logboek" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dziennik" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diário" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jurnal" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Журнал" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Denník" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loggbok" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kayıt Defteri" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Журнал" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nhật ký" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "日志" + } + } + } + }, + "logged" : { + "comment" : "Logbook bolus row detail: the dose was logged by hand rather than delivered by the pump", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "مُسجَّل يدويًا" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "zadáno ručně" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "registreret manuelt" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "eingetragen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "registrado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "kirjattu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "saisi" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "נרשם ידנית" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "naplózva" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "registrato" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "手動記録" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "registrert manuelt" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "handmatig vastgelegd" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "wpisany ręcznie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "registrado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "înregistrat manual" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "внесено вручную" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "zadané ručne" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "loggad" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "elle kaydedildi" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "внесено вручну" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "ghi tay" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "手动记录" + } + } + } + }, + "Loop" : { + "comment" : "Logbook filter chip: show only what Loop did automatically (Loop is the app's name) Logbook row detail: Loop made this change automatically (Loop is the app's name)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop" + } + } + } + }, + "Loop active" : { + "comment" : "Trends summary tile: share of the period Loop spent in closed loop", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop نشط" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop aktivní" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop aktiv" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop aktiv" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop activo" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop aktiivinen" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop actif" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop פעיל" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop aktív" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop attivo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop稼働" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop aktiv" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop actief" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop aktywny" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop ativo" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop activ" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop активен" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop aktívny" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop aktiv" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop aktif" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop активний" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop hoạt động" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop 运行" + } + } + } + }, + "Loop can't adjust insulin without readings." : { + "comment" : "Notice detail on the Now screen under a CGM problem when there is no recent glucose reading", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا يستطيع Loop تعديل الأنسولين بدون قراءات." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop nemůže upravovat inzulín bez hodnot." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop kan ikke justere insulin uden målinger." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop kann das Insulin ohne Messwerte nicht anpassen." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop no puede ajustar la insulina sin lecturas." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop ei voi säätää insuliinia ilman lukemia." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop ne peut pas ajuster l'insuline sans mesures." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop אינו יכול להתאים אינסולין ללא קריאות." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "A Loop értékek nélkül nem tudja módosítani az inzulint." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop non può regolare l'insulina senza letture." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "測定値がないとLoopはインスリンを調整できません。" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop kan ikke justere insulin uten målinger." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop kan de insuline niet aanpassen zonder glucosewaarden." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop nie może dostosowywać insuliny bez odczytów." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O Loop não consegue ajustar a insulina sem leituras." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop nu poate ajusta insulina fără citiri." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop не может регулировать инсулин без показаний." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop nemôže upravovať inzulín bez hodnôt." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop kan inte justera insulinet utan glukosvärden." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop okuma olmadan insülini ayarlayamaz." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop не може регулювати інсулін без показників." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop không thể điều chỉnh insulin khi không có chỉ số." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "没有读数,Loop 无法调整胰岛素。" + } + } + } + }, + "Loop didn't run" : { + "comment" : "Reason shown for a stretch of time when Loop did not complete a run and recorded no error", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لم يعمل Loop" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop neproběhl" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop kørte ikke" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop ist nicht gelaufen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop no se ejecutó" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop ei ajanut" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop ne s'est pas exécuté" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop לא רץ" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "A Loop nem futott" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop non eseguito" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopが実行されませんでした" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop kjørte ikke" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop heeft niet gedraaid" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop nie działał" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O Loop não executou" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop nu a rulat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop не запускался" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop neprebehol" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop kördes inte" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop çalışmadı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop не запускався" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop không chạy" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop 未运行" + } + } + } + }, + "Loop failed · %1$@" : { + "comment" : "Status pill on the Now screen: Loop hasn't completed for over 15 minutes. (1: duration since Loop last ran, e.g. 16m)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "فشل Loop · %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop selhal · %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop fejlede · %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop fehlgeschlagen · %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop falló · %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop epäonnistui · %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Échec de Loop · %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop נכשל · %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop sikertelen · %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop non riuscito · %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop失敗 · %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop feilet · %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop mislukt · %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Błąd Loop · %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop falhou · %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop eșuat · %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сбой Loop · %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop zlyhal · %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop misslyckades · %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop başarısız · %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Збій Loop · %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop lỗi · %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop 失败 · %1$@" + } + } + } + }, + "Loop hasn't run for %1$@" : { + "comment" : "Notice title on the Now screen when Loop hasn't completed recently. (1: duration since Loop last ran, e.g. 16m)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لم يعمل Loop منذ %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop neproběhl %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop har ikke kørt i %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop ist seit %1$@ nicht gelaufen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop no se ha ejecutado en %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopia ei ole ajettu %1$@ aikana" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop ne s'est pas exécuté depuis %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop לא רץ כבר %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "A Loop %1$@ óta nem futott" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop non eseguito da %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopが%1$@実行されていません" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop har ikke kjørt på %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop heeft %1$@ niet gedraaid" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop nie działa od %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O Loop não executa há %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop nu a rulat de %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop не запускался уже %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop neprebehol %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop har inte körts på %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop %1$@ süredir çalışmadı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop не запускався вже %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop đã không chạy trong %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop 已 %1$@未运行" + } + } + } + }, + "Loop keeps the last 7 days on this phone. Older doses and carbs are in Apple Health and on Trends." : { + "comment" : "Logbook footer explaining why only 7 days are listed", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يحتفظ Loop بآخر 7 أيام على هذا الهاتف. الجرعات والكارب الأقدم موجودة في Apple Health وفي الاتجاهات." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop uchovává posledních 7 dní v tomto telefonu. Starší dávky a sacharidy najdete v aplikaci Zdraví a v Trendech." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop gemmer de seneste 7 dage på denne telefon. Ældre doser og kulhydrater findes i Apple Sundhed og under Tendenser." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop speichert die letzten 7 Tage auf diesem iPhone. Ältere Dosen und KH findest Du in Apple Health und unter Trends." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop guarda los últimos 7 días en este teléfono. Las dosis y carbohidratos anteriores están en Apple Salud y en Tendencias." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop säilyttää viimeiset 7 päivää tässä puhelimessa. Vanhemmat annokset ja hiilihydraatit ovat Apple Terveydessä ja Trendeissä." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop conserve les 7 derniers jours sur ce téléphone. Les doses et glucides plus anciens sont dans Apple Santé et dans Tendances." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop שומר את 7 הימים האחרונים בטלפון זה. מנות ופחמימות ישנות יותר נמצאות ב-Apple Health ובמגמות." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "A Loop az utolsó 7 napot őrzi meg ezen a telefonon. A régebbi adagok és szénhidrátok az Apple Egészség appban és a Trendekben találhatók." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop conserva gli ultimi 7 giorni su questo telefono. Dosi e carboidrati precedenti sono in Apple Salute e in Tendenze." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopはこの端末に直近7日間を保持します。それより前の投与とカーボはAppleヘルスケアとトレンドにあります。" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop lagrer de siste 7 dagene på denne telefonen. Eldre doser og karbohydrater finner du i Apple Helse og under Trender." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop bewaart de laatste 7 dagen op deze telefoon. Oudere doses en koolhydraten vind je in Apple Health en bij Trends." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop przechowuje ostatnie 7 dni na tym telefonie. Starsze dawki i węglowodany są w aplikacji Zdrowie i w Trendach." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O Loop guarda os últimos 7 dias neste iPhone. Doses e carbs mais antigos estão no app Saúde e em Tendências." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop păstrează ultimele 7 zile pe acest telefon. Dozele și carbohidrații mai vechi sunt în Apple Sănătate și în Tendințe." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop хранит последние 7 дней на этом телефоне. Более ранние дозы и углеводы — в приложении «Здоровье» и в разделе «Тренды»." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop uchováva posledných 7 dní v tomto telefóne. Staršie dávky a sacharidy nájdete v aplikácii Zdravie a v Trendoch." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop sparar de senaste 7 dagarna på den här telefonen. Äldre doser och kolhydrater finns i Apple Hälsa och under Trender." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop son 7 günü bu telefonda saklar. Daha eski dozlar ve karbonhidratlar Apple Health'te ve Eğilimler'dedir." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop зберігає останні 7 днів на цьому телефоні. Старіші дози та вуглеводи є в програмі «Здоровʼя» і в розділі «Тренди»." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop lưu 7 ngày gần nhất trên điện thoại này. Các liều và Carbs cũ hơn có trong Apple Health và trong Xu hướng." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop 在此手机上保留最近 7 天的数据。更早的剂量和碳水可在 Apple 健康和“趋势”中查看。" + } + } + } + }, + "Loop may not be giving insulin." : { + "comment" : "Notice detail on the Now screen under a critical pump problem reported by the pump", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "قد لا يقوم Loop بإعطاء الأنسولين." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop možná nepodává inzulín." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop giver muligvis ikke insulin." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop gibt möglicherweise kein Insulin ab." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Es posible que Loop no esté administrando insulina." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop ei ehkä anna insuliinia." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop n'administre peut-être pas d'insuline." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "ייתכן ש-Loop אינו מספק אינסולין." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lehet, hogy a Loop nem ad inzulint." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop potrebbe non erogare insulina." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopがインスリンを注入していない可能性があります。" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop gir kanskje ikke insulin." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop geeft mogelijk geen insuline." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop może nie podawać insuliny." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O Loop pode não estar entregando insulina." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Este posibil ca Loop să nu administreze insulină." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Возможно, Loop не подаёт инсулин." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop možno nepodáva inzulín." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop kanske inte ger insulin." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop insülin vermiyor olabilir." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Можливо, Loop не подає інсулін." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop có thể không đang tiêm insulin." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop 可能未在输注胰岛素。" + } + } + } + }, + "Loop only works out active insulin and carbs for the days it keeps" : { + "comment" : "Day page placeholder for active insulin and carbs on a day older than Loop's 7-day history", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يحسب Loop الأنسولين والكارب النشطين فقط للأيام التي يحتفظ بها" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop počítá aktivní inzulín a sacharidy jen pro dny, které uchovává" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop beregner kun aktivt insulin og kulhydrater for de dage, den gemmer" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop berechnet aktives Insulin und aktive KH nur für die gespeicherten Tage" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop solo calcula la insulina y los carbohidratos activos de los días que guarda" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop laskee aktiivisen insuliinin ja hiilihydraatit vain säilyttämilleen päiville" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop calcule l'insuline et les glucides actifs uniquement pour les jours qu'il conserve" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop מחשב אינסולין ופחמימות פעילים רק עבור הימים שהוא שומר" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "A Loop csak a megőrzött napokra számolja ki az aktív inzulint és szénhidrátot" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop calcola insulina e carboidrati attivi solo per i giorni che conserva" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopが残存インスリンと残存糖質を計算するのは、保持している日のみです" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop beregner bare aktivt insulin og karbohydrater for dagene den lagrer" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop berekent actieve insuline en koolhydraten alleen voor de dagen die het bewaart" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop oblicza aktywną insulinę i węglowodany tylko dla przechowywanych dni" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O Loop só calcula a insulina e os carboidratos ativos dos dias que guarda" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop calculează insulina și carbohidrații activi doar pentru zilele pe care le păstrează" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop рассчитывает активный инсулин и углеводы только за хранимые дни" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop počíta aktívny inzulín a sacharidy len pre dni, ktoré uchováva" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop beräknar bara aktivt insulin och kolhydrater för de dagar som sparas" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop aktif insülini ve karbı yalnızca sakladığı günler için hesaplar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop розраховує активний інсулін і вуглеводи лише за дні, які зберігає" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop chỉ tính insulin và Carbs còn hoạt động cho những ngày nó lưu" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop 只为其保留的日期计算活性胰岛素和碳水" + } + } + } + }, + "Loop prediction" : { + "comment" : "Chart legend on the Now screen: dashed line is Loop's predicted glucose", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "توقع Loop" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predikce Loop" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-forudsigelse" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-Vorhersage" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predicción de Loop" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopin ennuste" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prédiction de Loop" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "תחזית Loop" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-előrejelzés" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Previsione di Loop" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopの予測" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-prognose" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-voorspelling" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prognoza Loop" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Previsão do Loop" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predicție Loop" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прогноз Loop" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predikcia Loop" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loops prognos" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop tahmini" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прогноз Loop" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dự đoán của Loop" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop 预测" + } + } + } + }, + "Loop predicts %1$@ %2$@ by %3$@" : { + "comment" : "VoiceOver summary of the prediction chart. (1: predicted glucose value; 2: glucose unit, mmol/L or mg/dL; 3: time of the prediction)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يتوقع Loop %1$@ %2$@ بحلول %3$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop predikuje %1$@ %2$@ do %3$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop forudsiger %1$@ %2$@ kl. %3$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop sagt %1$@ %2$@ bis %3$@ voraus" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop predice %1$@ %2$@ a las %3$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop ennustaa %1$@ %2$@ klo %3$@ mennessä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop prévoit %1$@ %2$@ à %3$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop צופה %1$@ %2$@ עד %3$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "A Loop előrejelzése %3$@ időpontra: %1$@ %2$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop prevede %1$@ %2$@ alle %3$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopの予測:%3$@までに%1$@ %2$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop forventer %1$@ %2$@ kl. %3$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop voorspelt %1$@ %2$@ om %3$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop prognozuje %1$@ %2$@ do %3$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O Loop prevê %1$@ %2$@ às %3$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop prezice %1$@ %2$@ până la %3$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop прогнозирует %1$@ %2$@ к %3$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop predikuje %1$@ %2$@ do %3$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop förutspår %1$@ %2$@ kl. %3$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop %3$@ itibarıyla %1$@ %2$@ tahmin ediyor" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop прогнозує %1$@ %2$@ до %3$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop dự đoán %1$@ %2$@ vào lúc %3$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop 预测到 %3$@ 时为 %1$@ %2$@" + } + } + } + }, + "Loop status" : { + "comment" : "Trends card title: how long Loop spent in closed loop, limited or open loop", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "حالة Loop" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stav Loop" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-status" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-Status" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Estado de Loop" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopin tila" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "État de Loop" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מצב Loop" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop állapota" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stato di Loop" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopの状態" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-status" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopstatus" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stan Loop" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Status do Loop" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Starea Loop" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Статус Loop" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stav Loop" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopstatus" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop durumu" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Стан Loop" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trạng thái Loop" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop 状态" + } + } + } + }, + "Loop status is recorded from when this version of Loop first ran." : { + "comment" : "Trends Loop status footnote when there is no Loop status history yet", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تُسجَّل حالة Loop منذ أول تشغيل لهذا الإصدار من Loop." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stav Loop se zaznamenává od prvního spuštění této verze Loop." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-status registreres fra første gang denne version af Loop kørte." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Der Loop-Status wird ab dem ersten Start dieser Loop-Version aufgezeichnet." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "El estado de Loop se registra desde la primera vez que se ejecutó esta versión de Loop." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopin tila tallennetaan siitä lähtien, kun tämä Loopin versio käynnistettiin ensimmäisen kerran." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "L'état de Loop est enregistré depuis le premier lancement de cette version de Loop." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מצב Loop נרשם מאז ההפעלה הראשונה של גרסה זו של Loop." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "A Loop állapota a Loop ezen verziójának első futtatásától kerül rögzítésre." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lo stato di Loop è registrato dal primo avvio di questa versione di Loop." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopの状態は、このバージョンのLoopを初めて起動した時点から記録されます。" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-status registreres fra første gang denne versjonen av Loop kjørte." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "De loopstatus wordt vastgelegd vanaf de eerste keer dat deze versie van Loop draaide." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stan Loop jest zapisywany od pierwszego uruchomienia tej wersji Loop." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O status do Loop é registrado desde a primeira vez que esta versão do Loop foi executada." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Starea Loop este înregistrată de la prima rulare a acestei versiuni de Loop." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Статус Loop записывается с первого запуска этой версии Loop." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stav Loop sa zaznamenáva od prvého spustenia tejto verzie Loop." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopstatus registreras från när den här versionen av Loop först kördes." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop durumu, Loop'un bu sürümünün ilk çalıştığı andan itibaren kaydedilir." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Стан Loop записується з першого запуску цієї версії Loop." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trạng thái Loop được ghi lại từ khi phiên bản Loop này chạy lần đầu." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop 状态从此版本 Loop 首次运行时开始记录。" + } + } + } + }, + "Loop trends" : { + "comment" : "Title of the exported Trends PDF report", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "اتجاهات Loop" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trendy Loop" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-tendenser" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-Trends" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendencias de Loop" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-trendit" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendances Loop" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מגמות Loop" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-trendek" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendenze Loop" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopトレンド" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-trender" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-trends" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trendy Loop" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendências do Loop" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendințe Loop" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тренды Loop" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trendy Loop" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-trender" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop eğilimleri" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тренди Loop" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Xu hướng Loop" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop 趋势" + } + } + } + }, + "Loop trends %1$@" : { + "comment" : "File name of an exported Trends report or CSV, without the extension. (1: date range of the period)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "اتجاهات Loop %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trendy Loop %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-tendenser %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-Trends %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendencias de Loop %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-trendit %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendances Loop %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מגמות Loop %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-trendek %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendenze Loop %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopトレンド %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-trender %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-trends %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trendy Loop %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendências do Loop %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendințe Loop %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тренды Loop %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trendy Loop %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop-trender %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop eğilimleri %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тренди Loop %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Xu hướng Loop %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop 趋势 %1$@" + } + } + } + }, + "Loop · %1$@ · tap to list" : { + "comment" : "Logbook folded automatic boluses row subtitle: Loop gave them, the time span they cover, and tapping lists them. (1: time range, e.g. 15:08 – 16:14)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · اضغط للعرض" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · klepnutím zobrazíte" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · tryk for at se listen" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · zum Auflisten tippen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · toca para ver la lista" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · napauta nähdäksesi" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · touchez pour la liste" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · הקש להצגה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · koppintson a listához" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · tocca per l'elenco" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · タップして一覧" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · trykk for å se listen" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · tik voor lijst" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · stuknij, aby wyświetlić" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · toque para ver a lista" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · atingeți pentru listă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · нажмите для списка" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · ťuknutím zobrazíte" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · tryck för att visa" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · listelemek için dokunun" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · торкніться для списку" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · chạm để xem" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %1$@ · 轻点查看列表" + } + } + } + }, + "Loop · %lld changes · tap to list" : { + "comment" : "Logbook basal adjustments row subtitle: how many temp basals Loop set in the day; tapping lists them. (1: number of changes)", + "localizations" : { + "en" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld change · tap to list" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld changes · tap to list" + } + } + } + } + }, + "ar" : { + "variations" : { + "plural" : { + "zero" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld تغيير · اضغط للعرض" + } + }, + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld تغيير · اضغط للعرض" + } + }, + "two" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld تغيير · اضغط للعرض" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld تغييرات · اضغط للعرض" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld تغييرًا · اضغط للعرض" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld تغيير · اضغط للعرض" + } + } + } + } + }, + "cs" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld změna · klepněte pro seznam" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld změny · klepněte pro seznam" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld změny · klepněte pro seznam" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld změn · klepněte pro seznam" + } + } + } + } + }, + "da" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld ændring · tryk for liste" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld ændringer · tryk for liste" + } + } + } + } + }, + "de" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld Änderung · tippen für Liste" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld Änderungen · tippen für Liste" + } + } + } + } + }, + "es" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld cambio · toca para ver" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld cambios · toca para ver" + } + } + } + } + }, + "fi" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld muutos · näytä napauttamalla" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld muutosta · näytä napauttamalla" + } + } + } + } + }, + "fr" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld modification · touchez pour la liste" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld modifications · touchez pour la liste" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld modifications · touchez pour la liste" + } + } + } + } + }, + "he" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld שינוי · הקש לרשימה" + } + }, + "two" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld שינויים · הקש לרשימה" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld שינויים · הקש לרשימה" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld שינויים · הקש לרשימה" + } + } + } + } + }, + "hu" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld módosítás · koppints a listához" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld módosítás · koppints a listához" + } + } + } + } + }, + "it" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld modifica · tocca per l'elenco" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld modifiche · tocca per l'elenco" + } + } + } + } + }, + "ja" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld件の変更 · タップで一覧表示" + } + } + } + } + }, + "nb" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld endring · trykk for liste" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld endringer · trykk for liste" + } + } + } + } + }, + "nl" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld wijziging · tik voor lijst" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld wijzigingen · tik voor lijst" + } + } + } + } + }, + "pl" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld zmiana · stuknij, aby wyświetlić" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld zmiany · stuknij, aby wyświetlić" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld zmian · stuknij, aby wyświetlić" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld zmiany · stuknij, aby wyświetlić" + } + } + } + } + }, + "pt-BR" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld alteração · toque para listar" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld alterações · toque para listar" + } + } + } + } + }, + "ro" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld modificare · atingeți pentru listă" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld modificări · atingeți pentru listă" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld de modificări · atingeți pentru listă" + } + } + } + } + }, + "ru" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld изменение · нажмите для списка" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld изменения · нажмите для списка" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld изменений · нажмите для списка" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld изменения · нажмите для списка" + } + } + } + } + }, + "sk" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld zmena · klepnite pre zoznam" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld zmeny · klepnite pre zoznam" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld zmeny · klepnite pre zoznam" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld zmien · klepnite pre zoznam" + } + } + } + } + }, + "sv" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld ändring · tryck för lista" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld ändringar · tryck för lista" + } + } + } + } + }, + "tr" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld değişiklik · listelemek için dokunun" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld değişiklik · listelemek için dokunun" + } + } + } + } + }, + "uk" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld зміна · торкніться для списку" + } + }, + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld зміни · торкніться для списку" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld змін · торкніться для списку" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld зміни · торкніться для списку" + } + } + } + } + }, + "vi" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld thay đổi · chạm để xem danh sách" + } + } + } + } + }, + "zh-Hans" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop · %lld次更改 · 轻点查看列表" + } + } + } + } + } + } + }, + "Manual %1$@" : { + "comment" : "Basal and bolus: bolus the person gave. (1: units, e.g. 16.2 U)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يدوي %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ručně %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuel %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuell %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manual %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuaalinen %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuel %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "ידני %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kézi %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuale %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "手動 %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuell %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Handmatig %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ręcznie %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manual %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manual %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вручную %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ručne %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuell %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuel %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вручну %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thủ công %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "手动 %1$@" + } + } + } + }, + "Manual · %1$@" : { + "comment" : "Logbook temp basal row subtitle: set by hand, then the rate and how long. (1: description, e.g. 0.50 U/h for 30m (scheduled 0.80))", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يدوي · %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ručně · %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuel · %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuell · %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manual · %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuaalinen · %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuel · %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "ידני · %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kézi · %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuale · %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "手動 · %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuell · %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Handmatig · %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ręcznie · %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manual · %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manual · %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вручную · %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ručne · %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuell · %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuel · %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вручну · %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thủ công · %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "手动 · %1$@" + } + } + } + }, + "Median" : { + "comment" : "Trends Daily pattern legend: the median glucose line", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الوسيط" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Medián" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Median" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Median" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mediana" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mediaani" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Médiane" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "חציון" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Medián" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mediana" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "中央値" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Median" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mediaan" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mediana" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mediana" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mediană" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Медиана" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Medián" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Median" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Medyan" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Медіана" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trung vị" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "中位数" + } + } + } + }, + "Medium" : { + "comment" : "Carb entry absorption choice: a typical mixed meal", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "متوسط" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Středně" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Middel" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mittel" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Medio" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keskinopea" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Moyen" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "בינוני" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Közepes" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Medio" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "普通" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Middels" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gemiddeld" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Średnio" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Médio" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mediu" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Средне" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stredne" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Medel" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Orta" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Середньо" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trung bình" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "中" + } + } + } + }, + "Missing data error" : { + "comment" : "Reason Loop could not run: data it needs was missing", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "خطأ بيانات مفقودة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chyba chybějících dat" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fejl: manglende data" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fehler: fehlende Daten" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error de datos faltantes" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tietoja puuttuu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erreur de données manquantes" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "שגיאת נתונים חסרים" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hiányzó adat" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Errore dati mancanti" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "データ欠落エラー" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Feil: manglende data" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fout: ontbrekende gegevens" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Błąd brakujących danych" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erro de dados ausentes" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eroare: date lipsă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ошибка: нет данных" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chyba chýbajúcich údajov" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Data saknas" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eksik veri hatası" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Помилка: немає даних" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lỗi thiếu dữ liệu" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "数据缺失错误" + } + } + } + }, + "Needs 10 days of CGM" : { + "comment" : "Trends GMI tile detail when fewer than 10 days of CGM data are available", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يحتاج إلى 10 أيام من CGM" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vyžaduje 10 dní CGM" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kræver 10 dages CGM" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Benötigt 10 Tage CGM" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Requiere 10 días de CGM" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vaatii 10 päivää CGM-dataa" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nécessite 10 jours de CGM" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "נדרשים 10 ימי CGM" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "10 nap CGM-adat szükséges" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Servono 10 giorni di CGM" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGMデータが10日分必要" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Krever 10 dager med CGM" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vereist 10 dagen CGM" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wymaga 10 dni CGM" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Requer 10 dias de CGM" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Necesită 10 zile de CGM" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нужно 10 дней CGM" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vyžaduje 10 dní CGM" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kräver 10 dagars CGM" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "10 günlük CGM gerekir" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Потрібно 10 днів CGM" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cần 10 ngày dữ liệu CGM" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "需要 10 天 CGM 数据" + } + } + } + }, + "New Interface" : { + "comment" : "Title of the toggle for the redesigned interface", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الواجهة الجديدة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nové rozhraní" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ny brugerflade" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neue Oberfläche" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nueva interfaz" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uusi käyttöliittymä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nouvelle interface" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "ממשק חדש" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Új felület" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nuova interfaccia" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "新しいインターフェイス" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nytt grensesnitt" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nieuwe interface" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nowy interfejs" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nova interface" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Interfață nouă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Новый интерфейс" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nové rozhranie" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nytt gränssnitt" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yeni Arayüz" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Новий інтерфейс" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Giao diện mới" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "新界面" + } + } + } + }, + "Next 3 hours" : { + "comment" : "Title of the prediction chart card on the Now screen", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الساعات الثلاث القادمة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Příští 3 hodiny" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Næste 3 timer" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nächste 3 Stunden" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Próximas 3 horas" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seuraavat 3 tuntia" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "3 prochaines heures" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "3 השעות הבאות" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Következő 3 óra" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prossime 3 ore" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "今後3時間" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neste 3 timer" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Komende 3 uur" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Następne 3 godziny" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Próximas 3 horas" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Următoarele 3 ore" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ближайшие 3 часа" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nasledujúce 3 hodiny" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kommande 3 timmar" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sonraki 3 saat" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Наступні 3 години" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "3 giờ tới" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未来 3 小时" + } + } + } + }, + "Next day" : { + "comment" : "Accessibility label for the day page button that steps forward a day", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "اليوم التالي" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Další den" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Næste dag" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nächster Tag" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Día siguiente" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seuraava päivä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jour suivant" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "היום הבא" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Következő nap" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Giorno successivo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "翌日" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neste dag" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Volgende dag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Następny dzień" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Próximo dia" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ziua următoare" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Следующий день" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ďalší deň" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nästa dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sonraki gün" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Наступний день" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ngày sau" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "后一天" + } + } + } + }, + "Next period" : { + "comment" : "VoiceOver label for the Trends button that moves forward one period", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الفترة التالية" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Další období" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Næste periode" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nächster Zeitraum" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Período siguiente" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seuraava jakso" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Période suivante" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "התקופה הבאה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Következő időszak" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Periodo successivo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "次の期間" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neste periode" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Volgende periode" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Następny okres" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Próximo período" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Perioada următoare" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Следующий период" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ďalšie obdobie" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nästa period" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sonraki dönem" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Наступний період" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Khoảng thời gian sau" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "下一时段" + } + } + } + }, + "No active insulin or carbs on this day" : { + "comment" : "Day page placeholder when there was no insulin or carbs on board all day", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا يوجد أنسولين أو كارب نشط في هذا اليوم" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "V tento den nebyl žádný aktivní inzulín ani sacharidy" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Intet aktivt insulin eller kulhydrater denne dag" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kein aktives Insulin und keine aktiven KH an diesem Tag" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin insulina ni carbohidratos activos este día" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei aktiivista insuliinia tai hiilihydraatteja tänä päivänä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune insuline ni glucides actifs ce jour-là" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אין אינסולין או פחמימות פעילים ביום זה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ezen a napon nem volt aktív inzulin vagy szénhidrát" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessuna insulina o carboidrati attivi in questo giorno" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "この日は残存インスリンも残存糖質もありません" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen aktivt insulin eller karbohydrater denne dagen" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geen actieve insuline of koolhydraten op deze dag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak aktywnej insuliny i węglowodanów tego dnia" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem insulina ou carboidratos ativos neste dia" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fără insulină sau carbohidrați activi în această zi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "В этот день не было активного инсулина или углеводов" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "V tento deň nebol žiadny aktívny inzulín ani sacharidy" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inget aktivt insulin eller kolhydrater denna dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu gün aktif insülin veya karb yok" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Цього дня не було активного інсуліну чи вуглеводів" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không có insulin hoặc Carbs còn hoạt động trong ngày này" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "这一天没有活性胰岛素或碳水" + } + } + } + }, + "No CGM data" : { + "comment" : "Trends Average tile detail when the period has no glucose readings", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا توجد بيانات CGM" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žádná data CGM" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen CGM-data" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine CGM-Daten" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin datos del CGM" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei CGM-dataa" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune donnée CGM" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אין נתוני CGM" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs CGM-adat" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessun dato CGM" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGMデータなし" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen CGM-data" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geen CGM-gegevens" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak danych CGM" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem dados do CGM" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fără date CGM" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нет данных CGM" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žiadne údaje CGM" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inga CGM-data" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM verisi yok" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Немає даних CGM" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không có dữ liệu CGM" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无 CGM 数据" + } + } + } + }, + "No CGM data %1$@ – %2$@" : { + "comment" : "Chart note: a gap in glucose readings. (1: start time; 2: end time)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا توجد بيانات CGM %1$@ – %2$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žádná data CGM %1$@ – %2$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen CGM-data %1$@ – %2$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine CGM-Daten %1$@ – %2$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin datos del CGM %1$@ – %2$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei CGM-dataa %1$@ – %2$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune donnée CGM %1$@ – %2$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אין נתוני CGM %1$@ – %2$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs CGM-adat %1$@ – %2$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessun dato CGM %1$@ – %2$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGMデータなし %1$@ – %2$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen CGM-data %1$@ – %2$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geen CGM-gegevens %1$@ – %2$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak danych CGM %1$@ – %2$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem dados do CGM %1$@ – %2$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fără date CGM %1$@ – %2$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нет данных CGM %1$@ – %2$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žiadne údaje CGM %1$@ – %2$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inga CGM-data %1$@ – %2$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM verisi yok %1$@ – %2$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Немає даних CGM %1$@ – %2$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không có dữ liệu CGM %1$@ – %2$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无 CGM 数据 %1$@ – %2$@" + } + } + } + }, + "No CGM data %1$@ – %2$@ and %3$@" : { + "comment" : "Chart note: the longest gap in glucose readings, plus the shorter ones. (1: start time; 2: end time; 3: number of shorter gaps, e.g. 2 shorter gaps)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا توجد بيانات CGM %1$@ – %2$@ و %3$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žádná data CGM %1$@ – %2$@ a %3$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen CGM-data %1$@ – %2$@ og %3$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine CGM-Daten %1$@ – %2$@ und %3$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin datos del CGM %1$@ – %2$@ y %3$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei CGM-dataa %1$@ – %2$@ ja %3$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune donnée CGM %1$@ – %2$@ et %3$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אין נתוני CGM %1$@ – %2$@ ו-%3$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs CGM-adat %1$@ – %2$@, és %3$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessun dato CGM %1$@ – %2$@ e %3$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGMデータなし %1$@ – %2$@(%3$@)" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen CGM-data %1$@ – %2$@ og %3$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geen CGM-gegevens %1$@ – %2$@ en %3$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak danych CGM %1$@ – %2$@ oraz %3$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem dados do CGM %1$@ – %2$@ e %3$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fără date CGM %1$@ – %2$@ și %3$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нет данных CGM %1$@ – %2$@ и ещё %3$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žiadne údaje CGM %1$@ – %2$@ a %3$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inga CGM-data %1$@ – %2$@ och %3$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM verisi yok %1$@ – %2$@ ve %3$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Немає даних CGM %1$@ – %2$@ і ще %3$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không có dữ liệu CGM %1$@ – %2$@ và %3$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无 CGM 数据 %1$@ – %2$@,%3$@" + } + } + } + }, + "No CGM data in the last 3 hours" : { + "comment" : "Empty prediction chart on the Now screen", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا توجد بيانات CGM في آخر 3 ساعات" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žádná data CGM za poslední 3 hodiny" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen CGM-data de seneste 3 timer" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine CGM-Daten in den letzten 3 Stunden" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin datos del CGM en las últimas 3 horas" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei CGM-dataa viimeisen 3 tunnin aikana" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune donnée CGM ces 3 dernières heures" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אין נתוני CGM ב-3 השעות האחרונות" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs CGM-adat az elmúlt 3 órában" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessun dato CGM nelle ultime 3 ore" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "過去3時間のCGMデータなし" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen CGM-data de siste 3 timene" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geen CGM-gegevens in de afgelopen 3 uur" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak danych CGM z ostatnich 3 godzin" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem dados do CGM nas últimas 3 horas" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fără date CGM în ultimele 3 ore" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нет данных CGM за последние 3 часа" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žiadne údaje CGM za posledné 3 hodiny" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inga CGM-data de senaste 3 timmarna" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Son 3 saatte CGM verisi yok" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Немає даних CGM за останні 3 години" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không có dữ liệu CGM trong 3 giờ qua" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "过去 3 小时无 CGM 数据" + } + } + } + }, + "No CGM data in this period" : { + "comment" : "Trends chart placeholder when the period has no glucose readings", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا توجد بيانات CGM في هذه الفترة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žádná data CGM v tomto období" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen CGM-data i denne periode" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine CGM-Daten in diesem Zeitraum" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin datos del CGM en este período" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei CGM-dataa tällä jaksolla" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune donnée CGM sur cette période" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אין נתוני CGM בתקופה זו" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs CGM-adat ebben az időszakban" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessun dato CGM in questo periodo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "この期間のCGMデータなし" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen CGM-data i denne perioden" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geen CGM-gegevens in deze periode" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak danych CGM w tym okresie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem dados do CGM neste período" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fără date CGM în această perioadă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нет данных CGM за этот период" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žiadne údaje CGM v tomto období" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inga CGM-data under denna period" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu dönemde CGM verisi yok" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Немає даних CGM за цей період" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không có dữ liệu CGM trong khoảng thời gian này" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "此时段无 CGM 数据" + } + } + } + }, + "No CGM data on this day" : { + "comment" : "Chart note: there are no glucose readings for the whole day", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا توجد بيانات CGM في هذا اليوم" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žádná data CGM v tento den" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen CGM-data denne dag" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine CGM-Daten an diesem Tag" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin datos del CGM este día" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei CGM-dataa tänä päivänä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune donnée CGM ce jour-là" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אין נתוני CGM ביום זה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs CGM-adat ezen a napon" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessun dato CGM in questo giorno" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "この日のCGMデータなし" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen CGM-data denne dagen" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geen CGM-gegevens op deze dag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak danych CGM tego dnia" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem dados do CGM neste dia" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fără date CGM în această zi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нет данных CGM за этот день" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žiadne údaje CGM v tento deň" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inga CGM-data denna dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu günde CGM verisi yok" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Немає даних CGM за цей день" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không có dữ liệu CGM trong ngày này" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "这一天无 CGM 数据" + } + } + } + }, + "No CGM data yet today" : { + "comment" : "Chart note: there are no glucose readings so far today", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا توجد بيانات CGM اليوم حتى الآن" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dnes zatím žádná data CGM" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen CGM-data endnu i dag" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Heute noch keine CGM-Daten" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aún no hay datos del CGM hoy" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei vielä CGM-dataa tänään" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune donnée CGM aujourd'hui pour l'instant" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אין עדיין נתוני CGM היום" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ma még nincs CGM-adat" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ancora nessun dato CGM oggi" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "今日はまだCGMデータなし" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen CGM-data ennå i dag" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vandaag nog geen CGM-gegevens" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dziś jeszcze brak danych CGM" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ainda sem dados do CGM hoje" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Încă nu există date CGM azi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сегодня пока нет данных CGM" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dnes zatiaľ žiadne údaje CGM" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inga CGM-data än i dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bugün henüz CGM verisi yok" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сьогодні ще немає даних CGM" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hôm nay chưa có dữ liệu CGM" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "今天尚无 CGM 数据" + } + } + } + }, + "No gaps: Loop ran closed loop all through this period." : { + "comment" : "Trends Gaps list when Loop was in closed loop for the whole period", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا فجوات: عمل Loop بالحلقة المغلقة طوال هذه الفترة." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žádné výpadky: Loop běžel v uzavřené smyčce celé období." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen huller: Loop kørte lukket Loop i hele perioden." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Lücken: Loop lief im gesamten Zeitraum im Closed Loop." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin interrupciones: Loop funcionó en circuito cerrado durante todo el período." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei katkoja: Loop toimi suljetulla säädöllä koko jakson ajan." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune interruption : Loop est resté en boucle fermée toute la période." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אין פערים: Loop פעל ב-Closed Loop לאורך כל התקופה." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs kimaradás: a Loop az egész időszakban zárt hurokban futott." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessuna interruzione: Loop è rimasto in loop chiuso per tutto il periodo." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "中断なし:この期間中、Loopは常にクローズドループで動作しました。" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen hull: Loop kjørte lukket Loop gjennom hele perioden." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geen onderbrekingen: Loop draaide de hele periode in actieve loop." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak przerw: Loop działał w pętli zamkniętej przez cały okres." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem interrupções: o Loop ficou em ciclo fechado durante todo o período." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fără întreruperi: Loop a rulat în buclă închisă toată perioada." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пропусков нет: Loop работал в замкнутом цикле весь период." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žiadne výpadky: Loop bežal v uzavretej slučke celé obdobie." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inga luckor: Loop körde sluten loop under hela perioden." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boşluk yok: Loop bu dönem boyunca kapalı döngüde çalıştı." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пропусків немає: Loop працював у замкнутому циклі весь період." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không gián đoạn: Loop chạy vòng lặp kín suốt khoảng thời gian này." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无中断:此时段内 Loop 一直以闭环运行。" + } + } + } + }, + "No insulin recorded in this period" : { + "comment" : "Trends Insulin per day chart placeholder when no insulin was delivered in the period", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لم يُسجَّل أنسولين في هذه الفترة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "V tomto období nebyl zaznamenán žádný inzulín" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Intet insulin registreret i denne periode" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kein Insulin in diesem Zeitraum erfasst" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se registró insulina en este período" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei kirjattua insuliinia tällä jaksolla" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune insuline enregistrée sur cette période" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לא נרשם אינסולין בתקופה זו" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs rögzített inzulin ebben az időszakban" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessuna insulina registrata in questo periodo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "この期間のインスリン記録なし" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen insulin registrert i denne perioden" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geen insuline vastgelegd in deze periode" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak zapisanej insuliny w tym okresie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhuma insulina registrada neste período" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nicio insulină înregistrată în această perioadă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "За этот период инсулин не записан" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "V tomto období nebol zaznamenaný žiadny inzulín" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inget insulin registrerat under denna period" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu dönemde kaydedilmiş insülin yok" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "За цей період інсулін не записано" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không có insulin được ghi lại trong khoảng thời gian này" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "此时段内无胰岛素记录" + } + } + } + }, + "No loop runs kept" : { + "comment" : "Trends Loop active tile detail when no Loop run history is available for the period", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا توجد تشغيلات Loop محفوظة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žádné uložené běhy" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen Loop-kørsler gemt" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Loop-Durchläufe gespeichert" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se guardaron ejecuciones de Loop" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei tallennettuja Loop-ajoja" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune exécution de Loop conservée" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לא נשמרו הרצות Loop" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs megőrzött Loop-futás" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessuna esecuzione di Loop conservata" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop実行記録なし" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen Loop-kjøringer lagret" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geen loop-runs bewaard" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak zapisanych cykli" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhuma execução do Loop guardada" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nu sunt păstrate rulări Loop" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нет сохранённых запусков" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žiadne uložené behy" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inga loopkörningar sparade" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Saklanan Loop çalışması yok" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Немає збережених запусків" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không lưu lần chạy Loop nào" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无 Loop 运行记录" + } + } + } + }, + "No loop runs recorded in this period" : { + "comment" : "Trends Loop status placeholder when there is no Loop run history for the period", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لم تُسجَّل تشغيلات Loop في هذه الفترة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "V tomto období nebyly zaznamenány žádné běhy Loop" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen Loop-kørsler registreret i denne periode" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Loop-Durchläufe in diesem Zeitraum erfasst" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se registraron ejecuciones de Loop en este período" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei Loop-ajoja tällä jaksolla" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune exécution de Loop enregistrée sur cette période" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לא נרשמו הרצות Loop בתקופה זו" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs rögzített Loop-futás ebben az időszakban" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessuna esecuzione di Loop registrata in questo periodo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "この期間のLoop実行記録なし" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen Loop-kjøringer registrert i denne perioden" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geen loop-runs vastgelegd in deze periode" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak zapisanych cykli Loop w tym okresie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhuma execução do Loop registrada neste período" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nicio rulare Loop înregistrată în această perioadă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "За этот период запуски Loop не записаны" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "V tomto období neboli zaznamenané žiadne behy Loop" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inga loopkörningar registrerade under denna period" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu dönemde kaydedilmiş Loop çalışması yok" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "За цей період запуски Loop не записано" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không có lần chạy Loop nào được ghi lại trong khoảng thời gian này" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "此时段内无 Loop 运行记录" + } + } + } + }, + "No reading" : { + "comment" : "Shown on the Now screen in place of the glucose value when there is no recent CGM reading", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا توجد قراءة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žádná hodnota" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen måling" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kein Wert" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin lectura" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei lukemaa" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune mesure" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אין קריאה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs érték" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessuna lettura" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "測定値なし" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingen måling" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geen waarde" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak odczytu" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem leitura" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nicio citire" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нет показаний" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žiadna hodnota" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inget värde" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Okuma yok" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Немає показників" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không có chỉ số" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无读数" + } + } + } + }, + "Not enough days for a pattern: %1$@ of 5" : { + "comment" : "Trends Daily pattern footnote when fewer than 5 days have readings; the chart shows raw readings instead. (1: days with readings)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "أيام غير كافية لنمط: %1$@ من 5" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Málo dní pro profil: %1$@ z 5" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ikke nok dage til et mønster: %1$@ af 5" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nicht genug Tage für ein Profil: %1$@ von 5" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No hay suficientes días para un patrón: %1$@ de 5" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Liian vähän päiviä profiiliin: %1$@ / 5" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pas assez de jours pour un profil : %1$@ sur 5" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אין מספיק ימים לדפוס: %1$@ מתוך 5" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs elég nap a mintázathoz: %1$@ / 5" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Giorni insufficienti per un andamento: %1$@ su 5" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "パターン作成には日数不足:%1$@ / 5日" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ikke nok dager for et mønster: %1$@ av 5" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Niet genoeg dagen voor een patroon: %1$@ van 5" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Za mało dni na profil: %1$@ z 5" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dias insuficientes para um padrão: %1$@ de 5" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prea puține zile pentru un tipar: %1$@ din 5" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Мало дней для профиля: %1$@ из 5" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Málo dní pre profil: %1$@ z 5" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "För få dagar för ett mönster: %1$@ av 5" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Örüntü için yeterli gün yok: %1$@ / 5" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Замало днів для профілю: %1$@ з 5" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không đủ ngày để tạo mẫu: %1$@ / 5" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "天数不足以生成模式:%1$@ / 5" + } + } + } + }, + "Nothing logged" : { + "comment" : "Logbook day with no entries", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا شيء مسجَّل" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nic zaznamenáno" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Intet registreret" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nichts eingetragen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nada registrado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei kirjauksia" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune saisie" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לא נרשם דבר" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs bejegyzés" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessuna registrazione" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "記録なし" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingenting registrert" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Niets vastgelegd" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak wpisów" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nada registrado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nimic înregistrat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нет записей" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nič zaznamenané" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inget loggat" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kayıt yok" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Немає записів" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không có ghi chép" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无记录" + } + } + } + }, + "Nothing logged in the last 7 days." : { + "comment" : "Logbook empty state when there are no entries at all", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا شيء مسجَّل في آخر 7 أيام." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Za posledních 7 dní nic zaznamenáno." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Intet registreret de seneste 7 dage." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "In den letzten 7 Tagen nichts eingetragen." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nada registrado en los últimos 7 días." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei kirjauksia viimeisen 7 päivän aikana." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune saisie ces 7 derniers jours." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לא נרשם דבר ב-7 הימים האחרונים." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs bejegyzés az elmúlt 7 napban." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nessuna registrazione negli ultimi 7 giorni." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "過去7日間の記録はありません。" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingenting registrert de siste 7 dagene." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Niets vastgelegd in de afgelopen 7 dagen." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak wpisów z ostatnich 7 dni." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nada registrado nos últimos 7 dias." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nimic înregistrat în ultimele 7 zile." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нет записей за последние 7 дней." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Za posledných 7 dní nič zaznamenané." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inget loggat de senaste 7 dagarna." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Son 7 günde kayıt yok." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Немає записів за останні 7 днів." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không có ghi chép nào trong 7 ngày qua." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "过去 7 天没有记录。" + } + } + } + }, + "Nothing to show for this filter" : { + "comment" : "Logbook day with no entries matching the selected filter", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا شيء لعرضه لهذا الفلتر" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pro tento filtr nic k zobrazení" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Intet at vise for dette filter" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Einträge für diesen Filter" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nada que mostrar con este filtro" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei näytettävää tällä suodattimella" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rien à afficher pour ce filtre" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אין מה להציג עבור מסנן זה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs megjeleníthető elem ehhez a szűrőhöz" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Niente da mostrare per questo filtro" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "このフィルタに該当する項目はありません" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ingenting å vise for dette filteret" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Niets te tonen voor dit filter" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brak wpisów dla tego filtra" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nada para mostrar neste filtro" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nimic de afișat pentru acest filtru" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нет записей для этого фильтра" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pre tento filter nič na zobrazenie" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inget att visa för detta filter" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu filtre için gösterilecek bir şey yok" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Немає записів для цього фільтра" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không có mục nào cho bộ lọc này" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "此筛选条件下没有内容" + } + } + } + }, + "Now" : { + "comment" : "Title at the top of the Now screen, showing current glucose and Loop status Title of the Now tab in the redesigned interface, showing current glucose and Loop status", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الآن" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nyní" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jetzt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ahora" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nyt" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Maintenant" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "עכשיו" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Most" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ora" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "現在" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nå" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nu" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Teraz" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agora" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Acum" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сейчас" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Teraz" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nu" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Şimdi" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Зараз" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hiện tại" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "当前" + } + } + } + }, + "now" : { + "comment" : "Prediction chart x-axis label marking the current time", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الآن" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "nyní" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "nu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "jetzt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "ahora" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "nyt" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "maint." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "עכשיו" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "most" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "ora" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "現在" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "nå" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "nu" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "teraz" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "agora" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "acum" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "сейчас" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "teraz" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "nu" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "şimdi" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "зараз" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "bây giờ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "现在" + } + } + } + }, + "Open loop / manual" : { + "comment" : "Trends Loop status: time closed loop was switched off", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "حلقة مفتوحة / يدوي" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otevřená smyčka / ručně" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åbent Loop / manuel" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open Loop / manuell" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Circuito abierto / manual" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avoin säätö / manuaalinen" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boucle ouverte / manuel" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open Loop / ידני" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nyitott hurok / kézi" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop aperto / manuale" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "オープンループ/手動" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åpen Loop / manuell" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open loop / handmatig" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pętla otwarta / ręcznie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ciclo aberto / manual" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buclă deschisă / manual" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Открытый цикл / вручную" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otvorená slučka / ručne" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öppen loop / manuell" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Açık döngü / manuel" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відкритий цикл / вручну" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vòng lặp mở / thủ công" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "开环 / 手动" + } + } + } + }, + "Open loop · %1$@" : { + "comment" : "Status pill on the Now screen: closed loop is off (Loop recommends only) and Loop last ran this long ago. (1: duration, e.g. 3m)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "حلقة مفتوحة · %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otevřená smyčka · %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åbent Loop · %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open Loop · %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Circuito abierto · %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avoin säätö · %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boucle ouverte · %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open Loop · %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nyitott hurok · %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop aperto · %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "オープンループ · %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åpen Loop · %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open loop · %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pętla otwarta · %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ciclo aberto · %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buclă deschisă · %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Открытый цикл · %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otvorená slučka · %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öppen loop · %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Açık döngü · %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відкритий цикл · %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vòng lặp mở · %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "开环 · %1$@" + } + } + } + }, + "Open loop · not run yet" : { + "comment" : "Status pill on the Now screen: closed loop is off and Loop has never completed", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "حلقة مفتوحة · لم يعمل بعد" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otevřená smyčka · zatím neproběhla" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åbent Loop · ikke kørt endnu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open Loop · noch nicht gelaufen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Circuito abierto · aún no se ha ejecutado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avoin säätö · ei vielä ajettu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boucle ouverte · pas encore exécuté" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open Loop · טרם רץ" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nyitott hurok · még nem futott" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop aperto · non ancora eseguito" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "オープンループ · 未実行" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åpen Loop · ikke kjørt ennå" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open loop · nog niet gedraaid" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pętla otwarta · jeszcze nie działała" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ciclo aberto · ainda não executou" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buclă deschisă · nu a rulat încă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Открытый цикл · ещё не запускался" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otvorená slučka · ešte neprebehla" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öppen loop · inte körd än" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Açık döngü · henüz çalışmadı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відкритий цикл · ще не запускався" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vòng lặp mở · chưa chạy" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "开环 · 尚未运行" + } + } + } + }, + "Opens carb absorption" : { + "comment" : "VoiceOver hint for the COB tile on the Now screen", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يفتح امتصاص الكارب" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otevře vstřebávání sacharidů" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åbner kulhydratoptagelse" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öffnet die KH-Aufnahme" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abre la absorción de carbohidratos" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avaa hiilihydraattien imeytymisen" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvre l'absorption des glucides" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "פותח ספיגת פחמימות" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Megnyitja a szénhidrát-felszívódást" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apre l'assorbimento dei carboidrati" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "糖質吸収を開きます" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åpner karbohydratabsorpsjon" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opent koolhydraatopname" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otwiera wchłanianie węglowodanów" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abre a absorção de carboidratos" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deschide absorbția carbohidraților" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Открывает усвоение углеводов" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otvorí vstrebávanie sacharidov" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öppnar kolhydratabsorption" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Karb emilimini açar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відкриває засвоєння вуглеводів" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mở hấp thu Carbs" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开碳水吸收" + } + } + } + }, + "Opens CGM settings" : { + "comment" : "VoiceOver hint for the CGM status pill and CGM notice on the Now screen", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يفتح إعدادات CGM" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otevře nastavení CGM" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åbner CGM-indstillinger" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öffnet die CGM-Einstellungen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abre los ajustes del CGM" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avaa CGM-asetukset" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvre les paramètres du CGM" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "פותח הגדרות CGM" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Megnyitja a CGM beállításait" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apre le impostazioni CGM" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM設定を開きます" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åpner CGM-innstillinger" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opent CGM-instellingen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otwiera ustawienia CGM" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abre as configurações do CGM" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deschide setările CGM" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Открывает настройки CGM" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otvorí nastavenia CGM" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öppnar CGM-inställningar" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM ayarlarını açar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відкриває налаштування CGM" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mở cài đặt CGM" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开 CGM 设置" + } + } + } + }, + "Opens insulin delivery history" : { + "comment" : "VoiceOver hint for the IOB tile on the Now screen", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يفتح سجل توصيل الأنسولين" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otevře historii podávání inzulínu" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åbner historik for insulintilførsel" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öffnet den Verlauf der Insulinabgabe" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abre el historial de administración de insulina" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avaa insuliinin annosteluhistorian" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvre l'historique d'administration de l'insuline" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "פותח היסטוריית מתן אינסולין" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Megnyitja az inzulinadagolási előzményeket" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apre la cronologia di erogazione insulina" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "インスリン注入履歴を開きます" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åpner historikk for insulinlevering" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opent geschiedenis van insulinetoediening" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otwiera historię podaży insuliny" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abre o histórico de entrega de insulina" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deschide istoricul administrării insulinei" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Открывает историю подачи инсулина" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otvorí históriu podávania inzulínu" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öppnar historik för insulindosering" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "İnsülin iletim geçmişini açar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відкриває історію подачі інсуліну" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mở lịch sử tiêm insulin" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开胰岛素输注历史" + } + } + } + }, + "Opens Loop's prediction breakdown" : { + "comment" : "VoiceOver hint for the prediction card on the Now screen", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يفتح تفاصيل توقع Loop" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otevře rozpis predikce Loop" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åbner opdelingen af Loops forudsigelse" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öffnet die Aufschlüsselung der Loop-Vorhersage" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abre el desglose de la predicción de Loop" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avaa Loopin ennusteen erittelyn" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvre le détail de la prédiction de Loop" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "פותח פירוט תחזית Loop" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Megnyitja a Loop előrejelzésének részleteit" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apre il dettaglio della previsione di Loop" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopの予測の内訳を開きます" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åpner oppdelingen av Loops prognose" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opent de onderbouwing van de Loop-voorspelling" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otwiera szczegóły prognozy Loop" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abre o detalhamento da previsão do Loop" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deschide detalierea predicției Loop" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Открывает разбор прогноза Loop" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otvorí rozpis predikcie Loop" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öppnar detaljer om Loops prognos" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop tahmin dökümünü açar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відкриває розбір прогнозу Loop" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mở chi tiết dự đoán của Loop" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开 Loop 预测明细" + } + } + } + }, + "Opens pump settings" : { + "comment" : "VoiceOver hint for the pump status pill and pump notice on the Now screen", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يفتح إعدادات المضخة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otevře nastavení pumpy" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åbner pumpeindstillinger" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öffnet die Pumpeneinstellungen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abre los ajustes de la microinfusadora" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avaa pumpun asetukset" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvre les paramètres de la pompe" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "פותח הגדרות משאבה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Megnyitja a pumpa beállításait" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apre le impostazioni della pompa" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ポンプ設定を開きます" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åpner pumpeinnstillinger" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opent pompinstellingen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otwiera ustawienia pompy" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abre as configurações da bomba" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deschide setările pompei" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Открывает настройки помпы" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otvorí nastavenia pumpy" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öppnar pumpinställningar" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompa ayarlarını açar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відкриває налаштування помпи" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mở cài đặt bơm" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开胰岛素泵设置" + } + } + } + }, + "Opens the full day" : { + "comment" : "VoiceOver hint for the Today so far card on the Now screen", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يفتح اليوم كاملًا" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otevře celý den" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åbner hele dagen" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öffnet den ganzen Tag" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abre el día completo" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avaa koko päivän" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvre la journée complète" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "פותח את היום המלא" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Megnyitja a teljes napot" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apre il giorno completo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "1日全体を開きます" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Åpner hele dagen" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opent de hele dag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otwiera cały dzień" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abre o dia completo" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deschide ziua completă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Открывает весь день" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otvorí celý deň" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öppnar hela dagen" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tüm günü açar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відкриває весь день" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mở toàn bộ ngày" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开全天视图" + } + } + } + }, + "PDF report" : { + "comment" : "Trends export menu item: share a PDF report of the period", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تقرير PDF" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zpráva PDF" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "PDF-rapport" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "PDF-Bericht" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Informe PDF" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "PDF-raportti" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rapport PDF" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "דוח PDF" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "PDF-jelentés" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Report PDF" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "PDFレポート" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "PDF-rapport" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "PDF-rapport" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Raport PDF" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Relatório PDF" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Raport PDF" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отчёт PDF" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Správa PDF" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "PDF-rapport" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "PDF raporu" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Звіт PDF" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Báo cáo PDF" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "PDF 报告" + } + } + } + }, + "per day, weekly average" : { + "comment" : "Trends Insulin per day card subtitle when each bar is a week's average daily total", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يوميًا، متوسط أسبوعي" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "za den, týdenní průměr" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "pr. dag, ugegennemsnit" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "pro Tag, Wochendurchschnitt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "por día, promedio semanal" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "päivässä, viikkokeskiarvo" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "par jour, moyenne hebdomadaire" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "ליום, ממוצע שבועי" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "naponta, heti átlag" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "al giorno, media settimanale" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "1日あたり、週平均" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "per dag, ukesgjennomsnitt" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "per dag, weekgemiddelde" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "dziennie, średnia tygodniowa" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "por dia, média semanal" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "pe zi, medie săptămânală" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "в день, среднее за неделю" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "za deň, týždenný priemer" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "per dag, veckomedel" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "günlük, haftalık ortalama" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "на день, середнє за тиждень" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "mỗi ngày, trung bình theo tuần" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "每日,按周平均" + } + } + } + }, + "Pod changed" : { + "comment" : "Pump event in the history: the pump or pod was replaced", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تم تغيير Pod" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod vyměněn" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod skiftet" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod gewechselt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod cambiado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod vaihdettu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod changé" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "ה-Pod הוחלף" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod cserélve" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod cambiato" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod交換" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod byttet" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod vervangen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wymieniono poda" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod trocado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod schimbat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Под заменён" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod vymenený" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod bytt" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pod değiştirildi" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Под замінено" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Đã thay Pod" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已更换 Pod" + } + } + } + }, + "Pre-meal on" : { + "comment" : "Presets button title on the Now screen while the pre-meal target is on", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pre-Meal مفعّل" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Před jídlem zapnuto" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Før-måltid til" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vor dem Essen an" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pre-comida activado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ennen ateriaa päällä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pré-repas activé" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pre-Meal פעיל" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pre-Meal bekapcsolva" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pre-pasto attivo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "食前 オン" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pre-måltid på" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voorafgaand aan maaltijd aan" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Przed posiłkiem wł." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pré-refeição ligado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Înainte de masă activ" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "До еды вкл." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pred jedlom zapnuté" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Före måltid på" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yemek öncesi açık" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Перед їжою увімк." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trước bữa ăn đang bật" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "餐前已开启" + } + } + } + }, + "Predicted" : { + "comment" : "Prediction chart callout label for Loop's predicted glucose at the touched time", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "متوقع" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predikce" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forudsagt" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vorhergesagt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predicción" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ennustettu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prédit" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "חזוי" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Előrejelzett" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prevista" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "予測" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forventet" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voorspeld" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prognoza" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prevista" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prezis" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прогноз" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predikcia" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Förutsagt" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tahmin" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прогноз" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dự đoán" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "预测" + } + } + } + }, + "Previous day" : { + "comment" : "Accessibility label for the day page button that steps back a day", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "اليوم السابق" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Předchozí den" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forrige dag" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vorheriger Tag" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Día anterior" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Edellinen päivä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jour précédent" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "היום הקודם" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Előző nap" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Giorno precedente" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "前日" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forrige dag" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vorige dag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Poprzedni dzień" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dia anterior" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ziua anterioară" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Предыдущий день" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predchádzajúci deň" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Föregående dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Önceki gün" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Попередній день" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ngày trước" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "前一天" + } + } + } + }, + "Previous period" : { + "comment" : "VoiceOver label for the Trends button that moves back one period", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الفترة السابقة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Předchozí období" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forrige periode" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vorheriger Zeitraum" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Período anterior" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Edellinen jakso" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Période précédente" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "התקופה הקודמת" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Előző időszak" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Periodo precedente" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "前の期間" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forrige periode" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vorige periode" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Poprzedni okres" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Período anterior" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Perioada anterioară" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Предыдущий период" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predchádzajúce obdobie" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Föregående period" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Önceki dönem" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Попередній період" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Khoảng thời gian trước" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "上一时段" + } + } + } + }, + "Primed" : { + "comment" : "Pump event in the history: the pump primed the tubing or cannula", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تمت التعبئة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Naplněno" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fyldt" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gefüllt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cebado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Täytetty" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Amorcé" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "בוצע תיחול" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Feltöltve" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Caricamento eseguito" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プライミング" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fylt" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gevuld" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Napełniono" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preenchido" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Amorsat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Заполнение выполнено" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Naplnené" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Primning klar" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dolum yapıldı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Заповнення виконано" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Đã mồi" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已充注" + } + } + } + }, + "Projected" : { + "comment" : "Chart legend: dashed lines show Loop's projection of insulin and carbs on board", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "المتوقع" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Předpověď" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forventet" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prognose" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Proyectado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ennuste" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Projeté" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "צפי" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Előrevetített" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Previsto" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "予測" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Beregnet" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verwacht" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prognozowane" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Projetado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Proiectat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прогнозируемые" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predpoveď" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prognos" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öngörülen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прогнозовані" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dự kiến" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "预计" + } + } + } + }, + "Pump alarm" : { + "comment" : "Pump event in the history: an alarm whose pump gave no title", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "إنذار المضخة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alarm pumpy" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpealarm" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpenalarm" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alarma de la microinfusadora" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpun hälytys" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alarme de la pompe" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "התראת משאבה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpariasztás" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Allarme pompa" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ポンプアラーム" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpealarm" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompalarm" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alarm pompy" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alarme da bomba" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alarmă pompă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тревога помпы" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alarm pumpy" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumplarm" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompa alarmı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тривога помпи" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cảnh báo bơm" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "胰岛素泵警报" + } + } + } + }, + "Pump data too old" : { + "comment" : "Reason Loop could not run: the latest pump data was too old", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "بيانات المضخة قديمة جدًا" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Data pumpy jsou příliš stará" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpedata for gamle" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpendaten zu alt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Datos de la microinfusadora demasiado antiguos" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpun tiedot liian vanhoja" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Données de la pompe trop anciennes" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "נתוני המשאבה ישנים מדי" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Túl régi pumpaadatok" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dati pompa troppo vecchi" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ポンプデータが古すぎます" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpedata for gamle" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompgegevens te oud" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dane pompy zbyt stare" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dados da bomba muito antigos" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Date pompă prea vechi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Данные помпы устарели" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Údaje pumpy sú príliš staré" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpdata för gamla" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompa verisi çok eski" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дані помпи застаріли" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dữ liệu bơm quá cũ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "胰岛素泵数据过旧" + } + } + } + }, + "Pump manager error" : { + "comment" : "Reason Loop could not run: the pump reported an error", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "خطأ في مدير المضخة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chyba správce pumpy" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpefejl" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpenfehler" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error del gestor de la microinfusadora" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpunhallinnan virhe" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erreur du gestionnaire de pompe" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "שגיאת מנהל המשאבה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpakezelő-hiba" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Errore del gestore pompa" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ポンプマネージャのエラー" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpefeil" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompfout" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Błąd menedżera pompy" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erro do gerenciador da bomba" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eroare manager pompă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ошибка менеджера помпы" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chyba správcu pumpy" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fel i pumphanteraren" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompa yöneticisi hatası" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Помилка менеджера помпи" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lỗi trình quản lý bơm" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "胰岛素泵管理器错误" + } + } + } + }, + "Pump suspended" : { + "comment" : "Reason Loop could not run: insulin delivery on the pump was suspended", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "المضخة متوقفة مؤقتًا" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpa pozastavena" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpe suspenderet" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpe unterbrochen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Microinfusadora suspendida" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumppu pysäytetty" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompe suspendue" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "המשאבה מושהית" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpa felfüggesztve" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompa sospesa" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ポンプ一時停止中" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpe stanset" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pomp onderbroken" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompa wstrzymana" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bomba suspensa" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompă suspendată" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Помпа приостановлена" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpa pozastavená" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pump pausad" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompa askıya alındı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Помпу призупинено" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bơm đã tạm ngưng" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "胰岛素泵已暂停" + } + } + } + }, + "Readings" : { + "comment" : "Chart legend on the Now screen: dots are CGM glucose readings", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "القراءات" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hodnoty" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Målinger" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Messwerte" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lecturas" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lukemat" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mesures" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "קריאות" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mért értékek" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Letture" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "測定値" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Målinger" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Waarden" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odczyty" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Leituras" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Citiri" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показания" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hodnoty" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Värden" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Okumalar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показники" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chỉ số" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "读数" + } + } + } + }, + "Recent readings unavailable: %1$@" : { + "comment" : "Error under the prediction chart on the Now screen. (1: system error description)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "القراءات الأخيرة غير متاحة: %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nedávné hodnoty nejsou k dispozici: %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seneste målinger ikke tilgængelige: %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktuelle Messwerte nicht verfügbar: %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lecturas recientes no disponibles: %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Viimeisimmät lukemat eivät saatavilla: %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mesures récentes indisponibles : %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "קריאות אחרונות אינן זמינות: %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "A legutóbbi értékek nem érhetők el: %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Letture recenti non disponibili: %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "最近の測定値を取得できません:%1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nylige målinger ikke tilgjengelige: %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recente waarden niet beschikbaar: %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ostatnie odczyty niedostępne: %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Leituras recentes indisponíveis: %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Citiri recente indisponibile: %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Последние показания недоступны: %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nedávne hodnoty nie sú k dispozícii: %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Senaste värden ej tillgängliga: %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Son okumalar kullanılamıyor: %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Останні показники недоступні: %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Không có chỉ số gần đây: %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "最近读数不可用:%1$@" + } + } + } + }, + "Recommendation expired" : { + "comment" : "Reason Loop could not run: its dosing recommendation expired before it was used", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "انتهت صلاحية التوصية" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Doporučení vypršelo" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anbefaling udløbet" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Empfehlung abgelaufen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recomendación caducada" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suositus vanhentunut" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recommandation expirée" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "תוקף ההמלצה פג" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lejárt javaslat" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suggerimento scaduto" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "推奨の有効期限切れ" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anbefaling utløpt" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Advies verlopen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zalecenie wygasło" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recomendação expirada" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recomandare expirată" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Рекомендация устарела" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odporúčanie vypršalo" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rekommendationen har gått ut" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Önerinin süresi doldu" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Рекомендація застаріла" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Khuyến nghị đã hết hạn" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "建议已过期" + } + } + } + }, + "Reservoir changed" : { + "comment" : "Pump event in the history: the insulin reservoir was replaced", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تم تغيير الخزان" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zásobník vyměněn" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reservoir skiftet" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reservoir gewechselt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reservorio cambiado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Säiliö vaihdettu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Réservoir changé" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "המאגר הוחלף" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tartály cserélve" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serbatoio cambiato" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リザーバ交換" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reservoar byttet" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reservoir vervangen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wymieniono zbiorniczek" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reservatório trocado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rezervor schimbat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Резервуар заменён" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zásobník vymenený" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reservoar bytt" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rezervuar değiştirildi" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Резервуар замінено" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Đã thay bình chứa" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已更换储药器" + } + } + } + }, + "Reservoir rewound" : { + "comment" : "Pump event in the history: the pump rewound its reservoir", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تم إرجاع الخزان" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zásobník převinut" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reservoir spolet tilbage" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reservoir zurückgespult" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reservorio rebobinado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Säiliö kelattu taakse" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Réservoir rembobiné" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "המאגר הוחזר לאחור" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tartály visszatekerve" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serbatoio riavvolto" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リザーバ巻き戻し" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reservoar spolt tilbake" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reservoir teruggespoeld" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cofnięto tłok zbiorniczka" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reservatório rebobinado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rezervor rebobinat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Резервуар перемотан" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zásobník pretočený" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reservoar tillbakaspolad" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rezervuar geri sarıldı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Резервуар перемотано" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Đã tua lại bình chứa" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "储药器已回位" + } + } + } + }, + "Scheduled %1$@" : { + "comment" : "Basal and bolus: what the basal schedule delivers over the same time, under the basal row. (1: units, e.g. 18.0 U)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "المجدول %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plánovaný %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Planlagt %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geplant %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Programado %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ohjelmoitu %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Programmé %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מתוזמן %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ütemezett %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Programmato %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "スケジュール %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Planlagt %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gepland %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zaplanowane %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Programada %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Programat %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Запланировано %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plánovaný %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schemalagd %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Planlanan %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "За розкладом %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Theo lịch %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "预设 %1$@" + } + } + } + }, + "Scheduled basal only" : { + "comment" : "Logbook day where only the scheduled basal ran, with nothing else logged", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "القاعدية المجدولة فقط" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jen plánovaný bazál" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kun planlagt basal" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nur geplante Basalrate" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Solo basal programada" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vain ohjelmoitu basaali" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal programmé uniquement" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal מתוזמן בלבד" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Csak ütemezett basal" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Solo basale programmata" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "スケジュールのベーサルのみ" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bare planlagt basal" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alleen gepland basaal" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tylko zaplanowana baza" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Somente basal programada" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Doar bazala programată" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Только базал по расписанию" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Len plánovaný bazál" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Endast schemalagd basal" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yalnızca planlanan bazal" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Лише базал за розкладом" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chỉ Basal theo lịch" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "仅预设基础率" + } + } + } + }, + "SD %1$@ · CV %2$@%%" : { + "comment" : "Trends Average tile detail: standard deviation and coefficient of variation of glucose. (1: standard deviation in the glucose unit, 2: coefficient of variation as a percentage number)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@%%" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@%%" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@ %%" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · VK %2$@ %%" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "DE %1$@ · CV %2$@%%" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@ %%" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ET %1$@ · CV %2$@ %%" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@%%" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@%%" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "DS %1$@ · CV %2$@%%" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@%%" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@ %%" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@%%" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@%%" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "DP %1$@ · CV %2$@%%" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@%%" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@%%" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@%%" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@ %%" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %%%2$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@%%" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@%%" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "SD %1$@ · CV %2$@%%" + } + } + } + }, + "Sets up a CGM" : { + "comment" : "VoiceOver hint for the Add CGM status pill on the Now screen", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يُعدّ جهاز CGM" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nastaví CGM" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opsætter en CGM" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Richtet ein CGM ein" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configura un CGM" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ottaa CGM:n käyttöön" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configure un CGM" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מגדיר CGM" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM beállítása" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configura un CGM" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGMを設定します" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Setter opp en CGM" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stelt een CGM in" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konfiguruje CGM" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configura um CGM" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configurează un CGM" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Настраивает CGM" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nastaví CGM" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lägger till en CGM" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM kurar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Налаштовує CGM" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thiết lập CGM" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置 CGM" + } + } + } + }, + "Sets up a pump" : { + "comment" : "VoiceOver hint for the Add pump status pill on the Now screen", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يُعدّ مضخة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nastaví pumpu" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opsætter en pumpe" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Richtet eine Pumpe ein" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configura una microinfusadora" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ottaa pumpun käyttöön" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configure une pompe" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מגדיר משאבה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpa beállítása" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configura una pompa" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ポンプを設定します" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Setter opp en pumpe" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stelt een pomp in" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konfiguruje pompę" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configura uma bomba" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configurează o pompă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Настраивает помпу" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nastaví pumpu" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lägger till en pump" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pompa kurar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Налаштовує помпу" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thiết lập bơm" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置胰岛素泵" + } + } + } + }, + "Shows when Loop last ran" : { + "comment" : "VoiceOver hint for the Loop status pill and the Loop notice on the Now screen", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يعرض آخر مرة عمل فيها Loop" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ukáže, kdy Loop naposledy proběhl" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Viser, hvornår Loop sidst kørte" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeigt, wann Loop zuletzt gelaufen ist" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Muestra cuándo se ejecutó Loop por última vez" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Näyttää, milloin Loop ajettiin viimeksi" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Indique la dernière exécution de Loop" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מציג מתי Loop רץ לאחרונה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Megmutatja, mikor futott utoljára a Loop" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostra l'ultima esecuzione di Loop" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loopの最終実行時刻を表示します" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Viser når Loop sist kjørte" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toont wanneer Loop voor het laatst draaide" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pokazuje, kiedy Loop ostatnio działał" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostra quando o Loop executou pela última vez" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Arată când a rulat Loop ultima dată" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показывает, когда Loop запускался последний раз" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ukáže, kedy Loop naposledy prebehol" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visar när Loop senast kördes" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loop'un son çalıştığı zamanı gösterir" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показує, коли Loop запускався востаннє" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hiển thị lần chạy gần nhất của Loop" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示 Loop 上次运行时间" + } + } + } + }, + "Slow" : { + "comment" : "Carb entry absorption choice: food that absorbs slowly, such as pizza", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "بطيء" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pomalu" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Langsom" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Langsam" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lento" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hidas" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lent" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "איטי" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lassú" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lento" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "遅い" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Langsom" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Langzaam" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wolno" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lento" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lent" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Медленно" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pomaly" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Långsam" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yavaş" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Повільно" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chậm" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "慢" + } + } + } + }, + "Summary" : { + "comment" : "Trends card title: the summary statistics tiles", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الملخص" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Souhrn" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oversigt" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Übersicht" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resumen" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yhteenveto" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Résumé" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "סיכום" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Összegzés" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Riepilogo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "概要" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oversikt" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Overzicht" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Podsumowanie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resumo" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rezumat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сводка" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Súhrn" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sammanfattning" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Özet" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Підсумок" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tóm tắt" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "摘要" + } + } + } + }, + "Suspended" : { + "comment" : "Chart callout value: insulin delivery was suspended at the touched time Shown in the Basal tile on the Now screen when insulin delivery is suspended", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "متوقف مؤقتًا" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pozastaveno" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suspenderet" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unterbrochen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suspendida" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pysäytetty" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suspendu" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מושהה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Felfüggesztve" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sospesa" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "一時停止中" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stanset" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Onderbroken" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wstrzymano" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suspensa" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suspendat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Приостановлено" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pozastavené" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pausad" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Askıya alındı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Призупинено" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Đã tạm ngưng" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已暂停" + } + } + } + }, + "Target %1$@–%2$@" : { + "comment" : "Glucose target range on the Now screen hero card. (1: lower bound; 2: upper bound)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الهدف %1$@–%2$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cíl %1$@–%2$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mål %1$@–%2$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ziel %1$@–%2$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Objetivo %1$@–%2$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tavoite %1$@–%2$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cible %1$@–%2$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "יעד %1$@–%2$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cél %1$@–%2$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obiettivo %1$@–%2$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "目標 %1$@–%2$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mål %1$@–%2$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Doel %1$@–%2$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cel %1$@–%2$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alvo %1$@–%2$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Țintă %1$@–%2$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Цель %1$@–%2$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cieľ %1$@–%2$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mål %1$@–%2$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hedef %1$@–%2$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ціль %1$@–%2$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mục tiêu %1$@–%2$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "目标 %1$@–%2$@" + } + } + } + }, + "Temp basal" : { + "comment" : "Logbook row title: a temporary basal rate", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "Temp Basal" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dočasný bazál" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Midlertidig basal" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Temporäre Basalrate" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal temporal" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Temp Basal" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basal temp." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Temp Basal" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Temp Basal" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basale temporanea" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "一時ベーサル" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Midlertidig basal" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tijdelijk basaal" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Baza tymczasowa" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Temp Basal" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bazală temporară" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Врем. базал" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dočasný bazál" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Temporär basal" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geçici bazal oranı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тимчасовий базал" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Temp Basal" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "临时基础率" + } + } + } + }, + "The new interface adds Trends and Logbook tabs. Turn it off to go back to the classic status screen; Settings closes to switch." : { + "comment" : "Footer for the interface choice in settings", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تضيف الواجهة الجديدة تبويبي الاتجاهات والسجل. أوقفها للعودة إلى شاشة الحالة الكلاسيكية؛ تُغلق الإعدادات للتبديل." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nové rozhraní přidává karty Trendy a Deník. Vypněte ho, chcete-li se vrátit ke klasické obrazovce stavu; Nastavení se pro přepnutí zavře." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Den nye brugerflade tilføjer fanerne Tendenser og Logbog. Slå den fra for at vende tilbage til den klassiske statusskærm; Indstillinger lukkes for at skifte." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Die neue Oberfläche fügt die Tabs Trends und Logbuch hinzu. Schalte sie aus, um zum klassischen Statusbildschirm zurückzukehren; die Einstellungen werden zum Wechseln geschlossen." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "La nueva interfaz añade las pestañas Tendencias y Registro. Desactívala para volver a la pantalla de estado clásica; Ajustes se cierra para cambiar." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uusi käyttöliittymä lisää Trendit- ja Lokikirja-välilehdet. Poista se käytöstä palataksesi perinteiseen tilanäyttöön; Asetukset suljetaan vaihtoa varten." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "La nouvelle interface ajoute les onglets Tendances et Journal. Désactivez-la pour revenir à l'écran d'état classique ; les Paramètres se ferment pour basculer." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "הממשק החדש מוסיף לשוניות מגמות ויומן. כבה אותו כדי לחזור למסך הסטטוס הקלאסי; ההגדרות ייסגרו לצורך המעבר." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Az új felület Trendek és Napló lapokkal bővül. Kapcsold ki, ha vissza szeretnél térni a klasszikus állapotképernyőhöz; a váltáshoz a Beállítások bezárul." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "La nuova interfaccia aggiunge le schede Tendenze e Diario. Disattivala per tornare alla classica schermata di stato; Impostazioni si chiude per cambiare." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "新しいインターフェイスではトレンドとログブックのタブが追加されます。オフにすると従来のステータス画面に戻ります。切り替えると設定が閉じます。" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Det nye grensesnittet legger til fanene Trender og Loggbok. Slå det av for å gå tilbake til den klassiske statusskjermen; Innstillinger lukkes for å bytte." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "De nieuwe interface voegt de tabbladen Trends en Logboek toe. Zet hem uit om terug te gaan naar het klassieke statusscherm; Instellingen sluit om te wisselen." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nowy interfejs dodaje karty Trendy i Dziennik. Wyłącz go, aby wrócić do klasycznego ekranu stanu; Ustawienia zamkną się, aby przełączyć." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "A nova interface adiciona as abas Tendências e Diário. Desligue-a para voltar à tela de status clássica; as Configurações fecham para trocar." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Noua interfață adaugă filele Tendințe și Jurnal. Dezactivați-o pentru a reveni la ecranul de stare clasic; Setările se închid pentru comutare." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Новый интерфейс добавляет вкладки «Тренды» и «Журнал». Выключите его, чтобы вернуться к классическому экрану статуса; для переключения настройки закроются." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nové rozhranie pridáva karty Trendy a Denník. Vypnite ho, ak sa chcete vrátiť ku klasickej obrazovke stavu; Nastavenia sa pri prepnutí zatvoria." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Det nya gränssnittet lägger till flikarna Trender och Loggbok. Stäng av det för att gå tillbaka till den klassiska statusskärmen; Inställningar stängs vid bytet." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yeni arayüz Eğilimler ve Kayıt Defteri sekmelerini ekler. Klasik durum ekranına dönmek için kapatın; geçiş için Ayarlar kapanır." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Новий інтерфейс додає вкладки «Тренди» і «Журнал». Вимкніть його, щоб повернутися до класичного екрана стану; для перемикання налаштування закриються." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Giao diện mới thêm các tab Xu hướng và Nhật ký. Tắt để quay lại màn hình trạng thái cổ điển; Cài đặt sẽ đóng để chuyển đổi." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "新界面增加了“趋势”和“日志”标签页。关闭后将返回经典状态屏幕;切换时设置会关闭。" + } + } + } + }, + "this day" : { + "comment" : "Trends Basal and bolus card subtitle when the period is one complete day", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "هذا اليوم" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "tento den" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "denne dag" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "dieser Tag" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "este día" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "tämä päivä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ce jour" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "יום זה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "ez a nap" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "questo giorno" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "この日" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "denne dagen" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "deze dag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "ten dzień" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "este dia" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "această zi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "этот день" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "tento deň" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "denna dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "bu gün" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "цей день" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "ngày này" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "当天" + } + } + } + }, + "Time" : { + "comment" : "Carb entry row label: the exact time the food was eaten", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الوقت" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Čas" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tidspunkt" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uhrzeit" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hora" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aika" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Heure" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "שעה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Időpont" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ora" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "時刻" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tidspunkt" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tijd" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Godzina" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hora" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ora" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Время" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Čas" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tid" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Saat" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Час" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thời gian" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "时间" + } + } + } + }, + "Time in range" : { + "comment" : "Trends card title: share of glucose readings in each range", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الوقت ضمن النطاق" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Čas v rozsahu" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tid i området" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeit im Zielbereich" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tiempo en rango" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aika tavoitealueella" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Temps dans la cible" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "זמן בטווח" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tartományban töltött idő" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tempo nell'intervallo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "範囲内時間" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tid i målområdet" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tijd binnen bereik" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Czas w zakresie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tempo no alvo" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Timp în interval" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Время в диапазоне" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Čas v rozsahu" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tid inom målområdet" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aralıkta geçen süre" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Час у діапазоні" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thời gian trong khoảng" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "目标范围内时间" + } + } + } + }, + "Time in range, daily pattern, insulin per day and Loop status" : { + "comment" : "Trends settings descriptive text", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الوقت ضمن النطاق والنمط اليومي والإنسولين اليومي وحالة Loop" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Čas v rozmezí, denní průběh, inzulin za den a stav Loop" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tid i område, døgnmønster, insulin pr. dag og Loop-status" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeit im Zielbereich, Tagesverlauf, Insulin pro Tag und Loop-Status" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tiempo en rango, patrón diario, insulina por día y estado de Loop" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aika tavoitealueella, päivittäinen kuvio, insuliini päivässä ja Loopin tila" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Temps dans la cible, profil quotidien, insuline par jour et état de Loop" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "זמן בטווח, דפוס יומי, אינסולין ליום ומצב Loop" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Céltartományban töltött idő, napi mintázat, napi inzulin és Loop állapot" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tempo nell'intervallo, andamento giornaliero, insulina al giorno e stato di Loop" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "範囲内時間、日内パターン、1日のインスリン、Loopの状態" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tid i målområdet, døgnmønster, insulin per dag og Loop-status" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tijd in bereik, dagpatroon, insuline per dag en loopstatus" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Czas w zakresie, profil dobowy, insulina na dzień i stan Loop" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tempo no alvo, padrão diário, insulina por dia e status do Loop" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Timp în interval, tipar zilnic, insulină pe zi și starea Loop" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Время в диапазоне, суточный профиль, инсулин в день и статус Loop" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Čas v rozmedzí, denný priebeh, inzulín za deň a stav Loop" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tid inom målområdet, dygnsmönster, insulin per dag och Loopstatus" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hedef aralıkta geçen süre, günlük örüntü, günlük insülin ve Loop durumu" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Час у діапазоні, добовий профіль, інсулін на день і стан Loop" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thời gian trong ngưỡng, mẫu hình hằng ngày, insulin mỗi ngày và trạng thái Loop" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "目标范围内时间、每日模式、每日胰岛素和 Loop 状态" + } + } + } + }, + "Today" : { + "comment" : "Day page title when showing today", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "اليوم" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dnes" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "I dag" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Heute" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hoy" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tänään" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aujourd'hui" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "היום" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ma" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oggi" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "今日" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "I dag" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vandaag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dziś" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hoje" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Azi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сегодня" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dnes" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "I dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bugün" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сьогодні" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hôm nay" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "今天" + } + } + } + }, + "Today so far" : { + "comment" : "Title of the card on the Now screen showing today's insulin and carbs", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "اليوم حتى الآن" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dnes zatím" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "I dag indtil nu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Heute bisher" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hoy hasta ahora" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tänään tähän mennessä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aujourd'hui jusqu'ici" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "היום עד כה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ma eddig" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oggi finora" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "今日のこれまで" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "I dag så langt" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vandaag tot nu toe" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dziś do tej pory" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hoje até agora" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Azi până acum" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сегодня на данный момент" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dnes zatiaľ" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hittills i dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bugün şu ana kadar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сьогодні наразі" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hôm nay đến giờ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "今日至今" + } + } + } + }, + "today so far" : { + "comment" : "Trends Basal and bolus card subtitle when the figures are today's total so far", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "اليوم حتى الآن" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "dnes zatím" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "i dag indtil nu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "heute bisher" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "hoy hasta ahora" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "tänään tähän mennessä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "aujourd'hui jusqu'ici" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "היום עד כה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "ma eddig" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "oggi finora" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "今日のこれまで" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "i dag så langt" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "vandaag tot nu toe" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "dziś do tej pory" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "hoje até agora" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "azi până acum" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "сегодня на данный момент" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "dnes zatiaľ" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "hittills i dag" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "bugün şu ana kadar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "сьогодні наразі" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "hôm nay đến giờ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "今日至今" + } + } + } + }, + "Today so far %1$@, usually %2$@ by %3$@." : { + "comment" : "Trends Insulin per day footnote: today's insulin so far against the median by this time of day over the last 14 days. (1: today's units, 2: usual units, 3: time of day)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "اليوم حتى الآن %1$@، وعادةً %2$@ بحلول %3$@." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dnes zatím %1$@, obvykle %2$@ do %3$@." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "I dag indtil nu %1$@, normalt %2$@ kl. %3$@." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Heute bisher %1$@, üblicherweise %2$@ bis %3$@." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hoy hasta ahora %1$@, normalmente %2$@ a las %3$@." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tänään tähän mennessä %1$@, yleensä %2$@ klo %3$@ mennessä." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aujourd'hui jusqu'ici %1$@, d'habitude %2$@ à %3$@." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "היום עד כה %1$@, בדרך כלל %2$@ עד %3$@." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ma eddig %1$@, általában %2$@ ilyenkorra (%3$@)." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oggi finora %1$@, di solito %2$@ entro le %3$@." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "今日のこれまで%1$@、通常は%3$@までに%2$@。" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "I dag så langt %1$@, vanligvis %2$@ innen %3$@." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vandaag tot nu toe %1$@, meestal %2$@ om %3$@." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dziś do tej pory %1$@, zwykle %2$@ do %3$@." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hoje até agora %1$@, normalmente %2$@ até %3$@." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Azi până acum %1$@, de obicei %2$@ până la %3$@." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сегодня на данный момент %1$@, обычно %2$@ к %3$@." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dnes zatiaľ %1$@, zvyčajne %2$@ do %3$@." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hittills i dag %1$@, vanligtvis %2$@ vid %3$@." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bugün şu ana kadar %1$@, genelde %3$@ itibarıyla %2$@." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сьогодні наразі %1$@, зазвичай %2$@ до %3$@." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hôm nay đến giờ %1$@, thường %2$@ trước %3$@." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "今日至今 %1$@,通常到 %3$@ 为 %2$@。" + } + } + } + }, + "Today · %1$@" : { + "comment" : "Logbook day header for today. (1: weekday and date, e.g. Sun 25 Oct)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "اليوم · %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dnes · %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "I dag · %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Heute · %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hoy · %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tänään · %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aujourd'hui · %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "היום · %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ma · %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oggi · %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "今日 · %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "I dag · %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vandaag · %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dziś · %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hoje · %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Azi · %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сегодня · %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dnes · %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "I dag · %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bugün · %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сьогодні · %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hôm nay · %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "今天 · %1$@" + } + } + } + }, + "Total" : { + "comment" : "Basal and bolus total row: basal plus bolus", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الإجمالي" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Celkem" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "I alt" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gesamt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yhteensä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "סה״כ" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Összesen" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totale" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "合計" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totalt" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totaal" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Razem" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Всего" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spolu" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totalt" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toplam" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Усього" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tổng" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "总计" + } + } + } + }, + "Total %1$@" : { + "comment" : "VoiceOver: total insulin. (1: units)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الإجمالي %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Celkem %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "I alt %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gesamt %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yhteensä %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "סה״כ %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Összesen %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totale %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "合計 %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totalt %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totaal %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Razem %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Всего %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spolu %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totalt %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toplam %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Усього %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tổng %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "总计 %1$@" + } + } + } + }, + "Total so far" : { + "comment" : "Basal and bolus total row for today, which isn't over yet", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الإجمالي حتى الآن" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Celkem zatím" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "I alt indtil nu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gesamt bisher" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total hasta ahora" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yhteensä tähän mennessä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total jusqu'ici" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "סה״כ עד כה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Összesen eddig" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totale finora" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "これまでの合計" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totalt så langt" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totaal tot nu toe" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Razem do tej pory" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total até agora" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total până acum" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Всего на данный момент" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spolu zatiaľ" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totalt hittills" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Şu ana kadar toplam" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Усього наразі" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tổng đến giờ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "至今总计" + } + } + } + }, + "Total so far %1$@" : { + "comment" : "VoiceOver: today's total insulin so far. (1: units)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الإجمالي حتى الآن %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Celkem zatím %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "I alt indtil nu %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gesamt bisher %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total hasta ahora %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yhteensä tähän mennessä %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total jusqu'ici %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "סה״כ עד כה %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Összesen eddig %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totale finora %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "これまでの合計 %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totalt så langt %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totaal tot nu toe %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Razem do tej pory %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total até agora %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Total până acum %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Всего на данный момент %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spolu zatiaľ %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Totalt hittills %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Şu ana kadar toplam %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Усього наразі %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tổng đến giờ %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "至今总计 %1$@" + } + } + } + }, + "Touch and hold a chart to see exact values." : { + "comment" : "Hint under the day page charts explaining how to scrub them", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "المس مخططًا مع الاستمرار لرؤية القيم الدقيقة." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Podržte prst na grafu a uvidíte přesné hodnoty." + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tryk og hold på et diagram for at se præcise værdier." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Halte ein Diagramm gedrückt, um genaue Werte zu sehen." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mantén pulsado un gráfico para ver los valores exactos." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pidä kaaviota painettuna nähdäksesi tarkat arvot." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Touchez un graphique de manière prolongée pour voir les valeurs exactes." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "גע והחזק תרשים כדי לראות ערכים מדויקים." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Érints meg és tarts lenyomva egy diagramot a pontos értékekért." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tieni premuto su un grafico per vedere i valori esatti." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "グラフを長押しすると正確な値が表示されます。" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trykk og hold på et diagram for å se nøyaktige verdier." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Houd een grafiek ingedrukt om exacte waarden te zien." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dotknij i przytrzymaj wykres, aby zobaczyć dokładne wartości." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toque e segure um gráfico para ver os valores exatos." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Țineți apăsat pe un grafic pentru a vedea valorile exacte." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нажмите и удерживайте график, чтобы увидеть точные значения." + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Podržte prst na grafe a uvidíte presné hodnoty." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tryck och håll på ett diagram för att se exakta värden." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tam değerleri görmek için bir grafiğe dokunup basılı tutun." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Торкніться й утримуйте графік, щоб побачити точні значення." + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chạm và giữ biểu đồ để xem giá trị chính xác." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "按住图表可查看精确数值。" + } + } + } + }, + "Trends" : { + "comment" : "Title of the Trends tab Title of the Trends tab in the redesigned interface", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "الاتجاهات" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trendy" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendenser" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trends" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendencias" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trendit" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendances" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מגמות" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trendek" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendenze" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "トレンド" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trender" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trends" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trendy" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendências" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tendințe" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тренды" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trendy" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trender" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eğilimler" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тренди" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Xu hướng" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "趋势" + } + } + } + }, + "U/h" : { + "comment" : "Abbreviation for insulin units per hour", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "وحدة لكل ساعة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "U/hod" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "E/t" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "IE/Std" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "U/h" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "U/h" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "U/h" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "U/hr" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "U/óra" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "U/ora" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "単位/時" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "E/t" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "E/uur" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "J/h" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "U/h" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "U/oră" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ед/ч" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "J/hod" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "IE/h" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ü/Sa" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "U/год" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "U/giờ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "U/小时" + } + } + } + }, + "unknown" : { + "comment" : "VoiceOver value when active insulin or carbs at the current time isn't known", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "غير معروف" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "neznámé" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "ukendt" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "unbekannt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "desconocido" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "tuntematon" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "inconnu" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לא ידוע" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "ismeretlen" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "sconosciuto" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "不明" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "ukjent" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "onbekend" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "nieznane" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "desconhecido" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "necunoscut" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "неизвестно" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "neznáme" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "okänt" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "bilinmiyor" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "невідомо" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "không rõ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未知" + } + } + } + }, + "Unknown error" : { + "comment" : "Reason Loop could not run: an unexpected error", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "خطأ غير معروف" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neznámá chyba" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ukendt fejl" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unbekannter Fehler" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error desconocido" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tuntematon virhe" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erreur inconnue" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "שגיאה לא ידועה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ismeretlen hiba" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Errore sconosciuto" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "不明なエラー" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ukjent feil" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Onbekende fout" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nieznany błąd" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erro desconhecido" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eroare necunoscută" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Неизвестная ошибка" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neznáma chyba" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Okänt fel" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bilinmeyen hata" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Невідома помилка" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lỗi không xác định" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未知错误" + } + } + } + }, + "Urgent low" : { + "comment" : "Pill under the glucose value on the Now screen when glucose is below 54 mg/dL (3.0 mmol/L)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "منخفض عاجل" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kriticky nízká" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Akut lav" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dringend niedrig" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Baja urgente" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vaarallisen matala" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basse urgente" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "נמוך דחוף" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sürgősen alacsony" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bassa urgente" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "緊急低値" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Akutt lav" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Urgent laag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Krytycznie niska" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Baixa urgente" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Urgent scăzută" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опасно низкая" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kriticky nízka" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Akut lågt" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Acil düşük" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Небезпечно низька" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thấp khẩn cấp" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "紧急低血糖" + } + } + } + }, + "Usually %1$@ by %2$@" : { + "comment" : "Trends: the median insulin delivered by this time of day over the last 14 days. (1: units, e.g. 28.4 U, 2: time of day)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "عادةً %1$@ بحلول %2$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obvykle %1$@ do %2$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Normalt %1$@ kl. %2$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Üblicherweise %1$@ bis %2$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Normalmente %1$@ a las %2$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yleensä %1$@ klo %2$@ mennessä" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "D'habitude %1$@ à %2$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "בדרך כלל %1$@ עד %2$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Általában %1$@ ilyenkorra (%2$@)" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Di solito %1$@ entro le %2$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "通常は%2$@までに%1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vanligvis %1$@ innen %2$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meestal %1$@ om %2$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zwykle %1$@ do %2$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Normalmente %1$@ até %2$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "De obicei %1$@ până la %2$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обычно %1$@ к %2$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zvyčajne %1$@ do %2$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vanligtvis %1$@ vid %2$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Genelde %2$@ itibarıyla %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Зазвичай %1$@ до %2$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thường %1$@ trước %2$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "通常到 %2$@ 为 %1$@" + } + } + } + }, + "Very high" : { + "comment" : "Time in range band: glucose above 250 mg/dL (13.9 mmol/L) Time-in-range band name: glucose above 250 mg/dL (13.9 mmol/L)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "مرتفع جدًا" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Velmi vysoká" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meget høj" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sehr hoch" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Muy alta" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erittäin korkea" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Très élevée" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "גבוה מאוד" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nagyon magas" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Molto alta" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "非常に高い" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Svært høy" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeer hoog" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bardzo wysoka" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Muito alta" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Foarte ridicată" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Очень высокая" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Veľmi vysoká" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mycket högt" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Çok yüksek" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дуже висока" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rất cao" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "非常高" + } + } + } + }, + "Very low" : { + "comment" : "Time in range band: glucose below 54 mg/dL (3.0 mmol/L) Time-in-range band name: glucose below 54 mg/dL (3.0 mmol/L)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "منخفض جدًا" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Velmi nízká" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meget lav" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sehr niedrig" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Muy baja" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erittäin matala" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Très basse" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "נמוך מאוד" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nagyon alacsony" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Molto bassa" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "非常に低い" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Svært lav" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeer laag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bardzo niska" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Muito baixa" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Foarte scăzută" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Очень низкая" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Veľmi nízka" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mycket lågt" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Çok düşük" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дуже низька" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rất thấp" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "非常低" + } + } + } + }, + "vs plan" : { + "comment" : "Logbook basal adjustments row: the insulin amount is the difference from the scheduled basal", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "مقارنة بالمجدول" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "oproti plánu" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "ift. plan" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "ggü. Plan" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "vs. lo programado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "vs. ohjelmoitu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "vs prévu" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לעומת התוכנית" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "a tervhez képest" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "vs programmata" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "予定比" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "mot plan" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "t.o.v. gepland" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "wzgl. planu" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "vs. programado" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "față de program" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "к плану" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "oproti plánu" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "mot schema" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "plana göre" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "до плану" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "so với lịch" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "较预设" + } + } + } + }, + "Yesterday · %1$@" : { + "comment" : "Logbook day header for yesterday. (1: weekday and date, e.g. Sat 24 Oct)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "أمس · %1$@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Včera · %1$@" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "I går · %1$@" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gestern · %1$@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ayer · %1$@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eilen · %1$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hier · %1$@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אתמול · %1$@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tegnap · %1$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ieri · %1$@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "昨日 · %1$@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "I går · %1$@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gisteren · %1$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wczoraj · %1$@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ontem · %1$@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ieri · %1$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вчера · %1$@" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Včera · %1$@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "I går · %1$@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dün · %1$@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Учора · %1$@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hôm qua · %1$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "昨天 · %1$@" + } + } + } }, " - " : { @@ -2423,6 +35696,48 @@ "state" : "translated", "value" : "%1$@ U" } + }, + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ وحدة" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ 単位" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ U" + } } } }, @@ -5631,6 +38946,24 @@ "state" : "translated", "value" : "添加CGM" } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Přidat CGM" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "CGM hozzáadása" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Додати CGM" + } } } }, @@ -9054,7 +42387,147 @@ "comment" : "title for when automation was unsuccessful" }, "Average" : { - "comment" : "Average glucose metric title" + "comment" : "Average glucose metric title", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "المتوسط" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Průměr" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gennemsnit" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Durchschnitt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Promedio" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keskiarvo" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Moyenne" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "ממוצע" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Átlag" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Media" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "平均" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gjennomsnitt" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gemiddeld" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Średnia" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Média" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Medie" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Среднее" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Priemer" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Medelvärde" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ortalama" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Середнє" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trung bình" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "平均值" + } + } + } }, "Average Glucose" : { @@ -9922,6 +43395,24 @@ "state" : "translated", "value" : "大剂量" } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bólus" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Болюс" + } } } }, @@ -15785,6 +49276,30 @@ "state" : "translated", "value" : "删除" } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Smazat" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Törlés" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "削除" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Видалити" + } } } }, @@ -21095,6 +54610,24 @@ "state" : "translated", "value" : "克" } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "g" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "g" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "г" + } } } }, @@ -21307,6 +54840,18 @@ "state" : "translated", "value" : "血糖" } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glükóz" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Глюкоза" + } } } }, @@ -22061,7 +55606,147 @@ "comment" : "Apple Health settings descriptive text" }, "GMI" : { - "comment" : "Glucose Management Indicator metric title" + "comment" : "Glucose Management Indicator metric title", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "GMI" + } + } + } }, "Go to Settings > Focus." : { "comment" : "Focus modes step 1" @@ -22159,7 +55844,147 @@ }, "High" : { - "comment" : "Time-in-range band label (181–250 mg/dL)" + "comment" : "Time-in-range band label (181–250 mg/dL)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "مرتفع" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vysoká" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Høj" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hoch" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alta" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Korkea" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Élevée" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "גבוה" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Magas" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alta" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "高値" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Høy" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hoog" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wysoka" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alta" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ridicată" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Высокая" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vysoká" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Högt" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yüksek" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Висока" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cao" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "高" + } + } + } }, "High Glucose" : { "localizations" : { @@ -23121,6 +56946,30 @@ "state" : "translated", "value" : "胰岛素" } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulín" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulin" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inzulín" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Інсулін" + } } } }, @@ -27024,7 +60873,147 @@ } }, "Low" : { - "comment" : "Time-in-range band label (54–69 mg/dL)" + "comment" : "Time-in-range band label (54–69 mg/dL)", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "منخفض" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nízká" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lav" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Niedrig" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Baja" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Matala" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basse" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "נמוך" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alacsony" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bassa" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "低値" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lav" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Laag" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Niska" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Baixa" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scăzută" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Низкая" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nízka" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lågt" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Düşük" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Низька" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thấp" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "低" + } + } + } }, "Low Glucose" : { "comment" : "Title for bolus screen warning when glucose is below glucose warning limit.\nTitle for bolus screen warning when glucose is below suspend threshold, but a bolus is recommended", @@ -27272,7 +61261,146 @@ } }, "Manual" : { - + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يدوي" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ručně" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuel" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuell" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manual" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuaalinen" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuel" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "ידני" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kézi" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuale" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "手動" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuell" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Handmatig" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ręcznie" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manual" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manual" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вручную" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ručne" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuell" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuel" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вручну" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thủ công" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "手动" + } + } + } }, "Manual Dose: %1$@ %2$@" : { "comment" : "Description of a bolus dose entry (1: value (? if no value) in bold, 2: unit)", @@ -31045,6 +65173,18 @@ "state" : "translated", "value" : "OK" } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } } } }, @@ -31221,7 +65361,147 @@ "isCommentAutoGenerated" : true }, "Other" : { - "comment" : "The group name for plugins that do not declare a manufacturer or category" + "comment" : "The group name for plugins that do not declare a manufacturer or category", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "أخرى" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jiné" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Andet" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Andere" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otro" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Muu" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Autre" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אחר" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Egyéb" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Altro" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "その他" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Annet" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anders" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inne" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Outro" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Altul" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Другое" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Iné" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Annat" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diğer" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Інше" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Khác" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "其他" + } + } + } }, "Override Presets" : { "comment" : "The title text for the override presets", @@ -32137,317 +66417,457 @@ } } }, - "prediction-description-integral-retrospective-correction" : { - "comment" : "Format string describing integral retrospective correction. (1: Integral glucose effect)(2: Total glucose effect)", + "prediction-description-integral-retrospective-correction" : { + "comment" : "Format string describing integral retrospective correction. (1: Integral glucose effect)(2: Total glucose effect)", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "prediction-description-integral-retrospective-correction" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Integraler Effekt: %1$@ \n Gesamtglukoseeffekt: %2$@" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Integral effect: %1$@\nTotal glucose effect: %2$@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Effet intégral: %1$@\nEffet total sur la glycémie: %2$@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "previsione-descrizione-integrale-correzione-retrospettiva" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "prediksjon-beskrivelse-integral-retrospektiv-korreksjon" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Integraal effect: %1$@\nTotaal glucose-effect: %2$@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "predykcja-opis-całka-retrospektywna-korekta" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Efect integral: %1$@ \nEfect total al glicemiei: %2$@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "описание прогнозирования с помощью интегральной ретроспективной коррекции" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "积分效应: %1$@\n总葡萄糖效应: %2$@" + } + } + } + }, + "prediction-description-retrospective-correction" : { + "comment" : "Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference)", + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "التوقع: %1$@\nالواقع: %2$@ (%3$@)" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forventet: %1$@\nFaktisk: %2$@ (%3$@)" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vorhergesagt: %1$@\nAktuell: %2$@ (%3$@)" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predicted: %1$@\nActual: %2$@ (%3$@)" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predicción: %1$@\nActual: %2$@ (%3$@)" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ennustettu: %1$@\nTodellinen: %2$@ (%3$@)" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prédit : %1$@\nRéel : %2$@ (%3$@)" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "חזוי: %1$@\nבפועל: %2$@ (%3$@)" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Previsto: %1$@\nEffettivo: %2$@ (%3$@)" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predicted: %1$@\nActual: %2$@ (%3$@)" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forventet: %1$@\nFaktisk: %2$@ ( %3$@ )" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voorspeld: %1$@\nActueel: %2$@ (%3$@)" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Przewidywana: %1$@Rzeczywista: %2$@ (%3$@)" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prevista: %1$@\nAtual: %2$@ (%3$@)" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prognozată: %1$@\nActuală: %2$@ (%3$@)" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прогноз: %1$@\nФакт: %2$@ (%3$@)" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Förväntat: %1$@\nFaktiskt: %2$@ (%3$@)" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tahmini: %1$@\nGüncel: %2$@ (%3$@)" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Được dự đoán: %1$@\nThực tế: %2$@ (%3$@)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "预测值: %1$@\n实际值: %2$@ (%3$@)" + } + } + } + }, + "Preparing Critical Event Logs" : { + "comment" : "Preparing critical event log text", "localizations" : { "da" : { "stringUnit" : { "state" : "translated", - "value" : "prediction-description-integral-retrospective-correction" + "value" : "Forbereder kritiske begivenhedslogs" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Integraler Effekt: %1$@ \n Gesamtglukoseeffekt: %2$@" + "value" : "Vorbereiten der kritischen Ereignis-Protokolle" } }, - "en" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Integral effect: %1$@\nTotal glucose effect: %2$@" + "value" : "Preparando registros de eventos críticos" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Valmistellaan tärkeiden tapahtumien lokia" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Effet intégral: %1$@\nEffet total sur la glycémie: %2$@" + "value" : "Préparation des journaux d’événements critiques" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מכין יומן שגיאות קריטיות" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "previsione-descrizione-integrale-correzione-retrospettiva" + "value" : "Preparazione dei registri degli eventi critici in corso" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "prediksjon-beskrivelse-integral-retrospektiv-korreksjon" + "value" : "Forbereder logg av kritiske hendelser" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Integraal effect: %1$@\nTotaal glucose-effect: %2$@" + "value" : "Kritieke gebeurtenislogboek voorbereiden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "predykcja-opis-całka-retrospektywna-korekta" + "value" : "Przygotowywanie dzienników zdarzeń krytycznych" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Efect integral: %1$@ \nEfect total al glicemiei: %2$@" + "value" : "Pregătire jurnal de evenimente critice" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "описание прогнозирования с помощью интегральной ретроспективной коррекции" + "value" : "Подготовка логов критических событий" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Förbereder kritiska händelseloggar" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kritik Olay Günlüklerinin Hazırlanması" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "积分效应: %1$@\n总葡萄糖效应: %2$@" + "value" : "正在准备关键事件日志" } } } }, - "prediction-description-retrospective-correction" : { - "comment" : "Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference)", - "extractionState" : "manual", + "Preset +6 Hours" : { + "comment" : "Title for a section of a screen that shows a comparison of a user's glucose performance during a preset and 6 hours after the preset.", + "isCommentAutoGenerated" : true + }, + "Preset Delivery" : { + + }, + "Preset Disabled" : { + + }, + "Preset Enabled" : { + + }, + "Preset Summary" : { + + }, + "Presets" : { + "comment" : "Presets screen title\nTitle text for button to Preset Settings", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "التوقع: %1$@\nالواقع: %2$@ (%3$@)" + "value" : "الإعدادات المسبقة" } }, - "da" : { + "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Forventet: %1$@\nFaktisk: %2$@ (%3$@)" + "value" : "Předvolby" } }, - "de" : { + "da" : { "stringUnit" : { "state" : "translated", - "value" : "Vorhergesagt: %1$@\nAktuell: %2$@ (%3$@)" + "value" : "Forudindstillinger" } }, - "en" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Predicted: %1$@\nActual: %2$@ (%3$@)" + "value" : "Voreinstellungen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Predicción: %1$@\nActual: %2$@ (%3$@)" + "value" : "Ajustes preestablecidos" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Ennustettu: %1$@\nTodellinen: %2$@ (%3$@)" + "value" : "Esiasetukset" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Prédit : %1$@\nRéel : %2$@ (%3$@)" + "value" : "Préréglages" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "חזוי: %1$@\nבפועל: %2$@ (%3$@)" + "value" : "הגדרות מראש" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Előbeállítások" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Previsto: %1$@\nEffettivo: %2$@ (%3$@)" + "value" : "Preimpostazioni" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "Predicted: %1$@\nActual: %2$@ (%3$@)" + "value" : "プリセット" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Forventet: %1$@\nFaktisk: %2$@ ( %3$@ )" + "value" : "Forhåndsinnstillinger" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Voorspeld: %1$@\nActueel: %2$@ (%3$@)" + "value" : "Programma's" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przewidywana: %1$@Rzeczywista: %2$@ (%3$@)" + "value" : "Ustawienia wstępne" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Prevista: %1$@\nAtual: %2$@ (%3$@)" + "value" : "Predefinições" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Prognozată: %1$@\nActuală: %2$@ (%3$@)" + "value" : "Presetări" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Прогноз: %1$@\nФакт: %2$@ (%3$@)" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "Förväntat: %1$@\nFaktiskt: %2$@ (%3$@)" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tahmini: %1$@\nGüncel: %2$@ (%3$@)" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Được dự đoán: %1$@\nThực tế: %2$@ (%3$@)" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "预测值: %1$@\n实际值: %2$@ (%3$@)" - } - } - } - }, - "Preparing Critical Event Logs" : { - "comment" : "Preparing critical event log text", - "localizations" : { - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "Forbereder kritiske begivenhedslogs" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vorbereiten der kritischen Ereignis-Protokolle" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Preparando registros de eventos críticos" - } - }, - "fi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Valmistellaan tärkeiden tapahtumien lokia" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Préparation des journaux d’événements critiques" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "מכין יומן שגיאות קריטיות" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Preparazione dei registri degli eventi critici in corso" - } - }, - "nb" : { - "stringUnit" : { - "state" : "translated", - "value" : "Forbereder logg av kritiske hendelser" + "value" : "Пресеты" } }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kritieke gebeurtenislogboek voorbereiden" - } - }, - "pl" : { + "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Przygotowywanie dzienników zdarzeń krytycznych" + "value" : "Predvoľby" } }, - "ro" : { + "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Pregătire jurnal de evenimente critice" + "value" : "Förinställningar" } }, - "ru" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Подготовка логов критических событий" + "value" : "Ön Ayarlar" } }, - "sv" : { + "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Förbereder kritiska händelseloggar" + "value" : "Пресети" } }, - "tr" : { + "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Kritik Olay Günlüklerinin Hazırlanması" + "value" : "Cài đặt sẵn" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "正在准备关键事件日志" + "value" : "预设" } } } }, - "Preset +6 Hours" : { - "comment" : "Title for a section of a screen that shows a comparison of a user's glucose performance during a preset and 6 hours after the preset.", - "isCommentAutoGenerated" : true - }, - "Preset Delivery" : { - - }, - "Preset Disabled" : { - - }, - "Preset Enabled" : { - - }, - "Preset Summary" : { - - }, - "Presets" : { - "comment" : "Presets screen title\nTitle text for button to Preset Settings" - }, "Presets can be used for a variety of situations. Explore the uses below to learn tips for these common scenarios." : { }, @@ -32848,6 +67268,30 @@ "state" : "translated", "value" : "胰岛素泵" } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpa" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpa" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pumpa" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Помпа" + } } } }, @@ -35531,6 +69975,18 @@ "state" : "translated", "value" : "重试" } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Újra" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Спробувати ще раз" + } } } }, @@ -35876,6 +70332,66 @@ "state" : "translated", "value" : "预设" } + }, + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "المجدول" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plánovaný" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ohjelmoitu" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ütemezett" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "スケジュール" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Programada" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plánovaný" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schemalagd" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "За розкладом" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Theo lịch" + } } } }, @@ -36303,6 +70819,24 @@ "state" : "translated", "value" : "设置" } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nastavení" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Beállítások" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Налаштування" + } } } }, @@ -40595,6 +75129,24 @@ "state" : "translated", "value" : "U" } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "U" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "U" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "U" + } } } }, diff --git a/Loop/Managers/LoopAppManager.swift b/Loop/Managers/LoopAppManager.swift index 10d4fcfc12..85ae6914e9 100644 --- a/Loop/Managers/LoopAppManager.swift +++ b/Loop/Managers/LoopAppManager.swift @@ -517,6 +517,19 @@ class LoopAppManager: NSObject { analyticsServicesManager.application(didFinishLaunchingWithOptions: launchOptions) + // Loop's automation history only goes back 36 hours, so the Trends screen keeps its own longer record. + // The debounce keeps a quick off-and-on from being recorded as open loop. + ObservablePublishers.tracking(settingsManager, keyPath: \.dosingEnabled) + .debounce(for: .seconds(15), scheduler: RunLoop.main) + .removeDuplicates() + .sink { LoopModeHistory.shared.record(closedLoop: $0) } + .store(in: &cancellables) + + NotificationCenter.default.publisher(for: LoopInterfacePreference.didChange) + .receive(on: RunLoop.main) + .sink { [weak self] _ in self?.relaunchHomeScreenForInterfaceChange() } + .store(in: &cancellables) + state = state.next await loopDataManager.updateDisplayState() @@ -618,14 +631,14 @@ class LoopAppManager: NSObject { return SetupHistory(settings: settings, experiments: experiments) } viewModel.outcomesPackageBuilder = criticalEventLogExportManager.map { OutcomesPackageBuilder(logs: $0.logs) } + viewModel.historyProvider = LoopHistoryProvider(deviceManager: deviceDataManager, loopDataManager: loopDataManager, + settingsManager: settingsManager, temporaryPresetsManager: temporaryPresetsManager) return viewModel }() - private func launchHomeScreen() async { - dispatchPrecondition(condition: .onQueue(.main)) - precondition(state == .launchHomeScreen) - + /// Builds the status screen and makes it (or the redesign's tabs around it) the window's root. + private func installHomeScreen() { let viewModel = StatusTableViewModel( alertPermissionsChecker: alertPermissionsChecker, alertMuter: alertManager.alertMuter, @@ -651,26 +664,57 @@ class LoopAppManager: NSObject { self.statusTableViewController = statusTableView.viewController - var rootNavigationController = rootViewController as? RootNavigationController - if rootNavigationController == nil { - rootNavigationController = RootNavigationController() - rootViewController = rootNavigationController - } + let showsActionTabs = !LoopInterfacePreference.useRedesign + let statusScreen = UIHostingController( + rootView: statusTableView + .showsActionTabs(showsActionTabs) + .environmentObject(deviceDataManager.displayGlucosePreference) + .environment(\.appName, Bundle.main.bundleDisplayName) + .environment(\.isInvestigationalDevice, FeatureFlags.isInvestigationalDevice) + .environment(\.guidanceColors, .default) + .environment(\.loopStatusColorPalette, .loopStatus) + .environment(\.colorPalette, .default) + .environment(\.settingsManager, settingsManager) + .environment(\.temporaryPresetsManager, temporaryPresetsManager) + .edgesIgnoringSafeArea(.top) + ) - rootNavigationController?.setViewControllers([ - UIHostingController( - rootView: statusTableView - .environmentObject(deviceDataManager.displayGlucosePreference) - .environment(\.appName, Bundle.main.bundleDisplayName) - .environment(\.isInvestigationalDevice, FeatureFlags.isInvestigationalDevice) - .environment(\.guidanceColors, .default) - .environment(\.loopStatusColorPalette, .loopStatus) - .environment(\.colorPalette, .default) - .environment(\.settingsManager, settingsManager) - .environment(\.temporaryPresetsManager, temporaryPresetsManager) - .edgesIgnoringSafeArea(.top) + if LoopInterfacePreference.useRedesign { + let rootNavigationController = RootNavigationController() + rootNavigationController.setViewControllers([statusScreen], animated: false) + rootViewController = MainTabBarController( + rootNavigationController: rootNavigationController, + statusTableViewController: statusTableView.viewController, + deviceManager: deviceDataManager, + loopDataManager: loopDataManager, + settingsManager: settingsManager ) - ], animated: true) + } else { + var rootNavigationController = rootViewController as? RootNavigationController + if rootNavigationController == nil { + rootNavigationController = RootNavigationController() + rootViewController = rootNavigationController + } + + rootNavigationController?.setViewControllers([statusScreen], animated: true) + } + } + + /// Swaps between the classic and redesigned interface once Loop is running. + private func relaunchHomeScreenForInterfaceChange() { + guard state == .launchComplete else { return } + if let presented = rootViewController?.presentedViewController { + presented.dismiss(animated: true) { [weak self] in self?.installHomeScreen() } + } else { + installHomeScreen() + } + } + + private func launchHomeScreen() async { + dispatchPrecondition(condition: .onQueue(.main)) + precondition(state == .launchHomeScreen) + + installHomeScreen() await deviceDataManager.refreshDeviceData() @@ -754,6 +798,7 @@ class LoopAppManager: NSObject { } analyticsServicesManager.didOpenFromLink() + (rootViewController as? MainTabBarController)?.showNow() switch deeplink { case let .carbEntry(carbEntryLink): diff --git a/Loop/Redesign/CarbEntryScreen.swift b/Loop/Redesign/CarbEntryScreen.swift new file mode 100644 index 0000000000..742fe73558 --- /dev/null +++ b/Loop/Redesign/CarbEntryScreen.swift @@ -0,0 +1,791 @@ +// +// CarbEntryScreen.swift +// Loop +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import SwiftUI +import LoopKit +import LoopKitUI +import Charts + +private typealias P = LoopDesign.Palette + +/// Adding or editing carbs in the redesign. It drives the same CarbEntryViewModel as CarbEntryView, so validation, +/// favourite foods and the hand-off to the bolus screen are unchanged; only the layout is new. +/// +/// The top of the screen is a preview of the meal as Loop will model it: the amount, over Loop's absorption curve +/// drawn on a real time axis. The amount sets the curve's height, the speed its length, and when it was eaten where +/// it sits against now, so every choice below shows its effect there. +struct CarbEntryScreen: View { + @EnvironmentObject private var displayGlucosePreference: DisplayGlucosePreference + @Environment(\.dismissAction) private var dismiss + @Environment(\.guidanceColors) private var guidanceColors + @Environment(\.dynamicTypeSize) private var typeSize + @Environment(\.accessibilityReduceMotion) private var reduceMotion + + @ObservedObject var viewModel: CarbEntryViewModel + + @State private var amountText: String + @State private var eatenChoice: Int? + @State private var showHowAbsorptionTimeWorks = false + @State private var showAddFavoriteFood = false + /// Other was tapped, so it stays chosen even while its food and time still match a preset. + @State private var otherChosen = false + @FocusState private var amountFocused: Bool + @FocusState private var foodTypeFocused: Bool + @Namespace private var thumbs + + private let isNewEntry: Bool + + /// Minutes before now offered as one-tap times. + private static let eatenChoices = [0, 15, 30, 60] + + init(viewModel: CarbEntryViewModel) { + self.viewModel = viewModel + isNewEntry = viewModel.originalCarbEntry == nil + _amountText = State(initialValue: viewModel.carbsQuantity.flatMap { $0 > 0 ? Self.amountFormatter.string(from: NSNumber(value: $0)) : nil } ?? "") + _eatenChoice = State(initialValue: viewModel.originalCarbEntry == nil && abs(viewModel.time.timeIntervalSinceNow) < 60 ? 0 : nil) + } + + var body: some View { + if isNewEntry { + NavigationStack { + content + .navigationTitle(NSLocalizedString("Add carbs", comment: "Button on the Now screen that opens carb entry")) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button(NSLocalizedString("Cancel", comment: "Button label for cancel"), action: dismiss) + } + } + } + } else { + content + } + } + + private var content: some View { + ScrollView { + VStack(alignment: .leading, spacing: 22) { + if let override = viewModel.currentOverride { + ActivePresetBanner(override: override) + } + ForEach(Array(viewModel.warnings).sorted { $0.priority < $1.priority }) { warning in + warningView(warning) + } + mealPreview + if isNewEntry, FeatureFlags.allowExperimentalFeatures { + favourites + } + absorption + eaten + } + .padding(.horizontal, LoopDesign.Space.s4) + .padding(.top, LoopDesign.Space.s2) + .padding(.bottom, LoopDesign.Space.s6) + } + // Any scroll puts the keyboard away: at large text sizes it otherwise covers the rest of the form for good. + .scrollDismissesKeyboard(.immediately) + .toolbar { + if amountFocused || foodTypeFocused { + ToolbarItem(placement: .navigationBarTrailing) { + Button("Done") { + amountFocused = false + foodTypeFocused = false + } + } + } + } + .background(P.surface) + .safeAreaInset(edge: .bottom) { continueBar } + .onAppear { + // As FoodTypeRow does: a new entry starts as the medium food, so the entry carries its emoji. + if isNewEntry && !viewModel.usesCustomFoodType && viewModel.selectedDefaultAbsorptionTimeEmoji.isEmpty { + viewModel.selectedDefaultAbsorptionTimeEmoji = Speed.medium.emoji + } + } + .task { + // Focus set while the sheet is still presenting is dropped, so wait for it to settle. + guard viewModel.shouldBeginEditingQuantity else { return } + try? await Task.sleep(for: .milliseconds(450)) + amountFocused = true + } + .onChange(of: viewModel.carbsQuantity) { _, quantity in + // A favourite fills the amount in. Typing already shows its own amount ("3." is 3), so only a change from + // elsewhere rewrites the field. Focus can't decide this: a tap's unfocus hasn't applied when this runs. + guard Self.amountFormatter.number(from: amountText)?.doubleValue != quantity else { return } + amountText = quantity.flatMap { $0 > 0 ? Self.amountFormatter.string(from: NSNumber(value: $0)) : nil } ?? "" + } + .navigationDestination(isPresented: Binding(get: { viewModel.bolusViewModel != nil }, set: { if !$0 { viewModel.bolusViewModel = nil } })) { + if let bolusViewModel = viewModel.bolusViewModel { + BolusEntryView(viewModel: bolusViewModel) + .environmentObject(displayGlucosePreference) + .environment(\.dismissAction, dismiss) + .environment(\.guidanceColors, guidanceColors) + } + } + .alert(item: $viewModel.alert, content: alert(for:)) + .sheet(isPresented: $showHowAbsorptionTimeWorks) { + HowAbsorptionTimeWorksView() + } + .sheet(isPresented: $showAddFavoriteFood) { + FavoriteFoodAddEditView(carbsQuantity: viewModel.carbsQuantity, + foodType: viewModel.usesCustomFoodType ? viewModel.foodType : viewModel.selectedDefaultAbsorptionTimeEmoji, + absorptionTime: viewModel.absorptionTime) { food in + showAddFavoriteFood = false + viewModel.onFavoriteFoodSave(food) + } + } + } + + private func animated(_ change: () -> Void) { + if reduceMotion { + change() + } else { + withAnimation(.snappy(duration: 0.3), change) + } + } + + // MARK: Meal preview + + private static let amountFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 1 + formatter.usesGroupingSeparator = false + return formatter + }() + + private var mealPreview: some View { + VStack(alignment: .leading, spacing: LoopDesign.Space.s2) { + HStack(alignment: .firstTextBaseline, spacing: LoopDesign.Space.s1) { + TextField(text: $amountText, prompt: Text(verbatim: "0").foregroundStyle(P.inkMuted.opacity(0.45))) { + Text(NSLocalizedString("Amount Consumed", comment: "Label for carb quantity entry row on carb entry screen")) + } + .font(.system(size: 72, weight: .bold, design: .rounded).monospacedDigit()) + .foregroundStyle(P.carbsInk) + .tint(P.carbsInk) + .keyboardType(.decimalPad) + .accessibilityLabel(NSLocalizedString("Amount Consumed", comment: "Label for carb quantity entry row on carb entry screen")) + .accessibilityIdentifier("textField_CarbsAmount") + .fixedSize() + .focused($amountFocused) + .onChange(of: amountText) { _, text in + viewModel.carbsQuantity = Self.amountFormatter.number(from: text)?.doubleValue + } + Text(NSLocalizedString("g", comment: "Abbreviation for grams")) + .font(.system(size: 30, weight: .semibold, design: .rounded)) + .foregroundStyle(P.inkMuted) + .accessibilityHidden(true) + Spacer(minLength: 0) + } + .contentShape(Rectangle()) + .onTapGesture { amountFocused = true } + + TimelineView(.everyMinute) { context in + MealAbsorptionChart(grams: viewModel.carbsQuantity ?? 0, + absorptionTime: viewModel.absorptionTime, + eaten: viewModel.time, + now: context.date, + axisLength: max(viewModel.defaultAbsorptionTimes.slow, viewModel.absorptionTime)) + } + } + .padding(.horizontal, LoopDesign.Space.s4) + .padding(.top, LoopDesign.Space.s1) + .padding(.bottom, LoopDesign.Space.s4) + .background(P.surfaceRaised, in: RoundedRectangle(cornerRadius: 22, style: .continuous)) + } + + // MARK: Favourites + + @ViewBuilder + private var favourites: some View { + if !viewModel.favoriteFoods.isEmpty || !viewModel.saveFavoriteFoodButtonDisabled { + VStack(alignment: .leading, spacing: LoopDesign.Space.s2) { + sectionTitle(NSLocalizedString("Favorite Foods", comment: "The section title for Carb entry screen where Favorite Foods can be selected")) + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: LoopDesign.Space.s2) { + ForEach(Array(viewModel.favoriteFoods.enumerated()), id: \.element.id) { index, food in + let selected = viewModel.selectedFavoriteFoodIndex == index + Button { + amountFocused = false + // A favourite brings its own food and time, so the speed shown follows it rather than an earlier Other. + otherChosen = false + animated { + viewModel.selectedFavoriteFoodIndex = selected ? -1 : index + // Letting a new meal's favourite go resets it to medium absorption, so the speed shown and + // the food type saved go back to medium too, rather than a speed chosen before the favourite. + if selected && viewModel.originalCarbEntry == nil { + viewModel.selectedDefaultAbsorptionTimeEmoji = Speed.medium.emoji + } + } + } label: { + HStack(spacing: 6) { + Text(food.foodType) + Text(food.name).foregroundStyle(P.ink) + Text(LoopFormat.grams(food.carbsQuantity.doubleValue(for: .gram))) + .monospacedDigit() + .foregroundStyle(selected ? P.carbsInk : P.inkMuted) + } + .font(LoopDesign.Typeface.subhead.weight(.medium)) + .padding(.horizontal, 14) + .frame(minHeight: 40) + .background(selected ? P.carbs.opacity(0.18) : P.surfaceRaised, in: Capsule()) + .overlay(Capsule().strokeBorder(selected ? P.carbs : .clear, lineWidth: 1.5)) + } + .buttonStyle(.plain) + .accessibilityAddTraits(selected ? .isSelected : []) + } + if viewModel.selectedFavoriteFood == nil { + Button { + showAddFavoriteFood = true + } label: { + Label(NSLocalizedString("Save as favorite food", comment: "Button on the carb entry screen to save the entry as a favourite food"), systemImage: "star") + .font(LoopDesign.Typeface.subhead.weight(.medium)) + .padding(.horizontal, 14) + .frame(minHeight: 40) + .overlay(Capsule().strokeBorder(P.separator.opacity(0.7), style: StrokeStyle(lineWidth: 1, dash: [3, 3]))) + } + .buttonStyle(.plain) + .foregroundStyle(P.accentInk) + .disabled(viewModel.saveFavoriteFoodButtonDisabled) + .opacity(viewModel.saveFavoriteFoodButtonDisabled ? 0.4 : 1) + } + } + .padding(.vertical, 1) + } + .scrollClipDisabled() + } + } + } + + // MARK: Eaten + + /// Loop's usual 12 hours back, reaching further for an older entry being edited: the picker shows a clamped time + /// otherwise, one the entry never had. + private var eatenRange: ClosedRange { + let earliest = viewModel.originalCarbEntry.map { min($0.startDate, viewModel.minimumDate) } ?? viewModel.minimumDate + return earliest...viewModel.maximumDate + } + + private func eatenTitle(_ minutes: Int) -> String { + minutes == 0 + ? NSLocalizedString("Now", comment: "Carb entry time choice: eaten now") + : String(format: NSLocalizedString("%1$@ ago", comment: "Carb entry time choice: eaten this long before now. (1: duration, e.g. 15m or 1h)"), LoopFormat.duration(Double(minutes) * 60)) + } + + private var eaten: some View { + VStack(alignment: .leading, spacing: LoopDesign.Space.s2) { + let header = typeSize.isAccessibilitySize + ? AnyLayout(VStackLayout(alignment: .leading, spacing: LoopDesign.Space.s2)) + : AnyLayout(HStackLayout(alignment: .center)) + header { + sectionTitle(NSLocalizedString("Eaten", comment: "Carb entry section title: when the food was eaten")) + if !typeSize.isAccessibilitySize { Spacer() } + DatePicker(selection: Binding(get: { viewModel.time }, set: { viewModel.time = $0; eatenChoice = nil }), + in: eatenRange, displayedComponents: [.date, .hourAndMinute]) { + Text(NSLocalizedString("Time", comment: "Carb entry row label: the exact time the food was eaten")) + } + .labelsHidden() + .tint(P.carbsInk) + } + ChoiceTrack(choices: Self.eatenChoices, selection: eatenChoice, thumb: "eaten", namespace: thumbs) { minutes in + amountFocused = false + let date = Date().addingTimeInterval(-Double(minutes) * 60) + animated { + eatenChoice = minutes + viewModel.time = min(max(date, viewModel.minimumDate), viewModel.maximumDate) + } + } label: { minutes, selected in + Text(eatenTitle(minutes)) + .font(LoopDesign.Typeface.subhead.weight(selected ? .semibold : .medium).monospacedDigit()) + .foregroundStyle(selected ? P.carbsInk : P.ink) + .lineLimit(1) + .minimumScaleFactor(0.8) + .padding(.vertical, LoopDesign.Space.s2) + } + } + } + + // MARK: Absorption + + enum Speed: CaseIterable { + case fast, medium, slow, other + + /// The emoji FoodTypeRow uses for each default absorption time; the entry stores it as its food type. + var emoji: String { + switch self { + case .fast: return "🍭" + case .medium: return "🌮" + case .slow: return "🍕" + case .other: return "🍽️" + } + } + + var title: String { + switch self { + case .fast: return NSLocalizedString("Fast", comment: "Carb entry absorption choice: food that absorbs quickly, such as sweets") + case .medium: return NSLocalizedString("Medium", comment: "Carb entry absorption choice: a typical mixed meal") + case .slow: return NSLocalizedString("Slow", comment: "Carb entry absorption choice: food that absorbs slowly, such as pizza") + case .other: return NSLocalizedString("Other", comment: "Carb entry absorption choice: set your own food and absorption time") + } + } + } + + private func hours(_ speed: Speed) -> TimeInterval { + switch speed { + case .fast: return viewModel.defaultAbsorptionTimes.fast + case .medium: return viewModel.defaultAbsorptionTimes.medium + case .slow: return viewModel.defaultAbsorptionTimes.slow + case .other: return viewModel.absorptionTime + } + } + + private var selectedSpeed: Speed { + let presets = Speed.allCases.filter { $0 != .other } + if viewModel.usesCustomFoodType { + if otherChosen { return .other } + // A saved entry comes back as a custom food; one that is exactly a preset's emoji and time shows as that preset. + return presets.first { $0.emoji == viewModel.foodType && hours($0) == viewModel.absorptionTime } ?? .other + } + return presets.first { $0.emoji == viewModel.selectedDefaultAbsorptionTimeEmoji } ?? .medium + } + + private var absorption: some View { + VStack(alignment: .leading, spacing: LoopDesign.Space.s2) { + HStack { + sectionTitle(NSLocalizedString("How fast it absorbs", comment: "Carb entry section title: choose how quickly the food's carbs absorb")) + Spacer() + Button { + showHowAbsorptionTimeWorks = true + } label: { + Image(systemName: "info.circle") + .font(LoopDesign.Typeface.subhead.weight(.semibold)) + .frame(minWidth: 44, minHeight: 28, alignment: .trailing) + } + .foregroundStyle(P.accentInk) + .accessibilityLabel(NSLocalizedString("Absorption Time", comment: "Accessibility label for the button that explains carb absorption time")) + } + ChoiceTrack(choices: Speed.allCases, selection: selectedSpeed, thumb: "speed", namespace: thumbs) { speed in + animated { choose(speed) } + } label: { speed, selected in + VStack(spacing: 2) { + Text(speed.title) + .font(LoopDesign.Typeface.subhead.weight(selected ? .semibold : .medium)) + .foregroundStyle(selected ? P.carbsInk : P.ink) + HStack(spacing: 3) { + // The food the entry is saved with; Other shows the one typed in. + Text(speed == .other && selected && !viewModel.foodType.isEmpty ? viewModel.foodType : speed.emoji) + .dynamicTypeSize(...DynamicTypeSize.xxxLarge) + Text(durationLabel(speed, selected: selected)) + .foregroundStyle(selected ? P.carbsInk : P.inkMuted) + } + .font(LoopDesign.Typeface.footnote.monospacedDigit()) + } + .lineLimit(1) + .minimumScaleFactor(0.75) + .padding(.vertical, 10) + .accessibilityElement(children: .ignore) + .accessibilityLabel("\(speed.title), \(durationLabel(speed, selected: selected))") + } + if selectedSpeed == .other { + otherFood + .transition(.opacity.combined(with: .move(edge: .top))) + } + } + } + + private func durationLabel(_ speed: Speed, selected: Bool) -> String { + speed == .other && !selected + ? NSLocalizedString("Custom", comment: "Carb entry absorption choice Other, before it is chosen: you set the food and absorption time yourself") + : LoopFormat.duration(hours(speed)) + } + + private var otherFood: some View { + VStack(spacing: 0) { + HStack { + Text(NSLocalizedString("Food Type", comment: "Label for the food type row on the carb entry screen")) + .font(LoopDesign.Typeface.body) + Spacer() + TextField(Speed.other.emoji, text: $viewModel.foodType) + .multilineTextAlignment(.trailing) + .font(.title3) + .focused($foodTypeFocused) + .frame(maxWidth: 120) + } + .padding(.vertical, 10) + Divider() + Stepper(value: Binding(get: { viewModel.absorptionTime }, set: { newValue in animated { viewModel.absorptionTime = newValue } }), + in: viewModel.absorptionRimesRange, step: 30 * 60) { + HStack { + Text(NSLocalizedString("Absorption Time", comment: "Accessibility label for the button that explains carb absorption time")) + .font(LoopDesign.Typeface.body) + Spacer() + Text(LoopFormat.duration(viewModel.absorptionTime)) + .font(LoopDesign.Typeface.body.monospacedDigit()) + .foregroundStyle(P.carbsInk) + } + } + .padding(.vertical, 10) + } + .padding(.horizontal, LoopDesign.Space.s4) + .background(P.surfaceRaised, in: RoundedRectangle(cornerRadius: 15, style: .continuous)) + } + + private func choose(_ speed: Speed) { + amountFocused = false + // Re-choosing the shown speed changes nothing, so an unedited entry stays unedited. + guard speed != selectedSpeed else { return } + // A favourite sets its own food and absorption; choosing a speed instead lets it go but keeps the amount. + if viewModel.selectedFavoriteFood != nil { + let quantity = viewModel.carbsQuantity + viewModel.selectedFavoriteFoodIndex = -1 + viewModel.carbsQuantity = quantity + } + otherChosen = speed == .other + if speed == .other { + viewModel.usesCustomFoodType = true + foodTypeFocused = true + } else { + viewModel.usesCustomFoodType = false + viewModel.foodType = "" + viewModel.selectedDefaultAbsorptionTimeEmoji = speed.emoji + viewModel.absorptionTime = hours(speed) + } + } + + // MARK: Continue + + private var continueBar: some View { + Button(action: viewModel.continueToBolus) { + Text(NSLocalizedString("Continue", comment: "Button label for continue")) + .font(LoopDesign.Typeface.headline) + .foregroundStyle(viewModel.continueButtonDisabled ? P.inkMuted : P.onFill) + .frame(maxWidth: .infinity, minHeight: 54) + .background(viewModel.continueButtonDisabled ? P.surfaceRaised : P.carbs, + in: RoundedRectangle(cornerRadius: 18, style: .continuous)) + } + .buttonStyle(.plain) + .disabled(viewModel.continueButtonDisabled) + .accessibilityIdentifier("button_Continue") + .padding(.horizontal, LoopDesign.Space.s4) + .padding(.top, LoopDesign.Space.s2) + .padding(.bottom, LoopDesign.Space.s1) + .background(P.surface) + } + + // MARK: Pieces + + private func sectionTitle(_ title: String) -> some View { + Text(title) + .font(LoopDesign.Typeface.subhead.weight(.semibold)) + .foregroundStyle(P.inkMuted) + .accessibilityAddTraits(.isHeader) + } + + private func warningView(_ warning: CarbEntryViewModel.Warning) -> some View { + HStack(alignment: .top, spacing: LoopDesign.Space.s3) { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundStyle(P.criticalInk) + .accessibilityHidden(true) + Text(warningText(warning)) + .font(LoopDesign.Typeface.subhead) + .foregroundStyle(P.ink) + .fixedSize(horizontal: false, vertical: true) + } + .padding(LoopDesign.Space.s3) + .frame(maxWidth: .infinity, alignment: .leading) + .background(P.statusCritical.opacity(0.16), in: RoundedRectangle(cornerRadius: LoopDesign.Radius.lg, style: .continuous)) + .overlay(RoundedRectangle(cornerRadius: LoopDesign.Radius.lg, style: .continuous).strokeBorder(P.statusCritical.opacity(0.45), lineWidth: 1)) + } + + private func warningText(_ warning: CarbEntryViewModel.Warning) -> String { + switch warning { + case .entryIsMissedMeal: + return NSLocalizedString("Loop has detected an missed meal and estimated its size. Edit the carb amount to match the amount of any carbs you may have eaten.", comment: "Warning displayed when user is adding a meal from an missed meal notification") + case .glucoseRisingRapidly: + return NSLocalizedString("Your glucose is rapidly rising. Check that any carbs you've eaten were logged. If you logged carbs, check that the time you entered lines up with when you started eating.", comment: "Warning to ensure the carb entry is accurate") + } + } + + private func alert(for alert: CarbEntryViewModel.Alert) -> SwiftUI.Alert { + switch alert { + case .maxQuantityExceded: + let message = String( + format: NSLocalizedString("The maximum allowed amount is %@ grams.", comment: "Alert body displayed for quantity greater than max (1: maximum quantity in grams)"), + NumberFormatter.localizedString(from: NSNumber(value: viewModel.maxCarbEntryQuantity.doubleValue(for: viewModel.preferredCarbUnit)), number: .none) + ) + let okMessage = NSLocalizedString("com.loudnate.LoopKit.errorAlertActionTitle", value: "OK", comment: "The title of the action used to dismiss an error alert") + return SwiftUI.Alert( + title: Text("Large Meal Entered", comment: "Title of the warning shown when a large meal was entered"), + message: Text(message), + dismissButton: .cancel(Text(okMessage), action: viewModel.clearAlert) + ) + case .warningQuantityValidation: + let message = String( + format: NSLocalizedString("Did you intend to enter %1$@ grams as the amount of carbohydrates for this meal?", comment: "Alert body when entered carbohydrates is greater than threshold (1: entered quantity in grams)"), + NumberFormatter.localizedString(from: NSNumber(value: viewModel.carbsQuantity ?? 0), number: .none) + ) + return SwiftUI.Alert( + title: Text("Large Meal Entered", comment: "Title of the warning shown when a large meal was entered"), + message: Text(message), + primaryButton: .default(Text("No, edit amount", comment: "The title of the action used when rejecting the the amount of carbohydrates entered."), action: viewModel.clearAlert), + secondaryButton: .cancel(Text("Yes", comment: "The title of the action used when confirming entered amount of carbohydrates."), action: viewModel.clearAlertAndContinueToBolus) + ) + } + } +} + +/// A row of choices sharing one track, with a thumb that slides to the chosen one. Two by two at accessibility text sizes. +private struct ChoiceTrack: View { + let choices: [Choice] + let selection: Choice? + let thumb: String + let namespace: Namespace.ID + let action: (Choice) -> Void + @ViewBuilder let label: (Choice, Bool) -> Label + + @Environment(\.dynamicTypeSize) private var typeSize + + var body: some View { + let columns = Array(repeating: GridItem(.flexible(), spacing: 4), count: typeSize.isAccessibilitySize ? 2 : choices.count) + LazyVGrid(columns: columns, spacing: 4) { + ForEach(choices, id: \.self) { choice in + let selected = choice == selection + Button { + action(choice) + } label: { + label(choice, selected) + .padding(.horizontal, 4) + .frame(maxWidth: .infinity, minHeight: 44) + .background { + if selected { + RoundedRectangle(cornerRadius: 11, style: .continuous) + .fill(P.carbs.opacity(0.18)) + .overlay(RoundedRectangle(cornerRadius: 11, style: .continuous).strokeBorder(P.carbs, lineWidth: 1.5)) + .matchedGeometryEffect(id: thumb, in: namespace) + } + } + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .accessibilityAddTraits(selected ? .isSelected : []) + } + } + .padding(4) + .background(P.surfaceRaised, in: RoundedRectangle(cornerRadius: 15, style: .continuous)) + } +} + +/// The meal's carbs absorbing over time, drawn the way the day page draws carbs on board: a soft fill under a line, +/// solid up to now and dashed after, over hour gridlines. Touch and hold to read how much is left at any time. +private struct MealAbsorptionChart: View { + let grams: Double + let absorptionTime: TimeInterval + let eaten: Date + let now: Date + /// The longest absorption on offer, so a faster food draws a visibly shorter curve. + let axisLength: TimeInterval + + @State private var selection: Date? + @Environment(\.accessibilityReduceMotion) private var reduceMotion + + private static let height: CGFloat = 120 + private static let nowLabelHeight: CGFloat = 20 + + private var absorbed: Date { eaten.addingTimeInterval(absorptionTime) } + + private var window: DateInterval { + let start = min(eaten, now) + return DateInterval(start: start, end: max(eaten.addingTimeInterval(axisLength), now.addingTimeInterval(15 * 60))) + } + + /// Loop's absorption rate as a share of its peak (PiecewiseLinearAbsorption in LoopAlgorithm): rising to the + /// peak at 15% of the absorption time, steady until half way, then falling to nothing. + private static func shape(_ t: Double) -> Double { + switch t { + case ..<0: return 0 + case ..<0.15: return t / 0.15 + case ..<0.5: return 1 + case ..<1: return (1 - t) / 0.5 + default: return 0 + } + } + + /// The share of the carbs absorbed by `t`: the area under `shape` so far, over its whole area of 0.675. + private static func absorbedShare(_ t: Double) -> Double { + let t = min(max(t, 0), 1) + let area: Double + if t < 0.15 { + area = t * t / 0.3 + } else if t < 0.5 { + area = 0.075 + (t - 0.15) + } else { + area = 0.425 + ((t - 0.5) - (t * t - 0.25) / 2) / 0.5 + } + return min(1, area / 0.675) + } + + /// The plotted height of the peak. Its area stands for the grams, so the peak is the rate, grams over + /// 0.675 × the absorption time; a square root keeps a small snack visible beside a large fast one. + private var peak: Double { + guard grams > 0, absorptionTime > 0 else { return 0.42 } + let gramsPerHour = grams / (0.675 * absorptionTime / 3600) + return min(1, max(0.14, (gramsPerHour / 150).squareRoot())) + } + + private struct Sample: Identifiable { + let date: Date + let value: Double + var id: Date { date } + } + + /// The curve's corners, plus the point where it crosses now, so the solid and dashed parts meet exactly. + private var samples: [Sample] { + var shares = [0, 0.15, 0.5, 1.0] + let nowShare = now.timeIntervalSince(eaten) / max(absorptionTime, 1) + if nowShare > 0 && nowShare < 1 { shares.append(nowShare) } + return shares.sorted().map { Sample(date: eaten.addingTimeInterval($0 * absorptionTime), value: Self.shape($0) * peak) } + } + + private var hourMarks: [Date] { + stride(from: 3600.0, to: window.duration, by: 3600).map { eaten.addingTimeInterval($0) }.filter { window.contains($0) } + } + + var body: some View { + let samples = samples + let past = samples.filter { $0.date <= now } + let future = samples.filter { $0.date >= now } + VStack(spacing: 6) { + Chart { + ForEach(samples) { sample in + AreaMark(x: .value("Time", sample.date), yStart: .value("Base", 0), yEnd: .value("Rate", sample.value), series: .value("Series", "fill")) + .foregroundStyle(P.carbs.opacity(grams > 0 ? 0.16 : 0)) + } + // Already absorbed: a firmer fill under the solid part. + ForEach(past) { sample in + AreaMark(x: .value("Time", sample.date), yStart: .value("Base", 0), yEnd: .value("Rate", sample.value), series: .value("Series", "past-fill")) + .foregroundStyle(P.carbs.opacity(grams > 0 ? 0.22 : 0)) + } + ForEach(past) { sample in + LineMark(x: .value("Time", sample.date), y: .value("Rate", sample.value), series: .value("Series", "past")) + .foregroundStyle(grams > 0 ? P.carbs : P.inkMuted.opacity(0.5)) + .lineStyle(StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round)) + } + ForEach(future) { sample in + LineMark(x: .value("Time", sample.date), y: .value("Rate", sample.value), series: .value("Series", "future")) + .foregroundStyle(grams > 0 ? P.carbs : P.inkMuted.opacity(0.5)) + .lineStyle(StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round, dash: [4, 3])) + } + RuleMark(y: .value("Zero", 0)) + .foregroundStyle(P.separator) + .lineStyle(StrokeStyle(lineWidth: 1)) + RuleMark(x: .value("Now", now)) + .foregroundStyle(P.inkMuted) + .lineStyle(StrokeStyle(lineWidth: 1, dash: [1, 3])) + if let selection { + RuleMark(x: .value("Selected", selection)).foregroundStyle(P.ink.opacity(0.6)) + } + } + .chartXScale(domain: window.start...window.end) + .chartYScale(domain: 0...1.0) + .chartXAxis { + AxisMarks(values: hourMarks) { _ in + AxisGridLine().foregroundStyle(P.grid) + } + } + .chartYAxis(.hidden) + .chartOverlay { proxy in + ZStack(alignment: .topLeading) { + ScrubOverlay(proxy: proxy, selection: $selection, clampTo: window) + if selection == nil, let nowX = proxy.position(forX: now) { + GeometryReader { geometry in + // Centred over the line, kept inside the chart. + Text(NSLocalizedString("Now", comment: "Carb entry time choice: eaten now")) + .font(LoopDesign.Typeface.caption) + .foregroundStyle(P.inkMuted) + .dynamicTypeSize(...DynamicTypeSize.xxxLarge) + .fixedSize() + .frame(width: 48) + .position(x: min(max(nowX, 24), geometry.size.width - 24), y: -Self.nowLabelHeight / 2) + .allowsHitTesting(false) + } + } + } + } + .overlay(alignment: .top) { + if let selection { + ScrubCallout(rows: scrubRows(at: selection)) + .offset(y: -Self.nowLabelHeight) + .allowsHitTesting(false) + } + } + .frame(height: Self.height) + .padding(.top, Self.nowLabelHeight) + .animation(reduceMotion ? nil : .snappy(duration: 0.35), value: grams) + .animation(reduceMotion ? nil : .snappy(duration: 0.35), value: absorptionTime) + .animation(reduceMotion ? nil : .snappy(duration: 0.35), value: eaten) + + AxisLabels(start: eaten.timeIntervalSince(window.start) / window.duration, end: absorbed.timeIntervalSince(window.start) / window.duration) { + Text(LoopFormat.time(eaten)) + .foregroundStyle(P.ink) + Text(String(format: NSLocalizedString("Absorbed by %1$@", comment: "Under the end of the carb absorption curve: when the carbs will have absorbed (1: time)"), LoopFormat.time(absorbed))) + .foregroundStyle(P.inkMuted) + } + .font(LoopDesign.Typeface.footnote.monospacedDigit()) + } + .accessibilityElement(children: .ignore) + .accessibilityLabel(String(format: NSLocalizedString("Eaten at %1$@, absorbed by %2$@", comment: "Accessibility summary of the carb entry preview chart (1: time eaten, 2: time absorbed)"), LoopFormat.time(eaten), LoopFormat.time(absorbed))) + } + + private func scrubRows(at date: Date) -> [ScrubCallout.Row] { + var rows: [ScrubCallout.Row] = [.init(label: nil, value: LoopFormat.time(date), color: P.ink)] + if grams > 0 { + let left = grams * (1 - Self.absorbedShare(date.timeIntervalSince(eaten) / max(absorptionTime, 1))) + rows.append(.init(label: NSLocalizedString("COB", comment: "Abbreviation for carbs on board (active carbohydrate)"), value: LoopFormat.grams(left), color: P.carbsInk)) + } + return rows + } +} + +/// Two labels on a time axis: the first starts at `start`, the second ends at `end`, pushed apart when they'd meet. +/// When they can't share a line, as at large text sizes, the second goes under the first. +private struct AxisLabels: Layout { + var start: Double + var end: Double + + private static let gap: CGFloat = 12 + + private func stacked(_ width: CGFloat, _ subviews: Subviews) -> Bool { + subviews.map { $0.sizeThatFits(.unspecified).width }.reduce(Self.gap, +) > width + } + + func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { + let width = proposal.width ?? 0 + guard stacked(width, subviews) else { + return CGSize(width: width, height: subviews.map { $0.sizeThatFits(.unspecified).height }.max() ?? 0) + } + // Stacked labels wrap to the width rather than run off it. + let height = subviews.map { $0.sizeThatFits(ProposedViewSize(width: width, height: nil)).height }.reduce(0, +) + return CGSize(width: width, height: height) + } + + func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { + guard subviews.count == 2 else { return } + if stacked(bounds.width, subviews) { + let wrapped = ProposedViewSize(width: bounds.width, height: nil) + let firstHeight = subviews[0].sizeThatFits(wrapped).height + subviews[0].place(at: CGPoint(x: bounds.minX, y: bounds.minY), proposal: wrapped) + subviews[1].place(at: CGPoint(x: bounds.minX, y: bounds.minY + firstHeight), proposal: wrapped) + return + } + let first = subviews[0].sizeThatFits(.unspecified) + let second = subviews[1].sizeThatFits(.unspecified) + let firstX = min(max(bounds.minX, bounds.minX + bounds.width * start), bounds.maxX - first.width) + let secondX = min(max(bounds.minX + bounds.width * end - second.width, firstX + first.width + Self.gap), bounds.maxX - second.width) + subviews[0].place(at: CGPoint(x: firstX, y: bounds.minY), proposal: ProposedViewSize(first)) + subviews[1].place(at: CGPoint(x: secondX, y: bounds.minY), proposal: ProposedViewSize(second)) + } +} diff --git a/Loop/Redesign/DayCharts.swift b/Loop/Redesign/DayCharts.swift new file mode 100644 index 0000000000..0b8350e73f --- /dev/null +++ b/Loop/Redesign/DayCharts.swift @@ -0,0 +1,660 @@ +// +// DayCharts.swift +// Loop +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import SwiftUI +import Charts +import UIKit + +private typealias P = LoopDesign.Palette + +// MARK: - Shared axis helpers + +enum DayAxis { + /// Midnight, 6 AM, noon and 6 PM inside `window`, by the clock, so a day when the clocks change still marks 6 AM. + static func quarterDayMarks(in window: DateInterval, calendar: Calendar = .current) -> [Date] { + var marks: [Date] = [] + var day = calendar.startOfDay(for: window.start) + while day <= window.end { + for hour in [0, 6, 12, 18] { + if let mark = calendar.date(bySettingHour: hour, minute: 0, second: 0, of: day), window.contains(mark) { + marks.append(mark) + } + } + guard let next = calendar.date(byAdding: .day, value: 1, to: day) else { break } + day = next + } + return marks + } + + static func hourLabel(_ date: Date) -> String { + date.formatted(.dateTime.hour()) + } + + /// Gaps of more than 30 minutes between readings, so a chart can say where data is missing. + static func gaps(in points: [GlucosePoint], window: DateInterval, until end: Date) -> [DateInterval] { + let limit: TimeInterval = 30 * 60 + var gaps: [DateInterval] = [] + var previous = window.start + for point in points.sorted(by: { $0.date < $1.date }) where window.contains(point.date) { + if point.date.timeIntervalSince(previous) > limit { + gaps.append(DateInterval(start: previous, end: point.date)) + } + previous = point.date + } + let last = min(end, window.end) + if last.timeIntervalSince(previous) > limit { + gaps.append(DateInterval(start: previous, end: last)) + } + return gaps + } + + static func gapMessage(_ gaps: [DateInterval], window: DateInterval, until end: Date) -> String? { + guard let longest = gaps.max(by: { $0.duration < $1.duration }) else { return nil } + // A gap over the whole period would read "0:00 – 0:00". + if gaps.count == 1, longest.start == window.start, longest.end == min(end, window.end) { + return window.contains(end) + ? NSLocalizedString("No CGM data yet today", comment: "Chart note: there are no glucose readings so far today") + : NSLocalizedString("No CGM data on this day", comment: "Chart note: there are no glucose readings for the whole day") + } + let start = LoopFormat.time(longest.start) + let end = LoopFormat.time(longest.end) + if gaps.count > 1 { + let others = String.localizedStringWithFormat(NSLocalizedString("%lld shorter gaps", comment: "Chart note: how many other, shorter gaps in glucose readings there were; follows the longest gap. (1: number of gaps)"), gaps.count - 1) + return String(format: NSLocalizedString("No CGM data %1$@ – %2$@ and %3$@", comment: "Chart note: the longest gap in glucose readings, plus the shorter ones. (1: start time; 2: end time; 3: number of shorter gaps, e.g. 2 shorter gaps)"), start, end, others) + } + return String(format: NSLocalizedString("No CGM data %1$@ – %2$@", comment: "Chart note: a gap in glucose readings. (1: start time; 2: end time)"), start, end) + } +} + +// MARK: - InsulinTimeline + +/// Carbs, glucose and insulin for one day on one shared time axis. +/// +/// The lanes are drawn in a single chart with stacked y ranges so they can't drift apart: insulin 0–30, +/// glucose 36–92 (the fixed 2–20 mmol/L scale), carb markers at 97. +struct InsulinTimelineView: View { + let history: DayHistory + let unit: GlucoseUnitStyle + let targetBands: [TargetBand] + var now: Date = Date() + var height: CGFloat = 260 + + @State private var selection: Date? + @Environment(\.colorScheme) private var colorScheme + + private static let insulinTop = 30.0 + private static let basalTop = 12.0 + private static let glucoseRange = 36.0...92.0 + private static let carbLane = 97.0 + + private var window: DateInterval { history.window } + + private func y(glucose mgdl: Double) -> Double { + let domain = unit.dayDomain + let value = min(max(unit.value(mgdl), domain.lowerBound), domain.upperBound) + let fraction = (value - domain.lowerBound) / (domain.upperBound - domain.lowerBound) + return Self.glucoseRange.lowerBound + fraction * (Self.glucoseRange.upperBound - Self.glucoseRange.lowerBound) + } + + private var maxBasal: Double { + max(0.5, (history.basal.map(\.rate) + history.scheduledBasal.map(\.rate)).max() ?? 0) + } + + private var maxBolus: Double { + max(1, history.boluses.map(\.units).max() ?? 0) + } + + private func y(basal rate: Double) -> Double { rate / maxBasal * Self.basalTop } + private func y(bolus units: Double) -> Double { max(1.5, units / maxBolus * (Self.insulinTop - 4)) } + + private var gaps: [DateInterval] { DayAxis.gaps(in: history.glucose, window: window, until: now) } + + /// Labels closer than this to the previous label are dropped so they don't overprint; the marks still draw. + private var labelSpacing: TimeInterval { window.duration / 7 } + + private func spacedIDs(_ items: [T], date: (T) -> Date, id: (T) -> String) -> Set { + var kept = Set() + var last: Date? + for item in items.sorted(by: { date($0) < date($1) }) { + if let last, date(item).timeIntervalSince(last) < labelSpacing { continue } + kept.insert(id(item)) + last = date(item) + } + return kept + } + + /// One label per run of carb entries closer together than `labelSpacing`, giving the run's total. + /// It sits after the run's last marker, so no later marker in the run draws over it. + private var carbLabels: [String: Double] { + var labels: [String: Double] = [:] + var runStart: Date? + var runLast: (id: String, total: Double)? + for carb in history.carbs.sorted(by: { $0.date < $1.date }) { + if let start = runStart, let last = runLast, carb.date.timeIntervalSince(start) < labelSpacing { + runLast = (carb.id, last.total + carb.grams) + } else { + if let last = runLast { labels[last.id] = last.total } + runStart = carb.date + runLast = (carb.id, carb.grams) + } + } + if let last = runLast { labels[last.id] = last.total } + return labels + } + private var labelledBoluses: Set { spacedIDs(history.boluses.filter { !$0.automatic }, date: \.date, id: \.id) } + + var body: some View { + VStack(alignment: .leading, spacing: LoopDesign.Space.s2) { + chart + .frame(height: height) + if let message = DayAxis.gapMessage(gaps, window: window, until: now), history.source == .loop || !history.glucose.isEmpty { + Text(message) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(LoopDesign.Palette.inkMuted) + } + } + } + + private var chart: some View { + Chart { + // Quarter-day rules + ForEach(DayAxis.quarterDayMarks(in: window), id: \.self) { mark in + RuleMark(x: .value("Time", mark)) + .foregroundStyle(P.grid) + .lineStyle(StrokeStyle(lineWidth: 1)) + } + + // Glucose lane + ForEach(Array(targetBands.enumerated()), id: \.offset) { _, band in + RectangleMark(xStart: .value("Start", band.start), xEnd: .value("End", band.end), + yStart: .value("Low", y(glucose: band.range.lowerBound)), yEnd: .value("High", y(glucose: band.range.upperBound))) + .foregroundStyle(P.targetBand) + } + ForEach(Array(history.glucose.enumerated()), id: \.offset) { _, point in + PointMark(x: .value("Time", point.date), y: .value("Glucose", y(glucose: point.mgdl))) + .symbolSize(10) + .foregroundStyle(P.glucose) + } + + // Carb markers + let carbLabels = carbLabels + ForEach(history.carbs) { carb in + PointMark(x: .value("Time", carb.date), y: .value("Carbs", Self.carbLane)) + .symbolSize(60) + .foregroundStyle(P.carbs) + .annotation(position: .trailing, spacing: 2) { + if let grams = carbLabels[carb.id] { + Text(LoopFormat.grams(grams)) + .font(LoopDesign.Typeface.caption) + .foregroundStyle(P.carbsInk) + } + } + } + + // Delivered basal, edged, then the schedule dashed over it + // Draw each recorded segment independently; a gap must not become an interpolated delivery area. + ForEach(Array(history.basal.enumerated()), id: \.offset) { _, segment in + RectangleMark(xStart: .value("Start", segment.start), xEnd: .value("End", segment.end), + yStart: .value("Base", 0), yEnd: .value("Basal", y(basal: segment.rate))) + .foregroundStyle(P.basal) + RuleMark(xStart: .value("Start", segment.start), xEnd: .value("End", segment.end), y: .value("Basal", y(basal: segment.rate))) + .foregroundStyle(P.basalEdge) + .lineStyle(StrokeStyle(lineWidth: 1.5)) + } + ForEach(Array(history.scheduledBasal.enumerated()), id: \.offset) { _, value in + RuleMark(xStart: .value("Start", value.start), xEnd: .value("End", value.end), y: .value("Scheduled", y(basal: value.rate))) + .foregroundStyle(P.basalScheduled) + .lineStyle(StrokeStyle(lineWidth: 1, dash: [3, 3])) + } + + // Boluses over basal: solid for manual with a label, hatched for automatic. + // Each sits in a card-coloured slot a point wider, so it never touches the basal behind it. + ForEach(history.boluses) { bolus in + RectangleMark(x: .value("Time", bolus.date), yStart: .value("Base", 0), yEnd: .value("Bolus", y(bolus: bolus.units)), width: .fixed(7)) + .foregroundStyle(P.surfaceRaised) + } + let bolusLabels = labelledBoluses + ForEach(history.boluses) { bolus in + if bolus.automatic { + RectangleMark(x: .value("Time", bolus.date), yStart: .value("Base", 0), yEnd: .value("Bolus", y(bolus: bolus.units)), width: .fixed(5)) + .foregroundStyle(Hatch.paint(P.bolus, scheme: colorScheme)) + } else { + RectangleMark(x: .value("Time", bolus.date), yStart: .value("Base", 0), yEnd: .value("Bolus", y(bolus: bolus.units)), width: .fixed(5)) + .foregroundStyle(P.bolus) + .annotation(position: .top, spacing: 2) { + if bolusLabels.contains(bolus.id) { + Text(LoopFormat.number(bolus.units, decimals: 1)) + .font(LoopDesign.Typeface.caption) + .foregroundStyle(P.insulinInk) + } + } + } + } + + // Now + if window.contains(now) { + RuleMark(x: .value("Now", now)) + .foregroundStyle(P.inkMuted) + .lineStyle(StrokeStyle(lineWidth: 1, dash: [2, 3])) + } + + if let selection { + RuleMark(x: .value("Selected", selection)) + .foregroundStyle(P.ink.opacity(0.6)) + .lineStyle(StrokeStyle(lineWidth: 1)) + } + } + .chartXScale(domain: window.start...window.end) + .chartYScale(domain: 0...104) + .chartXAxis { + AxisMarks(values: DayAxis.quarterDayMarks(in: window)) { value in + AxisValueLabel { + if let date = value.as(Date.self) { + Text(DayAxis.hourLabel(date)) + } + } + .font(LoopDesign.Typeface.caption) + .foregroundStyle(P.inkMuted) + } + } + .chartYAxis { + AxisMarks(position: .leading, values: unit.dayTicks.map { y(glucose: unit.mgdl($0)) }) { value in + AxisGridLine().foregroundStyle(P.grid) + AxisValueLabel { + if let mapped = value.as(Double.self), let tick = unit.dayTicks.first(where: { abs(y(glucose: unit.mgdl($0)) - mapped) < 0.01 }) { + Text(tick.formatted()) + } + } + .font(LoopDesign.Typeface.caption) + .foregroundStyle(P.inkMuted) + } + } + .chartOverlay { proxy in + ScrubOverlay(proxy: proxy, selection: $selection, clampTo: window) + } + .overlay(alignment: .top) { + if let selection { + ScrubCallout(rows: scrubRows(at: selection)) + .padding(.top, 2) + .allowsHitTesting(false) + } + } + .accessibilityElement(children: .ignore) + .accessibilityLabel(accessibilitySummary) + } + + private func scrubRows(at date: Date) -> [ScrubCallout.Row] { + var rows: [ScrubCallout.Row] = [.init(label: nil, value: LoopFormat.time(date), color: P.ink)] + if let glucose = nearest(history.glucose.map { ($0.date, $0.mgdl) }, to: date, within: 10 * 60) { + rows.append(.init(label: NSLocalizedString("Glucose", comment: "Chart callout label for the glucose reading at the touched time"), value: "\(unit.format(glucose)) \(unit.unitName)", color: P.glucoseInk)) + } + if let iob = nearest(history.iob.map { ($0.date, $0.value) }, to: date, within: 10 * 60) { + rows.append(.init(label: NSLocalizedString("IOB", comment: "Abbreviation for insulin on board (active insulin)"), value: LoopFormat.doseUnits(iob), color: P.insulinInk)) + } + if let cob = nearest(history.cob.map { ($0.date, $0.value) }, to: date, within: 10 * 60) { + rows.append(.init(label: NSLocalizedString("COB", comment: "Abbreviation for carbs on board (active carbohydrate)"), value: LoopFormat.grams(cob), color: P.carbsInk)) + } + if let segment = LoopHistoryMath.basalRate(at: date, in: history.basal) { + let value = segment.kind == .suspended + ? NSLocalizedString("Suspended", comment: "Chart callout value: insulin delivery was suspended at the touched time") + : LoopFormat.rate(segment.rate) + rows.append(.init(label: NSLocalizedString("Basal", comment: "Chart callout label for the basal rate at the touched time"), value: value, color: P.ink)) + } + if let bolus = history.boluses.min(by: { abs($0.date.timeIntervalSince(date)) < abs($1.date.timeIntervalSince(date)) }), + abs(bolus.date.timeIntervalSince(date)) < 5 * 60 { + rows.append(.init(label: bolus.automatic + ? NSLocalizedString("Automatic bolus", comment: "Chart callout label for a bolus Loop delivered automatically near the touched time") + : NSLocalizedString("Bolus", comment: "Chart callout label for a bolus near the touched time"), + value: LoopFormat.doseUnits(bolus.units), color: P.insulinInk)) + } + return rows + } + + private var accessibilitySummary: String { + let day = LoopFormat.weekdayDay(window.start) + let bolus = LoopFormat.totalUnits(history.bolusUnits) + let basal = LoopFormat.totalUnits(history.basalUnits) + let carbs = LoopFormat.grams(history.carbGrams) + if let last = history.glucose.last { + return String(format: NSLocalizedString("Insulin timeline, %1$@, bolus %2$@, basal %3$@, carbs %4$@, latest glucose %5$@ %6$@", comment: "VoiceOver summary of the day's insulin timeline chart. (1: weekday and date; 2: bolus total, e.g. 12.5 U; 3: basal total; 4: carbs total, e.g. 140 g; 5: latest glucose value; 6: glucose unit, mmol/L or mg/dL)"), day, bolus, basal, carbs, unit.format(last.mgdl), unit.unitName) + } + return String(format: NSLocalizedString("Insulin timeline, %1$@, bolus %2$@, basal %3$@, carbs %4$@", comment: "VoiceOver summary of the day's insulin timeline chart with no glucose readings. (1: weekday and date; 2: bolus total, e.g. 12.5 U; 3: basal total; 4: carbs total, e.g. 140 g)"), day, bolus, basal, carbs) + } +} + +func nearest(_ values: [(Date, Double)], to date: Date, within limit: TimeInterval) -> Double? { + guard let best = values.min(by: { abs($0.0.timeIntervalSince(date)) < abs($1.0.timeIntervalSince(date)) }), + abs(best.0.timeIntervalSince(date)) <= limit else { + return nil + } + return best.1 +} + +// MARK: - Scrubbing + +/// Touch and hold then drag to scrub; a plain swipe still scrolls the page. +/// +/// A UIKit long-press recogniser is used because a SwiftUI long-press-then-drag gesture inside a table cell +/// takes every drag that starts on the chart, so the page couldn't scroll past it. +struct ScrubOverlay: View { + let proxy: ChartProxy + @Binding var selection: Date? + let clampTo: DateInterval + + var body: some View { + GeometryReader { geometry in + if #available(iOS 18.0, *) { + Color.clear + .contentShape(Rectangle()) + .gesture(ScrubPressRecognizer { location in + update(location, in: geometry) + } onEnd: { + selection = nil + }) + } else { + Color.clear + } + } + } + + private func update(_ location: CGPoint, in geometry: GeometryProxy) { + guard let frame = proxy.plotFrame else { return } + let origin = geometry[frame].origin + if let date: Date = proxy.value(atX: location.x - origin.x) { + selection = min(max(date, clampTo.start), clampTo.end) + } + } +} + +@available(iOS 18.0, *) +private struct ScrubPressRecognizer: UIGestureRecognizerRepresentable { + let onChange: (CGPoint) -> Void + let onEnd: () -> Void + + func makeUIGestureRecognizer(context: Context) -> UILongPressGestureRecognizer { + let recognizer = UILongPressGestureRecognizer() + recognizer.minimumPressDuration = 0.3 + recognizer.allowableMovement = 12 + return recognizer + } + + func handleUIGestureRecognizerAction(_ recognizer: UILongPressGestureRecognizer, context: Context) { + switch recognizer.state { + case .began, .changed: + onChange(context.converter.localLocation) + case .ended, .cancelled, .failed: + onEnd() + default: + break + } + } +} + +struct ScrubCallout: View { + struct Row: Hashable { + let label: String? + let value: String + let color: Color + } + + let rows: [Row] + + var body: some View { + VStack(alignment: .leading, spacing: 2) { + ForEach(rows, id: \.self) { row in + HStack(spacing: LoopDesign.Space.s1) { + if let label = row.label { + Text(label).foregroundStyle(P.inkMuted) + } + Text(row.value).foregroundStyle(row.color).fontWeight(.semibold) + } + } + } + .font(LoopDesign.Typeface.footnote.monospacedDigit()) + .padding(.horizontal, LoopDesign.Space.s2) + .padding(.vertical, LoopDesign.Space.s1) + .background(P.surfaceRaised, in: RoundedRectangle(cornerRadius: LoopDesign.Radius.sm)) + .overlay(RoundedRectangle(cornerRadius: LoopDesign.Radius.sm).strokeBorder(P.separator)) + } +} + +// MARK: - Prediction + +/// The last three hours of readings and Loop's dashed prediction for the next three. +struct PredictionChartView: View { + let readings: [GlucosePoint] + let prediction: [GlucosePoint] + let unit: GlucoseUnitStyle + let targetBands: [TargetBand] + var now: Date = Date() + var height: CGFloat = 150 + + @State private var selection: Date? + + private var window: DateInterval { + DateInterval(start: now.addingTimeInterval(-3 * 3600), end: now.addingTimeInterval(3 * 3600)) + } + + private func clamp(_ mgdl: Double) -> Double { + min(max(unit.value(mgdl), unit.dayDomain.lowerBound), unit.dayDomain.upperBound) + } + + private var futurePrediction: [GlucosePoint] { + prediction.filter { $0.date >= now.addingTimeInterval(-5 * 60) && $0.date <= window.end } + } + + var body: some View { + Chart { + ForEach(Array(targetBands.enumerated()), id: \.offset) { _, band in + RectangleMark(xStart: .value("Start", band.start), xEnd: .value("End", band.end), + yStart: .value("Low", clamp(band.range.lowerBound)), yEnd: .value("High", clamp(band.range.upperBound))) + .foregroundStyle(P.targetBand) + } + ForEach(Array(readings.filter { window.contains($0.date) }.enumerated()), id: \.offset) { _, point in + PointMark(x: .value("Time", point.date), y: .value("Glucose", clamp(point.mgdl))) + .symbolSize(12) + .foregroundStyle(P.glucose) + } + ForEach(Array(futurePrediction.enumerated()), id: \.offset) { index, point in + LineMark(x: .value("Time", point.date), y: .value("Predicted", clamp(point.mgdl)), series: .value("Series", "prediction")) + .foregroundStyle(P.glucose) + .lineStyle(StrokeStyle(lineWidth: 1.5, dash: [4, 3])) + .annotation(position: .topTrailing, spacing: 0) { + if index == futurePrediction.count - 1 { + Text(unit.format(point.mgdl)) + .font(LoopDesign.Typeface.caption.weight(.semibold)) + .foregroundStyle(P.ink) + } + } + } + RuleMark(x: .value("Now", now)) + .foregroundStyle(P.inkMuted) + .lineStyle(StrokeStyle(lineWidth: 1, dash: [2, 3])) + if let selection { + RuleMark(x: .value("Selected", selection)) + .foregroundStyle(P.ink.opacity(0.6)) + } + } + .frame(height: height) + .chartXScale(domain: window.start...window.end) + .chartYScale(domain: unit.dayDomain) + .chartXAxis { + AxisMarks(values: [window.start, now, window.end]) { value in + AxisGridLine().foregroundStyle(P.grid) + AxisValueLabel { + if let date = value.as(Date.self) { + Text(abs(date.timeIntervalSince(now)) < 60 ? NSLocalizedString("now", comment: "Prediction chart x-axis label marking the current time") : LoopFormat.time(date)) + } + } + .font(LoopDesign.Typeface.caption) + .foregroundStyle(P.inkMuted) + } + } + .chartYAxis { + AxisMarks(position: .leading, values: unit.dayTicks) { _ in + AxisGridLine().foregroundStyle(P.grid) + AxisValueLabel().font(LoopDesign.Typeface.caption).foregroundStyle(P.inkMuted) + } + } + .chartOverlay { proxy in + ScrubOverlay(proxy: proxy, selection: $selection, clampTo: window) + } + .overlay(alignment: .top) { + if let selection { + ScrubCallout(rows: scrubRows(at: selection)).allowsHitTesting(false) + } + } + .accessibilityElement(children: .ignore) + .accessibilityLabel(accessibilitySummary) + } + + private func scrubRows(at date: Date) -> [ScrubCallout.Row] { + var rows: [ScrubCallout.Row] = [.init(label: nil, value: LoopFormat.time(date), color: P.ink)] + let source = date <= now ? readings : prediction + if let value = nearest(source.map { ($0.date, $0.mgdl) }, to: date, within: 10 * 60) { + rows.append(.init(label: date <= now + ? NSLocalizedString("Glucose", comment: "Chart callout label for the glucose reading at the touched time") + : NSLocalizedString("Predicted", comment: "Prediction chart callout label for Loop's predicted glucose at the touched time"), + value: "\(unit.format(value)) \(unit.unitName)", color: P.glucoseInk)) + } + return rows + } + + private var accessibilitySummary: String { + guard let last = futurePrediction.last else { + return NSLocalizedString("Glucose prediction unavailable", comment: "VoiceOver label for the prediction chart when Loop has no prediction") + } + return String(format: NSLocalizedString("Loop predicts %1$@ %2$@ by %3$@", comment: "VoiceOver summary of the prediction chart. (1: predicted glucose value; 2: glucose unit, mmol/L or mg/dL; 3: time of the prediction)"), unit.format(last.mgdl), unit.unitName, LoopFormat.time(last.date)) + } +} + +// MARK: - IOB and COB + +/// Insulin and carbs on board over the same window, each against its own coloured axis. +struct IOBCOBChartView: View { + let iob: [TimedValue] + let cob: [TimedValue] + let window: DateInterval + var now: Date = Date() + var height: CGFloat = 140 + + @State private var selection: Date? + + private var iobMax: Double { max(1, (iob.map(\.value).max() ?? 0) * 1.1) } + private var iobMin: Double { min(0, (iob.map(\.value).min() ?? 0) * 1.1) } + private var cobMax: Double { max(10, (cob.map(\.value).max() ?? 0) * 1.1) } + + /// COB drawn on the IOB scale. + private func scaled(cob value: Double) -> Double { value / cobMax * iobMax } + + private var iobTicks: [Double] { + let step = Self.niceStep(iobMax / 2) + return stride(from: (iobMin / step).rounded(.down) * step, through: iobMax, by: step).map { $0 } + } + + private var cobTicks: [Double] { + let step = Self.niceStep(cobMax / 2) + return stride(from: 0, through: cobMax, by: step).map { $0 } + } + + static func niceStep(_ raw: Double) -> Double { + guard raw > 0 else { return 1 } + let magnitude = pow(10, floor(log10(raw))) + let normalized = raw / magnitude + let nice: Double = normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10 + return nice * magnitude + } + + var body: some View { + Chart { + RuleMark(y: .value("Zero", 0)) + .foregroundStyle(P.inkMuted) + .lineStyle(StrokeStyle(lineWidth: 1)) + + ForEach(Array(cob.enumerated()), id: \.offset) { _, value in + AreaMark(x: .value("Time", value.date), yStart: .value("Base", 0), yEnd: .value("COB", scaled(cob: value.value)), series: .value("Series", "cob-fill")) + .foregroundStyle(P.carbs.opacity(0.14)) + LineMark(x: .value("Time", value.date), y: .value("COB", scaled(cob: value.value)), series: .value("Series", value.date > now ? "cob-projected" : "cob")) + .foregroundStyle(P.carbs) + .lineStyle(StrokeStyle(lineWidth: 1.5, dash: value.date > now ? [4, 3] : [])) + } + ForEach(Array(iob.enumerated()), id: \.offset) { _, value in + AreaMark(x: .value("Time", value.date), yStart: .value("Base", 0), yEnd: .value("IOB", value.value), series: .value("Series", "iob-fill")) + .foregroundStyle(P.insulin.opacity(0.14)) + LineMark(x: .value("Time", value.date), y: .value("IOB", value.value), series: .value("Series", value.date > now ? "iob-projected" : "iob")) + .foregroundStyle(P.insulin) + .lineStyle(StrokeStyle(lineWidth: 1.5, dash: value.date > now ? [4, 3] : [])) + } + if window.contains(now) { + RuleMark(x: .value("Now", now)) + .foregroundStyle(P.inkMuted) + .lineStyle(StrokeStyle(lineWidth: 1, dash: [1, 3])) + } + if let selection { + RuleMark(x: .value("Selected", selection)).foregroundStyle(P.ink.opacity(0.6)) + } + } + .frame(height: height) + .chartXScale(domain: window.start...window.end) + .chartYScale(domain: iobMin...iobMax) + .chartXAxis { + AxisMarks(values: DayAxis.quarterDayMarks(in: window)) { value in + AxisGridLine().foregroundStyle(P.grid) + AxisValueLabel { + if let date = value.as(Date.self) { Text(DayAxis.hourLabel(date)) } + } + .font(LoopDesign.Typeface.caption) + .foregroundStyle(P.inkMuted) + } + } + .chartYAxis { + AxisMarks(position: .leading, values: iobTicks) { value in + AxisGridLine().foregroundStyle(P.grid) + AxisValueLabel { + if let v = value.as(Double.self) { Text(v.formatted(.number.precision(.fractionLength(0...1)))) } + } + .font(LoopDesign.Typeface.caption) + .foregroundStyle(P.insulinInk) + } + AxisMarks(position: .trailing, values: cobTicks.map { scaled(cob: $0) }) { value in + AxisValueLabel { + if let v = value.as(Double.self) { Text((v / iobMax * cobMax).formatted(.number.precision(.fractionLength(0)))) } + } + .font(LoopDesign.Typeface.caption) + .foregroundStyle(P.carbsInk) + } + } + .chartOverlay { proxy in + ScrubOverlay(proxy: proxy, selection: $selection, clampTo: window) + } + .overlay(alignment: .top) { + if let selection { + ScrubCallout(rows: scrubRows(at: selection)).allowsHitTesting(false) + } + } + .accessibilityElement(children: .ignore) + .accessibilityLabel(accessibilitySummary) + } + + private func scrubRows(at date: Date) -> [ScrubCallout.Row] { + var rows: [ScrubCallout.Row] = [.init(label: nil, value: LoopFormat.time(date), color: P.ink)] + if let value = nearest(iob.map { ($0.date, $0.value) }, to: date, within: 10 * 60) { + rows.append(.init(label: NSLocalizedString("IOB", comment: "Abbreviation for insulin on board (active insulin)"), value: LoopFormat.doseUnits(value), color: P.insulinInk)) + } + if let value = nearest(cob.map { ($0.date, $0.value) }, to: date, within: 10 * 60) { + rows.append(.init(label: NSLocalizedString("COB", comment: "Abbreviation for carbs on board (active carbohydrate)"), value: LoopFormat.grams(value), color: P.carbsInk)) + } + return rows + } + + private var accessibilitySummary: String { + let iobNow = nearest(iob.map { ($0.date, $0.value) }, to: now, within: 10 * 60) + let cobNow = nearest(cob.map { ($0.date, $0.value) }, to: now, within: 10 * 60) + let unknown = NSLocalizedString("unknown", comment: "VoiceOver value when active insulin or carbs at the current time isn't known") + return String(format: NSLocalizedString("Active insulin %1$@, active carbs %2$@", comment: "VoiceOver summary of the active insulin and carbs chart. (1: insulin on board, e.g. 1.25 U, or unknown; 2: carbs on board, e.g. 20 g, or unknown)"), iobNow.map(LoopFormat.doseUnits) ?? unknown, cobNow.map(LoopFormat.grams) ?? unknown) + } +} diff --git a/Loop/Redesign/DayDetailView.swift b/Loop/Redesign/DayDetailView.swift new file mode 100644 index 0000000000..916db44136 --- /dev/null +++ b/Loop/Redesign/DayDetailView.swift @@ -0,0 +1,198 @@ +// +// DayDetailView.swift +// Loop +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import SwiftUI + +private typealias P = LoopDesign.Palette + +/// One full day on InsulinTimeline with IOB and COB under it, stepping a day at a time. +struct DayDetailView: View { + let provider: LoopHistoryProvider + + @State private var day: Date + @State private var history: DayHistory? + @State private var targetBands: [TargetBand] = [] + @State private var now = Date() + @State private var loadError: String? + + init(provider: LoopHistoryProvider, day: Date) { + self.provider = provider + _day = State(initialValue: Calendar.current.startOfDay(for: day)) + } + + private var window: DateInterval { + DateInterval(start: day, end: Calendar.current.date(byAdding: .day, value: 1, to: day)!) + } + + private var isToday: Bool { Calendar.current.isDate(day, inSameDayAs: now) } + + var body: some View { + ScrollView { + VStack(alignment: .leading, spacing: LoopDesign.Space.s6) { + dayStepper + if let history { + LoopCard { + CardHeader(title: NSLocalizedString("Insulin", comment: "Title of the day page card showing insulin, carbs and glucose on one timeline")) { + HStack(spacing: 4) { + Text(LoopFormat.totalUnits(history.totalUnits)).foregroundStyle(P.insulinInk) + Text("·") + Text(LoopFormat.grams(history.carbGrams)).foregroundStyle(P.carbsInk) + } + } + InsulinTimelineView(history: history, unit: provider.unitStyle, targetBands: targetBands, + now: now, height: 300) + ChartLegend(items: DayTotalsTiles.legend) + if history.source == .health { + Text(NSLocalizedString("From Apple Health: Loop keeps 7 days, so this day has no temp basal or schedule detail.", comment: "Day page note when the day is older than Loop's 7-day history and was read from Apple Health")) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + } + if #available(iOS 18.0, *) { + Text(NSLocalizedString("Touch and hold a chart to see exact values.", comment: "Hint under the day page charts explaining how to scrub them")) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + } + } + LoopCard { + CardHeader(title: NSLocalizedString("Basal and bolus", comment: "Trends card title: insulin split into basal and bolus")) + InsulinSplitView(split: history.insulinSplit, partial: isToday) + ValueTile(label: NSLocalizedString("Carbs", comment: "Label of a tile showing the day's total carbohydrate")) { + Text(LoopFormat.grams(history.carbGrams)) + .font(LoopDesign.Typeface.statValueSmall) + .foregroundStyle(P.carbsInk) + } + .fixedSize(horizontal: false, vertical: true) + } + LoopCard { + CardHeader(title: NSLocalizedString("Active insulin and carbs", comment: "Title of the day page card charting insulin and carbs on board")) + if history.iob.isEmpty && history.cob.isEmpty { + ChartPlaceholder(message: history.source == .health + ? NSLocalizedString("Loop only works out active insulin and carbs for the days it keeps", comment: "Day page placeholder for active insulin and carbs on a day older than Loop's 7-day history") + : NSLocalizedString("No active insulin or carbs on this day", comment: "Day page placeholder when there was no insulin or carbs on board all day")) + } else { + IOBCOBChartView(iob: history.iob, cob: history.cob, window: window, now: now) + } + ChartLegend(items: [ + LegendItem(swatch: .solid(P.insulin), label: NSLocalizedString("IOB (U, left)", comment: "Chart legend: insulin on board in units, on the left axis")), + LegendItem(swatch: .solid(P.carbs), label: NSLocalizedString("COB (g, right)", comment: "Chart legend: carbs on board in grams, on the right axis")), + LegendItem(swatch: .dash(P.inkMuted), label: NSLocalizedString("Projected", comment: "Chart legend: dashed lines show Loop's projection of insulin and carbs on board")), + ]) + } + } else if let loadError { + ChartPlaceholder(message: String(format: NSLocalizedString("Couldn't load this day: %1$@", comment: "Day page error when the day's history can't be read. (1: system error description)"), loadError)) + } else { + ProgressView() + .frame(maxWidth: .infinity, minHeight: 300) + } + } + .padding(LoopDesign.Space.s4) + } + .background(P.surface) + .navigationTitle(isToday ? NSLocalizedString("Today", comment: "Day page title when showing today") : LoopFormat.weekdayDay(day)) + .navigationBarTitleDisplayMode(.inline) + .task(id: day) { + now = Date() + let requestedWindow = window + history = nil + loadError = nil + do { + async let historyFetch = provider.dayHistory(requestedWindow) + async let targetsFetch = provider.glucoseTargetBands(in: requestedWindow) + let (result, targets) = try await (historyFetch, targetsFetch) + guard !Task.isCancelled, window == requestedWindow else { return } + history = result + targetBands = targets + } catch { + guard !Task.isCancelled, window == requestedWindow else { return } + loadError = error.localizedDescription + } + } + } + + private var dayStepper: some View { + HStack { + StepButton(systemName: "chevron.left", label: NSLocalizedString("Previous day", comment: "Accessibility label for the day page button that steps back a day")) { + day = Calendar.current.date(byAdding: .day, value: -1, to: day)! + } + Spacer() + Text(LoopFormat.day(day)) + .font(LoopDesign.Typeface.headline) + .foregroundStyle(P.ink) + Spacer() + StepButton(systemName: "chevron.right", label: NSLocalizedString("Next day", comment: "Accessibility label for the day page button that steps forward a day"), enabled: !isToday) { + day = Calendar.current.date(byAdding: .day, value: 1, to: day)! + } + } + } +} + +/// A day on one timeline with its totals: Today so far on Now, and a one-day period on Trends. +struct DayTimelineCard: View { + let title: String + let history: DayHistory + let unit: GlucoseUnitStyle + let targetBands: [TargetBand] + var now: Date = Date() + var height: CGFloat = 230 + /// The bolus, basal and carb tiles; off where a Basal and bolus card follows. + var showsTotals = true + + static var todayTitle: String { + NSLocalizedString("Today so far", comment: "Title of the card on the Now screen showing today's insulin and carbs") + } + + var body: some View { + LoopCard { + CardHeader(title: title) { + HStack(spacing: 4) { + Text(LoopFormat.totalUnits(history.totalUnits)).foregroundStyle(P.insulinInk) + Text(verbatim: "·") + Text(LoopFormat.grams(history.carbGrams)).foregroundStyle(P.carbsInk) + } + } + InsulinTimelineView(history: history, unit: unit, targetBands: targetBands, now: now, height: height) + if showsTotals { + DayTotalsTiles(history: history) + } + ChartLegend(items: DayTotalsTiles.legend) + } + } +} + +/// Bolus, basal and carb totals under a timeline. +struct DayTotalsTiles: View { + let history: DayHistory + + static let legend: [LegendItem] = [ + LegendItem(swatch: .solid(P.bolus), label: NSLocalizedString("Bolus", comment: "Chart legend: solid bars are manual boluses")), + LegendItem(swatch: .hatch(P.bolus), label: NSLocalizedString("Automatic", comment: "Chart legend: hatched bars are automatic boluses given by Loop")), + LegendItem(swatch: .basal, label: NSLocalizedString("Basal given", comment: "Chart legend: filled area is basal insulin actually delivered")), + LegendItem(swatch: .dash(P.basalScheduled), label: NSLocalizedString("Scheduled", comment: "Chart legend: dashed line is the scheduled basal rate")), + ] + + var body: some View { + AdaptiveRow { + ValueTile(label: NSLocalizedString("Bolus", comment: "Label of a tile showing the day's total bolus insulin")) { + Text(LoopFormat.totalUnits(history.bolusUnits)) + .font(LoopDesign.Typeface.statValueSmall) + .foregroundStyle(P.insulinInk) + } + ValueTile(label: NSLocalizedString("Basal", comment: "Label of a tile showing the current basal rate (Now screen) or the day's basal insulin total (Today so far totals)")) { + // Plain ink: basal is the pale fill on the chart, and orange here read as bolus. + Text(LoopFormat.totalUnits(history.basalUnits)) + .font(LoopDesign.Typeface.statValueSmall) + .foregroundStyle(P.ink) + } + ValueTile(label: NSLocalizedString("Carbs", comment: "Label of a tile showing the day's total carbohydrate")) { + Text(LoopFormat.grams(history.carbGrams)) + .font(LoopDesign.Typeface.statValueSmall) + .foregroundStyle(P.carbsInk) + } + } + .fixedSize(horizontal: false, vertical: true) + } +} diff --git a/Loop/Redesign/LogbookScreen.swift b/Loop/Redesign/LogbookScreen.swift new file mode 100644 index 0000000000..c812df3102 --- /dev/null +++ b/Loop/Redesign/LogbookScreen.swift @@ -0,0 +1,642 @@ +// +// LogbookScreen.swift +// Loop +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import SwiftUI +import LoopKit +import LoopKitUI + +private typealias P = LoopDesign.Palette + +/// One row of the logbook. +struct LogEntry: Identifiable { + enum Filter: String, CaseIterable, Identifiable { + case all = "All" + case bolus = "Bolus" + case basal = "Basal" + case carbs = "Carbs" + case loop = "Loop" + var id: String { rawValue } + + var title: String { + switch self { + case .all: return NSLocalizedString("All", comment: "Logbook filter chip: show every entry") + case .bolus: return NSLocalizedString("Bolus", comment: "Logbook filter chip: show only boluses") + case .basal: return NSLocalizedString("Basal", comment: "Logbook filter chip: show only basal changes") + case .carbs: return NSLocalizedString("Carbs", comment: "Logbook filter chip: show only carb entries") + case .loop: return NSLocalizedString("Loop", comment: "Logbook filter chip: show only what Loop did automatically (Loop is the app's name)") + } + } + } + + enum Action { + case deleteCarbs(StoredCarbEntry) + case deleteDose(DoseEntry) + } + + let id: String + let date: Date + let title: String + let subtitle: String + let amount: String + let amountColor: Color + let detail: String + let icon: String + let iconStyle: IconTile.Style + let filters: Set + /// Loop's own small, frequent doses: the amount is set lighter so the person's own entries stand out. + var quiet = false + var basalChanges: [BasalSegment] = [] + var edit: StoredCarbEntry? = nil + var delete: Action? = nil + /// Units of a single automatic bolus, so runs of them can be folded into one row. + var automaticUnits: Double? = nil + /// The automatic boluses a folded row stands for, newest first. + var bolusRun: [LogEntry] = [] + + func matches(_ filter: Filter) -> Bool { + filter == .all || filters.contains(filter) + } +} + +struct LogDay: Identifiable { + let day: Date + let entries: [LogEntry] + let insulin: InsulinSplit + let carbs: Double + var id: Date { day } +} + +@MainActor +final class LogbookModel: ObservableObject { + @Published private(set) var days: [LogDay] = [] + @Published private(set) var loaded = false + @Published var filter: LogEntry.Filter = .all + @Published var error: String? + @Published private(set) var loadError: String? + @Published private(set) var carbEditingAllowed = false + + let provider: LoopHistoryProvider + private var loadTask: Task? + + init(provider: LoopHistoryProvider) { + self.provider = provider + } + + func reload() { + loadTask?.cancel() + carbEditingAllowed = provider.allowsCarbEditing + loadError = nil + loadTask = Task { + do { + let result = try await Self.build(provider: provider) + guard !Task.isCancelled else { return } + self.days = result + } catch { + guard !Task.isCancelled else { return } + self.days = [] + self.loadError = error.localizedDescription + } + self.loaded = true + } + } + + func perform(_ action: LogEntry.Action) { + Task { + do { + switch action { + case .deleteCarbs(let entry): + guard provider.allowsCarbEditing else { return } + try await provider.delete(carb: entry) + case .deleteDose(let dose): try await provider.delete(dose: dose) + } + reload() + } catch { + self.error = error.localizedDescription + } + } + } + + // MARK: Building rows + + private static func build(provider: LoopHistoryProvider, now: Date = Date()) async throws -> [LogDay] { + let calendar = Calendar.current + let start = calendar.startOfDay(for: provider.localHistoryStart.addingTimeInterval(86400)) + let interval = DateInterval(start: start, end: now) + async let historyFetch = provider.dayHistory(interval, includeOnBoard: false) + async let pumpFetch = provider.pumpEvents(since: start) + let history = try await historyFetch + let pumpEvents = try await pumpFetch + let unit = provider.unitStyle + let canDelete = FeatureFlags.entryDeletionEnabled + + return LoopHistoryMath.days(in: interval).reversed().map { day in + var entries: [LogEntry] = [] + let inDay: (Date) -> Bool = { $0 >= day.start && $0 < day.end } + let carbsToday = history.carbs.filter { inDay($0.date) } + + for bolus in history.boluses where inDay(bolus.date) { + let glucose = glucoseContext(at: bolus.date, history.glucose, unit: unit, withTrend: bolus.automatic) + var parts = [bolus.automatic ? sourceLoop : sourceManual] + if !bolus.automatic, let meal = carbsToday.first(where: { abs($0.date.timeIntervalSince(bolus.date)) <= 15 * 60 }) { + parts.append(LoopFormat.grams(meal.grams)) + } + if let glucose { parts.append(glucose) } + if bolus.manuallyEntered { parts.append(NSLocalizedString("logged", comment: "Logbook bolus row detail: the dose was logged by hand rather than delivered by the pump")) } + entries.append(LogEntry( + id: "bolus-\(bolus.id)", date: bolus.date, + title: bolus.automatic + ? NSLocalizedString("Automatic bolus", comment: "Logbook row title: a bolus Loop delivered automatically") + : NSLocalizedString("Bolus", comment: "Logbook row title: a bolus"), + subtitle: joined(parts), + amount: LoopFormat.doseUnits(bolus.units), amountColor: P.insulinInk, + detail: LoopFormat.time(bolus.date), + icon: bolus.automatic ? "arrow.triangle.2.circlepath" : "syringe.fill", + iconStyle: bolus.automatic ? .hatch(P.bolus) : .solid(P.bolus), + filters: bolus.automatic ? [.bolus, .loop] : [.bolus], + quiet: bolus.automatic, + delete: canDelete && bolus.manuallyEntered ? bolus.dose.map { .deleteDose($0) } : nil, + automaticUnits: bolus.automatic && !bolus.manuallyEntered ? bolus.units : nil)) + } + + for carb in carbsToday { + var parts = [carb.createdByLoop ? sourceManual : NSLocalizedString("Health", comment: "Logbook carb row detail: the entry came from Apple Health")] + if let food = carb.foodType, !food.isEmpty { parts.append(food) } + if let hours = carb.absorptionHours { parts.append(String(format: NSLocalizedString("absorbs over %1$@ h", comment: "Logbook carb row detail: carb absorption time in hours. (1: hours, e.g. 3 or 2.5)"), hours.formatted(.number.precision(.fractionLength(0...1))))) } + let editable = carb.createdByLoop && provider.allowsCarbEditing ? carb.entry : nil + entries.append(LogEntry( + id: "carbs-\(carb.id)", date: carb.date, + title: NSLocalizedString("Carbs", comment: "Logbook row title: a carb entry"), subtitle: joined(parts), + amount: LoopFormat.grams(carb.grams), amountColor: P.carbsInk, + detail: LoopFormat.time(carb.date), + icon: "fork.knife", iconStyle: .solid(P.carbs), filters: [.carbs], + edit: editable, + delete: canDelete ? editable.map { .deleteCarbs($0) } : nil)) + } + + let segments = history.basal.compactMap { $0.clamped(to: day) } + let automaticTemps = segments.filter { $0.kind == .temp && $0.automatic } + if let latest = automaticTemps.last { + let net = automaticTemps.reduce(0) { $0 + $1.netUnits } + let count = automaticTemps.count + entries.append(LogEntry( + id: "basal-\(day.start.timeIntervalSince1970)", date: latest.start, + title: NSLocalizedString("Basal adjustments", comment: "Logbook row title summarising the temp basals Loop set in a day"), + subtitle: String.localizedStringWithFormat(NSLocalizedString("Loop · %lld changes · tap to list", comment: "Logbook basal adjustments row subtitle: how many temp basals Loop set in the day; tapping lists them. (1: number of changes)"), count), + amount: String(format: NSLocalizedString("%1$@ U", comment: "Format string for an amount of insulin in units. (1: amount)"), LoopFormat.signed(net, decimals: 1)), amountColor: P.ink, + detail: NSLocalizedString("vs plan", comment: "Logbook basal adjustments row: the insulin amount is the difference from the scheduled basal"), + icon: "waveform.path.ecg", iconStyle: .basal, filters: [.basal, .loop], + basalChanges: automaticTemps.reversed())) + } + for temp in segments where temp.kind == .temp && !temp.automatic { + entries.append(LogEntry( + id: "temp-\(temp.start.timeIntervalSince1970)", date: temp.start, + title: NSLocalizedString("Temp basal", comment: "Logbook row title: a temporary basal rate"), + subtitle: String(format: NSLocalizedString("Manual · %1$@", comment: "Logbook temp basal row subtitle: set by hand, then the rate and how long. (1: description, e.g. 0.50 U/h for 30m (scheduled 0.80))"), describe(temp)), + amount: LoopFormat.rate(temp.rate), amountColor: P.ink, + detail: LoopFormat.time(temp.start), + icon: "waveform.path.ecg", iconStyle: .basal, filters: [.basal])) + } + for suspend in segments where suspend.kind == .suspended { + var parts = [suspend.automatic ? sourceLoop : sourceManual, LoopFormat.duration(suspend.end.timeIntervalSince(suspend.start))] + if let glucose = glucoseContext(at: suspend.start, history.glucose, unit: unit, withTrend: false) { parts.append(glucose) } + entries.append(LogEntry( + id: "suspend-\(suspend.start.timeIntervalSince1970)", date: suspend.start, + title: NSLocalizedString("Basal suspended", comment: "Logbook row title: insulin delivery was suspended"), subtitle: joined(parts), + amount: String(format: NSLocalizedString("%1$@ U/h", comment: "Format string for an insulin rate in units per hour. (1: rate)"), LoopFormat.number(0, decimals: 0)), amountColor: P.ink, + detail: LoopFormat.time(suspend.start), + icon: "pause.fill", iconStyle: .solid(P.statusWarning), + filters: suspend.automatic ? [.basal, .loop] : [.basal])) + } + + for event in pumpEvents where inDay(event.date) { + let title: String + switch event.kind { + // Site change and alarm names come from the pump plugin as it reports them. + case .siteChange(let name): title = name + case .alarm(let name): title = name + case .resume: title = NSLocalizedString("Basal resumed", comment: "Logbook row title: insulin delivery resumed after a suspend") + } + entries.append(LogEntry( + id: "pump-\(event.id)", date: event.date, + title: title, subtitle: NSLocalizedString("Pump", comment: "Logbook row subtitle: the event was reported by the insulin pump"), + amount: "", amountColor: P.ink, + detail: LoopFormat.time(event.date), + icon: "circle.dashed", iconStyle: .solid(P.inkMuted.opacity(0.5)), filters: [])) + } + + let insulin = InsulinSplit(basal: segments.reduce(0) { $0 + $1.units }, boluses: history.boluses.filter { inDay($0.date) }) + return LogDay(day: day.start, entries: foldingAutomaticBoluses(entries.sorted { $0.date > $1.date }), insulin: insulin, + carbs: carbsToday.reduce(0) { $0 + $1.grams }) + } + } + + /// Loop gives many small automatic boluses; two or more in a row, with nothing else logged between them, become + /// one row that lists them. The day's Basal adjustments summary row is dated by its latest change, not where it + /// falls, so it doesn't break a run. + static func foldingAutomaticBoluses(_ entries: [LogEntry]) -> [LogEntry] { + var result: [LogEntry] = [] + var run: [LogEntry] = [] + var summaries: [LogEntry] = [] + + func flush() { + if run.count >= 2, let newest = run.first, let oldest = run.last { + let units = run.reduce(0) { $0 + ($1.automaticUnits ?? 0) } + result.append(LogEntry( + id: "autorun-\(newest.id)", date: newest.date, + title: NSLocalizedString("Automatic boluses", comment: "Logbook row title: several automatic boluses in a row, folded into one row that lists them"), + subtitle: String(format: NSLocalizedString("Loop · %1$@ · tap to list", comment: "Logbook folded automatic boluses row subtitle: Loop gave them, the time span they cover, and tapping lists them. (1: time range, e.g. 15:08 – 16:14)"), + "\(LoopFormat.time(oldest.date))–\(LoopFormat.time(newest.date))"), + amount: LoopFormat.doseUnits(units), amountColor: P.insulinInk, + detail: String.localizedStringWithFormat(NSLocalizedString("%lld doses", comment: "Logbook folded automatic boluses row: how many automatic boluses it holds. (1: number)"), run.count), + icon: "arrow.triangle.2.circlepath", iconStyle: .hatch(P.bolus), + filters: [.bolus, .loop], quiet: true, + bolusRun: run)) + } else { + result.append(contentsOf: run) + } + run = [] + } + + for entry in entries { + if entry.automaticUnits != nil { + run.append(entry) + } else if !entry.basalChanges.isEmpty { + summaries.append(entry) + } else { + flush() + result.append(entry) + } + } + flush() + // Put the summary rows back in date order. + return (result + summaries).sorted { $0.date > $1.date } + } + + private static var sourceLoop: String { + NSLocalizedString("Loop", comment: "Logbook row detail: Loop made this change automatically (Loop is the app's name)") + } + + private static var sourceManual: String { + NSLocalizedString("Manual", comment: "Logbook row detail: the user made this entry or change by hand") + } + + /// Row details are a list of independent facts ("Manual", "45 g", "glucose 7.5"), shown with a middle-dot separator. + private static func joined(_ parts: [String]) -> String { + parts.joined(separator: " · ") + } + + static func describe(_ segment: BasalSegment) -> String { + let rate = LoopFormat.rate(segment.rate) + let duration = LoopFormat.duration(segment.end.timeIntervalSince(segment.start)) + if let scheduled = segment.scheduledRate { + return String(format: NSLocalizedString("%1$@ for %2$@ (scheduled %3$@)", comment: "A temp basal: its rate, how long it ran and the scheduled rate it replaced. (1: rate, e.g. 0.50 U/h; 2: duration, e.g. 30m; 3: scheduled rate number, e.g. 0.80)"), rate, duration, LoopFormat.number(scheduled, decimals: 2)) + } + return String(format: NSLocalizedString("%1$@ for %2$@", comment: "A temp basal: its rate and how long it ran. (1: rate, e.g. 0.50 U/h; 2: duration, e.g. 30m)"), rate, duration) + } + + /// "glucose 7.5", or "glucose 11.0 rising" for Loop's own doses. + private static func glucoseContext(at date: Date, _ readings: [GlucosePoint], unit: GlucoseUnitStyle, withTrend: Bool) -> String? { + guard let reading = readings.last(where: { $0.date <= date.addingTimeInterval(60) }), date.timeIntervalSince(reading.date) <= 10 * 60 else { + return nil + } + let value = unit.format(reading.mgdl) + if withTrend, let earlier = readings.last(where: { $0.date <= reading.date.addingTimeInterval(-14 * 60) }), + reading.date.timeIntervalSince(earlier.date) <= 20 * 60 { + let perFifteen = (reading.mgdl - earlier.mgdl) / reading.date.timeIntervalSince(earlier.date) * 900 + if perFifteen > 10 { + return String(format: NSLocalizedString("glucose %1$@ rising", comment: "Logbook row detail: glucose when the dose was given, and rising. (1: glucose value, e.g. 11.0)"), value) + } else if perFifteen < -10 { + return String(format: NSLocalizedString("glucose %1$@ falling", comment: "Logbook row detail: glucose when the dose was given, and falling. (1: glucose value, e.g. 11.0)"), value) + } + } + return String(format: NSLocalizedString("glucose %1$@", comment: "Logbook row detail: glucose when the dose was given. (1: glucose value, e.g. 7.5)"), value) + } +} + +// MARK: - Screen + +struct LogbookView: View { + @StateObject private var model: LogbookModel + @State private var path: [Route] = [] + /// Editing is a sheet, as adding carbs is, with its own navigation for the bolus screen that can follow. + @State private var editingCarbs: CarbEditRoute? + @Environment(\.dynamicTypeSize) private var typeSize + private let deviceManager: DeviceDataManager + + enum Route: Hashable { + case basal(String) + case boluses(String) + } + + struct CarbEditRoute: Identifiable { + let id = UUID() + let entry: StoredCarbEntry + } + + /// Closes the Logbook when it is presented on its own; nil where it has nowhere to return to. + private let done: (() -> Void)? + + init(provider: LoopHistoryProvider, deviceManager: DeviceDataManager, done: (() -> Void)? = nil) { + _model = StateObject(wrappedValue: LogbookModel(provider: provider)) + self.deviceManager = deviceManager + self.done = done + } + + private var visibleDays: [LogDay] { + model.days.map { day in + LogDay(day: day.day, entries: day.entries.filter { $0.matches(model.filter) }, insulin: day.insulin, carbs: day.carbs) + } + } + + var body: some View { + NavigationStack(path: $path) { + List { + Section { + chips + .listRowInsets(EdgeInsets()) + .listRowBackground(Color.clear) + } + if let error = model.loadError { + Text(String(format: NSLocalizedString("Couldn't load Logbook: %1$@", comment: "Logbook error when its history can't be read. (1: system error description)"), error)).foregroundStyle(P.criticalInk) + Button(NSLocalizedString("Retry", comment: "Button to try loading the Logbook again")) { model.reload() } + } else if model.loaded && model.days.allSatisfy({ $0.entries.isEmpty }) { + Text(NSLocalizedString("Nothing logged in the last 7 days.", comment: "Logbook empty state when there are no entries at all")) + .foregroundStyle(P.inkMuted) + } + ForEach(visibleDays) { day in + Section { + if day.entries.isEmpty { + Text(model.filter != .all + ? NSLocalizedString("Nothing to show for this filter", comment: "Logbook day with no entries matching the selected filter") + : day.insulin.total > 0 + ? NSLocalizedString("Scheduled basal only", comment: "Logbook day where only the scheduled basal ran, with nothing else logged") + : NSLocalizedString("Nothing logged", comment: "Logbook day with no entries")) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + } + ForEach(day.entries) { entry in + row(entry) + } + } header: { + dayHeader(day) + } + } + Section { + EmptyView() + } footer: { + Text(NSLocalizedString("Loop keeps the last 7 days on this phone. Older doses and carbs are in Apple Health and on Trends.", comment: "Logbook footer explaining why only 7 days are listed")) + } + } + .listStyle(.insetGrouped) + .scrollContentBackground(.hidden) + .background(P.surface) + .navigationTitle(NSLocalizedString("Logbook", comment: "Title of the Logbook tab")) + .refreshable { model.reload() } + .toolbar { + if let done { + ToolbarItem(placement: .confirmationAction) { + Button(NSLocalizedString("Done", comment: "Button to close the Logbook"), action: done) + } + } + } + .overlay { + if !model.loaded { ProgressView() } + } + .navigationDestination(for: Route.self) { route in + destination(route) + } + } + .sheet(item: $editingCarbs) { route in + NavigationStack { + LogbookCarbEditor(entry: route.entry, deviceManager: deviceManager, provider: model.provider) { + editingCarbs = nil + model.reload() + } + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button(NSLocalizedString("Cancel", comment: "Button label for cancel")) { editingCarbs = nil } + } + } + } + } + .onAppear { model.reload() } + .onReceive(NotificationCenter.default.publisher(for: .LoopDataUpdated).debounce(for: .seconds(2), scheduler: RunLoop.main)) { _ in + model.reload() + } + .alert(NSLocalizedString("Couldn't delete", comment: "Alert title when deleting a Logbook entry fails"), isPresented: Binding(get: { model.error != nil }, set: { if !$0 { model.error = nil } })) { + Button(NSLocalizedString("OK", comment: "Button to dismiss the Logbook delete error alert"), role: .cancel) {} + } message: { + Text(model.error ?? "") + } + } + + private var chips: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: LoopDesign.Space.s2) { + ForEach(LogEntry.Filter.allCases) { filter in + Button { + model.filter = filter + } label: { + Text(filter.title) + .font(LoopDesign.Typeface.footnote.weight(.semibold)) + .padding(.horizontal, 12) + .padding(.vertical, 6) + .foregroundStyle(model.filter == filter ? Color(uiColor: .systemBackground) : P.ink) + .background(model.filter == filter ? P.ink : P.surfaceSunken, in: Capsule()) + } + .buttonStyle(.plain) + .accessibilityAddTraits(model.filter == filter ? .isSelected : []) + } + } + .padding(.vertical, LoopDesign.Space.s1) + } + } + + private func dayHeader(_ day: LogDay) -> some View { + let calendar = Calendar.current + let date = LoopFormat.weekdayDay(day.day) + let title = calendar.isDateInToday(day.day) + ? String(format: NSLocalizedString("Today · %1$@", comment: "Logbook day header for today. (1: weekday and date, e.g. Sun 25 Oct)"), date) + : calendar.isDateInYesterday(day.day) + ? String(format: NSLocalizedString("Yesterday · %1$@", comment: "Logbook day header for yesterday. (1: weekday and date, e.g. Sat 24 Oct)"), date) + : date + return VStack(alignment: .leading, spacing: LoopDesign.Space.s2) { + let totals = HStack(spacing: LoopDesign.Space.s3) { + if day.insulin.total > 0 { + Text(LoopFormat.totalUnits(day.insulin.total)) + .foregroundStyle(P.insulinInk) + } + Text(LoopFormat.grams(day.carbs)) + .foregroundStyle(P.carbsInk) + } + .font(LoopDesign.Typeface.footnote.monospacedDigit()) + .fixedSize() + // The totals drop under the date when both won't fit on one line, rather than breaking numbers apart. + ViewThatFits(in: .horizontal) { + HStack(alignment: .firstTextBaseline) { + Text(title) + .font(LoopDesign.Typeface.headline) + .foregroundStyle(P.ink) + .fixedSize() + Spacer() + totals + } + VStack(alignment: .leading, spacing: LoopDesign.Space.s1) { + Text(title) + .font(LoopDesign.Typeface.headline) + .foregroundStyle(P.ink) + totals + } + } + if day.insulin.total > 0 { + // The day's total sits on the line above, so the key keeps to one line. + InsulinSplitLine(split: day.insulin, partial: calendar.isDateInToday(day.day)) + } + } + .padding(.bottom, 2) + .textCase(nil) + .accessibilityElement(children: .combine) + } + + @ViewBuilder + private func row(_ entry: LogEntry) -> some View { + // At accessibility sizes the amount moves under the title, so neither column squeezes the other into fragments. + let stacked = typeSize.isAccessibilitySize + let titles = VStack(alignment: .leading, spacing: 2) { + Text(entry.title) + .font(LoopDesign.Typeface.body) + .foregroundStyle(P.ink) + Text(entry.subtitle) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + } + let amounts = VStack(alignment: stacked ? .leading : .trailing, spacing: 2) { + if !entry.amount.isEmpty { + Text(entry.amount) + .font(LoopDesign.Typeface.body.weight(entry.quiet ? .regular : .semibold).monospacedDigit()) + .foregroundStyle(entry.amountColor) + } + Text(entry.detail) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + } + let content = HStack(alignment: stacked ? .top : .center, spacing: LoopDesign.Space.s3) { + IconTile(systemName: entry.icon, style: entry.iconStyle) + if stacked { + VStack(alignment: .leading, spacing: LoopDesign.Space.s1) { + titles + amounts + } + Spacer(minLength: 0) + } else { + titles + Spacer(minLength: LoopDesign.Space.s2) + amounts + } + } + .accessibilityElement(children: .combine) + + if !entry.basalChanges.isEmpty { + NavigationLink(value: Route.basal(entry.id)) { content } + } else if !entry.bolusRun.isEmpty { + NavigationLink(value: Route.boluses(entry.id)) { content } + } else { + content + .swipeActions(edge: .trailing, allowsFullSwipe: false) { + if let action = entry.delete, canPerform(action) { + Button(role: .destructive) { model.perform(action) } label: { Label(NSLocalizedString("Delete", comment: "Swipe action to delete a Logbook entry"), systemImage: "trash") } + } + if let carb = entry.edit, model.carbEditingAllowed { + Button { + guard model.provider.allowsCarbEditing else { return } + editingCarbs = CarbEditRoute(entry: carb) + } label: { Label(NSLocalizedString("Edit", comment: "Swipe action to edit a Logbook carb entry"), systemImage: "pencil") } + .tint(P.accent) + } + } + } + } + + private func canPerform(_ action: LogEntry.Action) -> Bool { + switch action { + case .deleteCarbs: return model.carbEditingAllowed + case .deleteDose: return true + } + } + + @ViewBuilder + private func destination(_ route: Route) -> some View { + switch route { + case .basal(let id): + if let entry = model.days.flatMap(\.entries).first(where: { $0.id == id }) { + BasalChangesList(changes: entry.basalChanges) + } + case .boluses(let id): + if let entry = model.days.flatMap(\.entries).first(where: { $0.id == id }) { + List(entry.bolusRun) { bolus in + row(bolus) + } + .navigationTitle(NSLocalizedString("Automatic boluses", comment: "Logbook row title: several automatic boluses in a row, folded into one row that lists them")) + .navigationBarTitleDisplayMode(.inline) + } + } + } +} + +/// Own the draft independently of rows that refresh while the editor or its bolus screen is open. +private struct LogbookCarbEditor: View { + @StateObject private var viewModel: CarbEntryViewModel + let deviceManager: DeviceDataManager + let provider: LoopHistoryProvider + let dismiss: () -> Void + + init(entry: StoredCarbEntry, deviceManager: DeviceDataManager, provider: LoopHistoryProvider, dismiss: @escaping () -> Void) { + _viewModel = StateObject(wrappedValue: provider.carbEntryViewModel(editing: entry)) + self.deviceManager = deviceManager + self.provider = provider + self.dismiss = dismiss + } + + var body: some View { + CarbEntryScreen(viewModel: viewModel) + .environmentObject(deviceManager.displayGlucosePreference) + .environment(\.dismissAction, dismiss) + .navigationTitle(NSLocalizedString("Edit Carb Entry", comment: "Title of the screen for editing a carb entry from the Logbook")) + .navigationBarTitleDisplayMode(.inline) + .onAppear { if !provider.allowsCarbEditing { dismiss() } } + // Loop posts this from its data queue; SwiftUI faults on publishers that emit off the main thread. + .onReceive(NotificationCenter.default.publisher(for: .LoopDataUpdated).receive(on: RunLoop.main)) { _ in + if !provider.allowsCarbEditing { dismiss() } + } + } +} + +/// Every basal change Loop made in a day. +struct BasalChangesList: View { + let changes: [BasalSegment] + + var body: some View { + List(Array(changes.enumerated()), id: \.offset) { _, change in + HStack { + VStack(alignment: .leading, spacing: 2) { + Text(LoopFormat.time(change.start)) + .font(LoopDesign.Typeface.body) + Text(LogbookModel.describe(change)) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + } + Spacer() + Text(String(format: NSLocalizedString("%1$@ U", comment: "Format string for an amount of insulin in units. (1: amount)"), LoopFormat.signed(change.netUnits))) + .font(LoopDesign.Typeface.body.weight(.semibold).monospacedDigit()) + .foregroundStyle(P.insulinInk) + } + .accessibilityElement(children: .combine) + } + .navigationTitle(NSLocalizedString("Basal adjustments", comment: "Title of the list of every temp basal Loop set in a day")) + .navigationBarTitleDisplayMode(.inline) + } +} diff --git a/Loop/Redesign/LoopDesign.swift b/Loop/Redesign/LoopDesign.swift new file mode 100644 index 0000000000..25ee400867 --- /dev/null +++ b/Loop/Redesign/LoopDesign.swift @@ -0,0 +1,427 @@ +// +// LoopDesign.swift +// Loop +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import SwiftUI +import UIKit + +/// The tokens from the Loop design system (`design/project/tokens.json`). +/// +/// Quantity colours reuse Loop's existing assets (`glucose`, `insulin`, `carbs`, `fresh`, `warning`, `accent`) +/// so a DerivedAssetsOverride still recolours them. Grounds and text map onto the iOS system colours they +/// were taken from; the rest are colour sets in DerivedAssetsBase.xcassets named after their token. +/// +/// Insulin and carb amounts are set in `insulinInk` and `carbsInk` wherever they stand alone. Glucose, basal, +/// counts and figures beside a legend swatch stay `ink`. Red is only for something that needs action now. +enum LoopDesign { + + enum Palette { + // Grounds + static let surface = Color(uiColor: .systemGroupedBackground) + static let surfaceRaised = Color(uiColor: .secondarySystemGroupedBackground) + static let surfaceSunken = Color(uiColor: .systemGray5) + static let controlThumb = Color("control-thumb") + static let separator = Color(uiColor: .opaqueSeparator) + static let grid = Color(uiColor: .systemGray5) + + // Text + static let ink = Color(uiColor: .label) + static let inkMuted = Color("ink-muted") + static let accent = Color("accent") + static let accentInk = Color("accent-ink") + static let onFill = Color.black + + // Quantities + static let glucose = Color("glucose") + static let glucoseInk = Color("glucose-ink") + static let targetBand = Color("target-band") + static let agpOuter = Color("agp-outer") + static let agpInner = Color("agp-inner") + /// Insulin as a quantity and an action: the Bolus button and the active insulin curve. + static let insulin = Color("insulin") + /// Every insulin amount set as text, whether a bolus, a day's total or active insulin. + static let insulinInk = Color("insulin-ink") + static let basal = Color("basal") + static let basalEdge = Color("basal-edge") + /// Basal is a pale, quiet orange with a tan edge; bolus is insulin's orange made deep. Both stay orange, + /// and their lightness is far enough apart (3:1 even where they touch) to tell them apart at a glance. + static let bolus = Color("bolus") + static let basalScheduled = inkMuted + static let carbs = Color("carbs") + static let carbsInk = Color("carbs-ink") + + // Time in range ladder, top to bottom + static let rangeVeryHigh = Color("range-very-high") + static let rangeHigh = Color("range-high") + static let rangeIn = Color("range-in") + static let rangeLow = Color("range-low") + static let rangeVeryLow = Color("range-very-low") + + // Loop state + static let statusAutomated = Color("fresh") + static let statusWarning = Color("warning") + static let statusCritical = Color("status-critical") + static let criticalInk = Color("critical-ink") + } + + enum Space { + static let s1: CGFloat = 4 + static let s2: CGFloat = 8 + static let s3: CGFloat = 12 + static let s4: CGFloat = 16 + static let s6: CGFloat = 24 + static let s8: CGFloat = 32 + } + + enum Radius { + static let sm: CGFloat = 6 + static let md: CGFloat = 10 + static let lg: CGFloat = 14 + } + + /// Text styles follow Dynamic Type; the large numbers are fixed sizes in SF Pro Rounded with tabular figures. + enum Typeface { + static let glucoseHero = Font.system(size: 64, weight: .bold, design: .rounded).monospacedDigit() + static let statValue = Font.system(.title, design: .rounded).weight(.semibold).monospacedDigit() + static let statValueSmall = Font.system(.title3, design: .rounded).weight(.semibold).monospacedDigit() + static let title = Font.title.bold() + static let headline = Font.headline + static let body = Font.body + static let subhead = Font.subheadline + static let footnote = Font.footnote + static let caption = Font.caption2.weight(.medium).monospacedDigit() + } +} + +// MARK: - Card + +struct LoopCard: View { + var padding: CGFloat = LoopDesign.Space.s4 + @ViewBuilder var content: Content + + var body: some View { + VStack(alignment: .leading, spacing: LoopDesign.Space.s3) { + content + } + .padding(padding) + .frame(maxWidth: .infinity, alignment: .leading) + .background(LoopDesign.Palette.surfaceRaised, in: RoundedRectangle(cornerRadius: LoopDesign.Radius.lg, style: .continuous)) + } +} + +/// A card title with an optional trailing detail or action, as on every card in the mock-ups. +struct CardHeader: View { + let title: String + @ViewBuilder var trailing: Trailing + + var body: some View { + HStack(alignment: .firstTextBaseline) { + Text(title) + .font(LoopDesign.Typeface.headline) + .foregroundStyle(LoopDesign.Palette.ink) + .accessibilityAddTraits(.isHeader) + Spacer(minLength: LoopDesign.Space.s2) + trailing + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(LoopDesign.Palette.inkMuted) + } + } +} + +extension CardHeader where Trailing == EmptyView { + init(title: String) { + self.init(title: title) { EmptyView() } + } +} + +// MARK: - Legend + +struct LegendItem: Identifiable { + enum Swatch { + case solid(Color) + case outline(Color) + case dash(Color) + case dot(Color) + case hatch(Color) + case basal + } + + let id = UUID() + let swatch: Swatch + let label: String +} + +struct ChartLegend: View { + let items: [LegendItem] + + var body: some View { + FlowLayout(spacing: LoopDesign.Space.s3, lineSpacing: LoopDesign.Space.s1) { + ForEach(items) { item in + HStack(spacing: LoopDesign.Space.s1) { + LegendSwatch(swatch: item.swatch) + Text(item.label) + } + .accessibilityElement(children: .combine) + } + } + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(LoopDesign.Palette.inkMuted) + } +} + +/// The 10-point key for one kind of mark, shared by legends and the basal and bolus rows. +struct LegendSwatch: View { + let swatch: LegendItem.Swatch + @Environment(\.colorScheme) private var scheme + + var body: some View { + switch swatch { + case .solid(let color): + RoundedRectangle(cornerRadius: 2).fill(color).frame(width: 10, height: 10) + case .outline(let color): + RoundedRectangle(cornerRadius: 2).strokeBorder(color, lineWidth: 1.5).frame(width: 10, height: 10) + case .dash(let color): + Path { path in + path.move(to: CGPoint(x: 0, y: 1)) + path.addLine(to: CGPoint(x: 14, y: 1)) + } + .stroke(color, style: StrokeStyle(lineWidth: 2, dash: [4, 2])) + .frame(width: 14, height: 2) + case .dot(let color): + Circle().fill(color).frame(width: 8, height: 8) + case .hatch(let color): + RoundedRectangle(cornerRadius: 2).fill(Hatch.paint(color, scheme: scheme)).frame(width: 10, height: 10) + case .basal: + VStack(spacing: 0) { + Rectangle().fill(LoopDesign.Palette.basalEdge).frame(height: 2) + Rectangle().fill(LoopDesign.Palette.basal) + } + .frame(width: 10, height: 10) + .clipShape(RoundedRectangle(cornerRadius: 2)) + } + } +} + +/// Diagonal stripes over a pale tint of a colour: automatic bolus, wherever a mark is wide enough to show it. +enum Hatch { + static func paint(_ color: Color, scheme: ColorScheme) -> ImagePaint { + let resolved = UIColor(color).resolvedColor(with: UITraitCollection(userInterfaceStyle: scheme == .dark ? .dark : .light)) + let size: CGFloat = 5 + let image = UIGraphicsImageRenderer(size: CGSize(width: size, height: size)).image { context in + resolved.withAlphaComponent(scheme == .dark ? 0.3 : 0.22).setFill() + context.fill(CGRect(x: 0, y: 0, width: size, height: size)) + resolved.setStroke() + // One stripe corner to corner, plus the ends of its neighbours, so tiles join into unbroken lines. + let path = UIBezierPath() + path.lineWidth = 1.4 + for offset in [-size, 0, size] { + path.move(to: CGPoint(x: offset, y: size)) + path.addLine(to: CGPoint(x: offset + size, y: 0)) + } + path.stroke() + } + return ImagePaint(image: Image(uiImage: image)) + } +} + +/// Wraps its children onto new lines, for legends and chips. +struct FlowLayout: Layout { + var spacing: CGFloat + var lineSpacing: CGFloat + + func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { + let width = proposal.width ?? .infinity + var x: CGFloat = 0, y: CGFloat = 0, lineHeight: CGFloat = 0, maxX: CGFloat = 0 + for subview in subviews { + let size = subview.sizeThatFits(.unspecified) + if x > 0 && x + size.width > width { + x = 0 + y += lineHeight + lineSpacing + lineHeight = 0 + } + x += size.width + spacing + maxX = max(maxX, x - spacing) + lineHeight = max(lineHeight, size.height) + } + return CGSize(width: maxX, height: y + lineHeight) + } + + func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { + var x = bounds.minX, y = bounds.minY, lineHeight: CGFloat = 0 + for subview in subviews { + let size = subview.sizeThatFits(.unspecified) + if x > bounds.minX && x + size.width > bounds.maxX { + x = bounds.minX + y += lineHeight + lineSpacing + lineHeight = 0 + } + subview.place(at: CGPoint(x: x, y: y), proposal: ProposedViewSize(size)) + x += size.width + spacing + lineHeight = max(lineHeight, size.height) + } + } +} + +/// A row that stacks vertically at accessibility text sizes, so tiles and buttons keep their numbers on one line. +struct AdaptiveRow: View { + @Environment(\.dynamicTypeSize) private var typeSize + var spacing: CGFloat = LoopDesign.Space.s2 + @ViewBuilder var content: Content + + var body: some View { + let layout = typeSize.isAccessibilitySize + ? AnyLayout(VStackLayout(alignment: .leading, spacing: spacing)) + : AnyLayout(HStackLayout(spacing: spacing)) + layout { content } + } +} + +// MARK: - Pills and tiles + +struct StatusPill: View { + let text: String + var dot: Color? = nil + + var body: some View { + HStack(spacing: LoopDesign.Space.s1) { + if let dot { + Circle().fill(dot).frame(width: 8, height: 8) + } + Text(text) + .lineLimit(1) + } + .font(LoopDesign.Typeface.footnote.weight(.medium)) + .foregroundStyle(LoopDesign.Palette.ink) + .padding(.horizontal, 10) + .padding(.vertical, 5) + .background(LoopDesign.Palette.surfaceSunken, in: Capsule()) + } +} + +/// A small sunken tile with a label over a value, as used under the hero and the timeline. +struct ValueTile: View { + let label: String + @ViewBuilder var value: Value + + var body: some View { + VStack(alignment: .leading, spacing: 2) { + Text(label) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(LoopDesign.Palette.inkMuted) + .lineLimit(1) + .minimumScaleFactor(0.7) + .allowsTightening(true) + // A value with a long unit ("0.04 単位") shrinks to fit rather than breaking mid-word. + value + .lineLimit(1) + .minimumScaleFactor(0.6) + } + .padding(LoopDesign.Space.s3) + .frame(maxWidth: .infinity, minHeight: 64, maxHeight: .infinity, alignment: .topLeading) + .background(LoopDesign.Palette.surfaceSunken, in: RoundedRectangle(cornerRadius: LoopDesign.Radius.md, style: .continuous)) + .accessibilityElement(children: .combine) + } +} + +/// The square icon tile at the start of logbook rows. +/// +/// Insulin tiles are drawn the way the charts draw the same insulin, so a row and its mark read alike: manual bolus +/// solid, automatic bolus hatched, basal pale with its edge. +struct IconTile: View { + enum Style { + case solid(Color) + case hatch(Color) + case basal + } + + let systemName: String + let style: Style + @Environment(\.colorScheme) private var scheme + + init(systemName: String, style: Style) { + self.systemName = systemName + self.style = style + } + + private var shape: RoundedRectangle { RoundedRectangle(cornerRadius: LoopDesign.Radius.sm, style: .continuous) } + + var body: some View { + Image(systemName: systemName) + .font(.system(size: isHatched ? 11 : 13, weight: .bold)) + .foregroundStyle(glyph) + // Stripes behind a glyph blur it, so the hatched tile sets its glyph on a plain disc. + .frame(width: isHatched ? 18 : 28, height: isHatched ? 18 : 28) + .background { if isHatched { Circle().fill(LoopDesign.Palette.surfaceRaised) } } + .frame(width: 28, height: 28) + .background { background } + .accessibilityHidden(true) + } + + private var isHatched: Bool { + if case .hatch = style { return true } + return false + } + + private var glyph: Color { + switch style { + case .solid: return LoopDesign.Palette.onFill + case .hatch, .basal: return LoopDesign.Palette.insulinInk + } + } + + @ViewBuilder + private var background: some View { + switch style { + case .solid(let fill): + shape.fill(fill) + case .hatch(let color): + shape.fill(Hatch.paint(color, scheme: scheme)) + .overlay(shape.strokeBorder(color, lineWidth: 1.5)) + case .basal: + shape.fill(LoopDesign.Palette.basal) + .overlay(shape.strokeBorder(LoopDesign.Palette.basalEdge, lineWidth: 1.5)) + } + } +} + +/// The round previous and next buttons beside a date or period. +struct StepButton: View { + let systemName: String + let label: String + var enabled = true + let action: () -> Void + + var body: some View { + Button(action: action) { + Image(systemName: systemName) + .font(.footnote.weight(.semibold)) + .frame(width: 30, height: 30) + .background(LoopDesign.Palette.surfaceRaised, in: Circle()) + .overlay(Circle().strokeBorder(LoopDesign.Palette.separator)) + .frame(width: 44, height: 44) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .foregroundStyle(enabled ? LoopDesign.Palette.ink : LoopDesign.Palette.inkMuted.opacity(0.5)) + .disabled(!enabled) + .accessibilityLabel(label) + } +} + +/// A message in place of a chart that has nothing to draw, saying why. +struct ChartPlaceholder: View { + let message: String + var height: CGFloat = 120 + + var body: some View { + Text(message) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(LoopDesign.Palette.inkMuted) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity, minHeight: height) + .background(LoopDesign.Palette.surfaceSunken.opacity(0.5), in: RoundedRectangle(cornerRadius: LoopDesign.Radius.md)) + } +} diff --git a/Loop/Redesign/LoopHistoryMath.swift b/Loop/Redesign/LoopHistoryMath.swift new file mode 100644 index 0000000000..45ffaf8297 --- /dev/null +++ b/Loop/Redesign/LoopHistoryMath.swift @@ -0,0 +1,374 @@ +// +// LoopHistoryMath.swift +// Loop +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import Foundation + +/// Pure calculations behind the Trends and Logbook screens. Nothing here touches a store. +enum LoopHistoryMath { + + // MARK: Glucose statistics + + struct GlucoseStats: Equatable { + let count: Int + let meanMgdl: Double + let sdMgdl: Double + /// Coefficient of variation, 0–1. + let cv: Double + /// Glucose management indicator, as a percentage (Bergenstal 2018). + let gmiPercent: Double + var gmiMmolMol: Double { (gmiPercent - 2.152) * 10.929 } + } + + static func glucoseStats(_ points: [GlucosePoint]) -> GlucoseStats? { + guard points.count >= 2 else { return nil } + let values = points.map(\.mgdl) + let mean = values.reduce(0, +) / Double(values.count) + let variance = values.reduce(0) { $0 + pow($1 - mean, 2) } / Double(values.count - 1) + let sd = variance.squareRoot() + return GlucoseStats(count: values.count, meanMgdl: mean, sdMgdl: sd, cv: sd / mean, gmiPercent: 3.31 + 0.02392 * mean) + } + + /// The share of the period with CGM readings, assuming one reading every 5 minutes. + static func cgmActiveFraction(_ points: [GlucosePoint], over interval: DateInterval) -> Double { + let expected = interval.duration / 300 + guard expected > 0 else { return 0 } + return min(1, Double(points.count) / expected) + } + + // MARK: Time in range + + enum RangeBand: Int, CaseIterable, Identifiable { + case veryHigh, high, inRange, low, veryLow + + var id: Int { rawValue } + + static func band(for mgdl: Double) -> RangeBand { + switch mgdl { + case ..<54: return .veryLow + case ..<70: return .low + case ...180: return .inRange + case ...250: return .high + default: return .veryHigh + } + } + } + + /// Fractions (0–1) per band, in ladder order. Empty input gives all zeros. + static func timeInRange(_ points: [GlucosePoint]) -> [RangeBand: Double] { + var counts: [RangeBand: Int] = [:] + for point in points { + counts[RangeBand.band(for: point.mgdl), default: 0] += 1 + } + let total = Double(points.count) + return Dictionary(uniqueKeysWithValues: RangeBand.allCases.map { band in + (band, total > 0 ? Double(counts[band] ?? 0) / total : 0) + }) + } + + /// Whole-number percentages that add up to 100 (largest remainder), so the labels never sum to 99 or 101. + static func roundedPercentages(_ fractions: [RangeBand: Double]) -> [RangeBand: Int] { + let raw = RangeBand.allCases.map { (band: $0, value: (fractions[$0] ?? 0) * 100) } + let total = raw.reduce(0) { $0 + $1.value } + guard total > 0 else { return Dictionary(uniqueKeysWithValues: RangeBand.allCases.map { ($0, 0) }) } + var floors = raw.map { (band: $0.band, floor: Int($0.value.rounded(.down)), remainder: $0.value - $0.value.rounded(.down)) } + var missing = Int(total.rounded()) - floors.reduce(0) { $0 + $1.floor } + for index in floors.indices.sorted(by: { floors[$0].remainder > floors[$1].remainder }) where missing > 0 { + floors[index].floor += 1 + missing -= 1 + } + return Dictionary(uniqueKeysWithValues: floors.map { ($0.band, $0.floor) }) + } + + // MARK: AGP + + struct AGPSlot: Equatable { + /// Minutes after midnight at the start of the slot. + let minute: Int + let p5: Double + let p25: Double + let p50: Double + let p75: Double + let p95: Double + } + + /// Readings folded onto one day in `slotMinutes` slots, as mg/dL percentiles. Slots with fewer than 3 readings are skipped. + static func agp(_ points: [GlucosePoint], slotMinutes: Int = 15, calendar: Calendar = .current) -> [AGPSlot] { + var slots: [Int: [Double]] = [:] + for point in points { + let parts = calendar.dateComponents([.hour, .minute], from: point.date) + let minute = (parts.hour ?? 0) * 60 + (parts.minute ?? 0) + slots[minute / slotMinutes, default: []].append(point.mgdl) + } + return slots.keys.sorted().compactMap { key in + let values = slots[key]!.sorted() + guard values.count >= 3 else { return nil } + return AGPSlot(minute: key * slotMinutes, + p5: percentile(values, 0.05), p25: percentile(values, 0.25), p50: percentile(values, 0.5), + p75: percentile(values, 0.75), p95: percentile(values, 0.95)) + } + } + + /// Linear-interpolated percentile of sorted values. + static func percentile(_ sorted: [Double], _ p: Double) -> Double { + guard !sorted.isEmpty else { return .nan } + let position = p * Double(sorted.count - 1) + let lower = Int(position.rounded(.down)), upper = Int(position.rounded(.up)) + let weight = position - Double(lower) + return sorted[lower] * (1 - weight) + sorted[upper] * weight + } + + /// The number of distinct calendar days with readings. + static func daysWithReadings(_ points: [GlucosePoint], calendar: Calendar = .current) -> Int { + Set(points.map { calendar.startOfDay(for: $0.date) }).count + } + + /// Whole days from the first reading to the end of `interval`, when that falls short of the period by more than a + /// day; nil when the readings span the period, or there are none. + static func cgmCoveredDays(_ points: [GlucosePoint], in interval: DateInterval, periodDays: Int) -> Int? { + guard periodDays > 1, let first = points.map(\.date).min() else { return nil } + let available = interval.end.timeIntervalSince(first) / 86400 + guard available + 1 < Double(periodDays) else { return nil } + return max(1, Int(available.rounded())) + } + + /// Units the schedule would deliver over `span`, or nil where the points don't cover all of it. + static func scheduledUnits(_ points: [ScheduledBasalPoint], over span: DateInterval) -> Double? { + var units = 0.0 + var covered: TimeInterval = 0 + for point in points { + let overlap = min(point.end, span.end).timeIntervalSince(max(point.start, span.start)) + guard overlap > 0 else { continue } + units += point.rate * overlap / 3600 + covered += overlap + } + return covered >= span.duration - 60 ? units : nil + } + + // MARK: Days + + /// Midnight-to-midnight intervals covering `interval`, oldest first, the last one cut off at `interval.end`. + static func days(in interval: DateInterval, calendar: Calendar = .current) -> [DateInterval] { + var result: [DateInterval] = [] + var start = calendar.startOfDay(for: interval.start) + while start < interval.end { + guard let next = calendar.date(byAdding: .day, value: 1, to: start) else { break } + result.append(DateInterval(start: max(start, interval.start), end: min(next, interval.end))) + start = next + } + return result + } + + struct DayTotal: Equatable, Identifiable { + let day: Date + let split: InsulinSplit + /// False for a day still in progress, so its partial total doesn't pull per-day averages down. + var isComplete = true + var id: Date { day } + var basal: Double { split.basal } + var bolus: Double { split.bolus } + var total: Double { split.total } + + init(day: Date, split: InsulinSplit, isComplete: Bool = true) { + self.day = day + self.split = split + self.isComplete = isComplete + } + + /// A total with no manual or automatic detail: all of its bolus counts as manual. + init(day: Date, basal: Double, bolus: Double, isComplete: Bool = true) { + self.init(day: day, split: InsulinSplit(basal: basal, manualBolus: bolus), isComplete: isComplete) + } + } + + /// The days per-day insulin figures average over: days with insulin, leaving out a day still in progress + /// unless no complete day has any. + static func averagingDays(_ totals: [DayTotal]) -> [DayTotal] { + let recorded = totals.filter { $0.total > 0 } + let complete = recorded.filter(\.isComplete) + return complete.isEmpty ? recorded : complete + } + + /// One source for the summary, legend and chart rule, before any weekly aggregation. + static func averageDailyInsulin(_ totals: [DayTotal]) -> Double { + let days = averagingDays(totals) + guard !days.isEmpty else { return 0 } + return days.reduce(0) { $0 + $1.total } / Double(days.count) + } + + /// Averages daily totals by calendar week (Monday start), for periods past 30 days. + static func weekly(_ totals: [DayTotal], calendar: Calendar = .current) -> [DayTotal] { + var cal = calendar + cal.firstWeekday = 2 + let groups = Dictionary(grouping: totals) { cal.dateInterval(of: .weekOfYear, for: $0.day)?.start ?? $0.day } + return groups.keys.sorted().map { week in + DayTotal(day: week, split: InsulinSplit.average(averagingDays(groups[week]!).map(\.split))) + } + } + + // MARK: Basal + + struct ScheduleValue { + let start: Date + let end: Date + let rate: Double + } + + /// A basal record from the pump: scheduled delivery, a temp basal or a suspend. + struct BasalDelivery { + let start: Date + let end: Date + /// U/h; 0 for a suspend. + let rate: Double + let kind: BasalSegment.Kind + let automatic: Bool + } + + /// Delivered basal over `window` from the pump's records (the latest-starting one wins where they overlap). + /// + /// From `recordsStart` on, time without a basal record counts at `programmedRates`, since that is what the pump + /// delivers on its own; this matches Loop's own insulin totals (`InsulinDeliveryHistory`). Before it, or without + /// it, gaps stay unknown. + static func basalSegments(window: DateInterval, schedule: [ScheduleValue], deliveries temps: [BasalDelivery], + programmedRates: [ScheduleValue] = [], recordsStart: Date? = nil) -> [BasalSegment] { + var cuts = Set([window.start, window.end]) + if let recordsStart, window.contains(recordsStart) { cuts.insert(recordsStart) } + for value in schedule + programmedRates { + if window.contains(value.start) { cuts.insert(value.start) } + if window.contains(value.end) { cuts.insert(value.end) } + } + for temp in temps { + if window.contains(temp.start) { cuts.insert(temp.start) } + if window.contains(temp.end) { cuts.insert(temp.end) } + } + let sorted = cuts.sorted() + let sortedTemps = temps.sorted { $0.start < $1.start } + // The furthest end among records up to each index bounds the backward search, so a week of five-minute + // records stays linear rather than scanning every record for every cut. + var furthestEnd: [Date] = [] + furthestEnd.reserveCapacity(sortedTemps.count) + for temp in sortedTemps { furthestEnd.append(max(furthestEnd.last ?? temp.end, temp.end)) } + func latestRecord(covering mid: Date) -> BasalDelivery? { + var low = 0, high = sortedTemps.count + while low < high { + let middle = (low + high) / 2 + if sortedTemps[middle].start <= mid { low = middle + 1 } else { high = middle } + } + var index = low - 1 + while index >= 0, furthestEnd[index] > mid { + if mid < sortedTemps[index].end { return sortedTemps[index] } + index -= 1 + } + return nil + } + var segments: [BasalSegment] = [] + for (a, b) in zip(sorted, sorted.dropFirst()) where b > a { + let mid = a.addingTimeInterval(b.timeIntervalSince(a) / 2) + let scheduled = schedule.first { $0.start <= mid && mid < $0.end }?.rate + let segment: BasalSegment + if let temp = latestRecord(covering: mid) { + segment = BasalSegment(start: a, end: b, rate: temp.kind == .suspended ? 0 : temp.rate, scheduledRate: scheduled, + kind: temp.kind, automatic: temp.automatic) + } else if let recordsStart, a >= recordsStart, + let programmed = programmedRates.first(where: { $0.start <= mid && mid < $0.end })?.rate { + segment = BasalSegment(start: a, end: b, rate: programmed, scheduledRate: scheduled, kind: .scheduled, automatic: false) + } else { + continue + } + if let last = segments.last, last.end == segment.start, last.rate == segment.rate, last.kind == segment.kind, + last.scheduledRate == segment.scheduledRate, last.automatic == segment.automatic { + segments[segments.count - 1] = BasalSegment(start: last.start, end: segment.end, rate: last.rate, + scheduledRate: last.scheduledRate, kind: last.kind, automatic: last.automatic) + } else { + segments.append(segment) + } + } + return segments + } + + /// Basal rate at a moment, from delivered segments. + static func basalRate(at date: Date, in segments: [BasalSegment]) -> BasalSegment? { + segments.last { $0.start <= date && date < $0.end } + } + + // MARK: Loop modes + + struct LoopRun { + let date: Date + let succeeded: Bool + let error: String? + } + + struct ModeChange: Codable, Equatable { + let date: Date + let closedLoop: Bool + } + + /// Splits `interval` into closed, limited and open stretches. + /// + /// Each 5-minute slot is open when closed loop was switched off (from `modeChanges`), closed when Loop completed a + /// run in the 15 minutes before the slot ends, and limited otherwise. Before the first mode change the setting is + /// unknown, so the result starts at the later of `coverageStart` and the first mode change. + static func loopModeSpans(interval: DateInterval, runs: [LoopRun], modeChanges: [ModeChange], coverageStart: Date?, + recency: TimeInterval = 15 * 60, slot: TimeInterval = 5 * 60) -> [LoopModeSpan] { + guard let coverageStart, let firstChange = modeChanges.map(\.date).min() else { return [] } + let start = max(interval.start, coverageStart, firstChange) + guard interval.end > start else { return [] } + + let sortedRuns = runs.sorted { $0.date < $1.date } + let changes = modeChanges.sorted { $0.date < $1.date } + var spans: [LoopModeSpan] = [] + var runIndex = 0 + var lastSuccess: Date? = nil + var lastError: String? = nil + var changeIndex = 0 + var closedSetting: Bool? = nil + + var t = start + while t < interval.end { + let slotEnd = min(t.addingTimeInterval(slot), interval.end) + while runIndex < sortedRuns.count && sortedRuns[runIndex].date <= slotEnd { + let run = sortedRuns[runIndex] + if run.succeeded { + lastSuccess = run.date + lastError = nil + } else { + lastError = run.error ?? lastError + } + runIndex += 1 + } + while changeIndex < changes.count && changes[changeIndex].date <= slotEnd { + closedSetting = changes[changeIndex].closedLoop + changeIndex += 1 + } + + let mode: LoopModeSpan.Mode + let cause: String? + if closedSetting == false { + mode = .open + cause = NSLocalizedString("Closed loop off", comment: "Reason shown for a stretch of time when Loop was in open loop because closed loop was turned off") + } else if let lastSuccess, slotEnd.timeIntervalSince(lastSuccess) <= recency { + mode = .closed + cause = nil + } else { + mode = .limited + cause = lastError ?? NSLocalizedString("Loop didn't run", comment: "Reason shown for a stretch of time when Loop did not complete a run and recorded no error") + } + + if let last = spans.last, last.mode == mode, last.cause == cause, last.end == t { + spans[spans.count - 1] = LoopModeSpan(start: last.start, end: slotEnd, mode: mode, cause: cause) + } else { + spans.append(LoopModeSpan(start: t, end: slotEnd, mode: mode, cause: cause)) + } + t = slotEnd + } + return spans + } + + static func duration(of mode: LoopModeSpan.Mode, in spans: [LoopModeSpan]) -> TimeInterval { + spans.filter { $0.mode == mode }.reduce(0) { $0 + $1.duration } + } +} diff --git a/Loop/Redesign/LoopHistoryModels.swift b/Loop/Redesign/LoopHistoryModels.swift new file mode 100644 index 0000000000..a3cedc5319 --- /dev/null +++ b/Loop/Redesign/LoopHistoryModels.swift @@ -0,0 +1,366 @@ +// +// LoopHistoryModels.swift +// Loop +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import Foundation +import HealthKit +import LoopKit +import LoopAlgorithm + +/// Plain value types the redesigned screens draw from. Glucose is kept in mg/dL and converted only for display, +/// so the statistics don't depend on the display unit. + +struct GlucosePoint: Equatable { + let date: Date + let mgdl: Double + /// A fingerstick or other reading typed in: drawn on charts, but left out of CGM statistics. + var userEntered = false +} + +struct BolusEvent: Equatable, Identifiable { + let id: String + let date: Date + let units: Double + /// Issued by Loop (automatic bolus) rather than by the person. + let automatic: Bool + /// Logged in Loop by hand rather than delivered by the pump; these can be deleted. + let manuallyEntered: Bool + /// Kept so a manually entered dose can be deleted from the dose store. + var dose: DoseEntry? = nil + /// False when the record doesn't say whether Loop or the person gave it, such as another app's doses in Apple Health. + var automaticKnown = true + + static func == (lhs: BolusEvent, rhs: BolusEvent) -> Bool { + lhs.id == rhs.id && lhs.date == rhs.date && lhs.units == rhs.units && lhs.automatic == rhs.automatic + } +} + +struct BasalSegment: Equatable { + enum Kind: Equatable { + /// The scheduled rate, with no temp basal running. + case scheduled + /// A temp basal, set by Loop or by hand. + case temp + case suspended + } + + let start: Date + let end: Date + /// The rate actually delivered, in U/h. + let rate: Double + /// The profile rate at the time, in U/h, if known. + let scheduledRate: Double? + let kind: Kind + let automatic: Bool + + var hours: Double { end.timeIntervalSince(start) / 3600 } + var units: Double { rate * hours } + /// Units delivered above (positive) or below (negative) the schedule. + var netUnits: Double { + guard let scheduledRate else { return 0 } + return (rate - scheduledRate) * hours + } + + func clamped(to interval: DateInterval) -> BasalSegment? { + let s = max(start, interval.start), e = min(end, interval.end) + guard e > s else { return nil } + return BasalSegment(start: s, end: e, rate: rate, scheduledRate: scheduledRate, kind: kind, automatic: automatic) + } +} + +struct ScheduledBasalPoint: Equatable { + let start: Date + let end: Date + let rate: Double +} + +/// The correction range over part of a chart, in mg/dL. +struct TargetBand: Equatable { + let start: Date + let end: Date + let range: ClosedRange +} + +struct CarbEvent: Equatable, Identifiable { + let id: String + let date: Date + let grams: Double + let absorptionHours: Double? + let foodType: String? + let createdByLoop: Bool + /// Kept so the entry can be edited or deleted. + var entry: StoredCarbEntry? = nil + + static func == (lhs: CarbEvent, rhs: CarbEvent) -> Bool { + lhs.id == rhs.id && lhs.date == rhs.date && lhs.grams == rhs.grams + } +} + +struct TimedValue: Equatable { + let date: Date + let value: Double +} + +struct PumpLogEvent: Equatable, Identifiable { + enum Kind: Equatable { + case siteChange(String) + case alarm(String) + case resume + } + + let id: String + let date: Date + let kind: Kind +} + +/// Everything known about one day or reporting period, for InsulinTimeline and the Now screen. +struct DayHistory { + var window: DateInterval + var glucose: [GlucosePoint] = [] + var boluses: [BolusEvent] = [] + var basal: [BasalSegment] = [] + var scheduledBasal: [ScheduledBasalPoint] = [] + var carbs: [CarbEvent] = [] + var iob: [TimedValue] = [] + var cob: [TimedValue] = [] + /// Where the data came from, so older days can say they are missing Loop's own detail. + var source: HistorySource = .loop + + var bolusUnits: Double { boluses.reduce(0) { $0 + $1.units } } + var basalUnits: Double { basal.reduce(0) { $0 + $1.units } } + var carbGrams: Double { carbs.reduce(0) { $0 + $1.grams } } + var totalUnits: Double { bolusUnits + basalUnits } + var insulinSplit: InsulinSplit { InsulinSplit(basal: basalUnits, boluses: boluses) } + + /// Only what falls inside `interval`, with basal cut at its edges. + func clipped(to interval: DateInterval) -> DayHistory { + var history = self + let inside: (Date) -> Bool = { $0 >= interval.start && $0 < interval.end } + history.window = interval + history.glucose = glucose.filter { inside($0.date) } + history.boluses = boluses.filter { inside($0.date) } + history.basal = basal.compactMap { $0.clamped(to: interval) } + history.carbs = carbs.filter { inside($0.date) } + history.iob = iob.filter { inside($0.date) } + history.cob = cob.filter { inside($0.date) } + return history + } +} + +/// Basal and bolus for one day, or averaged per day, with bolus split into manual and automatic. +struct InsulinSplit: Equatable { + var basal = 0.0 + var manualBolus = 0.0 + var automaticBolus = 0.0 + /// Bolus that doesn't say whether it was manual or automatic; while there is any, the split isn't shown. + var unclassifiedBolus = 0.0 + + var bolus: Double { manualBolus + automaticBolus + unclassifiedBolus } + var total: Double { basal + bolus } + var isClassified: Bool { unclassifiedBolus < 0.005 } + + /// Shares in whole percent that add up to 100. + var bolusPercent: Int { total > 0 ? Int((bolus / total * 100).rounded()) : 0 } + var basalPercent: Int { total > 0 ? 100 - bolusPercent : 0 } +} + +extension InsulinSplit { + init(basal: Double, boluses: [BolusEvent]) { + self.basal = basal + for bolus in boluses { + if !bolus.automaticKnown { + unclassifiedBolus += bolus.units + } else if bolus.automatic { + automaticBolus += bolus.units + } else { + manualBolus += bolus.units + } + } + } + + /// The per-day average of several days' splits. + static func average(_ splits: [InsulinSplit]) -> InsulinSplit { + guard !splits.isEmpty else { return InsulinSplit() } + let n = Double(splits.count) + return InsulinSplit(basal: splits.reduce(0) { $0 + $1.basal } / n, + manualBolus: splits.reduce(0) { $0 + $1.manualBolus } / n, + automaticBolus: splits.reduce(0) { $0 + $1.automaticBolus } / n, + unclassifiedBolus: splits.reduce(0) { $0 + $1.unclassifiedBolus } / n) + } +} + +enum HistorySource { + /// Loop's own stores: manual versus automatic, temp basals and the schedule are all known. + case loop + /// Apple Health, for days older than Loop keeps. Doses are split into basal and bolus only. + case health +} + +/// One stretch of the period in a single loop mode. +struct LoopModeSpan: Equatable { + enum Mode: Equatable { + case closed + case limited + case open + } + + let start: Date + let end: Date + let mode: Mode + let cause: String? + + var duration: TimeInterval { end.timeIntervalSince(start) } +} + +// MARK: - Glucose unit + +struct GlucoseUnitStyle: Equatable { + let isMmol: Bool + + init(unit: LoopUnit) { + isMmol = unit == .millimolesPerLiter + } + + init(isMmol: Bool) { + self.isMmol = isMmol + } + + static let mgdlPerMmol = 18.0182 + + var unitName: String { isMmol ? "mmol/L" : "mg/dL" } + + func value(_ mgdl: Double) -> Double { isMmol ? mgdl / Self.mgdlPerMmol : mgdl } + func mgdl(_ displayValue: Double) -> Double { isMmol ? displayValue * Self.mgdlPerMmol : displayValue } + + func format(_ mgdl: Double) -> String { + isMmol ? LoopFormat.number(mgdl / Self.mgdlPerMmol, decimals: 1) : LoopFormat.number(mgdl, decimals: 0) + } + + /// Fixed chart scales from the design: 2–20 mmol/L on day views, 0–22 on AGP. + var dayDomain: ClosedRange { isMmol ? 2...20 : 36...360 } + var agpDomain: ClosedRange { isMmol ? 0...22 : 0...400 } + var dayTicks: [Double] { isMmol ? [4, 10, 16] : [70, 180, 300] } + var agpTicks: [Double] { isMmol ? [0, 4, 10, 16, 22] : [0, 70, 180, 250, 400] } +} + +// MARK: - Formatting + +enum LoopFormat { + /// A number in the user's locale, e.g. "7.3" or "7,3", with no grouping so values read like Loop's own screens. + static func number(_ value: Double, decimals: Int) -> String { + value.formatted(.number.precision(.fractionLength(decimals)).grouping(.never)) + } + + static func doseUnits(_ units: Double) -> String { + String(format: NSLocalizedString("%1$@ U", comment: "Format string for an amount of insulin in units. (1: amount)"), number(units, decimals: 2)) + } + + static func totalUnits(_ units: Double) -> String { + String(format: NSLocalizedString("%1$@ U", comment: "Format string for an amount of insulin in units. (1: amount)"), number(units, decimals: 1)) + } + + static func rate(_ rate: Double) -> String { + String(format: NSLocalizedString("%1$@ U/h", comment: "Format string for an insulin rate in units per hour. (1: rate)"), number(rate, decimals: 2)) + } + + static func grams(_ grams: Double) -> String { + String(format: NSLocalizedString("%1$@ g", comment: "Format string for an amount of carbohydrate in grams. (1: grams)"), number(grams, decimals: 0)) + } + + static func signed(_ value: Double, decimals: Int = 2) -> String { + let s = number(abs(value), decimals: decimals) + if abs(value) < pow(10, -Double(decimals)) / 2 { return "+" + s } + return (value < 0 ? "−" : "+") + s + } + + static func time(_ date: Date) -> String { + date.formatted(date: .omitted, time: .shortened) + } + + /// "25 Oct 2026" + static func day(_ date: Date) -> String { + date.formatted(.dateTime.day().month(.abbreviated).year()) + } + + /// "Sun 25 Oct" + static func weekdayDay(_ date: Date) -> String { + date.formatted(.dateTime.weekday(.abbreviated).day().month(.abbreviated)) + } + + /// "12 – 25 Oct 2026", "28 Sep – 25 Oct 2026", "20 Dec 2025 – 2 Jan 2026", in the user's locale. + static func range(_ start: Date, _ end: Date, calendar: Calendar = .current) -> String { + if calendar.isDate(start, inSameDayAs: end) { + return day(end) + } + let formatter = DateIntervalFormatter() + formatter.calendar = calendar + formatter.dateTemplate = "dMMMy" + return formatter.string(from: start, to: end) + } + + /// "13d 11h", "9h 45m", "40m" in English; abbreviated in the user's language. + static func duration(_ interval: TimeInterval) -> String { + let minutes = Int((interval / 60).rounded()) + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .abbreviated + formatter.allowedUnits = minutes >= 1440 ? [.day, .hour] : minutes >= 60 ? [.hour, .minute] : [.minute] + formatter.zeroFormattingBehavior = .dropAll + return formatter.string(from: TimeInterval(minutes * 60)) ?? "" + } + + /// "2m", "1h 5m": how long ago, for the status pill. + static func ago(_ date: Date, now: Date = Date()) -> String { + let minutes = max(0, Int(now.timeIntervalSince(date) / 60)) + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .abbreviated + formatter.allowedUnits = minutes >= 1440 ? [.day, .hour] : minutes >= 60 ? [.hour, .minute] : [.minute] + formatter.zeroFormattingBehavior = minutes == 0 ? .pad : .dropAll + return formatter.string(from: TimeInterval(minutes * 60)) ?? "" + } +} + +// MARK: - Store conversion boundaries + +extension BolusEvent { + init?(dose: DoseEntry, in interval: DateInterval) { + guard dose.type == .bolus, dose.startDate >= interval.start, dose.startDate < interval.end else { return nil } + self.init(id: dose.syncIdentifier ?? "\(dose.startDate.timeIntervalSince1970)", date: dose.startDate, + units: dose.unitsInDeliverableIncrements, automatic: dose.automatic ?? false, + manuallyEntered: dose.manuallyEntered, dose: dose) + } +} + +extension LoopHistoryMath.BasalDelivery { + init?(dose: DoseEntry) { + let kind: BasalSegment.Kind + switch dose.type { + case .basal: kind = .scheduled + case .tempBasal: kind = .temp + case .suspend: kind = .suspended + default: return nil + } + let hours = dose.endDate.timeIntervalSince(dose.startDate) / 3600 + guard hours > 0 else { return nil } + self.init(start: dose.startDate, end: dose.endDate, + rate: kind == .suspended ? 0 : dose.unitsInDeliverableIncrements / hours, + kind: kind, automatic: dose.automatic ?? (kind == .temp)) + } +} + +extension BasalSegment { + init?(healthSample sample: HKQuantitySample, in interval: DateInterval) { + let hours = sample.endDate.timeIntervalSince(sample.startDate) / 3600 + guard hours > 0 else { return nil } + let scheduled = (sample.metadata?["com.loopkit.InsulinKit.MetadataKeyScheduledBasalRate"] as? HKQuantity)? + .doubleValue(for: HKUnit.internationalUnit().unitDivided(by: .hour())) + let segment = BasalSegment(start: sample.startDate, end: sample.endDate, + rate: sample.quantity.doubleValue(for: .internationalUnit()) / hours, + scheduledRate: scheduled, kind: .temp, + automatic: (sample.metadata?["com.loopkit.InsulinKit.MetadataKeyAutomaticallyIssued"] as? NSNumber)?.boolValue ?? false) + guard let clamped = segment.clamped(to: interval) else { return nil } + self = clamped + } +} diff --git a/Loop/Redesign/LoopHistoryProvider.swift b/Loop/Redesign/LoopHistoryProvider.swift new file mode 100644 index 0000000000..a2f6fb6f49 --- /dev/null +++ b/Loop/Redesign/LoopHistoryProvider.swift @@ -0,0 +1,543 @@ +// +// LoopHistoryProvider.swift +// Loop +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import Foundation +import HealthKit +import LoopKit +import LoopCore +import LoopAlgorithm +import os + +/// Reads glucose, insulin, carbs and loop runs for the redesigned screens. +/// +/// Loop keeps `LOOP_LOCAL_CACHE_DURATION_DAYS` (7) days in its own stores. Inside that window everything comes from +/// those stores, so manual and automatic doses, temp basals and the schedule are known. Older days come from Apple +/// Health, which has glucose, carbs and insulin split only into basal and bolus. +final class LoopHistoryProvider { + + private let deviceManager: DeviceDataManager + private let loopDataManager: LoopDataManager + private let settingsManager: SettingsManager + private let temporaryPresetsManager: TemporaryPresetsManager + private let dosingDecisionStore: DosingDecisionStoreProtocol + private let doseStore: DoseStore + private let carbStore: CarbStore + private let glucoseStore: GlucoseStore + private let healthStore = HKHealthStore() + private let log = OSLog(category: "LoopHistoryProvider") + private let recordsStartCache = OSAllocatedUnfairLock<(fetched: Date, value: Date)?>(initialState: nil) + + @MainActor + init(deviceManager: DeviceDataManager, loopDataManager: LoopDataManager, settingsManager: SettingsManager, temporaryPresetsManager: TemporaryPresetsManager) { + self.deviceManager = deviceManager + self.loopDataManager = loopDataManager + self.settingsManager = settingsManager + self.temporaryPresetsManager = temporaryPresetsManager + self.dosingDecisionStore = loopDataManager.dosingDecisionStore + self.doseStore = deviceManager.doseStore + self.carbStore = deviceManager.carbStore + self.glucoseStore = deviceManager.glucoseStore + } + + @MainActor + var unitStyle: GlucoseUnitStyle { + GlucoseUnitStyle(unit: deviceManager.displayGlucosePreference.unit) + } + + /// Match the classic carb editor's eligibility rule, including mode changes while a screen is open. + @MainActor + var allowsCarbEditing: Bool { + settingsManager.dosingEnabled || !FeatureFlags.simpleBolusCalculatorEnabled + } + + /// The start of what Loop keeps locally. + var localHistoryStart: Date { + Date().addingTimeInterval(-Bundle.main.localCacheDuration) + } + + @MainActor + var glucoseTargetRange: ClosedRange? { + guard let range = temporaryPresetsManager.effectiveCorrectionRange() else { return nil } + return range.lowerBound.doubleValue(for: .milligramsPerDeciliter)...range.upperBound.doubleValue(for: .milligramsPerDeciliter) + } + + /// What the basal schedule delivers in a day, for days the stored profile history doesn't reach. + @MainActor + var scheduledBasalPerDay: Double? { + loopDataManager.settingsProvider.settings.basalRateSchedule?.total() + } + + /// The median insulin delivered from midnight to this time of day over the last 14 days, as on Statistics. + func typicalInsulinByNow() async -> (typical: TypicalInsulinDelivery, at: Date)? { + guard let history = try? await loopDataManager.insulinDeliveryHistory(days: 14), + let typical = history.typicalDelivery(by: history.end) else { return nil } + return (typical, history.end) + } + + /// The targets that actually applied, including dated presets and changes to the stored profile. + func glucoseTargetBands(in interval: DateInterval) async throws -> [TargetBand] { + if let store = settingsManager.settingsStore { + let previous = try await store.getStoredSettings(end: interval.start, limit: 1).first + if previous?.glucoseTargetRangeSchedule == nil { + let changes = try await store.getStoredSettings(start: interval.start, end: interval.end) + // LoopKit's historical query requires at least one valid profile, even if settings records exist. + guard changes.contains(where: { $0.glucoseTargetRangeSchedule != nil }) else { return [] } + } + } + return try await loopDataManager.glucoseTargetBands(in: interval) + } + + // MARK: - Days + + /// Everything in `window`; IOB and COB only when asked, as they are the slowest part. + func dayHistory(_ window: DateInterval, includeOnBoard: Bool = true) async throws -> DayHistory { + var history = DayHistory(window: window) + let boundary = localHistoryStart + + if window.end > boundary { + let local = DateInterval(start: max(window.start, boundary), end: window.end) + async let glucose = localGlucose(local) + async let insulin = localInsulin(local) + async let carbs = localCarbs(local) + history.glucose = try await glucose + (history.boluses, history.basal, history.scheduledBasal) = try await insulin + history.carbs = try await carbs + if includeOnBoard { + async let iob = localIOB(local) + async let cob = localCOB(local) + history.iob = try await iob + history.cob = try await cob + } + } + + if window.start < boundary { + let older = DateInterval(start: window.start, end: min(window.end, boundary)) + async let glucose = healthGlucose(older) + async let insulin = healthInsulin(older) + async let carbs = healthCarbs(older) + let (boluses, basal) = try await insulin + history.glucose = try await glucose + history.glucose + history.boluses = boluses + history.boluses + history.basal = basal + history.basal + history.carbs = try await carbs + history.carbs + if window.end <= boundary { + history.source = .health + } + } + try Task.checkCancellation() + return history + } + + /// CGM readings for any period, local and Health combined. + func glucose(_ interval: DateInterval) async throws -> [GlucosePoint] { + let boundary = localHistoryStart + var points: [GlucosePoint] = [] + if interval.start < boundary { + points += try await healthGlucose(DateInterval(start: interval.start, end: min(interval.end, boundary))) + } + if interval.end > boundary { + points += try await localGlucose(DateInterval(start: max(interval.start, boundary), end: interval.end)) + } + return points + } + + // MARK: - Loop runs + + /// Loop runs in the local window, oldest first, with when the record of them begins. + func loopRuns() async throws -> (runs: [LoopHistoryMath.LoopRun], coverageStart: Date?) { + let start = localHistoryStart + var runs: [LoopHistoryMath.LoopRun] + if let store = dosingDecisionStore as? DosingDecisionStore { + // Decode only the fields a run needs, and only Loop's local window. + runs = try await store.getDosingDecisions(start: start, end: Date(), as: LoopRunRecord.self) + .filter { $0.reason == "loop" } + .map { record in + LoopHistoryMath.LoopRun(date: record.date, succeeded: (record.errors ?? []).isEmpty, + error: record.errors?.first.map { Self.describe(issue: $0.id) }) + } + } else { + runs = try await anchoredLoopRuns() + } + try Task.checkCancellation() + runs = runs.filter { $0.date >= start }.sorted { $0.date < $1.date } + return (runs, runs.first?.date) + } + + private struct LoopRunRecord: Decodable { + struct Issue: Decodable { let id: String } + let date: Date + let reason: String + let errors: [Issue]? + } + + /// Fetch each snapshot independently: overlapping reloads must not append the same anchored page twice. + private func anchoredLoopRuns() async throws -> [LoopHistoryMath.LoopRun] { + var anchor: DosingDecisionStore.QueryAnchor? + var runs: [LoopHistoryMath.LoopRun] = [] + while true { + let page: (DosingDecisionStore.QueryAnchor, [StoredDosingDecision]) = try await withCheckedThrowingContinuation { continuation in + dosingDecisionStore.executeDosingDecisionQuery(fromQueryAnchor: anchor, limit: 1000) { result in + switch result { + case .success(let anchor, let decisions): continuation.resume(returning: (anchor, decisions)) + case .failure(let error): continuation.resume(throwing: error) + } + } + } + try Task.checkCancellation() + anchor = page.0 + runs += page.1.filter { $0.reason == "loop" }.map { decision in + LoopHistoryMath.LoopRun(date: decision.date, succeeded: decision.errors.isEmpty, + error: decision.errors.first.map { Self.describe(issue: $0.id) }) + } + if page.1.count < 1000 { break } + } + return runs + } + + /// "glucoseTooOld" → "Glucose too old". Loop's own issue ids are localised; any other id falls back to the + /// id split into English words. + static func describe(issue id: String) -> String { + switch id { + case "configurationError": return NSLocalizedString("Configuration error", comment: "Reason Loop could not run: its settings are incomplete") + case "connectionError": return NSLocalizedString("Connection error", comment: "Reason Loop could not run: no connected devices, or a device connection failed") + case "missingDataError": return NSLocalizedString("Missing data error", comment: "Reason Loop could not run: data it needs was missing") + case "glucoseTooOld": return NSLocalizedString("Glucose too old", comment: "Reason Loop could not run: the latest glucose reading was too old") + case "invalidFutureGlucose": return NSLocalizedString("Invalid future glucose", comment: "Reason Loop could not run: a glucose reading had a timestamp in the future") + case "pumpDataTooOld": return NSLocalizedString("Pump data too old", comment: "Reason Loop could not run: the latest pump data was too old") + case "recommendationExpired": return NSLocalizedString("Recommendation expired", comment: "Reason Loop could not run: its dosing recommendation expired before it was used") + case "pumpSuspended": return NSLocalizedString("Pump suspended", comment: "Reason Loop could not run: insulin delivery on the pump was suspended") + case "pumpManagerError": return NSLocalizedString("Pump manager error", comment: "Reason Loop could not run: the pump reported an error") + case "unknownError": return NSLocalizedString("Unknown error", comment: "Reason Loop could not run: an unexpected error") + default: break + } + var words: [String] = [] + var current = "" + for character in id { + if character.isUppercase, !current.isEmpty { + words.append(current) + current = "" + } + current.append(character) + } + words.append(current) + let sentence = words.joined(separator: " ").lowercased() + return sentence.prefix(1).uppercased() + sentence.dropFirst() + } + + // MARK: - Pump events + + func pumpEvents(since start: Date) async throws -> [PumpLogEvent] { + let events = try await doseStore.getPumpEventValues(since: start) + let items: [PumpLogEvent] = events.compactMap { event in + let id = event.objectIDURL.absoluteString + switch event.type { + case .replaceComponent(let component)?: + let name: String + switch component { + case .pump: name = NSLocalizedString("Pod changed", comment: "Pump event in the history: the pump or pod was replaced") + case .reservoir: name = NSLocalizedString("Reservoir changed", comment: "Pump event in the history: the insulin reservoir was replaced") + case .infusionSet: name = NSLocalizedString("Infusion set changed", comment: "Pump event in the history: the infusion set was replaced") + } + return PumpLogEvent(id: id, date: event.date, kind: .siteChange(name)) + case .rewind?: + return PumpLogEvent(id: id, date: event.date, kind: .siteChange(NSLocalizedString("Reservoir rewound", comment: "Pump event in the history: the pump rewound its reservoir"))) + case .prime?: + return PumpLogEvent(id: id, date: event.date, kind: .siteChange(NSLocalizedString("Primed", comment: "Pump event in the history: the pump primed the tubing or cannula"))) + case .resume?: + return PumpLogEvent(id: id, date: event.date, kind: .resume) + case .alarm?: + // `title` is a debug description; only a pump's own alarm text is meant for people. + let pumpText: String? + if case .other(let details)? = event.alarmType, !details.isEmpty { pumpText = details } else { pumpText = nil } + return PumpLogEvent(id: id, date: event.date, kind: .alarm(pumpText ?? NSLocalizedString("Pump alarm", comment: "Pump event in the history: an alarm whose pump gave no title"))) + default: + return nil + } + } + return items + } + + // MARK: - Editing + + /// Edits an entry the way Loop's carb absorption screen does. + @MainActor + func carbEntryViewModel(editing entry: StoredCarbEntry) -> CarbEntryViewModel { + let viewModel = CarbEntryViewModel(delegate: loopDataManager, originalCarbEntry: entry) + viewModel.analyticsServicesManager = loopDataManager.analyticsServicesManager + viewModel.deliveryDelegate = deviceManager + return viewModel + } + + func delete(carb entry: StoredCarbEntry) async throws { + _ = try await loopDataManager.deleteCarbEntry(entry) + } + + func delete(dose: DoseEntry) async throws { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + doseStore.deleteDose(dose) { error in + if let error { + continuation.resume(throwing: error) + } else { + continuation.resume() + } + } + } + } + + // MARK: - Loop's stores + + private func localGlucose(_ interval: DateInterval) async throws -> [GlucosePoint] { + do { + let samples = try await glucoseStore.getGlucoseSamples(start: interval.start, end: interval.end) + return samples + .filter { !$0.isDisplayOnly } + .map { GlucosePoint(date: $0.startDate, mgdl: $0.quantity.doubleValue(for: .milligramsPerDeciliter), userEntered: $0.wasUserEntered) } + } catch { + os_log("Glucose fetch failed: %{public}@", log: log, type: .error, String(describing: error)) + throw error + } + } + + /// Doses for `interval`, with the start of Loop's insulin records (its earliest local dose). A found start is kept + /// for a few minutes so day pages and periods don't each read the whole local history; when it isn't known, + /// one read from the start of the local history serves both. No dose before `interval.end` says nothing about + /// later days, so that isn't kept. + private func doses(for interval: DateInterval) async throws -> (doses: [DoseEntry], recordsStart: Date?) { + let localStart = localHistoryStart + if interval.start <= localStart { + let doses = try await doseStore.getNormalizedDoseEntries(start: interval.start, end: interval.end) + return (doses, doses.map(\.startDate).min()) + } + if let cached = recordsStartCache.withLock({ $0 }), Date().timeIntervalSince(cached.fetched) < 300 { + return (try await doseStore.getNormalizedDoseEntries(start: interval.start, end: interval.end), cached.value) + } + let local = try await doseStore.getNormalizedDoseEntries(start: localStart, end: interval.end) + let recordsStart = local.map(\.startDate).min() + if let recordsStart { + recordsStartCache.withLock { $0 = (fetched: Date(), value: recordsStart) } + } + return (local.filter { + $0.startDate < interval.end && ($0.type == .bolus ? $0.startDate >= interval.start : $0.endDate > interval.start) + }, recordsStart) + } + + private func localInsulin(_ interval: DateInterval) async throws -> ([BolusEvent], [BasalSegment], [ScheduledBasalPoint]) { + let doses: [DoseEntry] + let recordsStart: Date? + do { + (doses, recordsStart) = try await self.doses(for: interval) + } catch { + os_log("Dose fetch failed: %{public}@", log: log, type: .error, String(describing: error)) + throw error + } + + let boluses = doses.compactMap { BolusEvent(dose: $0, in: interval) } + + let basalHistory = try await loopDataManager.settingsProvider.getBasalHistory(startDate: interval.start, endDate: interval.end) + let temporaryPresetsManager = self.temporaryPresetsManager + let overrides = await MainActor.run { + temporaryPresetsManager.presetHistory.getOverrideHistory(startDate: interval.start, endDate: interval.end) + } + // A resolved daily schedule repeats recent presets on every day. Use the stored profile and dated overrides. + let scheduleValues = overrides.applyBasal(over: basalHistory) + let schedule = scheduleValues.map { LoopHistoryMath.ScheduleValue(start: $0.startDate, end: $0.endDate, rate: $0.value) } + let deliveries = doses.compactMap { LoopHistoryMath.BasalDelivery(dose: $0) } + let now = Date() + let window = DateInterval(start: interval.start, end: max(interval.start, min(interval.end, now))) + // As in Loop's insulin totals: after the first record, unrecorded time runs at the pump's programmed rate. + let programmed = basalHistory.map { LoopHistoryMath.ScheduleValue(start: $0.startDate, end: $0.endDate, rate: $0.value) } + let basal = window.duration > 0 + ? LoopHistoryMath.basalSegments(window: window, schedule: schedule, deliveries: deliveries, + programmedRates: programmed, recordsStart: recordsStart) + : [] + let scheduled = schedule.map { ScheduledBasalPoint(start: max($0.start, interval.start), end: min($0.end, interval.end), rate: $0.rate) } + return (boluses, basal, scheduled) + } + + private func localCarbs(_ interval: DateInterval) async throws -> [CarbEvent] { + do { + let entries = try await carbStore.getCarbEntries(start: interval.start, end: interval.end) + return entries.map { entry in + CarbEvent(id: entry.syncIdentifier ?? "\(entry.startDate.timeIntervalSince1970)", + date: entry.startDate, + grams: entry.quantity.doubleValue(for: .gram), + absorptionHours: entry.absorptionTime.map { $0 / 3600 }, + foodType: entry.foodType, + createdByLoop: entry.createdByCurrentApp, + entry: entry) + } + } catch { + os_log("Carb fetch failed: %{public}@", log: log, type: .error, String(describing: error)) + throw error + } + } + + private func localIOB(_ interval: DateInterval) async throws -> [TimedValue] { + try await loopDataManager.insulinOnBoardTimeline(in: interval).map { TimedValue(date: $0.startDate, value: $0.value) } + } + + private func localCOB(_ interval: DateInterval) async throws -> [TimedValue] { + try await loopDataManager.carbsOnBoardTimeline(in: interval).map { TimedValue(date: $0.startDate, value: $0.quantity.doubleValue(for: .gram)) } + } + + // MARK: - Apple Health + + private func healthSamples(_ type: HKQuantityType, _ interval: DateInterval, options: HKQueryOptions = .strictStartDate) async throws -> [HKQuantitySample] { + guard interval.duration > 0 else { return [] } + let log = self.log + return try await withCheckedThrowingContinuation { continuation in + let predicate = HKQuery.predicateForSamples(withStart: interval.start, end: interval.end, options: options) + let sort = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true) + let query = HKSampleQuery(sampleType: type, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: [sort]) { _, samples, error in + if let error { + os_log("Health query for %{public}@ failed: %{public}@", log: log, type: .error, type.identifier, String(describing: error)) + continuation.resume(throwing: error) + } else { + continuation.resume(returning: (samples as? [HKQuantitySample]) ?? []) + } + } + healthStore.execute(query) + } + } + + /// Several apps can write the same CGM reading to Health; keep one reading per 2½ minutes. + private func healthGlucose(_ interval: DateInterval) async throws -> [GlucosePoint] { + let samples = try await healthSamples(HKQuantityType(.bloodGlucose), interval) + var points: [GlucosePoint] = [] + let own = HKSource.default().bundleIdentifier + for sample in samples where !sample.isDisplayOnly + && (FeatureFlags.observeHealthKitGlucoseSamplesFromOtherApps || sample.sourceRevision.source.bundleIdentifier == own) { + if let last = points.last, sample.startDate.timeIntervalSince(last.date) < 150 { continue } + points.append(GlucosePoint(date: sample.startDate, mgdl: sample.quantity.doubleValue(for: .milligramsPerDeciliter), + userEntered: sample.wasUserEntered)) + } + return points + } + + private func healthInsulin(_ interval: DateInterval) async throws -> ([BolusEvent], [BasalSegment]) { + let samples = try await healthSamples(HKQuantityType(.insulinDelivery), interval, options: []) + var boluses: [BolusEvent] = [] + var basal: [BasalSegment] = [] + let own = HKSource.default().bundleIdentifier + for sample in samples where FeatureFlags.observeHealthKitDoseSamplesFromOtherApps || sample.sourceRevision.source.bundleIdentifier == own { + let units = sample.quantity.doubleValue(for: .internationalUnit()) + let reason = (sample.metadata?[HKMetadataKeyInsulinDeliveryReason] as? NSNumber)?.intValue + let automaticFlag = (sample.metadata?["com.loopkit.InsulinKit.MetadataKeyAutomaticallyIssued"] as? NSNumber)?.boolValue + let automatic = automaticFlag ?? false + if reason == HKInsulinDeliveryReason.basal.rawValue { + if let segment = BasalSegment(healthSample: sample, in: interval) { + basal.append(segment) + } + } else if sample.startDate >= interval.start && sample.startDate < interval.end { + boluses.append(BolusEvent(id: sample.uuid.uuidString, date: sample.startDate, units: units, automatic: automatic, manuallyEntered: false, + automaticKnown: automaticFlag != nil)) + } + } + return (boluses, basal) + } + + private func healthCarbs(_ interval: DateInterval) async throws -> [CarbEvent] { + let samples = try await healthSamples(HKQuantityType(.dietaryCarbohydrates), interval) + let own = HKSource.default().bundleIdentifier + return samples + .filter { FeatureFlags.observeHealthKitCarbSamplesFromOtherApps || $0.sourceRevision.source.bundleIdentifier == own } + .map { sample in + CarbEvent(id: sample.uuid.uuidString, date: sample.startDate, grams: sample.quantity.doubleValue(for: .gram()), + absorptionHours: nil, foodType: sample.metadata?[HKMetadataKeyFoodType] as? String, + createdByLoop: sample.sourceRevision.source.bundleIdentifier == own) + } + } +} + +// MARK: - Loop mode history + +/// Records when closed loop is switched on and off, because Loop's stored runs don't say which mode they ran in. +/// The Trends screen can only call time "open loop" from the first record on. +final class LoopModeHistory { + static let shared = LoopModeHistory() + + private let defaults: UserDefaults + private let key = "com.loopkit.Loop.redesign.loopModeChanges" + private let maximumAge: TimeInterval = .days(100) + + init(defaults: UserDefaults = .standard) { + self.defaults = defaults + } + + var changes: [LoopHistoryMath.ModeChange] { + guard let data = defaults.data(forKey: key), + let changes = try? JSONDecoder().decode([LoopHistoryMath.ModeChange].self, from: data) else { + return [] + } + return changes + } + + func record(closedLoop: Bool, at date: Date = Date()) { + var changes = self.changes + guard changes.last?.closedLoop != closedLoop else { return } + changes.append(.init(date: date, closedLoop: closedLoop)) + // Keep the last change before the retention window: it establishes the mode at its start. + let cutoff = date.addingTimeInterval(-maximumAge) + if let anchor = changes.lastIndex(where: { $0.date < cutoff }), anchor > 0 { + changes.removeFirst(anchor) + } + if let data = try? JSONEncoder().encode(changes) { + defaults.set(data, forKey: key) + } + } +} + +// MARK: - Loop's on-board history + +extension LoopDataManager { + /// A historical chart must use the profile and presets from that time, rather than repeat today's targets. + func glucoseTargetBands(in interval: DateInterval) async throws -> [TargetBand] { + guard settingsProvider.settings.glucoseTargetRangeSchedule != nil else { return [] } + let schedule = try await settingsProvider.getTargetRangeHistory(startDate: interval.start, endDate: interval.end) + var bands = schedule.map { + TargetBand(start: max($0.startDate, interval.start), end: min($0.endDate, interval.end), + range: $0.value.lowerBound.doubleValue(for: .milligramsPerDeciliter)...$0.value.upperBound.doubleValue(for: .milligramsPerDeciliter)) + } + let overrides = temporaryPresetsManager.presetHistory.getOverrideHistory(startDate: interval.start, endDate: interval.end) + for override in overrides.sorted(by: { $0.startDate < $1.startDate }) { + guard let target = override.settings.targetRange, + let active = override.activeInterval.intersection(with: interval), active.duration > 0 else { continue } + bands = bands.flatMap { band -> [TargetBand] in + var pieces: [TargetBand] = [] + if band.start < active.start { + pieces.append(TargetBand(start: band.start, end: min(band.end, active.start), range: band.range)) + } + if band.end > active.end { + pieces.append(TargetBand(start: max(band.start, active.end), end: band.end, range: band.range)) + } + return pieces + } + [TargetBand(start: active.start, end: active.end, + range: target.lowerBound.doubleValue(for: .milligramsPerDeciliter)...target.upperBound.doubleValue(for: .milligramsPerDeciliter))] + } + return bands.filter { $0.end > $0.start }.sorted { $0.start < $1.start } + } + + /// Insulin on board across `interval`, from the doses and basal history Loop keeps, as `iobValues` is for the display window. + func insulinOnBoardTimeline(in interval: DateInterval) async throws -> [InsulinValue] { + let dosesStart = interval.start.addingTimeInterval(-InsulinMath.defaultInsulinActivityDuration) + let doses = try await doseStore.getNormalizedDoseEntries(start: dosesStart, end: interval.end) + .map { $0.simpleDose(with: insulinModel(for: $0.insulinType)) } + let basalStart = min(dosesStart, doses.map(\.startDate).min() ?? dosesStart) + let basal = try await settingsProvider.getBasalHistory(startDate: basalStart, endDate: interval.end) + let overrides = temporaryPresetsManager.presetHistory.getOverrideHistory(startDate: basalStart, endDate: interval.end) + return doses.annotated(with: overrides.applyBasal(over: basal).algorithmSchedule()) + .insulinOnBoardTimeline(from: interval.start, to: interval.end) + .map(\.loopKit) + } + + /// Carbs on board across `interval`, using the same absorption review as Loop's carb screen. + func carbsOnBoardTimeline(in interval: DateInterval) async throws -> [CarbValue] { + let review = try await fetchCarbAbsorptionReview(start: interval.start.addingTimeInterval(-CarbMath.maximumAbsorptionTimeInterval), end: interval.end) + return review.carbStatuses.dynamicCarbsOnBoard(from: interval.start, to: interval.end, absorptionModel: carbAbsorptionModel.model) + .map(\.loopKit) + } +} diff --git a/Loop/Redesign/MainTabBarController.swift b/Loop/Redesign/MainTabBarController.swift new file mode 100644 index 0000000000..ee82791af4 --- /dev/null +++ b/Loop/Redesign/MainTabBarController.swift @@ -0,0 +1,82 @@ +// +// MainTabBarController.swift +// Loop +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import UIKit +import SwiftUI + +/// Whether Loop shows the redesigned interface (tabs: Now, Trends, Logbook, Settings) or the classic status screen. +enum LoopInterfacePreference { + private static let key = "com.loopkit.Loop.redesign.enabled" + + static let didChange = Notification.Name("LoopInterfacePreferenceDidChange") + + static var useRedesign: Bool { + get { UserDefaults.standard.object(forKey: key) as? Bool ?? true } + set { + guard newValue != useRedesign else { return } + UserDefaults.standard.set(newValue, forKey: key) + NotificationCenter.default.post(name: didChange, object: nil) + } + } +} + +/// The redesign's root. The Now tab is Loop's own RootNavigationController, so every existing flow +/// (bolus, carbs, presets, alerts, device settings, deeplinks) keeps working from StatusTableViewController. +final class MainTabBarController: UITabBarController, UITabBarControllerDelegate { + + let rootNavigationController: RootNavigationController + private weak var statusTableViewController: StatusTableViewController? + private let provider: LoopHistoryProvider + private let settingsPlaceholder = UIViewController() + + init(rootNavigationController: RootNavigationController, statusTableViewController: StatusTableViewController, deviceManager: DeviceDataManager, loopDataManager: LoopDataManager, settingsManager: SettingsManager) { + self.rootNavigationController = rootNavigationController + self.statusTableViewController = statusTableViewController + self.provider = LoopHistoryProvider(deviceManager: deviceManager, loopDataManager: loopDataManager, settingsManager: settingsManager, temporaryPresetsManager: statusTableViewController.temporaryPresetsManager) + super.init(nibName: nil, bundle: nil) + + rootNavigationController.tabBarItem = UITabBarItem(title: NSLocalizedString("Now", comment: "Title of the Now tab in the redesigned interface, showing current glucose and Loop status"), image: UIImage(systemName: "drop.fill"), tag: 0) + + let trends = UIHostingController(rootView: TrendsView(provider: provider)) + trends.tabBarItem = UITabBarItem(title: NSLocalizedString("Trends", comment: "Title of the Trends tab in the redesigned interface"), image: UIImage(systemName: "chart.bar.fill"), tag: 1) + + let logbook = UIHostingController(rootView: LogbookView(provider: provider, deviceManager: deviceManager)) + logbook.tabBarItem = UITabBarItem(title: NSLocalizedString("Logbook", comment: "Title of the Logbook tab in the redesigned interface, listing past events"), image: UIImage(systemName: "list.bullet"), tag: 2) + + settingsPlaceholder.tabBarItem = UITabBarItem(title: NSLocalizedString("Settings", comment: "Title of the Settings tab in the redesigned interface"), image: UIImage(systemName: "gearshape.fill"), tag: 3) + + viewControllers = [rootNavigationController, trends, logbook, settingsPlaceholder] + delegate = self + tabBar.tintColor = UIColor(named: "accent") + if #available(iOS 18.0, *) { + mode = .tabBar + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Settings stays a sheet, as in classic Loop, so the tab only presents it. It is presented from the Now tab: + /// device and service screens open from Settings through StatusTableViewController, which is only in the window + /// while Now is selected. + func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { + guard viewController === settingsPlaceholder else { return true } + showNow() + statusTableViewController?.presentSettingsFromTab() + return false + } + + func showNow() { + selectedViewController = rootNavigationController + } + + override func restoreUserActivityState(_ activity: NSUserActivity) { + showNow() + rootNavigationController.restoreUserActivityState(activity) + } +} diff --git a/Loop/Redesign/NowScreen.swift b/Loop/Redesign/NowScreen.swift new file mode 100644 index 0000000000..59f7da3e2f --- /dev/null +++ b/Loop/Redesign/NowScreen.swift @@ -0,0 +1,770 @@ +// +// NowScreen.swift +// Loop +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import SwiftUI +import HealthKit +import LoopKit +import LoopKitUI +import LoopCore +import LoopAlgorithm + +private typealias P = LoopDesign.Palette + +/// What the Now screen shows. StatusTableViewController owns it and asks it to refresh whenever Loop's data changes. +@MainActor +final class NowModel: ObservableObject { + + struct Reading: Equatable { + let mgdl: Double + let date: Date + let trend: GlucoseTrend? + /// Change since the previous reading, per 5 minutes. + let delta: Double? + } + + struct Device: Equatable { + let name: String + let message: String? + let state: DeviceStatusHighlightState? + /// Set when the message is the device's own status highlight, which the Now screen spells out in a notice. + var symbol: String? = nil + /// The device's lifecycle (pod, sensor) is near its end; the pill says so. + var endingSoon = false + } + + enum LoopState: Equatable { + case fresh + case stale + case failed + case never + } + + @Published private(set) var reading: Reading? + @Published private(set) var lastLoopCompleted: Date? + @Published private(set) var closedLoop = false + /// Closed loop is switched on but Loop has paused it, for example while the CGM has no session or data. + @Published private(set) var closedLoopPaused = false + @Published private(set) var iob: Double? + @Published private(set) var cob: Double? + @Published private(set) var basalDeliveryState: PumpManagerStatus.BasalDeliveryState? + @Published private(set) var programmedBasalRate: Double? + @Published private(set) var scheduledBasalRate: Double? + @Published private(set) var suspended = false + @Published private(set) var targetRange: ClosedRange? + @Published private(set) var predictionTargets: [TargetBand] = [] + @Published private(set) var todayTargets: [TargetBand] = [] + @Published private(set) var prediction: [GlucosePoint] = [] + @Published private(set) var recentGlucose: [GlucosePoint] = [] + @Published private(set) var historyError: String? + @Published private(set) var glucoseHistoryError: String? + @Published private(set) var today: DayHistory + @Published private(set) var pump: Device? + @Published private(set) var cgm: Device? + @Published private(set) var dosingAvailable = true + @Published private(set) var preMealOn = false + @Published private(set) var overrideName: String? + @Published private(set) var unit: GlucoseUnitStyle + @Published var now = Date() + /// Set by StatusTableViewController, which owns the onboarding state. + var isOnboardingComplete: () -> Bool = { true } + + let provider: LoopHistoryProvider + private let deviceManager: DeviceDataManager + private let loopDataManager: LoopDataManager + private let settingsManager: SettingsManager + private let temporaryPresetsManager: TemporaryPresetsManager + private var refreshTask: Task? + private var pendingRefresh = false + + init(deviceManager: DeviceDataManager, loopDataManager: LoopDataManager, settingsManager: SettingsManager, temporaryPresetsManager: TemporaryPresetsManager) { + self.deviceManager = deviceManager + self.loopDataManager = loopDataManager + self.settingsManager = settingsManager + self.temporaryPresetsManager = temporaryPresetsManager + self.provider = LoopHistoryProvider(deviceManager: deviceManager, loopDataManager: loopDataManager, settingsManager: settingsManager, temporaryPresetsManager: temporaryPresetsManager) + self.unit = provider.unitStyle + let start = Calendar.current.startOfDay(for: Date()) + self.today = DayHistory(window: DateInterval(start: start, duration: 86400)) + } + + var basalRate: Double? { + switch basalDeliveryState { + case .tempBasal(let dose)?: + return dose.startDate <= now && now < dose.endDate ? dose.unitsPerHour : programmedBasalRate + case .active?: return programmedBasalRate + case .suspended?, .suspending?: return 0 + default: return nil + } + } + + var loopState: LoopState { + guard let lastLoopCompleted else { return .never } + let age = now.timeIntervalSince(lastLoopCompleted) + if age <= 5 * 60 + 30 { return .fresh } + if age <= 15 * 60 { return .stale } + return .failed + } + + var readingIsStale: Bool { + guard let reading else { return true } + return now.timeIntervalSince(reading.date) > LoopCoreConstants.inputDataRecencyInterval + } + + /// Refreshes everything; calls made while a refresh runs are coalesced into one more. + func refresh() { + guard refreshTask == nil else { + pendingRefresh = true + return + } + refreshTask = Task { [weak self] in + await self?.load() + guard let self else { return } + self.refreshTask = nil + if self.pendingRefresh { + self.pendingRefresh = false + self.refresh() + } + } + } + + private func load() async { + let now = Date() + self.now = now + unit = provider.unitStyle + + let displayState = await loopDataManager.algorithmDisplayState + let predicted = (displayState.output?.predictedGlucose ?? []).map(\.loopKit).map { + GlucosePoint(date: $0.startDate, mgdl: $0.quantity.doubleValue(for: .milligramsPerDeciliter)) + } + let start = Calendar.current.startOfDay(for: now) + let window = DateInterval(start: start, end: Calendar.current.date(byAdding: .day, value: 1, to: start)!) + async let dayFetch = provider.dayHistory(window) + async let recentFetch = provider.glucose(DateInterval(start: now.addingTimeInterval(-3 * 3600), end: now)) + do { + let targets = try await provider.glucoseTargetBands(in: window) + today = try await dayFetch + todayTargets = targets + historyError = nil + } catch { + historyError = error.localizedDescription + } + do { + let predictionWindow = DateInterval(start: now.addingTimeInterval(-3 * 3600), end: now.addingTimeInterval(3 * 3600)) + predictionTargets = try await provider.glucoseTargetBands(in: predictionWindow) + recentGlucose = try await recentFetch + glucoseHistoryError = nil + } catch { + recentGlucose = [] + glucoseHistoryError = error.localizedDescription + } + + prediction = predicted + iob = loopDataManager.activeInsulin?.value + cob = loopDataManager.activeCarbs?.quantity.doubleValue(for: .gram) + lastLoopCompleted = loopDataManager.lastLoopCompleted + // Closed loop is on but paused while the CGM session or a manual temp basal rules out automation. + closedLoopPaused = settingsManager.dosingEnabled && deviceManager.closedLoopDisallowedLocalizedDescription != nil + closedLoop = settingsManager.dosingEnabled && !closedLoopPaused + targetRange = provider.glucoseTargetRange + + if let latest = loopDataManager.latestGlucose { + let mgdl = latest.quantity.doubleValue(for: .milligramsPerDeciliter) + let previous = recentGlucose.last { $0.date < latest.startDate.addingTimeInterval(-60) } + var delta: Double? = nil + if let previous, latest.startDate.timeIntervalSince(previous.date) <= 15 * 60 { + delta = (mgdl - previous.mgdl) / latest.startDate.timeIntervalSince(previous.date) * 300 + } + // The CGM's own trend, as the classic HUD shows it. + reading = Reading(mgdl: mgdl, date: latest.startDate, trend: deviceManager.glucoseDisplay(for: latest)?.trendType, delta: delta) + } else { + reading = nil + } + + let scheduled = temporaryPresetsManager.basalRateScheduleApplyingOverrideHistory?.value(at: now) + scheduledBasalRate = scheduled + // Presets change Loop's plan; without a temp basal the pump still delivers its programmed profile. + programmedBasalRate = settingsManager.settings.basalRateSchedule?.value(at: now) + basalDeliveryState = deviceManager.pumpManagerStatus?.basalDeliveryState + switch basalDeliveryState { + case .suspended?, .suspending?: suspended = true + default: suspended = false + } + + pump = deviceManager.pumpManager.map { manager in + device(name: manager.localizedTitle, highlight: deviceManager.pumpStatusHighlight, progress: deviceManager.pumpLifecycleProgress) + } + cgm = deviceManager.cgmManager.map { manager in + device(name: manager.localizedTitle, highlight: deviceManager.cgmStatusHighlight, progress: deviceManager.cgmLifecycleProgress) + } + + dosingAvailable = isOnboardingComplete() || deviceManager.pumpManager?.isOnboarded == true + preMealOn = temporaryPresetsManager.isPreMealTargetActive() + overrideName = preMealOn ? nil : temporaryPresetsManager.activePreset?.name + } + + private func device(name: String, highlight: DeviceStatusHighlight?, progress: DeviceLifecycleProgress?) -> Device { + if let highlight { + // Plugins wrap highlights onto two lines for the classic HUD ("Signal\nLoss"). + let message = highlight.localizedMessage.replacingOccurrences(of: "\n", with: " ") + return Device(name: name, message: message, state: highlight.state, symbol: highlight.imageName) + } + if let progress, progress.progressState == .warning || progress.progressState == .critical { + return Device(name: name, message: nil, state: progress.progressState == .critical ? .critical : .warning, endingSoon: true) + } + return Device(name: name, message: nil, state: nil) + } +} + +/// The buttons on the Now screen, wired to StatusTableViewController's existing flows. +struct NowActions { + var addCarbs: () -> Void = {} + var bolus: () -> Void = {} + var presets: () -> Void = {} + var showPrediction: () -> Void = {} + var showToday: () -> Void = {} + var showInsulin: () -> Void = {} + var showCarbs: () -> Void = {} + var pumpTapped: () -> Void = {} + var cgmTapped: () -> Void = {} + var loopStatusTapped: () -> Void = {} +} + +// MARK: - Header: title, status, hero, actions + +struct NowHeaderView: View { + @ObservedObject var model: NowModel + let actions: NowActions + + var body: some View { + VStack(alignment: .leading, spacing: LoopDesign.Space.s3) { + Text(NSLocalizedString("Now", comment: "Title at the top of the Now screen, showing current glucose and Loop status")) + .font(LoopDesign.Typeface.title) + .foregroundStyle(P.ink) + .accessibilityAddTraits(.isHeader) + statusRow + if !noticeItems.isEmpty { + notices + } + hero + actionRow + } + .padding(.horizontal, LoopDesign.Space.s4) + .padding(.top, LoopDesign.Space.s2) + .padding(.bottom, LoopDesign.Space.s2) + .frame(maxWidth: .infinity, alignment: .leading) + .background(P.surface) + .onReceive(Timer.publish(every: 30, on: .main, in: .common).autoconnect()) { model.now = $0 } + } + + // Status pills replace Loop's HUD icons with words. They wrap rather than scroll, so a device is never off-screen. + private var statusRow: some View { + FlowRow(spacing: LoopDesign.Space.s2) { + Button(action: actions.loopStatusTapped) { + StatusPill(text: loopText, dot: loopColor) + } + .accessibilityHint(NSLocalizedString("Shows when Loop last ran", comment: "VoiceOver hint for the Loop status pill and the Loop notice on the Now screen")) + Button(action: actions.pumpTapped) { + if let pump = model.pump { + StatusPill(text: pillText(pump), dot: pillColor(pump)) + } else { + StatusPill(text: NSLocalizedString("Add pump", comment: "Status pill on the Now screen when no pump is set up; tapping it starts pump setup"), dot: P.statusCritical) + } + } + .accessibilityHint(model.pump == nil + ? NSLocalizedString("Sets up a pump", comment: "VoiceOver hint for the Add pump status pill on the Now screen") + : NSLocalizedString("Opens pump settings", comment: "VoiceOver hint for the pump status pill and pump notice on the Now screen")) + Button(action: actions.cgmTapped) { + if let cgm = model.cgm { + StatusPill(text: pillText(cgm), dot: pillColor(cgm)) + } else { + StatusPill(text: NSLocalizedString("Add CGM", comment: "Status pill on the Now screen when no CGM is set up; tapping it starts CGM setup"), dot: P.statusCritical) + } + } + .accessibilityHint(model.cgm == nil + ? NSLocalizedString("Sets up a CGM", comment: "VoiceOver hint for the Add CGM status pill on the Now screen") + : NSLocalizedString("Opens CGM settings", comment: "VoiceOver hint for the CGM status pill and CGM notice on the Now screen")) + } + .buttonStyle(.plain) + } + + // A device problem or a stalled loop gets a notice in words under the pills, most serious first. + private var notices: some View { + let items = noticeItems + return VStack(spacing: LoopDesign.Space.s2) { + ForEach(items) { item in + Button(action: item.action) { + DeviceNotice(item: item) + } + .buttonStyle(.plain) + .accessibilityHint(item.hint) + } + } + } + + private var noticeItems: [DeviceNotice.Item] { + var items: [DeviceNotice.Item] = [] + if model.loopState == .failed, let last = model.lastLoopCompleted { + items.append(DeviceNotice.Item( + id: "loop", + severity: .critical, + symbol: "exclamationmark.arrow.circlepath", + title: String(format: NSLocalizedString("Loop hasn't run for %1$@", comment: "Notice title on the Now screen when Loop hasn't completed recently. (1: duration since Loop last ran, e.g. 16m)"), LoopFormat.ago(last, now: model.now)), + detail: NSLocalizedString("Insulin isn't being adjusted. Tap for details.", comment: "Notice detail on the Now screen when Loop hasn't completed recently"), + hint: NSLocalizedString("Shows when Loop last ran", comment: "VoiceOver hint for the Loop status pill and the Loop notice on the Now screen"), + action: actions.loopStatusTapped)) + } + // While suspended, Loop's own status row says so and offers Resume, so only a critical pump problem adds a notice. + if let pump = model.pump, let symbol = pump.symbol, let message = pump.message, + !model.suspended || pump.state == .critical { + let severity = DeviceNotice.Severity(pump.state) + items.append(DeviceNotice.Item( + id: "pump", + severity: severity, + symbol: symbol, + title: message, + detail: severity == .critical ? NSLocalizedString("Loop may not be giving insulin.", comment: "Notice detail on the Now screen under a critical pump problem reported by the pump") : nil, + hint: NSLocalizedString("Opens pump settings", comment: "VoiceOver hint for the pump status pill and pump notice on the Now screen"), + action: actions.pumpTapped)) + } + if let cgm = model.cgm, let symbol = cgm.symbol, let message = cgm.message { + let severity = DeviceNotice.Severity(cgm.state) + items.append(DeviceNotice.Item( + id: "cgm", + severity: severity, + symbol: symbol, + title: message, + detail: model.readingIsStale ? NSLocalizedString("Loop can't adjust insulin without readings.", comment: "Notice detail on the Now screen under a CGM problem when there is no recent glucose reading") : nil, + hint: NSLocalizedString("Opens CGM settings", comment: "VoiceOver hint for the CGM status pill and CGM notice on the Now screen"), + action: actions.cgmTapped)) + } + return items.sorted { $0.severity > $1.severity } + } + + private var loopText: String { + guard let last = model.lastLoopCompleted else { + if model.closedLoop { + return NSLocalizedString("Closed loop · not run yet", comment: "Status pill on the Now screen: closed loop is on but Loop has never completed") + } else if model.closedLoopPaused { + return NSLocalizedString("Closed loop paused · not run yet", comment: "Status pill on the Now screen: closed loop is switched on but paused (e.g. no CGM data), and Loop has never completed") + } else { + return NSLocalizedString("Open loop · not run yet", comment: "Status pill on the Now screen: closed loop is off and Loop has never completed") + } + } + let ago = LoopFormat.ago(last, now: model.now) + if model.loopState == .failed { + return String(format: NSLocalizedString("Loop failed · %1$@", comment: "Status pill on the Now screen: Loop hasn't completed for over 15 minutes. (1: duration since Loop last ran, e.g. 16m)"), ago) + } else if model.closedLoop { + return String(format: NSLocalizedString("Closed loop · %1$@", comment: "Status pill on the Now screen: closed loop is on and Loop last ran this long ago. (1: duration, e.g. 3m)"), ago) + } else if model.closedLoopPaused { + return String(format: NSLocalizedString("Closed loop paused · %1$@", comment: "Status pill on the Now screen: closed loop is switched on but paused (e.g. no CGM data), and Loop last ran this long ago. (1: duration, e.g. 3m)"), ago) + } else { + return String(format: NSLocalizedString("Open loop · %1$@", comment: "Status pill on the Now screen: closed loop is off (Loop recommends only) and Loop last ran this long ago. (1: duration, e.g. 3m)"), ago) + } + } + + /// Green only while closed loop is running; open loop that ran on time is neutral. + private var loopColor: Color { + switch model.loopState { + case .fresh: return model.closedLoop ? P.statusAutomated : model.closedLoopPaused ? P.statusWarning : P.inkMuted + case .stale: return P.statusWarning + case .failed, .never: return P.statusCritical + } + } + + /// A highlight (always a plugin message with a symbol) is spelled out in its notice, so its pill keeps to the device name. + private func pillText(_ device: NowModel.Device) -> String { + guard device.symbol == nil else { return device.name } + if device.endingSoon { + return String(format: NSLocalizedString("%1$@ · ending soon", comment: "Status pill on the Now screen when a pump pod or CGM sensor is near the end of its life. (1: device name from the pump or CGM plugin, e.g. Omnipod 5)"), device.name) + } + return device.name + } + + private func pillColor(_ device: NowModel.Device) -> Color? { + switch device.state { + case .critical?: return P.statusCritical + case .warning?: return P.statusWarning + default: return device.symbol == nil ? nil : P.inkMuted + } + } + + private var hero: some View { + LoopCard { + HStack(alignment: .top) { + VStack(alignment: .leading, spacing: 0) { + HStack(alignment: .center, spacing: LoopDesign.Space.s2) { + if let reading = model.reading, !model.readingIsStale { + Text(model.unit.format(reading.mgdl)) + .font(LoopDesign.Typeface.glucoseHero) + .foregroundStyle(P.ink) + .lineLimit(1) + .minimumScaleFactor(0.6) + if let trend = reading.trend { + Image(systemName: Self.symbol(for: trend)) + .font(.system(size: 30, weight: .bold)) + .foregroundStyle(P.glucose) + .accessibilityLabel(trend.localizedDescription) + } + } else { + Text(NSLocalizedString("No reading", comment: "Shown on the Now screen in place of the glucose value when there is no recent CGM reading")) + .font(.system(size: 34, weight: .bold, design: .rounded)) + .foregroundStyle(P.inkMuted) + } + } + Text(readingDetail) + .font(LoopDesign.Typeface.subhead) + .foregroundStyle(P.inkMuted) + if let rangePill { + StatusPill(text: rangePill.0, dot: rangePill.1) + .padding(.top, LoopDesign.Space.s1) + } + } + Spacer(minLength: LoopDesign.Space.s2) + VStack(alignment: .trailing, spacing: 2) { + if let reading = model.reading { + Text(LoopFormat.time(reading.date)) + } + if let target = model.targetRange { + Text(String(format: NSLocalizedString("Target %1$@–%2$@", comment: "Glucose target range on the Now screen hero card. (1: lower bound; 2: upper bound)"), model.unit.format(target.lowerBound), model.unit.format(target.upperBound))) + } + } + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + .padding(.top, LoopDesign.Space.s4) + } + .accessibilityElement(children: .combine) + + AdaptiveRow { + // As on the classic charts: insulin opens Loop's delivery history, carbs its absorption screen. + Button(action: actions.showInsulin) { + ValueTile(label: NSLocalizedString("IOB", comment: "Abbreviation for insulin on board; label of a tile on the Now screen")) { + Text(model.iob.map(LoopFormat.doseUnits) ?? "–") + .font(LoopDesign.Typeface.statValueSmall) + .foregroundStyle(P.insulinInk) + } + } + .buttonStyle(.plain) + .accessibilityHint(NSLocalizedString("Opens insulin delivery history", comment: "VoiceOver hint for the IOB tile on the Now screen")) + Button(action: actions.showCarbs) { + ValueTile(label: NSLocalizedString("COB", comment: "Abbreviation for carbs on board; label of a tile on the Now screen")) { + Text(model.cob.map(LoopFormat.grams) ?? "–") + .font(LoopDesign.Typeface.statValueSmall) + .foregroundStyle(P.carbsInk) + } + } + .buttonStyle(.plain) + .accessibilityHint(NSLocalizedString("Opens carb absorption", comment: "VoiceOver hint for the COB tile on the Now screen")) + ValueTile(label: NSLocalizedString("Basal", comment: "Label of a tile showing the current basal rate (Now screen) or the day's basal insulin total (Today so far totals)")) { + VStack(alignment: .leading, spacing: 0) { + HStack(alignment: .firstTextBaseline, spacing: 2) { + Text(model.basalRate.map { LoopFormat.number($0, decimals: 2) } ?? "–") + .font(LoopDesign.Typeface.statValueSmall) + .foregroundStyle(P.ink) + Text(NSLocalizedString("U/h", comment: "Abbreviation for insulin units per hour")) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + } + if model.suspended { + Text(NSLocalizedString("Suspended", comment: "Shown in the Basal tile on the Now screen when insulin delivery is suspended")) + .font(LoopDesign.Typeface.footnote.weight(.semibold)) + .foregroundStyle(P.criticalInk) + } else if let rate = model.basalRate, let scheduled = model.scheduledBasalRate { + Text(String(format: NSLocalizedString("%1$@ vs plan", comment: "Basal tile on the Now screen: how the current basal rate differs from the scheduled rate. (1: signed difference in U/h, e.g. +0.25)"), LoopFormat.signed(rate - scheduled))) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + .lineLimit(1) + .minimumScaleFactor(0.8) + } + } + } + } + .fixedSize(horizontal: false, vertical: true) + } + .opacity(model.readingIsStale && model.reading != nil ? 0.7 : 1) + } + + private var readingDetail: String { + guard let reading = model.reading else { return model.unit.unitName } + if model.readingIsStale { + return String(format: NSLocalizedString("Last reading %1$@ ago", comment: "Shown on the Now screen under No reading when the latest CGM reading is too old. (1: duration, e.g. 25m)"), LoopFormat.ago(reading.date, now: model.now)) + } + guard let delta = reading.delta else { return model.unit.unitName } + let change = model.unit.isMmol ? LoopFormat.signed(delta / GlucoseUnitStyle.mgdlPerMmol, decimals: 1) : LoopFormat.signed(delta, decimals: 0) + return String(format: NSLocalizedString("%1$@ · %2$@ in 5 min", comment: "Under the glucose value on the Now screen: the glucose unit and how much glucose changed over 5 minutes. (1: glucose unit, e.g. mmol/L; 2: signed change, e.g. +0.3)"), model.unit.unitName, change) + } + + /// Low and high get a pill with words; the number itself is never tinted. + private var rangePill: (String, Color)? { + guard let reading = model.reading, !model.readingIsStale else { return nil } + if reading.mgdl < 54 { return (NSLocalizedString("Urgent low", comment: "Pill under the glucose value on the Now screen when glucose is below 54 mg/dL (3.0 mmol/L)"), P.statusCritical) } + if reading.mgdl < 70 { return (NSLocalizedString("Low", comment: "Pill under the glucose value on the Now screen when glucose is below 70 mg/dL (3.9 mmol/L)"), P.statusCritical) } + if reading.mgdl > 250 { return (NSLocalizedString("High", comment: "Pill under the glucose value on the Now screen when glucose is above 250 mg/dL (13.9 mmol/L)"), P.statusWarning) } + return nil + } + + static func symbol(for trend: GlucoseTrend) -> String { + switch trend { + case .upUpUp: return "arrow.up.to.line" + case .upUp: return "chevron.up.2" + case .up: return "arrow.up" + case .flat: return "arrow.right" + case .down: return "arrow.down" + case .downDown: return "chevron.down.2" + case .downDownDown: return "arrow.down.to.line" + } + } + + private var actionRow: some View { + AdaptiveRow { + actionButton(NSLocalizedString("Add carbs", comment: "Button on the Now screen that opens carb entry"), systemImage: "fork.knife", fill: P.carbs, text: P.onFill, action: actions.addCarbs) + .disabled(!model.dosingAvailable) + .opacity(model.dosingAvailable ? 1 : 0.4) + actionButton(NSLocalizedString("Bolus", comment: "Button on the Now screen that opens the bolus screen"), systemImage: "syringe.fill", fill: P.insulin, text: P.onFill, action: actions.bolus) + .disabled(!model.dosingAvailable) + .opacity(model.dosingAvailable ? 1 : 0.4) + // Pre-meal and the other presets share Loop's Presets screen. + Button(action: actions.presets) { + actionLabel(presetsTitle, systemImage: "slider.horizontal.3", fill: presetsFill, text: P.ink) + } + .buttonStyle(.plain) + .accessibilityLabel(NSLocalizedString("Presets", comment: "Button on the Now screen that opens override presets (and pre-meal); also its VoiceOver label and menu item")) + .accessibilityValue(presetsActive ? presetsTitle : "") + } + .fixedSize(horizontal: false, vertical: true) + } + + private var presetsFill: Color { + model.preMealOn || model.overrideName != nil ? P.accent.opacity(0.18) : P.surfaceSunken + } + + private var presetsActive: Bool { + model.preMealOn || model.overrideName != nil + } + + private var presetsTitle: String { + if model.preMealOn { return NSLocalizedString("Pre-meal on", comment: "Presets button title on the Now screen while the pre-meal target is on") } + if let name = model.overrideName { return name } + return NSLocalizedString("Presets", comment: "Button on the Now screen that opens override presets (and pre-meal); also its VoiceOver label and menu item") + } + + private func actionButton(_ title: String, systemImage: String, fill: Color, text: Color, action: @escaping () -> Void) -> some View { + Button(action: action) { + actionLabel(title, systemImage: systemImage, fill: fill, text: text) + } + .buttonStyle(.plain) + } + + private func actionLabel(_ title: String, systemImage: String, fill: Color, text: Color) -> some View { + // Longer languages ("Agregar carbohidratos") wrap onto a second line rather than truncate. + Text(title) + .font(LoopDesign.Typeface.subhead.weight(.semibold)) + .lineLimit(2) + .multilineTextAlignment(.center) + .minimumScaleFactor(0.8) + .allowsTightening(true) + .foregroundStyle(text) + .padding(.horizontal, LoopDesign.Space.s1) + .padding(.vertical, LoopDesign.Space.s1) + .frame(maxWidth: .infinity, minHeight: 44, maxHeight: .infinity) + .background(fill, in: RoundedRectangle(cornerRadius: LoopDesign.Radius.md, style: .continuous)) + .contentShape(RoundedRectangle(cornerRadius: LoopDesign.Radius.md)) + } +} + +// MARK: - Charts: next 3 hours and today so far + +struct NowChartsView: View { + @ObservedObject var model: NowModel + let actions: NowActions + + var body: some View { + VStack(spacing: LoopDesign.Space.s4) { + predictionCard + todayCard + } + .padding(.horizontal, LoopDesign.Space.s4) + .padding(.top, LoopDesign.Space.s2) + .padding(.bottom, LoopDesign.Space.s6) + .frame(maxWidth: .infinity) + .background(P.surface) + } + + private var readings: [GlucosePoint] { + model.recentGlucose.filter { $0.date >= model.now.addingTimeInterval(-3 * 3600) } + } + + private var predictionCard: some View { + Button(action: actions.showPrediction) { + LoopCard { + CardHeader(title: NSLocalizedString("Next 3 hours", comment: "Title of the prediction chart card on the Now screen")) { + // Loop's eventual glucose is the end of its prediction, which can run past the three hours drawn. + if let last = model.prediction.last { + Text(String(format: NSLocalizedString("Eventually %1$@ %2$@", comment: "Prediction card on the Now screen: Loop's eventual predicted glucose. (1: glucose value; 2: glucose unit, e.g. mmol/L)"), model.unit.format(last.mgdl), model.unit.unitName)) + } + } + if readings.isEmpty && model.prediction.isEmpty { + ChartPlaceholder(message: model.glucoseHistoryError == nil + ? NSLocalizedString("No CGM data in the last 3 hours", comment: "Empty prediction chart on the Now screen") + : NSLocalizedString("Couldn't load recent CGM readings", comment: "Prediction chart on the Now screen when recent glucose couldn't be read")) + } else { + PredictionChartView(readings: readings, prediction: model.prediction, unit: model.unit, + targetBands: model.predictionTargets, now: model.now) + } + ChartLegend(items: [ + LegendItem(swatch: .dot(P.glucose), label: NSLocalizedString("Readings", comment: "Chart legend on the Now screen: dots are CGM glucose readings")), + LegendItem(swatch: .dash(P.glucose), label: NSLocalizedString("Loop prediction", comment: "Chart legend on the Now screen: dashed line is Loop's predicted glucose")), + ]) + if let error = model.glucoseHistoryError { + Text(String(format: NSLocalizedString("Recent readings unavailable: %1$@", comment: "Error under the prediction chart on the Now screen. (1: system error description)"), error)) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.criticalInk) + } + } + } + .buttonStyle(.plain) + .accessibilityHint(NSLocalizedString("Opens Loop's prediction breakdown", comment: "VoiceOver hint for the prediction card on the Now screen")) + } + + private var todayCard: some View { + Button(action: actions.showToday) { + if let error = model.historyError { + LoopCard { + CardHeader(title: DayTimelineCard.todayTitle) + ChartPlaceholder(message: String(format: NSLocalizedString("Couldn't load today's history: %1$@", comment: "Shown on the Now screen's Today so far card when history couldn't be read. (1: system error description)"), error)) + } + } else { + DayTimelineCard(title: DayTimelineCard.todayTitle, history: model.today, unit: model.unit, targetBands: model.todayTargets, + now: model.now, height: 230) + } + } + .buttonStyle(.plain) + .accessibilityHint(NSLocalizedString("Opens the full day", comment: "VoiceOver hint for the Today so far card on the Now screen")) + } +} + +// MARK: - Notices + +/// One problem in words: what the device or Loop reports, what it means, and where to go. +struct DeviceNotice: View { + enum Severity: Int, Comparable { + case info, warning, critical + + init(_ state: DeviceStatusHighlightState?) { + switch state { + case .critical?: self = .critical + case .warning?: self = .warning + default: self = .info + } + } + + static func < (lhs: Severity, rhs: Severity) -> Bool { lhs.rawValue < rhs.rawValue } + } + + struct Item: Identifiable { + let id: String + let severity: Severity + let symbol: String + let title: String + let detail: String? + let hint: String + let action: () -> Void + } + + let item: Item + + private var tint: Color { + switch item.severity { + case .critical: return P.statusCritical + case .warning: return P.statusWarning + case .info: return P.inkMuted + } + } + + var body: some View { + HStack(alignment: .center, spacing: LoopDesign.Space.s3) { + Image(systemName: item.symbol) + .font(.system(size: 20, weight: .semibold)) + .foregroundStyle(item.severity == .critical ? P.criticalInk : tint) + .frame(width: 24) + .accessibilityHidden(true) + VStack(alignment: .leading, spacing: 2) { + Text(item.title) + .font(LoopDesign.Typeface.headline) + .foregroundStyle(item.severity == .critical ? P.criticalInk : P.ink) + if let detail = item.detail { + Text(detail) + .font(LoopDesign.Typeface.subhead) + .foregroundStyle(P.ink) + .fixedSize(horizontal: false, vertical: true) + } + } + Spacer(minLength: 0) + Image(systemName: "chevron.right") + .font(.footnote.weight(.semibold)) + .foregroundStyle(P.inkMuted) + .accessibilityHidden(true) + } + .padding(LoopDesign.Space.s3) + .frame(maxWidth: .infinity, alignment: .leading) + .background(tint.opacity(item.severity == .info ? 0.12 : 0.16), in: RoundedRectangle(cornerRadius: LoopDesign.Radius.lg, style: .continuous)) + .overlay( + RoundedRectangle(cornerRadius: LoopDesign.Radius.lg, style: .continuous) + .strokeBorder(tint.opacity(item.severity == .info ? 0 : 0.45), lineWidth: 1) + ) + .contentShape(Rectangle()) + .accessibilityElement(children: .combine) + } +} + +/// Lays its children out left to right and starts a new line when the next one won't fit. +struct FlowRow: Layout { + var spacing: CGFloat = 8 + + func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { + let rows = arrange(width: proposal.width ?? .infinity, subviews: subviews) + let width = rows.map { $0.width }.max() ?? 0 + let height = rows.map { $0.height }.reduce(0, +) + spacing * CGFloat(max(rows.count - 1, 0)) + return CGSize(width: proposal.width ?? width, height: height) + } + + func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { + var y = bounds.minY + for row in arrange(width: bounds.width, subviews: subviews) { + var x = bounds.minX + for index in row.indices { + let size = subviews[index].sizeThatFits(ProposedViewSize(width: bounds.width, height: nil)) + subviews[index].place(at: CGPoint(x: x, y: y), proposal: ProposedViewSize(width: min(size.width, bounds.width), height: size.height)) + x += min(size.width, bounds.width) + spacing + } + y += row.height + spacing + } + } + + private struct Row { + var indices: [Int] = [] + var width: CGFloat = 0 + var height: CGFloat = 0 + } + + private func arrange(width: CGFloat, subviews: Subviews) -> [Row] { + var rows: [Row] = [] + var row = Row() + for index in subviews.indices { + let size = subviews[index].sizeThatFits(ProposedViewSize(width: width, height: nil)) + let itemWidth = min(size.width, width) + if !row.indices.isEmpty && row.width + spacing + itemWidth > width { + rows.append(row) + row = Row() + } + row.width += (row.indices.isEmpty ? 0 : spacing) + itemWidth + row.height = max(row.height, size.height) + row.indices.append(index) + } + if !row.indices.isEmpty { rows.append(row) } + return rows + } +} diff --git a/Loop/Redesign/TrendsComponents.swift b/Loop/Redesign/TrendsComponents.swift new file mode 100644 index 0000000000..0a2c29feae --- /dev/null +++ b/Loop/Redesign/TrendsComponents.swift @@ -0,0 +1,825 @@ +// +// TrendsComponents.swift +// Loop +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import SwiftUI +import Charts + +private typealias P = LoopDesign.Palette + +// MARK: - RangeSelector + +enum TrendsPeriod: Int, CaseIterable, Identifiable { + case day = 1 + case week = 7 + case twoWeeks = 14 + case month = 30 + case quarter = 90 + + var id: Int { rawValue } + + var label: String { + self == .day + ? NSLocalizedString("Day (Trends period)", value: "Day", comment: "Trends period picker: a period of one calendar day (not daytime)") + : String(format: NSLocalizedString("%1$@ d", comment: "Trends period picker, short label for a period of several days, e.g. 7 d. (1: number of days)"), + LoopFormat.number(Double(rawValue), decimals: 0)) + } + + var accessibilityName: String { + self == .day + ? NSLocalizedString("Day (Trends period)", value: "Day", comment: "Trends period picker: a period of one calendar day (not daytime)") + : String.localizedStringWithFormat(NSLocalizedString("%lld days", comment: "Trends period picker, VoiceOver name for a period of several days. (1: number of days)"), rawValue) + } + + /// The period ending on `endDay` (inclusive), cut off at `now` when it ends today. + func interval(endingOn endDay: Date, now: Date = Date(), calendar: Calendar = .current) -> DateInterval { + let endOfDay = calendar.date(byAdding: .day, value: 1, to: calendar.startOfDay(for: endDay))! + let start = calendar.date(byAdding: .day, value: -rawValue, to: endOfDay)! + return DateInterval(start: start, end: min(endOfDay, now)) + } +} + +struct RangeSelectorView: View { + @Binding var period: TrendsPeriod + @Binding var endDay: Date + var now: Date = Date() + + private var calendar: Calendar { .current } + + private var canStepForward: Bool { + !calendar.isDate(endDay, inSameDayAs: now) && endDay < now + } + + var body: some View { + VStack(spacing: LoopDesign.Space.s3) { + HStack(spacing: 2) { + ForEach(TrendsPeriod.allCases) { option in + Button { + withAnimation(.snappy(duration: 0.2)) { period = option } + } label: { + Text(option.label) + .font(.footnote.weight(.semibold)) + .foregroundStyle(P.ink) + .frame(maxWidth: .infinity, minHeight: 30) + .background { + if option == period { + Capsule().fill(P.controlThumb).shadow(color: .black.opacity(0.12), radius: 3, y: 1) + } + } + .contentShape(Capsule()) + } + .buttonStyle(.plain) + .accessibilityLabel(option.accessibilityName) + .accessibilityAddTraits(option == period ? .isSelected : []) + } + } + .padding(2) + .background(P.surfaceSunken, in: Capsule()) + + HStack { + StepButton(systemName: "chevron.left", label: NSLocalizedString("Previous period", comment: "VoiceOver label for the Trends button that moves back one period")) { step(-1) } + Spacer() + Text(LoopFormat.range(period.interval(endingOn: endDay, now: now).start, endDay)) + .font(LoopDesign.Typeface.headline.monospacedDigit()) + .foregroundStyle(P.ink) + Spacer() + StepButton(systemName: "chevron.right", label: NSLocalizedString("Next period", comment: "VoiceOver label for the Trends button that moves forward one period"), enabled: canStepForward) { step(1) } + } + } + } + + private func step(_ direction: Int) { + guard let next = calendar.date(byAdding: .day, value: direction * period.rawValue, to: endDay) else { return } + endDay = min(next, now) + } +} + +// MARK: - StatGrid + +struct StatTile: Identifiable { + let label: String + let value: String + let unit: String? + let detail: String? + var valueColor: Color = LoopDesign.Palette.ink + var id: String { label } +} + +struct StatGridView: View { + @Environment(\.dynamicTypeSize) private var typeSize + let tiles: [StatTile] + + /// Pairs of tiles, so both tiles in a row take the taller one's height; one per row at accessibility sizes. + private var rows: [[StatTile]] { + let shown = Array(tiles.prefix(6)) + let perRow = typeSize.isAccessibilitySize ? 1 : 2 + return stride(from: 0, to: shown.count, by: perRow).map { Array(shown[$0.. some View { + VStack(alignment: .leading, spacing: 2) { + Text(tile.label) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + HStack(alignment: .firstTextBaseline, spacing: 2) { + Text(tile.value) + .font(LoopDesign.Typeface.statValue) + .foregroundStyle(tile.valueColor) + .minimumScaleFactor(0.7) + .lineLimit(1) + if let unit = tile.unit { + Text(unit) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + } + } + if let detail = tile.detail { + Text(detail) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + .lineLimit(2) + } + } + .padding(LoopDesign.Space.s3) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .background(P.surfaceSunken, in: RoundedRectangle(cornerRadius: LoopDesign.Radius.md, style: .continuous)) + .accessibilityElement(children: .combine) + } +} + +// MARK: - TimeInRange + +extension LoopHistoryMath.RangeBand { + /// The band's name in the user's language. + var localizedName: String { + switch self { + case .veryHigh: return NSLocalizedString("Very high", comment: "Time in range band: glucose above 250 mg/dL (13.9 mmol/L)") + case .high: return NSLocalizedString("High", comment: "Time in range band: glucose 181–250 mg/dL (10.1–13.9 mmol/L)") + case .inRange: return NSLocalizedString("In range", comment: "Time in range band: glucose 70–180 mg/dL (3.9–10.0 mmol/L)") + case .low: return NSLocalizedString("Low", comment: "Time in range band: glucose 54–69 mg/dL (3.0–3.8 mmol/L)") + case .veryLow: return NSLocalizedString("Very low", comment: "Time in range band: glucose below 54 mg/dL (3.0 mmol/L)") + } + } + + /// Consensus thresholds with the user's decimal separator, e.g. "3,9–10,0". + func localizedThresholds(_ unit: GlucoseUnitStyle) -> String { + let decimals = unit.isMmol ? 1 : 0 + let n: (Double) -> String = { LoopFormat.number($0, decimals: decimals) } + switch (self, unit.isMmol) { + case (.veryHigh, true): return "> " + n(13.9) + case (.high, true): return n(10.1) + "–" + n(13.9) + case (.inRange, true): return n(3.9) + "–" + n(10.0) + case (.low, true): return n(3.0) + "–" + n(3.8) + case (.veryLow, true): return "< " + n(3.0) + case (.veryHigh, false): return "> " + n(250) + case (.high, false): return n(181) + "–" + n(250) + case (.inRange, false): return n(70) + "–" + n(180) + case (.low, false): return n(54) + "–" + n(69) + case (.veryLow, false): return "< " + n(54) + } + } +} + +/// "12%" in the user's language. +private func percentText(_ value: Double, decimals: Int = 0) -> String { + String(format: NSLocalizedString("%1$@%%", comment: "A percentage. (1: number)"), LoopFormat.number(value, decimals: decimals)) +} + +struct TimeInRangeView: View { + @Environment(\.dynamicTypeSize) private var typeSize + + /// Fractions per band, 0–1. + let fractions: [LoopHistoryMath.RangeBand: Double] + let unit: GlucoseUnitStyle + + private var percentages: [LoopHistoryMath.RangeBand: Int] { LoopHistoryMath.roundedPercentages(fractions) } + + static func color(_ band: LoopHistoryMath.RangeBand) -> Color { + switch band { + case .veryHigh: return P.rangeVeryHigh + case .high: return P.rangeHigh + case .inRange: return P.rangeIn + case .low: return P.rangeLow + case .veryLow: return P.rangeVeryLow + } + } + + var body: some View { + HStack(alignment: .top, spacing: LoopDesign.Space.s4) { + bar + .frame(width: 14) + VStack(spacing: LoopDesign.Space.s2) { + ForEach(LoopHistoryMath.RangeBand.allCases) { band in + row(band) + } + } + } + .fixedSize(horizontal: false, vertical: true) + } + + private var bar: some View { + GeometryReader { geometry in + let minimum: CGFloat = 4 + let bands = LoopHistoryMath.RangeBand.allCases + let raw = bands.map { CGFloat(fractions[$0] ?? 0) } + let floors = raw.map { $0 > 0 ? max(minimum, $0 * geometry.size.height) : 0 } + let spare = geometry.size.height - floors.reduce(0, +) + // Trim the space the minimums borrowed from the largest band. + let largest = raw.indices.max { raw[$0] < raw[$1] } ?? 2 + VStack(spacing: 1) { + ForEach(Array(bands.enumerated()), id: \.offset) { index, band in + let height = index == largest ? max(0, floors[index] + spare) : floors[index] + if height > 0 { + Rectangle().fill(Self.color(band)).frame(height: max(0, height - 1)) + } + } + } + .clipShape(Capsule()) + } + .accessibilityHidden(true) + } + + private func row(_ band: LoopHistoryMath.RangeBand) -> some View { + let fraction = fractions[band] ?? 0 + let hours = fraction * 24 + let thresholds = Text(band.localizedThresholds(unit)) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + return VStack(alignment: .leading, spacing: 0) { + HStack(alignment: .firstTextBaseline, spacing: LoopDesign.Space.s2) { + Text(percentText(Double(percentages[band] ?? 0))) + .font(LoopDesign.Typeface.statValueSmall) + .foregroundStyle(P.ink) + .frame(minWidth: typeSize.isAccessibilitySize ? nil : 52, alignment: .trailing) + Text(band.localizedName) + .font(LoopDesign.Typeface.subhead) + .foregroundStyle(P.ink) + if !typeSize.isAccessibilitySize { + thresholds + } + Spacer(minLength: LoopDesign.Space.s1) + Text(String(format: NSLocalizedString("%1$@ h", comment: "Time in range row: average hours a day spent in the band, abbreviated. (1: hours with one decimal)"), + LoopFormat.number(hours, decimals: 1))) + .font(LoopDesign.Typeface.footnote.monospacedDigit()) + .foregroundStyle(P.inkMuted) + } + if typeSize.isAccessibilitySize { + thresholds + } + } + .accessibilityElement(children: .ignore) + .accessibilityLabel(String(format: NSLocalizedString("%1$@, %2$@ %3$@: %4$@ percent, %5$@ hours a day", comment: "VoiceOver label for a Time in range row. (1: band name, e.g. In range, 2: glucose thresholds, e.g. 3.9–10.0, 3: glucose unit, 4: percentage number, 5: average hours a day with one decimal)"), + band.localizedName, band.localizedThresholds(unit), unit.unitName, + LoopFormat.number(Double(percentages[band] ?? 0), decimals: 0), LoopFormat.number(hours, decimals: 1))) + } +} + +// MARK: - AGP + +struct TrendsAGPChart: View { + let slots: [LoopHistoryMath.AGPSlot] + let readings: [GlucosePoint] + let days: Int + let unit: GlucoseUnitStyle + let targetRange: ClosedRange? + var height: CGFloat = 180 + + private func v(_ mgdl: Double) -> Double { + min(max(unit.value(mgdl), unit.agpDomain.lowerBound), unit.agpDomain.upperBound) + } + + private static let hourTicks = [0, 360, 720, 1080, 1440] + + private static func label(minute: Int) -> String { + let minute = minute % 1440 + let date = Calendar.current.date(bySettingHour: minute / 60, minute: minute % 60, second: 0, of: Date())! + return DayAxis.hourLabel(date) + } + + var enoughDays: Bool { days >= 5 } + + var body: some View { + VStack(alignment: .leading, spacing: LoopDesign.Space.s2) { + if readings.isEmpty { + ChartPlaceholder(message: NSLocalizedString("No CGM data in this period", comment: "Trends chart placeholder when the period has no glucose readings"), height: height) + } else { + chart.frame(height: height) + if !enoughDays { + Text(String(format: NSLocalizedString("Not enough days for a pattern: %1$@ of 5", comment: "Trends Daily pattern footnote when fewer than 5 days have readings; the chart shows raw readings instead. (1: days with readings)"), + LoopFormat.number(Double(days), decimals: 0))) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + } + } + } + } + + private func minuteOfDay(_ date: Date) -> Int { + let parts = Calendar.current.dateComponents([.hour, .minute], from: date) + return (parts.hour ?? 0) * 60 + (parts.minute ?? 0) + } + + private var chart: some View { + Chart { + if let targetRange { + RectangleMark(xStart: .value("Start", 0), xEnd: .value("End", 1440), + yStart: .value("Low", v(targetRange.lowerBound)), yEnd: .value("High", v(targetRange.upperBound))) + .foregroundStyle(P.targetBand) + } + if enoughDays { + ForEach(slots, id: \.minute) { slot in + AreaMark(x: .value("Time", slot.minute), yStart: .value("5%", v(slot.p5)), yEnd: .value("95%", v(slot.p95)), series: .value("Band", "outer")) + .foregroundStyle(P.agpOuter) + .interpolationMethod(.monotone) + } + ForEach(slots, id: \.minute) { slot in + AreaMark(x: .value("Time", slot.minute), yStart: .value("25%", v(slot.p25)), yEnd: .value("75%", v(slot.p75)), series: .value("Band", "inner")) + .foregroundStyle(P.agpInner) + .interpolationMethod(.monotone) + } + ForEach(slots, id: \.minute) { slot in + LineMark(x: .value("Time", slot.minute), y: .value("Median", v(slot.p50)), series: .value("Band", "median")) + .foregroundStyle(P.glucose) + .lineStyle(StrokeStyle(lineWidth: 2.5)) + .interpolationMethod(.monotone) + } + } else { + ForEach(Array(readings.enumerated()), id: \.offset) { _, point in + PointMark(x: .value("Time", minuteOfDay(point.date)), y: .value("Glucose", v(point.mgdl))) + .symbolSize(8) + .foregroundStyle(P.glucose) + } + } + } + .chartXScale(domain: 0...1440) + .chartYScale(domain: unit.agpDomain) + .chartXAxis { + AxisMarks(values: Self.hourTicks) { value in + AxisGridLine().foregroundStyle(P.grid) + AxisValueLabel { + if let minute = value.as(Int.self) { Text(Self.label(minute: minute)) } + } + .font(LoopDesign.Typeface.caption) + .foregroundStyle(P.inkMuted) + } + } + .chartYAxis { + AxisMarks(position: .leading, values: unit.agpTicks) { _ in + AxisGridLine().foregroundStyle(P.grid) + AxisValueLabel().font(LoopDesign.Typeface.caption).foregroundStyle(P.inkMuted) + } + } + .accessibilityElement(children: .ignore) + .accessibilityLabel(accessibilitySummary) + } + + private var accessibilitySummary: String { + guard enoughDays, let high = slots.max(by: { $0.p50 < $1.p50 }), let low = slots.min(by: { $0.p50 < $1.p50 }) else { + return NSLocalizedString("Daily pattern: not enough days", comment: "VoiceOver label for the Trends Daily pattern chart when fewer than 5 days have readings") + } + let span = String.localizedStringWithFormat(NSLocalizedString("%lld days", comment: "A number of days, e.g. a Trends period or how many days are averaged. (1: number of days)"), days) + return String(format: NSLocalizedString("Daily pattern over %1$@. Median highest %2$@ at %3$@, lowest %4$@ at %5$@", comment: "VoiceOver label for the Trends Daily pattern chart. (1: days with readings, e.g. 14 days; 2: highest median glucose; 3: time of day; 4: lowest median glucose; 5: time of day)"), + span, unit.format(high.p50), Self.label(minute: high.minute), unit.format(low.p50), Self.label(minute: low.minute)) + } +} + +// MARK: - InsulinSplit + +/// Basal against bolus in one capsule: basal, then manual bolus, then automatic bolus. +struct InsulinSplitBar: View { + let split: InsulinSplit + /// A 6-point line for list headers. It is still tall enough to show the automatic bolus stripes. + var hairline = false + @Environment(\.colorScheme) private var scheme + + private enum Part: CaseIterable { case basal, manual, automatic } + + private func units(_ part: Part) -> Double { + switch part { + case .basal: return split.basal + // Unclassified bolus draws as plain bolus. + case .manual: return split.manualBolus + split.unclassifiedBolus + case .automatic: return split.automaticBolus + } + } + + var body: some View { + GeometryReader { geometry in + let parts = Part.allCases.filter { units($0) > 0 } + let gap: CGFloat = hairline ? 1.5 : 2.5 + let width = geometry.size.width - gap * CGFloat(max(0, parts.count - 1)) + let minimum: CGFloat = hairline ? 2 : 4 + let widths = parts.map { max(minimum, width * units($0) / max(split.total, .ulpOfOne)) } + // Take what the minimums add from the widest part, so the bar keeps its length. + let excess = widths.reduce(0, +) - width + let widest = widths.indices.max { widths[$0] < widths[$1] } + HStack(spacing: gap) { + ForEach(Array(parts.enumerated()), id: \.offset) { index, part in + segment(part).frame(width: max(0, widths[index] - (index == widest ? excess : 0))) + } + } + .clipShape(Capsule()) + } + .frame(height: hairline ? 6 : 14) + .accessibilityHidden(true) + } + + @ViewBuilder + private func segment(_ part: Part) -> some View { + switch part { + case .basal: + Rectangle().fill(LoopDesign.Palette.basal) + .overlay(alignment: .trailing) { + if !hairline { Rectangle().fill(LoopDesign.Palette.basalEdge).frame(width: 2) } + } + case .manual: + Rectangle().fill(LoopDesign.Palette.bolus) + case .automatic: + Rectangle().fill(Hatch.paint(LoopDesign.Palette.bolus, scheme: scheme)) + } + } +} + +/// The basal and bolus card body: the bar, units and shares, how the bolus divides, and the total. +struct InsulinSplitView: View { + let split: InsulinSplit + /// Today so far, rather than a whole day or a per-day average. + var partial = false + /// What the basal schedule delivers over the same time, shown under the basal row. + var scheduledBasal: Double? + /// Today against a usual day, e.g. "Usually 28.4 U by 19:46", under the total. + var usual: String? + + var body: some View { + VStack(alignment: .leading, spacing: LoopDesign.Space.s3) { + InsulinSplitBar(split: split) + VStack(alignment: .leading, spacing: LoopDesign.Space.s2) { + row(.basal, NSLocalizedString("Basal", comment: "Basal and bolus row: insulin delivered as basal"), split.basal, split.basalPercent) + if let scheduledBasal { + part(.dash(LoopDesign.Palette.basalScheduled), String(format: NSLocalizedString("Scheduled %1$@", comment: "Basal and bolus: what the basal schedule delivers over the same time, under the basal row. (1: units, e.g. 18.0 U)"), LoopFormat.totalUnits(scheduledBasal))) + .font(LoopDesign.Typeface.footnote.monospacedDigit()) + .foregroundStyle(LoopDesign.Palette.inkMuted) + .padding(.leading, 10 + LoopDesign.Space.s2) + } + row(.solid(LoopDesign.Palette.bolus), NSLocalizedString("Bolus", comment: "Basal and bolus row: insulin delivered as bolus, manual and automatic"), split.bolus, split.bolusPercent) + if split.isClassified && split.bolus > 0 { + FlowLayout(spacing: LoopDesign.Space.s3, lineSpacing: 2) { + part(.solid(LoopDesign.Palette.bolus), String(format: NSLocalizedString("Manual %1$@", comment: "Basal and bolus: bolus the person gave. (1: units, e.g. 16.2 U)"), LoopFormat.totalUnits(split.manualBolus))) + part(.hatch(LoopDesign.Palette.bolus), String(format: NSLocalizedString("Automatic %1$@", comment: "Basal and bolus: bolus Loop gave automatically. (1: units, e.g. 5.4 U)"), LoopFormat.totalUnits(split.automaticBolus))) + } + .font(LoopDesign.Typeface.footnote.monospacedDigit()) + .foregroundStyle(LoopDesign.Palette.inkMuted) + .padding(.leading, 10 + LoopDesign.Space.s2) + } + Divider().padding(.vertical, 2) + HStack(alignment: .firstTextBaseline) { + Text(partial + ? NSLocalizedString("Total so far", comment: "Basal and bolus total row for today, which isn't over yet") + : NSLocalizedString("Total", comment: "Basal and bolus total row: basal plus bolus")) + .font(LoopDesign.Typeface.subhead.weight(.semibold)) + .foregroundStyle(LoopDesign.Palette.ink) + Spacer() + Text(LoopFormat.totalUnits(split.total)) + .font(LoopDesign.Typeface.statValueSmall) + .foregroundStyle(LoopDesign.Palette.insulinInk) + } + if let usual { + Text(usual) + .font(LoopDesign.Typeface.footnote.monospacedDigit()) + .foregroundStyle(LoopDesign.Palette.inkMuted) + .frame(maxWidth: .infinity, alignment: .trailing) + } + } + } + .accessibilityElement(children: .ignore) + .accessibilityLabel(Self.accessibilityLabel(split, partial: partial, scheduledBasal: scheduledBasal, usual: usual)) + } + + private func row(_ swatch: LegendItem.Swatch, _ label: String, _ units: Double, _ percent: Int) -> some View { + HStack(alignment: .firstTextBaseline, spacing: LoopDesign.Space.s2) { + LegendSwatch(swatch: swatch).alignmentGuide(.firstTextBaseline) { $0[.bottom] - 1 } + Text(label) + .font(LoopDesign.Typeface.subhead) + .foregroundStyle(LoopDesign.Palette.ink) + Spacer() + Text(LoopFormat.totalUnits(units)) + .font(LoopDesign.Typeface.body.weight(.semibold).monospacedDigit()) + .foregroundStyle(LoopDesign.Palette.ink) + Text(verbatim: "\(percent)%") + .font(LoopDesign.Typeface.footnote.monospacedDigit()) + .foregroundStyle(LoopDesign.Palette.inkMuted) + .frame(minWidth: 38, alignment: .trailing) + } + } + + private func part(_ swatch: LegendItem.Swatch, _ text: String) -> some View { + HStack(spacing: LoopDesign.Space.s1) { + LegendSwatch(swatch: swatch) + Text(text) + } + } + + static func accessibilityLabel(_ split: InsulinSplit, partial: Bool, scheduledBasal: Double? = nil, usual: String? = nil) -> String { + var parts = [ + String(format: NSLocalizedString("Basal %1$@, %2$@ percent", comment: "VoiceOver: basal units and share. (1: units, 2: percentage number)"), LoopFormat.totalUnits(split.basal), "\(split.basalPercent)"), + String(format: NSLocalizedString("Bolus %1$@, %2$@ percent", comment: "VoiceOver: bolus units and share. (1: units, 2: percentage number)"), LoopFormat.totalUnits(split.bolus), "\(split.bolusPercent)"), + ] + if split.isClassified && split.bolus > 0 { + parts.append(String(format: NSLocalizedString("%1$@ manual, %2$@ automatic", comment: "VoiceOver: how the bolus divides. (1: manual units, 2: automatic units)"), + LoopFormat.totalUnits(split.manualBolus), LoopFormat.totalUnits(split.automaticBolus))) + } + parts.append(String(format: partial + ? NSLocalizedString("Total so far %1$@", comment: "VoiceOver: today's total insulin so far. (1: units)") + : NSLocalizedString("Total %1$@", comment: "VoiceOver: total insulin. (1: units)"), + LoopFormat.totalUnits(split.total))) + if let scheduledBasal { + parts.insert(String(format: NSLocalizedString("Scheduled %1$@", comment: "Basal and bolus: what the basal schedule delivers over the same time, under the basal row. (1: units, e.g. 18.0 U)"), LoopFormat.totalUnits(scheduledBasal)), at: 1) + } + if let usual { + parts.append(usual) + } + return parts.joined(separator: ". ") + } +} + +/// A day's basal and bolus under a list header: the line, then each part keyed the way the charts and rows draw it. +/// The header shows the day's total itself. +struct InsulinSplitLine: View { + let split: InsulinSplit + var partial = false + + var body: some View { + VStack(alignment: .leading, spacing: 6) { + InsulinSplitBar(split: split, hairline: true) + FlowLayout(spacing: LoopDesign.Space.s3, lineSpacing: 2) { + part(.basal, Text(NSLocalizedString("Basal", comment: "Basal and bolus row: insulin delivered as basal")) + Text(verbatim: " ") + Text(LoopFormat.number(split.basal, decimals: 1))) + if split.isClassified { + if split.manualBolus > 0 { + part(.solid(LoopDesign.Palette.bolus), Text(String(format: NSLocalizedString("Manual %1$@", comment: "Basal and bolus: bolus the person gave. (1: units, e.g. 16.2 U)"), LoopFormat.number(split.manualBolus, decimals: 1)))) + } + if split.automaticBolus > 0 { + part(.hatch(LoopDesign.Palette.bolus), Text(String(format: NSLocalizedString("Automatic %1$@", comment: "Basal and bolus: bolus Loop gave automatically. (1: units, e.g. 5.4 U)"), LoopFormat.number(split.automaticBolus, decimals: 1)))) + } + } else { + part(.solid(LoopDesign.Palette.bolus), Text(NSLocalizedString("Bolus", comment: "Basal and bolus row: insulin delivered as bolus, manual and automatic")) + Text(verbatim: " ") + Text(LoopFormat.number(split.bolus, decimals: 1))) + } + } + .font(LoopDesign.Typeface.footnote.monospacedDigit()) + .foregroundStyle(LoopDesign.Palette.inkMuted) + } + .accessibilityElement(children: .ignore) + .accessibilityLabel(InsulinSplitView.accessibilityLabel(split, partial: partial)) + } + + private func part(_ swatch: LegendItem.Swatch, _ text: Text) -> some View { + HStack(spacing: LoopDesign.Space.s1) { + LegendSwatch(swatch: swatch) + text + } + } +} + +// MARK: - InsulinDailyTotals + +struct InsulinDailyTotalsView: View { + let totals: [LoopHistoryMath.DayTotal] + /// Weekly averages instead of days, for periods past 30 days. + let weekly: Bool + /// Calculated from the original daily totals, even when the bars are weekly averages. + let average: Double + var height: CGFloat = 170 + var onSelectDay: ((Date) -> Void)? = nil + @Environment(\.colorScheme) private var scheme + + private var yMax: Double { + let top = max(totals.map(\.total).max() ?? 0, average) + return max(10, IOBCOBChartView.niceStep(top / 4) * 5) + } + + var body: some View { + if totals.allSatisfy({ $0.total == 0 }) { + ChartPlaceholder(message: NSLocalizedString("No insulin recorded in this period", comment: "Trends Insulin per day chart placeholder when no insulin was delivered in the period"), height: height) + } else { + chart.frame(height: height) + } + } + + private var unitWidth: Calendar.Component { weekly ? .weekOfYear : .day } + + private var chart: some View { + Chart { + ForEach(totals) { day in + BarMark(x: .value("Day", day.day, unit: unitWidth), yStart: .value("Base", 0), yEnd: .value("Basal", day.basal), width: .ratio(0.7)) + .foregroundStyle(P.basal) + BarMark(x: .value("Day", day.day, unit: unitWidth), yStart: .value("Basal", max(0, day.basal - yMax * 0.006)), yEnd: .value("Edge", day.basal), width: .ratio(0.7)) + .foregroundStyle(P.basalEdge) + // A thin gap under each bolus part, so it never touches the part below it + let gap = yMax * 0.01 + let manualTop = day.basal + day.split.manualBolus + day.split.unclassifiedBolus + BarMark(x: .value("Day", day.day, unit: unitWidth), yStart: .value("Basal", day.basal + (manualTop - day.basal > gap * 2 ? gap : 0)), yEnd: .value("Bolus", manualTop), width: .ratio(0.7)) + .foregroundStyle(P.bolus) + if day.split.automaticBolus > 0 { + BarMark(x: .value("Day", day.day, unit: unitWidth), yStart: .value("Bolus", manualTop + (day.total - manualTop > gap * 2 ? gap : 0)), yEnd: .value("Total", day.total), width: .ratio(0.7)) + .foregroundStyle(Hatch.paint(P.bolus, scheme: scheme)) + } + } + RuleMark(y: .value("Average", average)) + .foregroundStyle(P.ink) + .lineStyle(StrokeStyle(lineWidth: 1, dash: [4, 3])) + .annotation(position: .top, alignment: .trailing, spacing: 2) { + Text(LoopFormat.number(average, decimals: 1)) + .font(LoopDesign.Typeface.caption.weight(.semibold)) + .foregroundStyle(P.ink) + } + } + .chartYScale(domain: 0...yMax) + .chartXAxis { + AxisMarks(values: axisDates) { value in + AxisValueLabel(centered: true) { + if let date = value.as(Date.self) { + Text(axisLabel(date)) + .multilineTextAlignment(.center) + } + } + .font(LoopDesign.Typeface.caption) + .foregroundStyle(P.inkMuted) + } + } + .chartYAxis { + AxisMarks(position: .leading) { _ in + AxisGridLine().foregroundStyle(P.grid) + AxisValueLabel().font(LoopDesign.Typeface.caption).foregroundStyle(P.inkMuted) + } + } + .chartOverlay { proxy in + GeometryReader { geometry in + Rectangle().fill(.clear).contentShape(Rectangle()) + .onTapGesture { location in + guard let onSelectDay, !weekly, let frame = proxy.plotFrame else { return } + let x = location.x - geometry[frame].origin.x + if let date: Date = proxy.value(atX: x), + let day = totals.min(by: { abs($0.day.addingTimeInterval(43200).timeIntervalSince(date)) < abs($1.day.addingTimeInterval(43200).timeIntervalSince(date)) }) { + onSelectDay(day.day) + } + } + } + } + .accessibilityElement(children: .ignore) + .accessibilityLabel(String(format: weekly + ? NSLocalizedString("Insulin per day, weekly average, average %1$@", comment: "VoiceOver label for the Trends Insulin per day chart when bars are weekly averages. (1: average insulin per day, e.g. 32.1 U)") + : NSLocalizedString("Insulin per day, average %1$@", comment: "VoiceOver label for the Trends Insulin per day chart. (1: average insulin per day, e.g. 32.1 U)"), + LoopFormat.totalUnits(average))) + } + + private var axisDates: [Date] { + if weekly { + let stride = max(1, Int(ceil(Double(totals.count) / 6))) + return totals.enumerated().compactMap { index, total in index % stride == 0 ? total.day : nil } + } + if totals.count > 14 { + return totals.map(\.day).filter { Calendar.current.component(.weekday, from: $0) == 2 } + } + return totals.map(\.day) + } + + private func axisLabel(_ date: Date) -> String { + if weekly { + return date.formatted(.dateTime.day().month(.abbreviated)) + } + let weekday = String(date.formatted(.dateTime.weekday(.narrow))) + if totals.count <= 14 { + let first = totals.first?.day == date, last = totals.last?.day == date + let isMonday = Calendar.current.component(.weekday, from: date) == 2 + return first || last || isMonday ? "\(weekday)\n\(date.formatted(.dateTime.day()))" : weekday + } + // 30 days: label every Monday only. + return Calendar.current.component(.weekday, from: date) == 2 ? date.formatted(.dateTime.day().month(.abbreviated)) : "" + } +} + +// MARK: - LoopModeBar + +struct LoopModeBarView: View { + let spans: [LoopModeSpan] + + private var total: TimeInterval { spans.reduce(0) { $0 + $1.duration } } + + static func color(_ mode: LoopModeSpan.Mode) -> Color { + switch mode { + case .closed: return P.statusAutomated + case .limited: return P.statusWarning + case .open: return P.inkMuted + } + } + + static func name(_ mode: LoopModeSpan.Mode) -> String { + switch mode { + case .closed: return NSLocalizedString("Closed loop", comment: "Trends Loop status: time Loop was running in closed loop") + case .limited: return NSLocalizedString("Limited", comment: "Trends Loop status: time closed loop was on but Loop wasn't completing runs") + case .open: return NSLocalizedString("Open loop / manual", comment: "Trends Loop status: time closed loop was switched off") + } + } + + private let modes: [LoopModeSpan.Mode] = [.closed, .limited, .open] + + var body: some View { + if total <= 0 { + ChartPlaceholder(message: NSLocalizedString("No loop runs recorded in this period", comment: "Trends Loop status placeholder when there is no Loop run history for the period"), height: 60) + } else { + VStack(spacing: LoopDesign.Space.s3) { + bar.frame(height: 10) + ForEach(modes, id: \.self) { mode in + row(mode) + } + } + } + } + + private func fraction(_ mode: LoopModeSpan.Mode) -> Double { + LoopHistoryMath.duration(of: mode, in: spans) / total + } + + private var bar: some View { + GeometryReader { geometry in + let minimum: CGFloat = 4 + let widths = modes.map { fraction($0) > 0 ? max(minimum, fraction($0) * geometry.size.width) : 0 } + let excess = widths.reduce(0, +) - geometry.size.width + HStack(spacing: 1) { + ForEach(Array(modes.enumerated()), id: \.offset) { index, mode in + if widths[index] > 0 { + Rectangle() + .fill(Self.color(mode)) + .frame(width: max(0, index == 0 ? widths[index] - excess - 2 : widths[index])) + } + } + } + .clipShape(Capsule()) + } + .accessibilityHidden(true) + } + + private func row(_ mode: LoopModeSpan.Mode) -> some View { + let duration = LoopHistoryMath.duration(of: mode, in: spans) + return HStack(spacing: LoopDesign.Space.s2) { + Circle().fill(Self.color(mode)).frame(width: 8, height: 8) + Text(Self.name(mode)) + .font(LoopDesign.Typeface.subhead) + .foregroundStyle(P.ink) + Text("· \(LoopFormat.duration(duration))") + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + Spacer() + Text(percentText(fraction(mode) * 100, decimals: 1)) + .font(LoopDesign.Typeface.statValueSmall) + .foregroundStyle(P.ink) + } + .accessibilityElement(children: .combine) + } +} + +struct LoopGapsList: View { + let spans: [LoopModeSpan] + + private var gaps: [LoopModeSpan] { + spans.filter { $0.mode != .closed }.sorted { $0.start > $1.start } + } + + var body: some View { + List { + if gaps.isEmpty { + Text(NSLocalizedString("No gaps: Loop ran closed loop all through this period.", comment: "Trends Gaps list when Loop was in closed loop for the whole period")) + .foregroundStyle(P.inkMuted) + } + ForEach(Array(gaps.enumerated()), id: \.offset) { _, gap in + HStack(alignment: .top, spacing: LoopDesign.Space.s3) { + Circle().fill(LoopModeBarView.color(gap.mode)).frame(width: 8, height: 8).padding(.top, 6) + VStack(alignment: .leading, spacing: 2) { + Text(gap.cause ?? LoopModeBarView.name(gap.mode)) + .font(LoopDesign.Typeface.body) + Text("\(gap.start.formatted(.dateTime.weekday(.abbreviated).day().month(.abbreviated).hour().minute())) · \(LoopFormat.duration(gap.duration))") + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + } + } + .accessibilityElement(children: .combine) + } + } + .navigationTitle(NSLocalizedString("Gaps", comment: "Trends Loop status button and screen title: list of times Loop wasn't in closed loop")) + .navigationBarTitleDisplayMode(.inline) + } +} diff --git a/Loop/Redesign/TrendsScreen.swift b/Loop/Redesign/TrendsScreen.swift new file mode 100644 index 0000000000..d3f1ba3584 --- /dev/null +++ b/Loop/Redesign/TrendsScreen.swift @@ -0,0 +1,623 @@ +// +// TrendsScreen.swift +// Loop +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import SwiftUI +import UIKit + +private typealias P = LoopDesign.Palette + +/// The period review: everything on the screen follows one RangeSelector period. +@MainActor +final class TrendsModel: ObservableObject { + + struct Summary { + var interval: DateInterval + var unit: GlucoseUnitStyle + /// CGM readings only: typed-in fingersticks are left out of every figure, as on Statistics. + var glucose: [GlucosePoint] = [] + /// Days from the first reading, when that is short of the period; nil when the readings span it. + var cgmCoveredDays: Int? + var stats: LoopHistoryMath.GlucoseStats? + var cgmActive: Double = 0 + var timeInRange: [LoopHistoryMath.RangeBand: Double] = [:] + var agp: [LoopHistoryMath.AGPSlot] = [] + var agpDays = 0 + var totals: [LoopHistoryMath.DayTotal] = [] + /// Carbs and bolus counts cover only the days per-day figures divide by (see `dataDays`). + var carbGrams = 0.0 + var carbEntries = 0 + var carbsByDay: [Date: Double] = [:] + var manualBoluses = 0 + var automaticBoluses = 0 + var loopSpans: [LoopModeSpan] = [] + var targetRange: ClosedRange? + /// The whole calendar day, for a one-day period: drawn as the same timeline as Now's Today so far. + var day: DayHistory? + var dayTargets: [TargetBand] = [] + /// What the basal schedule delivers over the same days the split averages. + var scheduledBasal: Double? + /// The median insulin by this time of day over the last 14 days, when the period ends now. + var typicalByNow: (typical: TypicalInsulinDelivery, at: Date)? + + /// The days per-day figures divide by: insulin's averaging days, or complete days with CGM data when there is + /// no insulin (today only when no complete day has any), never the period length. + var dataDays = 0 + var days: Double { Double(max(1, dataDays)) } + var basalUnits: Double { LoopHistoryMath.averagingDays(totals).reduce(0) { $0 + $1.basal } } + var bolusUnits: Double { LoopHistoryMath.averagingDays(totals).reduce(0) { $0 + $1.bolus } } + var insulinPerDay: Double { LoopHistoryMath.averageDailyInsulin(totals) } + var basalShare: Double { basalUnits + bolusUnits > 0 ? basalUnits / (basalUnits + bolusUnits) : 0 } + /// Per day over the averaged days; a single day in progress when that is all there is. + var split: InsulinSplit { InsulinSplit.average(LoopHistoryMath.averagingDays(totals).map(\.split)) } + /// The split is today's total so far rather than an average of complete days. + var splitIsPartial: Bool { LoopHistoryMath.averagingDays(totals).allSatisfy { !$0.isComplete } } + } + + @Published var period: TrendsPeriod = .twoWeeks + @Published var endDay = Date() + @Published private(set) var summary: Summary? + @Published private(set) var loading = false + @Published private(set) var loadError: String? + + let provider: LoopHistoryProvider + private var loadTask: Task? + + init(provider: LoopHistoryProvider) { + self.provider = provider + } + + var interval: DateInterval { period.interval(endingOn: endDay) } + + func reload() { + loadTask?.cancel() + let interval = self.interval + let oneDay = period == .day + loading = true + summary = nil + loadError = nil + loadTask = Task { + do { + let result = try await Self.load(provider: provider, interval: interval, oneDay: oneDay) + guard !Task.isCancelled else { return } + self.summary = result + } catch { + guard !Task.isCancelled else { return } + self.loadError = error.localizedDescription + } + self.loading = false + } + } + + private static func load(provider: LoopHistoryProvider, interval: DateInterval, oneDay: Bool) async throws -> Summary { + var summary = Summary(interval: interval, unit: provider.unitStyle) + summary.targetRange = provider.glucoseTargetRange + + // A single day is read to midnight, as Today so far is, so the scheduled basal still to come is drawn. + let fetchWindow = oneDay ? DateInterval(start: interval.start, end: Calendar.current.date(byAdding: .day, value: 1, to: interval.start)!) : interval + async let historyFetch = provider.dayHistory(fetchWindow, includeOnBoard: false) + async let runsFetch = provider.loopRuns() + var history = try await historyFetch + let (runs, coverageStart) = try await runsFetch + if oneDay { + summary.day = history + summary.dayTargets = try await provider.glucoseTargetBands(in: fetchWindow) + history = history.clipped(to: interval) + } + + let cgm = history.glucose.filter { !$0.userEntered } + summary.glucose = cgm + summary.stats = LoopHistoryMath.glucoseStats(cgm) + summary.cgmActive = LoopHistoryMath.cgmActiveFraction(cgm, over: interval) + summary.timeInRange = LoopHistoryMath.timeInRange(cgm) + summary.agp = LoopHistoryMath.agp(cgm) + summary.agpDays = LoopHistoryMath.daysWithReadings(cgm) + summary.cgmCoveredDays = LoopHistoryMath.cgmCoveredDays(cgm, in: interval, + periodDays: LoopHistoryMath.days(in: interval).count) + + let calendar = Calendar.current + summary.totals = LoopHistoryMath.days(in: interval).map { day in + let basal = history.basal.compactMap { $0.clamped(to: day) }.reduce(0) { $0 + $1.units } + let boluses = history.boluses.filter { $0.date >= day.start && $0.date < day.end } + return LoopHistoryMath.DayTotal(day: day.start, split: InsulinSplit(basal: basal, boluses: boluses), + isComplete: calendar.date(byAdding: .day, value: 1, to: day.start) == day.end) + } + let glucoseDays = Set(history.glucose.map { calendar.startOfDay(for: $0.date) }) + // Carbs and bolus counts share insulin's days, so a period with years of CGM data but a few days of Loop + // isn't spread thin. Without insulin, fall back to days with CGM data. + let withData = summary.totals.filter { $0.total > 0 || glucoseDays.contains($0.day) } + let insulinDays = LoopHistoryMath.averagingDays(summary.totals) + let averaged = !insulinDays.isEmpty ? insulinDays + : withData.contains(where: \.isComplete) ? withData.filter(\.isComplete) : withData + let averagedDays = Set(averaged.map(\.day)) + let inAveragedDays: (Date) -> Bool = { averagedDays.contains(calendar.startOfDay(for: $0)) } + summary.dataDays = averaged.count + summary.carbsByDay = Dictionary(grouping: history.carbs) { calendar.startOfDay(for: $0.date) } + .mapValues { $0.reduce(0) { $0 + $1.grams } } + let carbs = history.carbs.filter { inAveragedDays($0.date) } + summary.carbGrams = carbs.reduce(0) { $0 + $1.grams } + summary.carbEntries = carbs.count + let boluses = history.boluses.filter { inAveragedDays($0.date) } + summary.manualBoluses = boluses.filter { !$0.automatic }.count + summary.automaticBoluses = boluses.filter(\.automatic).count + + // Days the stored profile history doesn't reach (older ones, read from Health) use today's schedule. + let spans = Dictionary(uniqueKeysWithValues: LoopHistoryMath.days(in: interval).map { ($0.start, $0) }) + let perDay = provider.scheduledBasalPerDay + let scheduled = insulinDays.compactMap { day -> Double? in + guard let span = spans[day.day] else { return nil } + return LoopHistoryMath.scheduledUnits(history.scheduledBasal, over: span) ?? perDay.map { $0 * span.duration / 86400 } + } + if !scheduled.isEmpty, scheduled.count == insulinDays.count { + summary.scheduledBasal = scheduled.reduce(0, +) / Double(scheduled.count) + } + if interval.end.timeIntervalSinceNow > -300 { + summary.typicalByNow = await provider.typicalInsulinByNow() + } + + summary.loopSpans = LoopHistoryMath.loopModeSpans(interval: interval, runs: runs, modeChanges: LoopModeHistory.shared.changes, + coverageStart: coverageStart) + return summary + } + + // MARK: Tiles + + func tiles(_ s: Summary) -> [StatTile] { + let unit = s.unit + var tiles: [StatTile] = [] + let averageLabel = NSLocalizedString("Average", comment: "Trends summary tile: mean glucose over the period") + let gmiLabel = NSLocalizedString("GMI", comment: "Trends summary tile: abbreviation for Glucose Management Indicator, an estimate of HbA1c from CGM data") + let loopActiveLabel = NSLocalizedString("Loop active", comment: "Trends summary tile: share of the period Loop spent in closed loop") + if let stats = s.stats { + tiles.append(StatTile(label: averageLabel, value: unit.format(stats.meanMgdl), unit: unit.unitName, + detail: String(format: NSLocalizedString("SD %1$@ · CV %2$@%%", comment: "Trends Average tile detail: standard deviation and coefficient of variation of glucose. (1: standard deviation in the glucose unit, 2: coefficient of variation as a percentage number)"), + unit.format(stats.sdMgdl), LoopFormat.number(stats.cv * 100, decimals: 0)))) + let enough = s.cgmActive * s.interval.duration / 86400 >= 10 + tiles.append(StatTile(label: gmiLabel, value: LoopFormat.number(stats.gmiPercent, decimals: 1), unit: "%", + detail: enough + ? String(format: NSLocalizedString("%1$@ mmol/mol", comment: "Trends GMI tile detail: the GMI in IFCC units. (1: value)"), + LoopFormat.number(stats.gmiMmolMol, decimals: 0)) + : NSLocalizedString("Needs 10 days of CGM", comment: "Trends GMI tile detail when fewer than 10 days of CGM data are available"))) + } else { + tiles.append(StatTile(label: averageLabel, value: "–", unit: unit.unitName, + detail: NSLocalizedString("No CGM data", comment: "Trends Average tile detail when the period has no glucose readings"))) + tiles.append(StatTile(label: gmiLabel, value: "–", unit: "%", detail: nil)) + } + let basalPercent = LoopFormat.number(s.basalShare * 100, decimals: 0) + let insulinDetail: String + if let note = daysNote(s) { + insulinDetail = String(format: NSLocalizedString("%1$@%% basal · %2$@", comment: "Trends Insulin per day tile detail when only some days are averaged. (1: basal share as a percentage number, 2: days averaged, e.g. 5 of 14 days)"), + basalPercent, note) + } else { + insulinDetail = String(format: NSLocalizedString("%1$@%% basal", comment: "Trends Insulin per day tile detail: share of insulin delivered as basal. (1: percentage number)"), + basalPercent) + } + // One day's figures are that day's own, so its tiles drop "per day" and show whole counts. + let oneDay = s.day != nil + let countDecimals = oneDay ? 0 : 1 + tiles.append(StatTile(label: oneDay + ? NSLocalizedString("Insulin", comment: "Trends summary tile for a one-day period: the day's total insulin") + : NSLocalizedString("Insulin per day", comment: "Trends tile and card title: average total insulin per day"), + value: LoopFormat.number(s.insulinPerDay, decimals: 1), + unit: NSLocalizedString("U", comment: "Abbreviation for insulin units"), + detail: insulinDetail, valueColor: P.insulinInk)) + tiles.append(StatTile(label: oneDay + ? NSLocalizedString("Carbs", comment: "Trends summary tile for a one-day period: the day's total carbohydrate") + : NSLocalizedString("Carbs per day", comment: "Trends summary tile: average grams of carbohydrate entered per day"), + value: LoopFormat.number(s.carbGrams / s.days, decimals: 0), + unit: NSLocalizedString("g", comment: "Abbreviation for grams"), + detail: oneDay + ? String(format: NSLocalizedString("%lld entries", comment: "Trends Carbs tile detail for a one-day period: how many carb entries were made that day. (1: number)"), s.carbEntries) + : String(format: NSLocalizedString("%1$@ entries", comment: "Trends Carbs per day tile detail: average number of carb entries per day, a fractional value such as 2.5, so not pluralised. (1: number with one decimal)"), + LoopFormat.number(Double(s.carbEntries) / s.days, decimals: 1)), + valueColor: P.carbsInk)) + tiles.append(StatTile(label: oneDay + ? NSLocalizedString("Boluses", comment: "Trends summary tile for a one-day period: how many manual boluses were given that day") + : NSLocalizedString("Boluses per day", comment: "Trends summary tile: average number of manual boluses per day"), + value: LoopFormat.number(Double(s.manualBoluses) / s.days, decimals: countDecimals), unit: nil, + detail: String(format: NSLocalizedString("+ %1$@ automatic", comment: "Trends Boluses per day tile detail: average number of automatic boluses per day on top of the manual ones, a fractional value such as 4.2. (1: number with one decimal)"), + LoopFormat.number(Double(s.automaticBoluses) / s.days, decimals: countDecimals)))) + let covered = s.loopSpans.reduce(0) { $0 + $1.duration } + if covered > 0 { + let closed = LoopHistoryMath.duration(of: .closed, in: s.loopSpans) + tiles.append(StatTile(label: loopActiveLabel, value: LoopFormat.number(closed / covered * 100, decimals: 0), unit: "%", + detail: String(format: NSLocalizedString("%1$@ of %2$@", comment: "Trends Loop active tile detail: time in closed loop out of the time Loop status is known. (1: duration in closed loop, e.g. 6d 4h, 2: duration covered, e.g. 7d)"), + LoopFormat.duration(closed), LoopFormat.duration(covered)))) + } else { + tiles.append(StatTile(label: loopActiveLabel, value: "–", unit: "%", + detail: NSLocalizedString("No loop runs kept", comment: "Trends Loop active tile detail when no Loop run history is available for the period"))) + } + return tiles + } + + /// "5 of 14 days" when only part of the period is averaged: days without data, or today while it runs. + private func daysNote(_ s: Summary) -> String? { + let insulinDays = LoopHistoryMath.averagingDays(s.totals).count + guard insulinDays < s.totals.count else { return nil } + let total = String.localizedStringWithFormat(NSLocalizedString("%lld days", comment: "A number of days, e.g. a Trends period or how many days are averaged. (1: number of days)"), s.totals.count) + return String(format: NSLocalizedString("%1$@ of %2$@", comment: "Trends: how many days of the period are averaged. (1: days averaged, a number; 2: days in the period, e.g. 14 days)"), LoopFormat.number(Double(insulinDays), decimals: 0), total) + } + + // MARK: Export + + static func exportCSV(_ s: Summary) throws -> URL { + // The CSV header and values are deliberately not localised: fixed English columns and '.' decimals for data interchange. + var lines = ["date,mean_glucose_\(s.unit.isMmol ? "mmol_l" : "mg_dl"),readings,time_in_range_pct,basal_u,bolus_u,total_u,carbs_g,manual_bolus_u,auto_bolus_u"] + let calendar = Calendar.current + let byDay = Dictionary(grouping: s.glucose) { calendar.startOfDay(for: $0.date) } + for total in s.totals { + let readings = byDay[total.day] ?? [] + let mean = readings.isEmpty ? "" : String(format: "%.*f", locale: Locale(identifier: "en_US_POSIX"), + s.unit.isMmol ? 1 : 0, s.unit.value(readings.reduce(0) { $0 + $1.mgdl } / Double(readings.count))) + let tir = readings.isEmpty ? "" : String(format: "%.0f", (LoopHistoryMath.timeInRange(readings)[.inRange] ?? 0) * 100) + let date = Date.ISO8601FormatStyle(timeZone: .current).year().month().day().format(total.day) + let carbs = String(format: "%.0f", s.carbsByDay[total.day] ?? 0) + // Left blank when some of the day's bolus doesn't say whether it was manual or automatic. + let bolusSplit = total.split.isClassified ? String(format: "%.2f,%.2f", total.split.manualBolus, total.split.automaticBolus) : "," + lines.append("\(date),\(mean),\(readings.count),\(tir),\(String(format: "%.2f,%.2f,%.2f", total.basal, total.bolus, total.total)),\(carbs),\(bolusSplit)") + } + let url = FileManager.default.temporaryDirectory.appendingPathComponent(Self.fileName(s.interval) + ".csv") + try lines.joined(separator: "\n").write(to: url, atomically: true, encoding: .utf8) + return url + } + + func exportPDF(_ s: Summary) throws -> URL { + let url = FileManager.default.temporaryDirectory.appendingPathComponent(Self.fileName(s.interval) + ".pdf") + let report = TrendsReport(summary: s, tiles: tiles(s)) + .frame(width: 560) + .environment(\.colorScheme, .light) + let renderer = ImageRenderer(content: report) + renderer.proposedSize = ProposedViewSize(width: 560, height: nil) + var thrown: Error? + renderer.render { size, draw in + var box = CGRect(x: 0, y: 0, width: 612, height: max(792, size.height + 52)) + guard let consumer = CGDataConsumer(url: url as CFURL), let context = CGContext(consumer: consumer, mediaBox: &box, nil) else { + thrown = CocoaError(.fileWriteUnknown) + return + } + context.beginPDFPage(nil) + context.translateBy(x: 26, y: box.height - size.height - 26) + draw(context) + context.endPDFPage() + context.closePDF() + } + if let thrown { throw thrown } + return url + } + + private static func fileName(_ interval: DateInterval) -> String { + String(format: NSLocalizedString("Loop trends %1$@", comment: "File name of an exported Trends report or CSV, without the extension. (1: date range of the period)"), + fileStamp(interval)) + } + + private static func fileStamp(_ interval: DateInterval) -> String { + LoopFormat.range(interval.start, interval.end.addingTimeInterval(-1)).replacingOccurrences(of: " – ", with: " to ") + } +} + +// MARK: - Screen + +struct TrendsView: View { + @StateObject private var model: TrendsModel + @State private var path: [TrendsRoute] = [] + @State private var exportURL: URL? + @State private var exportError: String? + + /// Closes Trends when it is presented on its own; nil where Trends has nowhere to return to. + private let done: (() -> Void)? + + enum TrendsRoute: Hashable { + case day(Date) + case gaps + } + + init(provider: LoopHistoryProvider, done: (() -> Void)? = nil) { + _model = StateObject(wrappedValue: TrendsModel(provider: provider)) + self.done = done + } + + var body: some View { + NavigationStack(path: $path) { + ScrollView { + LazyVStack(alignment: .leading, spacing: LoopDesign.Space.s6, pinnedViews: [.sectionHeaders]) { + Section { + content + } header: { + RangeSelectorView(period: $model.period, endDay: $model.endDay) + .padding(.vertical, LoopDesign.Space.s2) + .background(P.surface) + } + } + .padding(.horizontal, LoopDesign.Space.s4) + .padding(.bottom, LoopDesign.Space.s6) + } + .background(P.surface) + .navigationTitle(NSLocalizedString("Trends", comment: "Title of the Trends tab")) + .refreshable { model.reload() } + .toolbar { + if let done { + ToolbarItem(placement: .confirmationAction) { + Button(NSLocalizedString("Done", comment: "Button to close Trends"), action: done) + } + } + } + .navigationDestination(for: TrendsRoute.self) { route in + switch route { + case .day(let day): + DayDetailView(provider: model.provider, day: day) + case .gaps: + LoopGapsList(spans: model.summary?.loopSpans ?? []) + } + } + } + .onAppear { model.reload() } + .onChange(of: model.period) { _, _ in model.reload() } + .onChange(of: model.endDay) { _, _ in model.reload() } + .sheet(item: $exportURL) { url in + ShareSheet(items: [url]) + } + .alert(NSLocalizedString("Couldn't export", comment: "Alert title when exporting the Trends PDF or CSV fails"), + isPresented: Binding(get: { exportError != nil }, set: { if !$0 { exportError = nil } })) { + Button(NSLocalizedString("OK", comment: "Button to dismiss an alert"), role: .cancel) {} + } message: { + Text(exportError ?? "") + } + } + + @ViewBuilder + private var content: some View { + if let s = model.summary { + summaryCard(s) + // A single day leads with its timeline, as Today so far does on Now. + if let day = s.day { + dayCard(day, targets: s.dayTargets, unit: s.unit) + } + LoopCard { + CardHeader(title: NSLocalizedString("Time in range", comment: "Trends card title: share of glucose readings in each range")) { + Text(String(format: NSLocalizedString("%1$@%% CGM active", comment: "Trends Time in range card: share of the period with CGM readings. (1: percentage number)"), + LoopFormat.number(s.cgmActive * 100, decimals: 0))) + } + if s.glucose.isEmpty { + ChartPlaceholder(message: NSLocalizedString("No CGM data in this period", comment: "Trends chart placeholder when the period has no glucose readings")) + } else { + TimeInRangeView(fractions: s.timeInRange, unit: s.unit) + } + Footnote(String(format: NSLocalizedString("Aim: over 70%% in range; under 25%% above and 4%% below; under 5%% very high and 1%% very low; CGM active over 70%%.", comment: "Trends Time in range card footnote: the consensus targets for time in range, above and below range, very high, very low and CGM wear"))) + } + if s.day == nil { + patternCard(s) + } + LoopCard { + CardHeader(title: NSLocalizedString("Basal and bolus", comment: "Trends card title: insulin split into basal and bolus")) { + Text(s.splitIsPartial + ? NSLocalizedString("today so far", comment: "Trends Basal and bolus card subtitle when the figures are today's total so far") + : s.totals.count == 1 + ? NSLocalizedString("this day", comment: "Trends Basal and bolus card subtitle when the period is one complete day") + : NSLocalizedString("average per day", comment: "Trends Basal and bolus card subtitle when the figures are averaged per day over the period")) + } + if s.split.total > 0 { + InsulinSplitView(split: s.split, partial: s.splitIsPartial, scheduledBasal: s.scheduledBasal, + usual: s.splitIsPartial ? usualByNow(s) : nil) + } else { + ChartPlaceholder(message: NSLocalizedString("No insulin recorded in this period", comment: "Trends Insulin per day chart placeholder when no insulin was delivered in the period"), height: 80) + } + } + // One day is one bar: the timeline and the Basal and bolus card already say it all. + if s.day == nil { + LoopCard { + CardHeader(title: NSLocalizedString("Insulin per day", comment: "Trends tile and card title: average total insulin per day")) { + Text(s.totals.count > 30 + ? NSLocalizedString("per day, weekly average", comment: "Trends Insulin per day card subtitle when each bar is a week's average daily total") + : NSLocalizedString("basal and bolus", comment: "Trends Insulin per day card subtitle when each bar is one day's basal and bolus")) + } + InsulinDailyTotalsView(totals: s.totals.count > 30 ? LoopHistoryMath.weekly(s.totals) : s.totals, + weekly: s.totals.count > 30, average: s.insulinPerDay) { day in + path.append(.day(day)) + } + ChartLegend(items: insulinLegend(s)) + if let today = todaySoFar(s) { + Footnote(today) + } + } + } + LoopCard { + CardHeader(title: NSLocalizedString("Loop status", comment: "Trends card title: how long Loop spent in closed loop, limited or open loop")) { + Button(NSLocalizedString("Gaps", comment: "Trends Loop status button and screen title: list of times Loop wasn't in closed loop")) { path.append(.gaps) } + .foregroundStyle(P.accentInk) + .disabled(s.loopSpans.isEmpty) + } + LoopModeBarView(spans: s.loopSpans) + if let note = coverageNote(s) { + Footnote(note) + } + } + } else if let error = model.loadError { + LoopCard { + ChartPlaceholder(message: String(format: NSLocalizedString("Couldn't load Trends: %1$@", comment: "Trends error message when the history can't be read. (1: error description)"), error)) + Button(NSLocalizedString("Retry", comment: "Button to try loading Trends again after an error")) { model.reload() }.foregroundStyle(P.accentInk) + } + } else { + ProgressView().frame(maxWidth: .infinity, minHeight: 300) + } + } + + /// The same timeline as Today so far: glucose, every bolus and basal change, and carbs, through the day. + private func dayCard(_ day: DayHistory, targets: [TargetBand], unit: GlucoseUnitStyle) -> some View { + let isToday = Calendar.current.isDateInToday(day.window.start) + return Button { + path.append(.day(day.window.start)) + } label: { + DayTimelineCard(title: isToday ? DayTimelineCard.todayTitle : LoopFormat.weekdayDay(day.window.start), + history: day, unit: unit, targetBands: targets, height: 260, showsTotals: false) + } + .buttonStyle(.plain) + .accessibilityHint(NSLocalizedString("Opens the full day", comment: "VoiceOver hint for the Today so far card on the Now screen")) + } + + private func patternCard(_ s: TrendsModel.Summary) -> some View { + LoopCard { + CardHeader(title: NSLocalizedString("Daily pattern", comment: "Trends card title: the ambulatory glucose profile, glucose by time of day across the period")) { Text(s.unit.unitName) } + TrendsAGPChart(slots: s.agp, readings: s.glucose, days: s.agpDays, unit: s.unit, targetRange: s.targetRange) + ChartLegend(items: [ + LegendItem(swatch: .solid(P.glucose), label: NSLocalizedString("Median", comment: "Trends Daily pattern legend: the median glucose line")), + LegendItem(swatch: .solid(P.agpInner), label: NSLocalizedString("25–75%", comment: "Trends Daily pattern legend: band between the 25th and 75th percentiles of glucose")), + LegendItem(swatch: .solid(P.agpOuter), label: NSLocalizedString("5–95%", comment: "Trends Daily pattern legend: band between the 5th and 95th percentiles of glucose")), + ]) + } + } + + private func summaryCard(_ s: TrendsModel.Summary) -> some View { + LoopCard { + CardHeader(title: NSLocalizedString("Summary", comment: "Trends card title: the summary statistics tiles")) { + Menu(NSLocalizedString("Export", comment: "Trends menu title: export the period as a PDF report or CSV file")) { + Button(NSLocalizedString("PDF report", comment: "Trends export menu item: share a PDF report of the period")) { export { try model.exportPDF(s) } } + Button(NSLocalizedString("CSV, one row per day", comment: "Trends export menu item: share a CSV spreadsheet file with one row for each day")) { export { try TrendsModel.exportCSV(s) } } + } + .foregroundStyle(P.accentInk) + } + if let covered = s.cgmCoveredDays { + Footnote(String(format: NSLocalizedString("CGM data covers %1$@.", comment: "Trends Summary note when glucose readings start partway through the period. (1: days covered, e.g. 5 of 14 days)"), + daysOf(covered, s.totals.count))) + } + StatGridView(tiles: model.tiles(s)) + .opacity(model.loading ? 0.5 : 1) + if s.stats != nil { + Footnote(String(format: NSLocalizedString("Aim: average under %1$@ %2$@, GMI under 7%%, CV 36%% or less.", comment: "Trends Summary footnote: the consensus targets for average glucose, GMI and glucose variability (CV). (1: glucose value, 2: glucose unit)"), + s.unit.isMmol ? LoopFormat.number(8.6, decimals: 1) : "154", s.unit.unitName)) + } + } + } + + /// "5 of 14 days". + private func daysOf(_ count: Int, _ total: Int) -> String { + let days = String.localizedStringWithFormat(NSLocalizedString("%lld days", comment: "A number of days, e.g. a Trends period or how many days are averaged. (1: number of days)"), total) + return String(format: NSLocalizedString("%1$@ of %2$@", comment: "Trends: how many days of the period are averaged. (1: days averaged, a number; 2: days in the period, e.g. 14 days)"), LoopFormat.number(Double(count), decimals: 0), days) + } + + /// "Usually 28.4 U by 19:46": today's insulin against the last 14 days at the same time. + private func usualByNow(_ s: TrendsModel.Summary) -> String? { + guard let usual = s.typicalByNow else { return nil } + return String(format: NSLocalizedString("Usually %1$@ by %2$@", comment: "Trends: the median insulin delivered by this time of day over the last 14 days. (1: units, e.g. 28.4 U, 2: time of day)"), + LoopFormat.totalUnits(usual.typical.median), LoopFormat.time(usual.at)) + } + + /// Today's bar is still growing, so say where it stands against a usual day. + private func todaySoFar(_ s: TrendsModel.Summary) -> String? { + guard let today = s.totals.last, !today.isComplete, today.total > 0, let usual = s.typicalByNow else { return nil } + return String(format: NSLocalizedString("Today so far %1$@, usually %2$@ by %3$@.", comment: "Trends Insulin per day footnote: today's insulin so far against the median by this time of day over the last 14 days. (1: today's units, 2: usual units, 3: time of day)"), + LoopFormat.totalUnits(today.total), LoopFormat.totalUnits(usual.typical.median), LoopFormat.time(usual.at)) + } + + private func export(_ make: () throws -> URL) { + do { + exportURL = try make() + } catch { + exportError = error.localizedDescription + } + } + + /// The figures are on the Basal and bolus card above, so the legend only names the marks. + private func insulinLegend(_ s: TrendsModel.Summary) -> [LegendItem] { + [ + LegendItem(swatch: .basal, label: NSLocalizedString("Basal", comment: "Chart legend: the basal part of each day's insulin")), + LegendItem(swatch: .solid(P.bolus), label: NSLocalizedString("Bolus", comment: "Chart legend: solid bars are manual boluses")), + LegendItem(swatch: .hatch(P.bolus), label: NSLocalizedString("Automatic", comment: "Chart legend: hatched bars are automatic boluses given by Loop")), + LegendItem(swatch: .dash(P.ink), + label: String(format: NSLocalizedString("Average %1$@ U", comment: "Trends Insulin per day legend: the dashed line showing average total insulin per day. (1: units)"), + LoopFormat.number(s.insulinPerDay, decimals: 1))), + ] + } + + private func coverageNote(_ s: TrendsModel.Summary) -> String? { + guard let start = s.loopSpans.first?.start else { + return NSLocalizedString("Loop status is recorded from when this version of Loop first ran.", comment: "Trends Loop status footnote when there is no Loop status history yet") + } + guard start > s.interval.start else { return nil } + return String(format: NSLocalizedString("Covers %1$@: Loop keeps 7 days of runs, and closed or open loop is recorded from when this version of Loop first ran.", comment: "Trends Loop status footnote when the status covers only part of the period. (1: date range covered)"), + LoopFormat.range(start, s.interval.end)) + } +} + +extension URL: @retroactive Identifiable { + public var id: String { absoluteString } +} + +struct ShareSheet: UIViewControllerRepresentable { + let items: [Any] + + func makeUIViewController(context: Context) -> UIActivityViewController { + UIActivityViewController(activityItems: items, applicationActivities: nil) + } + + func updateUIViewController(_ controller: UIActivityViewController, context: Context) {} +} + +/// A card's quiet closing line: a target, a caveat or a comparison. +private struct Footnote: View { + let text: String + + init(_ text: String) { + self.text = text + } + + var body: some View { + Text(text) + .font(LoopDesign.Typeface.footnote) + .foregroundStyle(P.inkMuted) + .fixedSize(horizontal: false, vertical: true) + } +} + +// MARK: - PDF report + +/// The Trends cards laid out for paper. +private struct TrendsReport: View { + let summary: TrendsModel.Summary + let tiles: [StatTile] + + var body: some View { + VStack(alignment: .leading, spacing: LoopDesign.Space.s4) { + VStack(alignment: .leading, spacing: 2) { + Text(NSLocalizedString("Loop trends", comment: "Title of the exported Trends PDF report")).font(.title2.bold()) + Text(LoopFormat.range(summary.interval.start, summary.interval.end.addingTimeInterval(-1))) + .font(.subheadline) + .foregroundStyle(P.inkMuted) + } + StatGridView(tiles: tiles) + HStack(alignment: .top, spacing: LoopDesign.Space.s4) { + VStack(alignment: .leading, spacing: LoopDesign.Space.s2) { + Text(NSLocalizedString("Time in range", comment: "Trends card title: share of glucose readings in each range")).font(.headline) + TimeInRangeView(fractions: summary.timeInRange, unit: summary.unit) + } + .frame(maxWidth: .infinity) + } + if let day = summary.day { + VStack(alignment: .leading, spacing: LoopDesign.Space.s2) { + Text(LoopFormat.weekdayDay(day.window.start)).font(.headline) + InsulinTimelineView(history: day, unit: summary.unit, targetBands: summary.dayTargets, height: 220) + ChartLegend(items: DayTotalsTiles.legend) + } + } else { + VStack(alignment: .leading, spacing: LoopDesign.Space.s2) { + Text(NSLocalizedString("Daily pattern", comment: "Trends card title: the ambulatory glucose profile, glucose by time of day across the period")).font(.headline) + TrendsAGPChart(slots: summary.agp, readings: summary.glucose, days: summary.agpDays, unit: summary.unit, + targetRange: summary.targetRange, height: 160) + } + VStack(alignment: .leading, spacing: LoopDesign.Space.s2) { + Text(NSLocalizedString("Insulin per day", comment: "Trends tile and card title: average total insulin per day")).font(.headline) + InsulinDailyTotalsView(totals: summary.totals.count > 30 ? LoopHistoryMath.weekly(summary.totals) : summary.totals, + weekly: summary.totals.count > 30, average: summary.insulinPerDay, height: 140) + } + } + VStack(alignment: .leading, spacing: LoopDesign.Space.s2) { + Text(NSLocalizedString("Loop status", comment: "Trends card title: how long Loop spent in closed loop, limited or open loop")).font(.headline) + LoopModeBarView(spans: summary.loopSpans) + } + } + .padding(LoopDesign.Space.s2) + .background(Color.white) + } +} diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index ece45a5df3..f2e811b515 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -94,6 +94,7 @@ final class StatusTableViewController: LoopChartsTableViewController { tableView.register(BolusProgressTableViewCell.nib(), forCellReuseIdentifier: BolusProgressTableViewCell.className) tableView.register(InsulinSuspendedTableViewCell.nib(), forCellReuseIdentifier: InsulinSuspendedTableViewCell.className) tableView.register(RecentGlucoseTableViewCell.nib(), forCellReuseIdentifier: RecentGlucoseTableViewCell.className) + tableView.register(UITableViewCell.self, forCellReuseIdentifier: Self.nowHostingCellIdentifier) applyDisplayPreferences() @@ -228,6 +229,11 @@ final class StatusTableViewController: LoopChartsTableViewController { addScenarioStepGestureRecognizers() setupPresetsStatusBar() + + if usesRedesign { + tableView.backgroundColor = .systemGroupedBackground + tableView.separatorStyle = .none + } } override func didReceiveMemoryWarning() { @@ -360,6 +366,32 @@ final class StatusTableViewController: LoopChartsTableViewController { private var bolusProgressReporter: DoseProgressReporter? + // MARK: - Redesign + + /// Read once, so a table never mixes the two layouts; LoopAppManager rebuilds the screen when the preference changes. + let usesRedesign = LoopInterfacePreference.useRedesign + + private static let nowHostingCellIdentifier = "NowHostingCell" + + private lazy var nowModel: NowModel = { + let model = NowModel(deviceManager: deviceManager, loopDataManager: loopManager, settingsManager: settingsManager, temporaryPresetsManager: temporaryPresetsManager) + model.isOnboardingComplete = { [weak self] in self?.onboardingManager.isComplete ?? false } + return model + }() + + private lazy var nowActions = NowActions( + addCarbs: { [weak self] in self?.userTappedAddCarbs() }, + bolus: { [weak self] in self?.presentBolusScreen() }, + presets: { [weak self] in self?.presentPresets() }, + showPrediction: { [weak self] in self?.showPredictionFromNow() }, + showToday: { [weak self] in self?.showDay(Date()) }, + showInsulin: { [weak self] in self?.showInsulinDelivery(sender: self) }, + showCarbs: { [weak self] in self?.performSegue(withIdentifier: CarbAbsorptionViewController.className, sender: self) }, + pumpTapped: { [weak self] in self?.executeHUDTapAction(self?.deviceManager.didTapOnPumpStatus(), from: self?.view) }, + cgmTapped: { [weak self] in self?.executeHUDTapAction(self?.deviceManager.didTapOnCGMStatus(), from: self?.view) }, + loopStatusTapped: { [weak self] in self?.showLoopStatusFromNow() } + ) + private func updateBolusProgress() { if let cell = tableView.cellForRow(at: IndexPath(row: StatusRow.status.rawValue, section: Section.status.rawValue)) as? BolusProgressTableViewCell { if case let .bolusing(_, total) = cell.configuration { @@ -402,7 +434,7 @@ final class StatusTableViewController: LoopChartsTableViewController { } private var shouldShowHUD: Bool { - return !landscapeMode + return !landscapeMode && !usesRedesign } // With a vertical status bar there is no top safe area inset, so keep the first row clear of the display corners. @@ -412,7 +444,7 @@ final class StatusTableViewController: LoopChartsTableViewController { } private var shouldShowStatus: Bool { - return !landscapeMode && statusRowMode.hasRow + return (!landscapeMode || usesRedesign) && statusRowMode.hasRow } override func glucoseUnitDidChange() { @@ -699,10 +731,14 @@ final class StatusTableViewController: LoopChartsTableViewController { let statusRowMode = self.determineStatusRowMode() updateBannerAndHUDandStatusRows(statusRowMode: statusRowMode, newSize: currentContext.newSize, animated: animated) - tableView.contentInset = UIEdgeInsets(top: shouldShowBannerWarning ? 0 : cornerClearanceTopInset, left: 0, bottom: ActionTabBarMetrics.tableContentInset, right: 0) + tableView.contentInset = UIEdgeInsets(top: shouldShowBannerWarning ? 0 : cornerClearanceTopInset, left: 0, bottom: usesRedesign ? 0 : ActionTabBarMetrics.tableContentInset, right: 0) redrawCharts() + if usesRedesign { + nowModel.refresh() + } + reloading = false let reloadNow = !self.refreshContext.isEmpty @@ -717,8 +753,10 @@ final class StatusTableViewController: LoopChartsTableViewController { case presets case alertWarning case hud + case nowHeader case status case charts + case nowCharts } // MARK: - Chart Section Data @@ -979,9 +1017,11 @@ final class StatusTableViewController: LoopChartsTableViewController { case .hud: return shouldShowHUD ? 1 : 0 case .charts: - return ChartRow.allCases.count + return usesRedesign ? 0 : ChartRow.allCases.count case .status: return shouldShowStatus ? StatusRow.allCases.count : 0 + case .nowHeader, .nowCharts: + return usesRedesign ? 1 : 0 } } @@ -1067,6 +1107,10 @@ final class StatusTableViewController: LoopChartsTableViewController { cell.hudView.loopCompletionHUD.loopStatusColors = .loopStatus return cell + case .nowHeader: + return nowHostingCell(for: indexPath) { NowHeaderView(model: self.nowModel, actions: self.nowActions) } + case .nowCharts: + return nowHostingCell(for: indexPath) { NowChartsView(model: self.nowModel, actions: self.nowActions) } case .charts: let cell = tableView.dequeueReusableCell(withIdentifier: ChartTableViewCell.className, for: indexPath) as! ChartTableViewCell @@ -1273,7 +1317,7 @@ final class StatusTableViewController: LoopChartsTableViewController { cell.setSubtitleLabel(label: nil) } } - case .presets, .hud, .status, .alertWarning: + case .presets, .hud, .status, .alertWarning, .nowHeader, .nowCharts: break } } @@ -1298,7 +1342,7 @@ final class StatusTableViewController: LoopChartsTableViewController { } case .alertWarning: return UITableView.automaticDimension - case .presets, .hud, .status: + case .presets, .hud, .status, .nowHeader, .nowCharts: return UITableView.automaticDimension } } @@ -1315,7 +1359,7 @@ final class StatusTableViewController: LoopChartsTableViewController { tableView.deselectRow(at: indexPath, animated: true) presentUnmuteAlertConfirmation() } - case .hud: + case .hud, .nowHeader, .nowCharts: break case .status: switch StatusRow(rawValue: indexPath.row)! { @@ -1389,70 +1433,7 @@ final class StatusTableViewController: LoopChartsTableViewController { performSegue(withIdentifier: PredictionTableViewController.className, sender: indexPath) } case .iob: - let showLegacy = false - - if !showLegacy, let pumpManager = deviceManager.pumpManager { - let hostingController = UIHostingController( - rootView: InsulinDeliveryLog( - viewModel: InsulinDeliveryLogViewModel( - loopDataManager: loopManager, - pumpManager: pumpManager - ), - onTapGesture: { [weak navigationController] doseEntry in - Task { - var dosingDecision: StoredDosingDecision? - if let decisionId = doseEntry.decisionId { - do { - dosingDecision = try await self.loopManager.dosingDecisionStore.findDosingDecisionsById(decisionId) - } catch { - self.log.error("Error finding dosing decision: %{public}@", String(describing: error)) - } - } - - let viewController = CommandResponseViewController(command: { (completionHandler) -> String in - var description = [String]() - - let timeFormatter: DateFormatter = { - let formatter = DateFormatter() - - formatter.dateStyle = .none - formatter.timeStyle = .short - - return formatter - }() - - description.append(timeFormatter.string(from: doseEntry.startDate)) - - description.append(String(describing: doseEntry)) - - if let dosingDecision { - description.append(String(describing: dosingDecision)) - } - - return description.joined(separator: "\n\n") - }) - - navigationController?.pushViewController(viewController, animated: true) - } - }, - onEnterManualDose: { [weak self] in - self?.presentManualDoseEntry() - } - ) - .navigationTitle(Text("Insulin")) - .environment(\.colorPalette, .default) - .environment(\.loopStatusColorPalette, .loopStatus) - ) - - hostingController.hidesBottomBarWhenPushed = true - - navigationController?.pushViewController( - hostingController, - animated: true - ) - } else { - performSegue(withIdentifier: InsulinDeliveryTableViewController.className, sender: indexPath) - } + showInsulinDelivery(sender: indexPath) case .cob: performSegue(withIdentifier: CarbAbsorptionViewController.className, sender: indexPath) } @@ -1605,9 +1586,14 @@ final class StatusTableViewController: LoopChartsTableViewController { if let activity { viewModel.restoreUserActivityState(activity) } - let carbEntryView = CarbEntryView(viewModel: viewModel) - .environmentObject(deviceManager.displayGlucosePreference) - let hostingController = DismissibleHostingController(rootView: carbEntryView, isModalInPresentation: false) + let hostingController: UIViewController + if LoopInterfacePreference.useRedesign { + hostingController = DismissibleHostingController(rootView: CarbEntryScreen(viewModel: viewModel) + .environmentObject(deviceManager.displayGlucosePreference), isModalInPresentation: false) + } else { + hostingController = DismissibleHostingController(rootView: CarbEntryView(viewModel: viewModel) + .environmentObject(deviceManager.displayGlucosePreference), isModalInPresentation: false) + } present(hostingController, animated: true) } analyticsServicesManager?.didDisplayCarbEntryScreen() @@ -1807,6 +1793,74 @@ final class StatusTableViewController: LoopChartsTableViewController { } } + /// The insulin screen behind the IOB chart and the Now screen's IOB tile. + private func showInsulinDelivery(sender: Any?) { + let showLegacy = false + + if !showLegacy, let pumpManager = deviceManager.pumpManager { + let hostingController = UIHostingController( + rootView: InsulinDeliveryLog( + viewModel: InsulinDeliveryLogViewModel( + loopDataManager: loopManager, + pumpManager: pumpManager + ), + onTapGesture: { [weak navigationController] doseEntry in + Task { + var dosingDecision: StoredDosingDecision? + if let decisionId = doseEntry.decisionId { + do { + dosingDecision = try await self.loopManager.dosingDecisionStore.findDosingDecisionsById(decisionId) + } catch { + self.log.error("Error finding dosing decision: %{public}@", String(describing: error)) + } + } + + let viewController = CommandResponseViewController(command: { (completionHandler) -> String in + var description = [String]() + + let timeFormatter: DateFormatter = { + let formatter = DateFormatter() + + formatter.dateStyle = .none + formatter.timeStyle = .short + + return formatter + }() + + description.append(timeFormatter.string(from: doseEntry.startDate)) + + description.append(String(describing: doseEntry)) + + if let dosingDecision { + description.append(String(describing: dosingDecision)) + } + + return description.joined(separator: "\n\n") + }) + + navigationController?.pushViewController(viewController, animated: true) + } + }, + onEnterManualDose: { [weak self] in + self?.presentManualDoseEntry() + } + ) + .navigationTitle(Text("Insulin")) + .environment(\.colorPalette, .default) + .environment(\.loopStatusColorPalette, .loopStatus) + ) + + hostingController.hidesBottomBarWhenPushed = true + + navigationController?.pushViewController( + hostingController, + animated: true + ) + } else { + performSegue(withIdentifier: InsulinDeliveryTableViewController.className, sender: sender) + } + } + private func addPumpManagerViewToHUD(_ view: BaseHUDView) { if let hudView = hudView { view.stateColors = .pumpStatus @@ -1843,7 +1897,7 @@ final class StatusTableViewController: LoopChartsTableViewController { let error: Error? // First, check whether we have a device error after the most recent completion date if let deviceError = deviceManager.lastError, - deviceError.date > (hudView?.loopCompletionHUD.lastLoopCompleted ?? .distantPast) + deviceError.date > (loopManager.lastLoopCompleted ?? .distantPast) { error = deviceError.error } else if let lastLoopError = lastLoopError { @@ -2382,3 +2436,47 @@ private struct WarningBannerStyle: ViewModifier { } } } + +// MARK: - Redesigned Now screen + +extension StatusTableViewController { + private func nowHostingCell(for indexPath: IndexPath, @ViewBuilder content: () -> Content) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: Self.nowHostingCellIdentifier, for: indexPath) + cell.selectionStyle = .none + cell.backgroundColor = .clear + cell.contentConfiguration = UIHostingConfiguration(content: content).margins(.all, 0) + return cell + } + + func presentSettingsFromTab() { + if presentedViewController != nil { + return + } + presentSettings() + } + + private func showPredictionFromNow() { + guard settingsManager.dosingEnabled || !FeatureFlags.simpleBolusCalculatorEnabled else { return } + performSegue(withIdentifier: PredictionTableViewController.className, sender: self) + } + + func showDay(_ day: Date) { + let detail = UIHostingController(rootView: DayDetailView(provider: nowModel.provider, day: day)) + // As for the Insulin, Carbohydrates and Predicted Glucose screens pushed from Now. + detail.hidesBottomBarWhenPushed = true + navigationController?.pushViewController(detail, animated: true) + } + + private func showLoopStatusFromNow() { + let lastCompleted = loopManager.lastLoopCompleted + if let deviceError = deviceManager.lastError, deviceError.date > (lastCompleted ?? .distantPast) { + showLastError(self) + } else if lastLoopError != nil { + showLastError(self) + } else { + // The classic HUD keeps this current; the new interface has no HUD, so set it before presenting. + loopCompletionModalViewModel.ago = lastCompleted.map { Date().timeIntervalSince($0) } + showLoopCompletionMessage(self) + } + } +} diff --git a/Loop/View Models/CarbEntryViewModel.swift b/Loop/View Models/CarbEntryViewModel.swift index 7b84705527..f2d6e34353 100644 --- a/Loop/View Models/CarbEntryViewModel.swift +++ b/Loop/View Models/CarbEntryViewModel.swift @@ -262,8 +262,9 @@ final class CarbEntryViewModel: ObservableObject { } private func observeFavoriteFoodIndexChange() { + // Selection is already on the main actor. Apply it before a caller sets a replacement + // amount or absorption time; a queued reset would otherwise overwrite that choice. $selectedFavoriteFoodIndex - .receive(on: RunLoop.main) .dropFirst() .sink { [weak self] index in self?.favoriteFoodSelected(at: index) diff --git a/Loop/View Models/SettingsViewModel.swift b/Loop/View Models/SettingsViewModel.swift index 626ae21e28..9762fe1efe 100644 --- a/Loop/View Models/SettingsViewModel.swift +++ b/Loop/View Models/SettingsViewModel.swift @@ -120,6 +120,8 @@ class SettingsViewModel { @ObservationIgnored var insulinDeliveryHistory: ((Int) async throws -> InsulinDeliveryHistory)? + @ObservationIgnored var historyProvider: LoopHistoryProvider? + @ObservationIgnored var setupHistory: (() async throws -> SetupHistory)? @ObservationIgnored var outcomesPackageBuilder: OutcomesPackageBuilder? diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index 70f6d26b4e..8c20d06d0e 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -55,6 +55,8 @@ struct SettingsView: View { case favoriteFoods case presets + case trends + case logbook } } @@ -85,6 +87,7 @@ struct SettingsView: View { } alertManagementSection statisticsSection + interfaceSection if viewModel.pumpManagerSettingsViewModel.isSetUp() { therapySection } @@ -156,6 +159,15 @@ struct SettingsView: View { } case .favoriteFoods: FavoriteFoodsView(insightsDelegate: viewModel.favoriteFoodInsightsDelegate) + case .trends: + if let historyProvider = viewModel.historyProvider { + // Trends brings its own navigation stack, so it is a sheet rather than a push. Done closes only Trends. + TrendsView(provider: historyProvider, done: { self.sheet = nil }) + } + case .logbook: + if let historyProvider = viewModel.historyProvider, let deviceManager = viewModel.deviceManager { + LogbookView(provider: historyProvider, deviceManager: deviceManager, done: { self.sheet = nil }) + } } } .environmentObject(displayGlucosePreference) @@ -341,22 +353,47 @@ extension SettingsView { private var statisticsSection: some View { if let glucoseStore = viewModel.deviceManager?.glucoseStore { Section { - NavigationLink(destination: StatisticsView( - glucoseStore: glucoseStore, - insulinDeliveryHistory: viewModel.insulinDeliveryHistory, - scheduledBasalTotal: viewModel.therapySettings().basalRateSchedule?.total() - )) { + // The new interface's Trends and Logbook tabs cover these. + if !LoopInterfacePreference.useRedesign { + NavigationLink(destination: StatisticsView( + glucoseStore: glucoseStore, + insulinDeliveryHistory: viewModel.insulinDeliveryHistory, + scheduledBasalTotal: viewModel.therapySettings().basalRateSchedule?.total() + )) { + LargeButton( + action: {}, + includeArrow: false, + imageView: Image(systemName: "chart.xyaxis.line") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 30), + label: NSLocalizedString("Statistics", comment: "Statistics settings button text"), + descriptiveText: NSLocalizedString("Glucose overview, ambulatory glucose profile and daily insulin", comment: "Statistics settings descriptive text") + ) + .accessibilityIdentifier("settingsViewStatistics") + } + } + if viewModel.historyProvider != nil && !LoopInterfacePreference.useRedesign { LargeButton( - action: {}, - includeArrow: false, - imageView: Image(systemName: "chart.xyaxis.line") + action: { sheet = .trends }, + imageView: Image(systemName: "chart.bar.doc.horizontal") .resizable() .aspectRatio(contentMode: .fit) .frame(width: 30), - label: NSLocalizedString("Statistics", comment: "Statistics settings button text"), - descriptiveText: NSLocalizedString("Glucose overview, ambulatory glucose profile and daily insulin", comment: "Statistics settings descriptive text") + label: NSLocalizedString("Trends", comment: "Trends settings button text"), + descriptiveText: NSLocalizedString("Time in range, daily pattern, insulin per day and Loop status", comment: "Trends settings descriptive text") ) - .accessibilityIdentifier("settingsViewStatistics") + .accessibilityIdentifier("settingsViewTrends") + LargeButton( + action: { sheet = .logbook }, + imageView: Image(systemName: "list.bullet.rectangle") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 30), + label: NSLocalizedString("Logbook", comment: "Logbook settings button text"), + descriptiveText: NSLocalizedString("Boluses, basal changes, carbs and pump events from the last 7 days", comment: "Logbook settings descriptive text") + ) + .accessibilityIdentifier("settingsViewLogbook") } NavigationLink(destination: CurrentSetupView(viewModel: CurrentSetupViewModel( selected: setupChange, @@ -453,6 +490,15 @@ extension SettingsView { .environment(\.insulinTintColor, self.insulinTintColor) } + private var interfaceSection: some View { + Section(footer: Text(NSLocalizedString("The new interface adds Trends and Logbook tabs. Turn it off to go back to the classic status screen; Settings closes to switch.", comment: "Footer for the interface choice in settings"))) { + Toggle(NSLocalizedString("New Interface", comment: "Title of the toggle for the redesigned interface"), isOn: Binding( + get: { LoopInterfacePreference.useRedesign }, + set: { LoopInterfacePreference.useRedesign = $0 } + )) + } + } + private var therapySection: some View { Section { NavigationLink(destination: therapySettingsView) { diff --git a/Loop/Views/StatusTableView.swift b/Loop/Views/StatusTableView.swift index a09e8b9690..f850532f37 100644 --- a/Loop/Views/StatusTableView.swift +++ b/Loop/Views/StatusTableView.swift @@ -97,7 +97,10 @@ struct StatusTableView: View { var wrappedView: some View { wrapped } @Bindable var viewModel: StatusTableViewModel - + + /// The redesign puts these actions on the Now screen and its own tab bar. + private var actionTabsShown = true + init(viewModel: StatusTableViewModel) { self.viewModel = viewModel @@ -123,20 +126,39 @@ struct StatusTableView: View { ) } + func showsActionTabs(_ shown: Bool) -> StatusTableView { + var view = self + view.actionTabsShown = shown + return view + } + + private var statusContent: some View { + wrappedView + .ignoresSafeArea(edges: [.horizontal, .bottom]) + .onChange(of: viewModel.temporaryPresetsManager.activeOverride) { _, _ in + Task { + await viewController.reloadData(animated: true) + } + } + .sheet(item: $viewModel.pendingPreset) { preset in + // This is the active preset; edit disabled + PresetDetentView(preset: preset, roundBasalRate: viewModel.loopDataManager.deliveryDelegate?.roundBasalRate, didTapEdit: { }) + .accessibilityIdentifier("bar_Presets") + } + } + + @ViewBuilder var body: some View { + if actionTabsShown { + actionTabView + } else { + statusContent + } + } + + private var actionTabView: some View { ActionTabView { - wrappedView - .ignoresSafeArea(edges: [.horizontal, .bottom]) - .onChange(of: viewModel.temporaryPresetsManager.activeOverride) { _, _ in - Task { - await viewController.reloadData(animated: true) - } - } - .sheet(item: $viewModel.pendingPreset) { preset in - // This is the active preset; edit disabled - PresetDetentView(preset: preset, roundBasalRate: viewModel.loopDataManager.deliveryDelegate?.roundBasalRate, didTapEdit: { }) - .accessibilityIdentifier("bar_Presets") - } + statusContent } tabs: { ActionTab( title: "Add Carbs", diff --git a/LoopTests/Managers/LoopDataManagerTests.swift b/LoopTests/Managers/LoopDataManagerTests.swift index fab964e866..60d7356ea3 100644 --- a/LoopTests/Managers/LoopDataManagerTests.swift +++ b/LoopTests/Managers/LoopDataManagerTests.swift @@ -648,6 +648,90 @@ class LoopDataManagerTests: XCTestCase { try await assertMissingCarbScheduleThrows(.insulinSensitivitySchedule, preview: true) } + func testFavoriteSelectionCannotOverwriteAReplacementMeal() async throws { + let viewModel = CarbEntryViewModel(delegate: loopDataManager) + let savedFoods = viewModel.favoriteFoods + defer { viewModel.favoriteFoods = savedFoods } + viewModel.favoriteFoods = [StoredFavoriteFood(name: "Meal", carbsQuantity: LoopQuantity(unit: .gram, doubleValue: 35), + foodType: "🌮", absorptionTime: .hours(3))] + viewModel.selectedFavoriteFoodIndex = 0 + let selected = expectation(description: "Favourite selection settled") + RunLoop.main.perform { selected.fulfill() } + await fulfillment(of: [selected], timeout: 2) + XCTAssertEqual(viewModel.carbsQuantity, 35) + + // The person replaces the favourite with a fast meal, retaining its amount. + let quantity = viewModel.carbsQuantity + viewModel.selectedFavoriteFoodIndex = -1 + viewModel.carbsQuantity = quantity + viewModel.usesCustomFoodType = false + viewModel.selectedDefaultAbsorptionTimeEmoji = "🍭" + viewModel.absorptionTime = .minutes(30) + let settled = expectation(description: "Replacement meal settled") + RunLoop.main.perform { settled.fulfill() } + await fulfillment(of: [settled], timeout: 2) + + XCTAssertEqual(viewModel.carbsQuantity, 35) + XCTAssertEqual(viewModel.absorptionTime, .minutes(30)) + XCTAssertFalse(viewModel.continueButtonDisabled) + } + + func testHistoricalTargetsUseStoredProfilesAndEndedPresets() async throws { + func target(_ low: Double, _ high: Double) -> ClosedRange { + LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: low)...LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: high) + } + let window = DateInterval(start: d(-.hours(3)), end: d(-.minutes(30))) + settingsProvider.targetRangeHistory = [ + AbsoluteScheduleValue(startDate: window.start, endDate: d(-.hours(2)), value: target(100, 110)), + AbsoluteScheduleValue(startDate: d(-.hours(2)), endDate: window.end, value: target(120, 130)), + ] + var preset = TemporaryScheduleOverride(context: .custom, + settings: TemporaryPresetSettings(targetRange: target(150, 160), insulinNeedsScaleFactor: 1), + startDate: d(-.minutes(150)), duration: .finite(.hours(2)), + enactTrigger: .local, syncIdentifier: UUID()) + preset.actualEnd = .early(d(-.minutes(90))) + temporaryPresetsManager.presetHistory.recordOverride(preset, at: d(-.minutes(150))) + + let bands = try await loopDataManager.glucoseTargetBands(in: window) + XCTAssertEqual(bands, [ + TargetBand(start: window.start, end: d(-.minutes(150)), range: 100...110), + TargetBand(start: d(-.minutes(150)), end: d(-.minutes(90)), range: 150...160), + TargetBand(start: d(-.minutes(90)), end: window.end, range: 120...130), + ]) + } + + func testTargetBandsAreUnknownBeforeTherapyIsConfigured() async throws { + settingsProvider.settings.glucoseTargetRangeSchedule = nil + let bands = try await loopDataManager.glucoseTargetBands(in: DateInterval(start: d(-.hours(3)), end: now)) + XCTAssertTrue(bands.isEmpty) + } + + func testHistoricalIOBUsesPresetAdjustedBasal() async throws { + settingsProvider.basalHistory = [AbsoluteScheduleValue(startDate: d(-.hours(6)), endDate: now, value: 1.0)] + let window = DateInterval(start: d(-.minutes(30)), end: now) + + for multiplier in [0.5, 1.5] { + let preset = TemporaryScheduleOverride( + context: .custom, + settings: TemporaryPresetSettings(targetRange: nil, insulinNeedsScaleFactor: multiplier), + startDate: d(-.hours(1)), + duration: .finite(.hours(2)), + enactTrigger: .local, + syncIdentifier: UUID() + ) + temporaryPresetsManager.presetHistory.recordOverride(preset, at: now) + doseStore.doseHistory = [DoseEntry(type: .tempBasal, startDate: window.start, endDate: window.end, + value: multiplier, unit: .unitsPerHour, decisionId: nil)] + + let timeline = try await loopDataManager.insulinOnBoardTimeline(in: window) + XCTAssertFalse(timeline.isEmpty) + for value in timeline { + XCTAssertEqual(value.value, 0, accuracy: 1e-9, + "Delivery matching a \(multiplier)× preset's basal must add no net IOB") + } + } + } + func testFetchDataWithHighInsulinNeedsPresetMitigation() async throws { var input = try await loopDataManager.fetchData(for: now) XCTAssertEqual(input.targetMgdl.count, 1) diff --git a/LoopTests/Models/LoopHistoryMathTests.swift b/LoopTests/Models/LoopHistoryMathTests.swift new file mode 100644 index 0000000000..4c4628ca36 --- /dev/null +++ b/LoopTests/Models/LoopHistoryMathTests.swift @@ -0,0 +1,385 @@ +// +// LoopHistoryMathTests.swift +// LoopTests +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import XCTest +import HealthKit +import LoopKit +@testable import Loop + +class LoopHistoryMathTests: XCTestCase { + + private let start = Date(timeIntervalSinceReferenceDate: 800_000_000) + private func at(_ minutes: Double) -> Date { start.addingTimeInterval(minutes * 60) } + + // MARK: Basal + + func testUnrecordedBasalCountsAtTheProgrammedRateOnlyAfterRecordsBegin() { + let window = DateInterval(start: at(0), end: at(240)) + let programmed = [LoopHistoryMath.ScheduleValue(start: at(0), end: at(240), rate: 1.2)] + let temp = LoopHistoryMath.BasalDelivery(start: at(120), end: at(150), rate: 0.4, kind: .temp, automatic: true) + let segments = LoopHistoryMath.basalSegments(window: window, schedule: programmed, deliveries: [temp], + programmedRates: programmed, recordsStart: at(60)) + // Nothing before the first record; 60–120 and 150–240 at 1.2 U/h; 120–150 at the temp rate. + XCTAssertNil(LoopHistoryMath.basalRate(at: at(30), in: segments)) + XCTAssertEqual(LoopHistoryMath.basalRate(at: at(90), in: segments)?.kind, .scheduled) + XCTAssertEqual(LoopHistoryMath.basalRate(at: at(135), in: segments)?.rate, 0.4) + XCTAssertEqual(segments.reduce(0) { $0 + $1.units }, 1.2 * 2.5 + 0.4 * 0.5, accuracy: 1e-9) + } + + func testBasalGapsDoNotInventDelivery() { + let window = DateInterval(start: at(0), end: at(120)) + let schedule = [LoopHistoryMath.ScheduleValue(start: at(-60), end: at(240), rate: 1.0)] + let temp = LoopHistoryMath.BasalDelivery(start: at(60), end: at(90), rate: 2.0, kind: .temp, automatic: true) + let segments = LoopHistoryMath.basalSegments(window: window, schedule: schedule, deliveries: [temp]) + XCTAssertEqual(segments.count, 1) + XCTAssertEqual(segments.reduce(0) { $0 + $1.units }, 1, accuracy: 1e-9) + XCTAssertEqual(segments[0].netUnits, 0.5, accuracy: 1e-9) + } + + func testSuspendDeliversNothingAndLaterRecordWins() { + let window = DateInterval(start: at(0), end: at(60)) + let schedule = [LoopHistoryMath.ScheduleValue(start: at(0), end: at(60), rate: 1.2)] + let deliveries = [ + LoopHistoryMath.BasalDelivery(start: at(0), end: at(60), rate: 3.0, kind: .temp, automatic: true), + LoopHistoryMath.BasalDelivery(start: at(20), end: at(40), rate: 0, kind: .suspended, automatic: false), + ] + let segments = LoopHistoryMath.basalSegments(window: window, schedule: schedule, deliveries: deliveries) + XCTAssertEqual(segments.map(\.kind), [.temp, .suspended, .temp]) + XCTAssertEqual(segments[1].units, 0) + XCTAssertEqual(segments[1].netUnits, -0.4, accuracy: 1e-9) + } + + func testNormalisedBasalPreservesDeliveredQuantityInPartialWindow() throws { + // The pump reports 1.05 U delivered, although the programmed rate implies 1.0625 U. + let dose = DoseEntry(type: .tempBasal, startDate: at(0), endDate: at(30), value: 2.125, + unit: .unitsPerHour, decisionId: nil, deliveredUnits: 1.05, automatic: true) + let delivery = try XCTUnwrap(LoopHistoryMath.BasalDelivery(dose: dose)) + for (end, expected) in [(30.0, 1.05), (15.0, 0.525)] { + let segments = LoopHistoryMath.basalSegments(window: DateInterval(start: at(0), end: at(end)), + schedule: [], deliveries: [delivery]) + XCTAssertEqual(segments.reduce(0) { $0 + $1.units }, expected, accuracy: 1e-9) + } + } + + func testHealthAndLocalPartitionsConserveBasalDelivery() throws { + let sample = HKQuantitySample(type: HKQuantityType(.insulinDelivery), + quantity: HKQuantity(unit: .internationalUnit(), doubleValue: 1), + start: at(0), end: at(60), + metadata: [HKMetadataKeyInsulinDeliveryReason: HKInsulinDeliveryReason.basal.rawValue]) + let left = try XCTUnwrap(BasalSegment(healthSample: sample, in: DateInterval(start: at(-30), end: at(30)))) + let right = try XCTUnwrap(BasalSegment(healthSample: sample, in: DateInterval(start: at(30), end: at(90)))) + XCTAssertEqual(left.units, 0.5, accuracy: 1e-9) + XCTAssertEqual(right.units, 0.5, accuracy: 1e-9, "Include basal beginning before the selected interval") + let dose = DoseEntry(type: .basal, startDate: at(0), endDate: at(60), value: 1, unit: .units, decisionId: nil, deliveredUnits: 1) + let local = LoopHistoryMath.basalSegments(window: DateInterval(start: at(30), end: at(90)), schedule: [], + deliveries: [try XCTUnwrap(LoopHistoryMath.BasalDelivery(dose: dose))]) + XCTAssertEqual(left.units + local.reduce(0) { $0 + $1.units }, 1, accuracy: 1e-9) + XCTAssertNil(BasalSegment(healthSample: sample, in: DateInterval(start: at(60), end: at(90)))) + + } + + func testBolusWindowIsHalfOpenAndPreservesDeliveredUnits() { + let window = DateInterval(start: at(0), end: at(60)) + for (minute, expected) in [(-1.0, false), (0.0, true), (30.0, true), (60.0, false)] { + let dose = DoseEntry(type: .bolus, startDate: at(minute), value: 1, unit: .units, decisionId: nil, deliveredUnits: 0.75) + let event = BolusEvent(dose: dose, in: window) + XCTAssertEqual(event != nil, expected, "Bolus at minute \(minute)") + if expected { XCTAssertEqual(event?.units, 0.75) } + } + } + + // MARK: Glucose + + func testTimeInRangeBandsAndRoundingSumTo100() { + let values: [Double] = [50, 60, 100, 100, 100, 200, 300] + let points = values.enumerated().map { GlucosePoint(date: at(Double($0.offset * 5)), mgdl: $0.element) } + let fractions = LoopHistoryMath.timeInRange(points) + XCTAssertEqual(fractions[.veryLow]!, 1 / 7, accuracy: 1e-9) + XCTAssertEqual(fractions[.low]!, 1 / 7, accuracy: 1e-9) + XCTAssertEqual(fractions[.inRange]!, 3 / 7, accuracy: 1e-9) + XCTAssertEqual(fractions[.high]!, 1 / 7, accuracy: 1e-9) + XCTAssertEqual(fractions[.veryHigh]!, 1 / 7, accuracy: 1e-9) + XCTAssertEqual(LoopHistoryMath.roundedPercentages(fractions).values.reduce(0, +), 100) + } + + func testBandEdgesFollowConsensusThresholds() { + XCTAssertEqual(LoopHistoryMath.RangeBand.band(for: 53.9), .veryLow) + XCTAssertEqual(LoopHistoryMath.RangeBand.band(for: 54), .low) + XCTAssertEqual(LoopHistoryMath.RangeBand.band(for: 70), .inRange) + XCTAssertEqual(LoopHistoryMath.RangeBand.band(for: 180), .inRange) + XCTAssertEqual(LoopHistoryMath.RangeBand.band(for: 181), .high) + XCTAssertEqual(LoopHistoryMath.RangeBand.band(for: 251), .veryHigh) + } + + func testGMIFromMean() { + let points = [GlucosePoint(date: at(0), mgdl: 150), GlucosePoint(date: at(5), mgdl: 150)] + let stats = LoopHistoryMath.glucoseStats(points)! + XCTAssertEqual(stats.gmiPercent, 3.31 + 0.02392 * 150, accuracy: 1e-9) + XCTAssertEqual(stats.gmiMmolMol, (stats.gmiPercent - 2.152) * 10.929, accuracy: 1e-9) + XCTAssertNil(LoopHistoryMath.glucoseStats([points[0]])) + } + + func testCGMCoverageNotedOnlyWhenReadingsStartLate() { + let interval = DateInterval(start: at(0), end: at(14 * 1440)) + let late = [GlucosePoint(date: at(9 * 1440), mgdl: 120), GlucosePoint(date: at(10 * 1440), mgdl: 130)] + XCTAssertEqual(LoopHistoryMath.cgmCoveredDays(late, in: interval, periodDays: 14), 5) + // Within a day of the full period, or a one-day period, says nothing. + XCTAssertNil(LoopHistoryMath.cgmCoveredDays([GlucosePoint(date: at(600), mgdl: 120)], in: interval, periodDays: 14)) + XCTAssertNil(LoopHistoryMath.cgmCoveredDays(late, in: DateInterval(start: at(0), end: at(1440)), periodDays: 1)) + XCTAssertNil(LoopHistoryMath.cgmCoveredDays([], in: interval, periodDays: 14)) + } + + func testScheduledUnitsNeedTheWholeSpanCovered() { + let points = [ScheduledBasalPoint(start: at(0), end: at(120), rate: 1.0), ScheduledBasalPoint(start: at(120), end: at(240), rate: 0.5)] + XCTAssertEqual(try XCTUnwrap(LoopHistoryMath.scheduledUnits(points, over: DateInterval(start: at(60), end: at(180)))), 1.5, accuracy: 1e-9) + XCTAssertNil(LoopHistoryMath.scheduledUnits(points, over: DateInterval(start: at(60), end: at(360)))) + } + + func testPercentileInterpolates() { + XCTAssertEqual(LoopHistoryMath.percentile([1, 2, 3, 4, 5], 0.5), 3) + XCTAssertEqual(LoopHistoryMath.percentile([1, 2, 3, 4], 0.5), 2.5) + XCTAssertEqual(LoopHistoryMath.percentile([10, 20], 0.25), 12.5) + } + + // MARK: Loop modes + + func testLoopModesStartAtFirstModeRecord() { + let interval = DateInterval(start: at(0), end: at(60)) + let runs = stride(from: 0.0, through: 60, by: 5).map { LoopHistoryMath.LoopRun(date: at($0), succeeded: true, error: nil) } + let spans = LoopHistoryMath.loopModeSpans(interval: interval, runs: runs, + modeChanges: [.init(date: at(30), closedLoop: true)], coverageStart: at(0)) + XCTAssertEqual(spans.first?.start, at(30), "Before the first record the mode is unknown and left out") + XCTAssertEqual(LoopHistoryMath.duration(of: .closed, in: spans), 30 * 60, accuracy: 1) + + XCTAssertTrue(LoopHistoryMath.loopModeSpans(interval: interval, runs: runs, modeChanges: [], coverageStart: at(0)).isEmpty) + } + + func testLoopModesClassifyOpenAndLimited() { + let interval = DateInterval(start: at(0), end: at(90)) + // Runs succeed until 20 min, then fail with an error, then stop. + var runs = stride(from: 0.0, through: 20, by: 5).map { LoopHistoryMath.LoopRun(date: at($0), succeeded: true, error: nil) } + runs.append(.init(date: at(25), succeeded: false, error: "Glucose too old")) + let changes: [LoopHistoryMath.ModeChange] = [.init(date: at(0), closedLoop: true), .init(date: at(60), closedLoop: false)] + + let spans = LoopHistoryMath.loopModeSpans(interval: interval, runs: runs, modeChanges: changes, coverageStart: at(0)) + XCTAssertEqual(spans.map(\.mode), [.closed, .limited, .open]) + XCTAssertEqual(spans[1].cause, "Glucose too old") + XCTAssertEqual(spans[1].start, at(35), "Limited starts once the last good run is over 15 minutes old") + XCTAssertEqual(spans[2].start, at(55), "The slot ending at the switch-off counts as open") + } + + // MARK: Calendar + + func testDaysAndQuarterMarksOnClockChangeDay() throws { + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = try XCTUnwrap(TimeZone(identifier: "Europe/London")) + // 25 Oct 2026: clocks go back an hour, so the day is 25 hours long. + let day = try XCTUnwrap(calendar.date(from: DateComponents(year: 2026, month: 10, day: 25))) + let next = try XCTUnwrap(calendar.date(byAdding: .day, value: 1, to: day)) + XCTAssertEqual(next.timeIntervalSince(day), 25 * 3600) + + let marks = DayAxis.quarterDayMarks(in: DateInterval(start: day, end: next), calendar: calendar) + XCTAssertEqual(marks.map { calendar.component(.hour, from: $0) }, [0, 6, 12, 18, 0]) + + let days = LoopHistoryMath.days(in: DateInterval(start: day, end: next.addingTimeInterval(3600)), calendar: calendar) + XCTAssertEqual(days.count, 2) + XCTAssertEqual(days[0].duration, 25 * 3600) + } + + func testWeeklyAveragesDays() { + let monday = Date(timeIntervalSinceReferenceDate: 815_270_400) // 9436 days after Monday 1 Jan 2001, so a Monday at midnight UTC + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = TimeZone(identifier: "UTC")! + let totals = (0..<14).map { LoopHistoryMath.DayTotal(day: monday.addingTimeInterval(Double($0) * 86400), basal: 10, bolus: Double($0)) } + let weeks = LoopHistoryMath.weekly(totals, calendar: calendar) + XCTAssertEqual(weeks.count, 2) + XCTAssertEqual(weeks[0].bolus, 3, accuracy: 1e-9) + XCTAssertEqual(weeks[1].bolus, 10, accuracy: 1e-9) + } + + func testDailyInsulinAverageLeavesOutTheDayInProgress() { + let day0 = Date(timeIntervalSinceReferenceDate: 815_270_400) + let complete = (0..<3).map { LoopHistoryMath.DayTotal(day: day0.addingTimeInterval(Double($0) * 86400), basal: 10, bolus: 20) } + let today = LoopHistoryMath.DayTotal(day: day0.addingTimeInterval(3 * 86400), basal: 3, bolus: 2, isComplete: false) + XCTAssertEqual(LoopHistoryMath.averageDailyInsulin(complete + [today]), 30, accuracy: 1e-9) + XCTAssertEqual(LoopHistoryMath.averagingDays(complete + [today]).count, 3) + // With nothing complete, the day in progress is all there is to show. + XCTAssertEqual(LoopHistoryMath.averageDailyInsulin([today]), 5, accuracy: 1e-9) + } + + func testInsulinSplitSeparatesManualAutomaticAndUnclassifiedBolus() { + let boluses = [ + BolusEvent(id: "m", date: at(0), units: 4, automatic: false, manuallyEntered: false), + BolusEvent(id: "a", date: at(5), units: 0.5, automatic: true, manuallyEntered: false), + BolusEvent(id: "u", date: at(10), units: 1, automatic: false, manuallyEntered: false, automaticKnown: false), + ] + let split = InsulinSplit(basal: 14.5, boluses: Array(boluses.prefix(2))) + XCTAssertEqual(split.manualBolus, 4, accuracy: 1e-9) + XCTAssertEqual(split.automaticBolus, 0.5, accuracy: 1e-9) + XCTAssertEqual(split.total, 19, accuracy: 1e-9) + XCTAssertTrue(split.isClassified) + // Shares round independently but always add up to 100. + XCTAssertEqual(split.basalPercent + split.bolusPercent, 100) + XCTAssertEqual(split.bolusPercent, 24) + + // A bolus that doesn't say who gave it still counts, but hides the manual and automatic detail. + let health = InsulinSplit(basal: 14.5, boluses: boluses) + XCTAssertEqual(health.bolus, 5.5, accuracy: 1e-9) + XCTAssertFalse(health.isClassified) + + let average = InsulinSplit.average([split, InsulinSplit(basal: 10.5, manualBolus: 2, automaticBolus: 1.5)]) + XCTAssertEqual(average.basal, 12.5, accuracy: 1e-9) + XCTAssertEqual(average.manualBolus, 3, accuracy: 1e-9) + XCTAssertEqual(average.automaticBolus, 1, accuracy: 1e-9) + } + + func testWeeklyInsulinUsesTheSummaryAveragingDays() { + let monday = Date(timeIntervalSinceReferenceDate: 815_270_400) + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = TimeZone(identifier: "UTC")! + let totals = [ + LoopHistoryMath.DayTotal(day: monday, basal: 0, bolus: 0), + LoopHistoryMath.DayTotal(day: monday.addingTimeInterval(86400), basal: 10, bolus: 20), + LoopHistoryMath.DayTotal(day: monday.addingTimeInterval(2 * 86400), basal: 10, bolus: 20), + LoopHistoryMath.DayTotal(day: monday.addingTimeInterval(3 * 86400), basal: 1, bolus: 2, isComplete: false), + ] + let weeks = LoopHistoryMath.weekly(totals, calendar: calendar) + XCTAssertEqual(weeks.count, 1) + XCTAssertEqual(weeks[0].basal, 10, accuracy: 1e-9) + XCTAssertEqual(weeks[0].bolus, 20, accuracy: 1e-9) + + // A current week with no complete day must still show the data it has. + let currentWeek = LoopHistoryMath.weekly([totals[3]], calendar: calendar) + XCTAssertEqual(currentWeek.first?.total, 3) + } + + func testGapMessageForADayWithoutReadings() { + let start = Date(timeIntervalSinceReferenceDate: 815_270_400) + let day = DateInterval(start: start, duration: 86400) + let empty = DayAxis.gaps(in: [], window: day, until: day.end.addingTimeInterval(3600)) + XCTAssertEqual(DayAxis.gapMessage(empty, window: day, until: day.end.addingTimeInterval(3600)), "No CGM data on this day") + let morning = start.addingTimeInterval(9 * 3600) + XCTAssertEqual(DayAxis.gapMessage(DayAxis.gaps(in: [], window: day, until: morning), window: day, until: morning), "No CGM data yet today") + let reading = [GlucosePoint(date: start.addingTimeInterval(12 * 3600), mgdl: 120)] + let partial = DayAxis.gaps(in: reading, window: day, until: day.end) + XCTAssertTrue(DayAxis.gapMessage(partial, window: day, until: day.end)?.hasSuffix("and 1 shorter gap") ?? false) + } + + // MARK: Formatting + + func testRangeFormatting() throws { + let calendar = Calendar.current + let a = try XCTUnwrap(calendar.date(from: DateComponents(year: 2026, month: 10, day: 12, hour: 12))) + let b = try XCTUnwrap(calendar.date(from: DateComponents(year: 2026, month: 10, day: 25, hour: 12))) + let c = try XCTUnwrap(calendar.date(from: DateComponents(year: 2026, month: 9, day: 28, hour: 12))) + // Ranges and durations come from the system's formatters, so they follow the user's locale; + // the test host runs in en_US. + XCTAssertEqual(LoopFormat.range(b, b), LoopFormat.day(b)) + let sameMonth = LoopFormat.range(a, b) + XCTAssertTrue(sameMonth.contains("12") && sameMonth.contains("25")) + XCTAssertEqual(sameMonth.components(separatedBy: "2026").count, 2, "the year appears once") + let twoMonths = LoopFormat.range(c, b) + XCTAssertTrue(twoMonths.contains("Sep") && twoMonths.contains("Oct")) + XCTAssertEqual(LoopFormat.duration(13 * 86400 + 11 * 3600), "13d 11h") + XCTAssertEqual(LoopFormat.duration(9 * 3600 + 45 * 60), "9h 45m") + XCTAssertEqual(LoopFormat.number(7.25, decimals: 1), 7.25.formatted(.number.precision(.fractionLength(1)).grouping(.never))) + XCTAssertEqual(LoopFormat.signed(-0.4), "−0.40") + XCTAssertEqual(LoopFormat.signed(0.0001), "+0.00") + } + + @MainActor + func testCSVExportsTenColumnsWithASCIINumbers() throws { + let day = Calendar.current.startOfDay(for: start) + let nextDay = day.addingTimeInterval(86400) + for isMmol in [true, false] { + var summary = TrendsModel.Summary(interval: DateInterval(start: day, duration: 2 * 86400), + unit: GlucoseUnitStyle(isMmol: isMmol)) + summary.glucose = [GlucosePoint(date: day, mgdl: 131.53286)] + summary.totals = [ + .init(day: day, split: InsulinSplit(basal: 1.25, manualBolus: 2, automaticBolus: 0.5)), + // From another app in Health: the bolus doesn't say whether it was manual or automatic. + .init(day: nextDay, split: InsulinSplit(basal: 1, unclassifiedBolus: 3)), + ] + summary.carbsByDay = [day: 45] + let url = try TrendsModel.exportCSV(summary) + defer { try? FileManager.default.removeItem(at: url) } + let rows = try String(contentsOf: url, encoding: .utf8).components(separatedBy: "\n") + XCTAssertEqual(rows.count, 3) + XCTAssertEqual(rows[0].components(separatedBy: ",").count, 10) + XCTAssertTrue(rows[0].hasSuffix(",carbs_g,manual_bolus_u,auto_bolus_u")) + let columns = rows[1].components(separatedBy: ",") + XCTAssertEqual(columns.count, 10) + XCTAssertEqual(Array(columns.dropFirst()), [isMmol ? "7.3" : "132", "1", "100", "1.25", "2.50", "3.75", "45", "2.00", "0.50"]) + XCTAssertEqual(Array(rows[2].components(separatedBy: ",").suffix(5)), ["3.00", "4.00", "0", "", ""]) + } + } + + func testModeHistoryRetainsTheModeBeforeTheRetentionWindow() throws { + let suite = "LoopHistoryMathTests-\(UUID().uuidString)" + let defaults = try XCTUnwrap(UserDefaults(suiteName: suite)) + defer { defaults.removePersistentDomain(forName: suite) } + let history = LoopModeHistory(defaults: defaults) + history.record(closedLoop: false, at: at(0)) + history.record(closedLoop: true, at: at(60)) + let switchDate = at(200 * 1440) + history.record(closedLoop: false, at: switchDate) + XCTAssertEqual(history.changes, [.init(date: at(60), closedLoop: true), .init(date: switchDate, closedLoop: false)]) + let interval = DateInterval(start: switchDate.addingTimeInterval(-3600), end: switchDate) + let spans = LoopHistoryMath.loopModeSpans(interval: interval, runs: [], modeChanges: history.changes, + coverageStart: interval.start) + XCTAssertEqual(spans.first?.start, interval.start, "Retaining the old mode must preserve recent coverage") + } + + func testIssueDescriptions() { + XCTAssertEqual(LoopHistoryProvider.describe(issue: "glucoseTooOld"), "Glucose too old") + XCTAssertEqual(LoopHistoryProvider.describe(issue: "pumpDataTooOld"), "Pump data too old") + } + + // MARK: Day history and Logbook folding + + func testDayHistoryClippedToNowDropsLaterEventsAndCutsBasal() { + var history = DayHistory(window: DateInterval(start: at(0), end: at(1440))) + history.glucose = [GlucosePoint(date: at(10), mgdl: 100), GlucosePoint(date: at(700), mgdl: 120)] + history.boluses = [BolusEvent(id: "a", date: at(30), units: 1, automatic: false, manuallyEntered: false), + BolusEvent(id: "b", date: at(650), units: 2, automatic: true, manuallyEntered: false)] + history.basal = [BasalSegment(start: at(0), end: at(720), rate: 1, scheduledRate: 1, kind: .scheduled, automatic: false)] + let clipped = history.clipped(to: DateInterval(start: at(0), end: at(600))) + XCTAssertEqual(clipped.window.end, at(600)) + XCTAssertEqual(clipped.glucose.count, 1) + XCTAssertEqual(clipped.boluses.map(\.id), ["a"]) + XCTAssertEqual(clipped.basalUnits, 10, accuracy: 1e-9) + } + + private func logRow(_ id: String, _ minutes: Double, automaticUnits: Double? = nil, basalSummary: Bool = false) -> LogEntry { + LogEntry(id: id, date: at(minutes), title: id, subtitle: "", amount: "", amountColor: .primary, detail: "", + icon: "plus", iconStyle: .solid(.primary), filters: [], + basalChanges: basalSummary ? [BasalSegment(start: at(0), end: at(30), rate: 0, scheduledRate: 1, kind: .temp, automatic: true)] : [], + automaticUnits: automaticUnits) + } + + @MainActor + func testLogbookFoldsRunsOfAutomaticBolusesBetweenOtherEntries() { + // Newest first, as the Logbook lists them. + let rows = [ + logRow("auto5", 100, automaticUnits: 0.3), + logRow("auto4", 95, automaticUnits: 0.2), + logRow("basal", 90, basalSummary: true), + logRow("auto3", 85, automaticUnits: 0.1), + logRow("carbs", 60), + logRow("auto2", 30, automaticUnits: 0.5), + logRow("manual", 20), + logRow("auto1", 10, automaticUnits: 0.1), + ] + let folded = LogbookModel.foldingAutomaticBoluses(rows) + // auto5, auto4 and auto3 fold together: the basal summary doesn't break a run, the carbs do. + XCTAssertEqual(folded.map(\.id), ["autorun-auto5", "basal", "carbs", "auto2", "manual", "auto1"]) + XCTAssertEqual(folded[0].bolusRun.map(\.id), ["auto5", "auto4", "auto3"]) + XCTAssertEqual(folded[0].date, at(100)) + XCTAssertTrue(folded[0].matches(.loop) && folded[0].matches(.bolus)) + XCTAssertEqual(folded[0].amount, LoopFormat.doseUnits(0.6)) + } +}