diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index b147fc93e2..f87be065c4 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -726,6 +726,15 @@ 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 */; }; + 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 +1605,15 @@ 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 = ""; }; + 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 +1936,7 @@ 43776F8E1B8022E90074EA36 /* Loop */ = { isa = PBXGroup; children = ( + 8574687EF267DC59043C0242 /* Redesign */, C16DA84022E8E104008624C2 /* Plugins */, B66D1F322E6A5D6600471149 /* Localizable.xcstrings */, B66D1F382E6A5D6600471149 /* InfoPlist.xcstrings */, @@ -2816,6 +2835,7 @@ 6C1F8E3BA2D45079B3E2F512 /* OutcomesPackageTests.swift */, BE36167100E59D7413C50F63 /* SetupComparisonTests.swift */, FEEDF0000000000000000005 /* GlucoseStatisticsTests.swift */, + 42B08644C3031D9E815F9892 /* LoopHistoryMathTests.swift */, A9DFAFB224F0415E00950D1E /* CarbBackfillRequestUserInfoTests.swift */, A963B279252CEBAE0062AA12 /* SetBolusUserInfoTests.swift */, C19008FF252271BB00721625 /* SimpleBolusCalculatorTests.swift */, @@ -2992,6 +3012,21 @@ path = Fixtures; sourceTree = ""; }; + 8574687EF267DC59043C0242 /* Redesign */ = { + isa = PBXGroup; + children = ( + AF0FD64B2B3E9C9AA8764684 /* TrendsScreen.swift */, + 4BFDE56977CC73CBDA1170C5 /* TrendsComponents.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 +3685,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5CD0888FF33D6F844A81E638 /* TrendsScreen.swift in Sources */, + 591F13AF435B37B4818E2F2B /* TrendsComponents.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 +4144,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/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/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..ddaba7520d 100644 --- a/Loop/Localizable.xcstrings +++ b/Loop/Localizable.xcstrings @@ -12,6 +12,19623 @@ }, " -" : { + }, + "%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$@ 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$@%%" : { + "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 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%" + } + } + } + }, + "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" : "活性胰岛素和碳水" + } + } + } + }, + "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%%。" + } + } + } + }, + "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" : "自动大剂量" + } + } + } + }, + "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 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" : "已输注基础" + } + } + } + }, + "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" : "每日大剂量次数" + } + } + } + }, + "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" : "闭环已关闭" + } + } + } + }, + "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 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 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 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,每天一行" + } + } + } + }, + "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" : "日" + } + } + } + }, + "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" : "导出" + } + } + } + }, + "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 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" : "血糖数据过旧" + } + } + } + }, + "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" : "范围内" + } + } + } + }, + "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,左轴)" + } + } + } + }, + "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" : "受限" + } + } + } + }, + "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 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 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 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$@" + } + } + } + }, + "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$@" + } + } + } + }, + "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" : "中位数" + } + } + } + }, + "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 数据" + } + } + } + }, + "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 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 运行记录" + } + } + } + }, + "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" + } + } + } + }, + "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" : "当前" + } + } + } + }, + "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" : "开环 / 手动" + } + } + } + }, + "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" : "每日,按周平均" + } + } + } + }, + "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" : "上一时段" + } + } + } + }, + "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 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" : "胰岛素泵已暂停" + } + } + } + }, + "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" : "建议已过期" + } + } + } + }, + "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$@" + } + } + } + }, + "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$@%%" + } + } + } + }, + "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" : "已暂停" + } + } + } + }, + "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" : "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" : { + "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 %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$@。" + } + } + } + }, + "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" : "趋势" + } + } + } + }, + "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" : "未知错误" + } + } + } + }, + "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" : "非常低" + } + } + } }, " - " : { @@ -2423,6 +22040,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" + } } } }, @@ -9054,7 +28713,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 +29721,24 @@ "state" : "translated", "value" : "大剂量" } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bolus" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bólus" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Болюс" + } } } }, @@ -21095,6 +40912,24 @@ "state" : "translated", "value" : "克" } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "g" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "g" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "г" + } } } }, @@ -21307,6 +41142,18 @@ "state" : "translated", "value" : "血糖" } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Glükóz" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Глюкоза" + } } } }, @@ -22061,7 +41908,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 +42146,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 +43248,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 +47175,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", @@ -31045,6 +51336,18 @@ "state" : "translated", "value" : "OK" } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } } } }, @@ -35531,6 +55834,18 @@ "state" : "translated", "value" : "重试" } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Újra" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Спробувати ще раз" + } } } }, @@ -35876,6 +56191,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" + } } } }, @@ -40595,6 +60970,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..aee8e93430 100644 --- a/Loop/Managers/LoopAppManager.swift +++ b/Loop/Managers/LoopAppManager.swift @@ -517,6 +517,14 @@ 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) + state = state.next await loopDataManager.updateDisplayState() @@ -618,6 +626,8 @@ 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 }() diff --git a/Loop/Redesign/DayCharts.swift b/Loop/Redesign/DayCharts.swift new file mode 100644 index 0000000000..83e2032d74 --- /dev/null +++ b/Loop/Redesign/DayCharts.swift @@ -0,0 +1,548 @@ +// +// 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)) + } +} + +/// 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..0f8186b71b --- /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, for 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/LoopDesign.swift b/Loop/Redesign/LoopDesign.swift new file mode 100644 index 0000000000..0fb74edffd --- /dev/null +++ b/Loop/Redesign/LoopDesign.swift @@ -0,0 +1,341 @@ +// +// 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 accentInk = Color("accent-ink") + + // 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") + } + + 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 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: - Tiles + +/// A small sunken tile with a label over a value, as used under 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 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..cfdca173dc --- /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 screen. 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..3fc71c5b47 --- /dev/null +++ b/Loop/Redesign/LoopHistoryModels.swift @@ -0,0 +1,344 @@ +// +// 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 +} + +/// Everything known about one day or reporting period, for InsulinTimeline. +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)) ?? "" + } +} + +// 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..0c5236808a --- /dev/null +++ b/Loop/Redesign/LoopHistoryProvider.swift @@ -0,0 +1,464 @@ +// +// 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) + } + + /// 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 + } + + // 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: - 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/TrendsComponents.swift b/Loop/Redesign/TrendsComponents.swift new file mode 100644 index 0000000000..c98336a72c --- /dev/null +++ b/Loop/Redesign/TrendsComponents.swift @@ -0,0 +1,788 @@ +// +// 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: ". ") + } +} + +// 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..9f8c5960a6 --- /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 one timeline. + 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. + 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 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..babad179cc 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -55,6 +55,7 @@ struct SettingsView: View { case favoriteFoods case presets + case trends } } @@ -156,6 +157,11 @@ 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 }) + } } } .environmentObject(displayGlucosePreference) @@ -358,6 +364,18 @@ extension SettingsView { ) .accessibilityIdentifier("settingsViewStatistics") } + if viewModel.historyProvider != nil { + LargeButton( + action: { sheet = .trends }, + imageView: Image(systemName: "chart.bar.doc.horizontal") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 30), + 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("settingsViewTrends") + } NavigationLink(destination: CurrentSetupView(viewModel: CurrentSetupViewModel( selected: setupChange, loadHistory: { [viewModel] in try await viewModel.setupHistory?() ?? SetupHistory(settings: [], experiments: []) }, diff --git a/LoopTests/Managers/LoopDataManagerTests.swift b/LoopTests/Managers/LoopDataManagerTests.swift index fab964e866..e82fdd17ea 100644 --- a/LoopTests/Managers/LoopDataManagerTests.swift +++ b/LoopTests/Managers/LoopDataManagerTests.swift @@ -648,6 +648,62 @@ class LoopDataManagerTests: XCTestCase { try await assertMissingCarbScheduleThrows(.insulinSensitivitySchedule, preview: true) } + 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..baa7b68960 --- /dev/null +++ b/LoopTests/Models/LoopHistoryMathTests.swift @@ -0,0 +1,356 @@ +// +// 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 + + 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) + } +}