diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2a12101..9c30113 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -61,7 +61,7 @@ Match the surrounding code. It's consistent, and consistency beats personal pref
- **Design:** iOS 26 Liquid Glass. `.glassEffect()` on card surfaces, `Color(.systemBackground)` for page backgrounds. Don't nest glass in glass.
- **Motion:** Every animation respects `accessibilityReduceMotion`.
- **Comments:** Explain *why*, not *what*. The non-obvious constraint, the bug this guards against, the reason it isn't the simpler thing.
-- **No third-party dependencies.** Apple frameworks only. This is intentional.
+- **Almost no third-party dependencies.** Google Mobile Ads and its User Messaging Platform dependency support the ad-funded free tier; everything else is Apple frameworks. Adding a new third-party package needs a strong justification and an explicit decision.
## Commit messages
diff --git a/README.md b/README.md
index ef5d6e7..f941875 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,8 @@
Built for iOS with SwiftUI, SwiftData, and WeatherKit.
- No accounts. No tracking. No third-party dependencies.
+ No accounts. The free version shows banner ads; the optional SunHat Ad-Free
+ subscription removes them.
---
@@ -52,7 +53,7 @@ You describe the conditions you care about. SunHat checks the forecast in the ba
| **Spotlight indexing** | Find reminders from iOS search |
| **Data export and deletion** | Full GDPR-style export and deletion, covered by schema-parity tests |
| **Liquid Glass UI** | Native iOS 26 design with `.glassEffect()` surfaces |
-| **Zero tracking** | No accounts, no analytics, no third-party SDKs |
+| **Ad-free option** | The free version shows banner ads (Google AdMob); the SunHat Ad-Free subscription removes them. No accounts, and SunHat collects no analytics of its own |
## Screenshots
@@ -79,9 +80,11 @@ Captured on iOS 26 (iPhone). SunHat is iPhone-first. iPad, widget, and watch sur
| Xcode | 26+ |
| Swift | 6.2 |
| Minimum iOS | 26.0 |
-| Dependencies | None. Apple frameworks only. |
+| Dependencies | Google Mobile Ads + User Messaging Platform (SPM), for the ad-supported free tier. Everything else is Apple frameworks. |
-No CocoaPods, no SPM packages, no Carthage. Every import is an Apple framework, and that's intentional.
+No CocoaPods, no Carthage. The only SPM packages are Google Mobile Ads and its
+User Messaging Platform dependency, which exist to support the ad-funded free
+tier; every other import is an Apple framework, and that's intentional.
## Getting started
diff --git a/SunHat.storekit b/SunHat.storekit
new file mode 100644
index 0000000..9ae55c1
--- /dev/null
+++ b/SunHat.storekit
@@ -0,0 +1,86 @@
+{
+ "identifier" : "FDFD818F-1B0F-4818-8178-F54144221F17",
+ "nonRenewingSubscriptions" : [
+
+ ],
+ "products" : [
+
+ ],
+ "settings" : {
+ "_askToBuyEnabled" : false,
+ "_billingGracePeriodEnabled" : true,
+ "_developerTeamID" : "HD39MR492X",
+ "_failTransactionsEnabled" : false,
+ "_locale" : "en_US",
+ "_storefront" : "USA"
+ },
+ "subscriptionGroups" : [
+ {
+ "id" : "A0B1E0B0-E228-4BD8-B4E4-EEF9FC150D77",
+ "localizations" : [
+ {
+ "description" : "Removes all ads from SunHat.",
+ "displayName" : "SunHat Ad-Free",
+ "locale" : "en_US"
+ }
+ ],
+ "name" : "SunHat Ad-Free",
+ "subscriptions" : [
+ {
+ "adHocOffers" : [
+
+ ],
+ "codeOffers" : [
+
+ ],
+ "displayPrice" : "1.00",
+ "familyShareable" : false,
+ "groupNumber" : 1,
+ "internalID" : "64420497-E937-44EC-A22A-785480ABDFDF",
+ "introductoryOffer" : null,
+ "localizations" : [
+ {
+ "description" : "Removes all ads from SunHat.",
+ "displayName" : "Ad-Free Monthly",
+ "locale" : "en_US"
+ }
+ ],
+ "productID" : "org.wesley.sunhat.adfree.monthly",
+ "recurringSubscriptionPeriod" : "P1M",
+ "referenceName" : "Ad-Free Monthly",
+ "subscriptionGroupID" : "A0B1E0B0-E228-4BD8-B4E4-EEF9FC150D77",
+ "type" : "RecurringSubscription"
+ },
+ {
+ "adHocOffers" : [
+
+ ],
+ "codeOffers" : [
+
+ ],
+ "displayPrice" : "10.00",
+ "familyShareable" : false,
+ "groupNumber" : 1,
+ "internalID" : "B7A93E52-4C11-4E8D-9F30-2D6E8C41A9D0",
+ "introductoryOffer" : null,
+ "localizations" : [
+ {
+ "description" : "Best value — two months free vs. monthly.",
+ "displayName" : "Ad-Free Annual",
+ "locale" : "en_US"
+ }
+ ],
+ "productID" : "org.wesley.sunhat.adfree.yearly",
+ "recurringSubscriptionPeriod" : "P1Y",
+ "referenceName" : "Ad-Free Annual",
+ "subscriptionGroupID" : "A0B1E0B0-E228-4BD8-B4E4-EEF9FC150D77",
+ "type" : "RecurringSubscription"
+ }
+ ]
+ }
+ ],
+ "version" : {
+ "major" : 3,
+ "minor" : 0
+ }
+}
diff --git a/SunHat.xcodeproj/project.pbxproj b/SunHat.xcodeproj/project.pbxproj
index 4bfac31..80a978c 100644
--- a/SunHat.xcodeproj/project.pbxproj
+++ b/SunHat.xcodeproj/project.pbxproj
@@ -60,11 +60,16 @@
};
/* End PBXFileSystemSynchronizedRootGroup section */
+/* Begin PBXBuildFile section */
+ ADF0000000000000000000C3 /* GoogleMobileAds in Frameworks */ = {isa = PBXBuildFile; productRef = ADF0000000000000000000C2 /* GoogleMobileAds */; };
+/* End PBXBuildFile section */
+
/* Begin PBXFrameworksBuildPhase section */
64120D192E2D64CB0034CB20 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ ADF0000000000000000000C3 /* GoogleMobileAds in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -148,6 +153,7 @@
);
name = SunHat;
packageProductDependencies = (
+ ADF0000000000000000000C2 /* GoogleMobileAds */,
);
productName = SunHat;
productReference = 64120D1C2E2D64CB0034CB20 /* SunHat.app */;
@@ -232,6 +238,9 @@
);
mainGroup = 64120D132E2D64CA0034CB20;
minimizedProjectReferenceProxies = 1;
+ packageReferences = (
+ ADF0000000000000000000C1 /* XCRemoteSwiftPackageReference "swift-package-manager-google-mobile-ads" */,
+ );
preferredProjectObjectVersion = 77;
productRefGroup = 64120D1D2E2D64CB0034CB20 /* Products */;
projectDirPath = "";
@@ -325,7 +334,7 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
- IPHONEOS_DEPLOYMENT_TARGET = 26.4;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -362,7 +371,7 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
- IPHONEOS_DEPLOYMENT_TARGET = 26.4;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -432,7 +441,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 26.4;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.5;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
@@ -491,7 +500,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 26.4;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.5;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
@@ -510,7 +519,7 @@
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = HD39MR492X;
GENERATE_INFOPLIST_FILE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 26.4;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.wesley.sunhatTests;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -531,7 +540,7 @@
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = HD39MR492X;
GENERATE_INFOPLIST_FILE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 26.4;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.wesley.sunhatTests;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -622,6 +631,25 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+ ADF0000000000000000000C1 /* XCRemoteSwiftPackageReference "swift-package-manager-google-mobile-ads" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "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/googleads/swift-package-manager-google-mobile-ads.git";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 12.0.0;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ ADF0000000000000000000C2 /* GoogleMobileAds */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = ADF0000000000000000000C1 /* XCRemoteSwiftPackageReference "swift-package-manager-google-mobile-ads" */;
+ productName = GoogleMobileAds;
+ };
+/* End XCSwiftPackageProductDependency section */
};
rootObject = 64120D142E2D64CA0034CB20 /* Project object */;
}
diff --git a/SunHat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SunHat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 0000000..ac79b89
--- /dev/null
+++ b/SunHat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,24 @@
+{
+ "originHash" : "3b9752ff554d5fdc91a7c01c2322a5d851b0979c36f02eb55188d9ab9e3d7e77",
+ "pins" : [
+ {
+ "identity" : "swift-package-manager-google-mobile-ads",
+ "kind" : "remoteSourceControl",
+ "location" : "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/googleads/swift-package-manager-google-mobile-ads.git",
+ "state" : {
+ "revision" : "1239c23464503053c17d37ee5daa70de3455e9c8",
+ "version" : "12.14.0"
+ }
+ },
+ {
+ "identity" : "swift-package-manager-google-user-messaging-platform",
+ "kind" : "remoteSourceControl",
+ "location" : "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/googleads/swift-package-manager-google-user-messaging-platform.git",
+ "state" : {
+ "revision" : "13b248eaa73b7826f0efb1bcf455e251d65ecb1b",
+ "version" : "3.1.0"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/SunHat.xcodeproj/xcshareddata/xcschemes/SunHat.xcscheme b/SunHat.xcodeproj/xcshareddata/xcschemes/SunHat.xcscheme
index 85a3735..a1ed0c6 100644
--- a/SunHat.xcodeproj/xcshareddata/xcschemes/SunHat.xcscheme
+++ b/SunHat.xcodeproj/xcshareddata/xcschemes/SunHat.xcscheme
@@ -57,6 +57,9 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
+
+
+
+
org.wesley.sunhat.weather-refresh
org.wesley.sunhat.trigger-evaluation
+ GADApplicationIdentifier
+ ca-app-pub-3940256099942544~1458002511
+ GADDelayAppMeasurementInit
+
+ NSUserTrackingUsageDescription
+ This allows SunHat to show more relevant ads, which helps keep the free version free.
+ SKAdNetworkItems
+
+
+ SKAdNetworkIdentifier
+ 22mmun2rn5.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 2fnua5tdw4.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 2u9pt9hc89.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 3qcr597p9d.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 3qy4746246.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 3rd42ekr43.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 3sh42y64q3.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 4468km3ulz.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 44jx6755aq.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 47vhws6wlr.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 4dzt52r2t5.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 4fzdc2evr5.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 578prtvx9j.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 7ug5zh24hu.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 8c4e2ghe7u.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 8s468mfl3y.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 97r2b46745.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 9t245vhmpl.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ a2p9lx4jpn.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ c3frkrj4fj.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ c6k4g5qg8m.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ cp8zw746q7.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ cstr6suwn9.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ e5fvkxwrpn.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ f38h382jlk.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ gta9lk7p23.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ hs6bdukanm.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ k674qkevps.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ kbd757ywx3.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ kbmxgpxpgc.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ klf5c3l5u5.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ ludvb6z3bs.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ mlmmfzh3r3.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ n38lu8286q.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ p78axxw29g.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ ppxm28t8ap.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ s39g8k73mm.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ su67r6k2v3.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ t38b2kh725.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ tl55sbb4fm.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ uw77j35x4d.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ v4nxqhlyqp.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ v72qych5uu.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ v9wttpbfk9.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ vutu7akeur.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ wg4vff78zm.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ wzmmz9fp6w.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ y5ghdn5j9k.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ yclnxrl5pm.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ ydx93a7ass.skadnetwork
+
+
ITSAppUsesNonExemptEncryption
NSLocationAccuracyAuthorizationDescription
diff --git a/SunHat/PrivacyInfo.xcprivacy b/SunHat/PrivacyInfo.xcprivacy
index 22e1546..3df0f8a 100644
--- a/SunHat/PrivacyInfo.xcprivacy
+++ b/SunHat/PrivacyInfo.xcprivacy
@@ -2,8 +2,22 @@
+
NSPrivacyTracking
-
+
NSPrivacyTrackingDomains
NSPrivacyCollectedDataTypes
diff --git a/SunHat/Resources/InfoPlist.xcstrings b/SunHat/Resources/InfoPlist.xcstrings
index 1d9b9d8..a14868b 100644
--- a/SunHat/Resources/InfoPlist.xcstrings
+++ b/SunHat/Resources/InfoPlist.xcstrings
@@ -1,94 +1,112 @@
{
- "sourceLanguage" : "en",
- "strings" : {
- "CFBundleName" : {
- "comment" : "Bundle name",
- "extractionState" : "extracted_with_value",
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "new",
- "value" : "SunHat"
+ "sourceLanguage": "en",
+ "strings": {
+ "CFBundleName": {
+ "comment": "Bundle name",
+ "extractionState": "extracted_with_value",
+ "localizations": {
+ "en": {
+ "stringUnit": {
+ "state": "new",
+ "value": "SunHat"
}
},
- "es" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "SunHat"
+ "es": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "SunHat"
}
}
}
},
- "NSLocationAccuracyAuthorizationDescription" : {
- "comment" : "Privacy - Location Accuracy Usage Description",
- "extractionState" : "manual",
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "SunHat requires precise location to provide accurate weather-based reminders for your exact location."
+ "NSLocationAccuracyAuthorizationDescription": {
+ "comment": "Privacy - Location Accuracy Usage Description",
+ "extractionState": "manual",
+ "localizations": {
+ "en": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "SunHat requires precise location to provide accurate weather-based reminders for your exact location."
}
},
- "es" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "SunHat necesita tu ubicación precisa para ofrecer recordatorios del clima exactos para tu ubicación."
+ "es": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "SunHat necesita tu ubicación precisa para ofrecer recordatorios del clima exactos para tu ubicación."
}
}
}
},
- "NSLocationAlwaysAndWhenInUseUsageDescription" : {
- "comment" : "Privacy - Location Always and When In Use Usage Description",
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "SunHat needs location access to provide weather-based reminders and background updates for your locations."
+ "NSLocationAlwaysAndWhenInUseUsageDescription": {
+ "comment": "Privacy - Location Always and When In Use Usage Description",
+ "localizations": {
+ "en": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "SunHat needs location access to provide weather-based reminders and background updates for your locations."
}
},
- "es" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "SunHat necesita acceso a la ubicación para ofrecer recordatorios del clima y actualizaciones en segundo plano para tus ubicaciones."
+ "es": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "SunHat necesita acceso a la ubicación para ofrecer recordatorios del clima y actualizaciones en segundo plano para tus ubicaciones."
}
}
}
},
- "NSLocationTemporaryUsageDescriptionDictionary.WeatherReminderAccuracy" : {
- "comment" : "Privacy - Location Temporary Usage Description Dictionary - WeatherReminderAccuracy purpose key",
- "extractionState" : "manual",
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "SunHat uses precise location temporarily to match weather forecasts to your current area for accurate reminders."
+ "NSLocationTemporaryUsageDescriptionDictionary.WeatherReminderAccuracy": {
+ "comment": "Privacy - Location Temporary Usage Description Dictionary - WeatherReminderAccuracy purpose key",
+ "extractionState": "manual",
+ "localizations": {
+ "en": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "SunHat uses precise location temporarily to match weather forecasts to your current area for accurate reminders."
}
},
- "es" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "SunHat usa tu ubicación precisa de forma temporal para ajustar los pronósticos del clima a tu área actual y darte recordatorios exactos."
+ "es": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "SunHat usa tu ubicación precisa de forma temporal para ajustar los pronósticos del clima a tu área actual y darte recordatorios exactos."
}
}
}
},
- "NSLocationWhenInUseUsageDescription" : {
- "comment" : "Privacy - Location When In Use Usage Description",
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "SunHat needs location access to provide weather-based reminders for your current area."
+ "NSLocationWhenInUseUsageDescription": {
+ "comment": "Privacy - Location When In Use Usage Description",
+ "localizations": {
+ "en": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "SunHat needs location access to provide weather-based reminders for your current area."
}
},
- "es" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "SunHat necesita acceso a la ubicación para ofrecer recordatorios del clima de tu área actual."
+ "es": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "SunHat necesita acceso a la ubicación para ofrecer recordatorios del clima de tu área actual."
+ }
+ }
+ }
+ },
+ "NSUserTrackingUsageDescription": {
+ "comment": "Privacy - Tracking Usage Description",
+ "extractionState": "manual",
+ "localizations": {
+ "en": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "This allows SunHat to show more relevant ads, which helps keep the free version free."
+ }
+ },
+ "es": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "Esto permite que SunHat muestre anuncios más relevantes, lo que ayuda a mantener gratuita la versión gratuita."
}
}
}
}
},
- "version" : "1.0"
-}
\ No newline at end of file
+ "version": "1.0"
+}
diff --git a/SunHat/Resources/Localizable.xcstrings b/SunHat/Resources/Localizable.xcstrings
index 867c5ec..e98f795 100644
--- a/SunHat/Resources/Localizable.xcstrings
+++ b/SunHat/Resources/Localizable.xcstrings
@@ -1,10895 +1,11613 @@
{
- "sourceLanguage": "en",
- "strings": {
- "": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": ""
+ "sourceLanguage" : "en",
+ "strings" : {
+ "" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : ""
}
}
}
},
- "\n---\nApp Version: %@\nBuild: %@\nDevice: %@\niOS Version: %@": {
- "comment": "Pre-filled body of the feedback email the app composes, listing diagnostic info after the user's own message",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "\n---\nApp Version: %1$@\nBuild: %2$@\nDevice: %3$@\niOS Version: %4$@"
+ "\n---\nApp Version: %@\nBuild: %@\nDevice: %@\niOS Version: %@" : {
+ "comment" : "Pre-filled body of the feedback email the app composes, listing diagnostic info after the user's own message",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "\n---\nApp Version: %1$@\nBuild: %2$@\nDevice: %3$@\niOS Version: %4$@"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "\n---\nVersión de la app: %@\nCompilación: %@\nDispositivo: %@\nVersión de iOS: %@"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "\n---\nVersión de la app: %@\nCompilación: %@\nDispositivo: %@\nVersión de iOS: %@"
}
}
}
},
- "\nLegal Basis: %@\n": {
- "comment": "Line in the privacy-request email template naming the applicable GDPR/CCPA legal basis",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "\nBase legal: %@\n"
+ "\nLegal Basis: %@\n" : {
+ "comment" : "Line in the privacy-request email template naming the applicable GDPR/CCPA legal basis",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "\nBase legal: %@\n"
}
}
}
},
- "%.0f degrees": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%.0f grados"
+ ", %lld percent chance of precipitation" : {
+ "comment" : "Accessibility label suffix noting the chance of precipitation for a weekly forecast row",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : ", %lld por ciento de probabilidad de precipitación"
}
}
}
},
- "%.0f°": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%.0f°"
+ "[Please describe your privacy request]" : {
+ "comment" : "Placeholder in the privacy-request email template when the user left the inquiry field empty",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "[Describe tu solicitud de privacidad]"
}
}
}
},
- "%.1f° then": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%.1f° luego"
+ "[Your email address]" : {
+ "comment" : "Placeholder in the privacy-request email template when the user left the email field empty",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "[Tu dirección de correo]"
}
}
}
},
- "%@": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@"
+ "%.0f degrees" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%.0f grados"
}
}
}
},
- "%@ (%@)": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$@ (%2$@)"
+ "%.0f°" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%.0f°"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@ (%@)"
+ }
+ }
+ },
+ "%.1f° then" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%.1f° luego"
+ }
+ }
+ }
+ },
+ "%@" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@"
}
}
}
},
- "%@ - %@": {
- "comment": "Quiet hours time range, e.g. '10:00 PM - 7:00 AM'\nQuiet hours time range, e.g. 10:00 PM - 7:00 AM",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$@ - %2$@"
+ "%@ - %@" : {
+ "comment" : "Quiet hours time range, e.g. '10:00 PM - 7:00 AM'\nQuiet hours time range, e.g. 10:00 PM - 7:00 AM",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$@ - %2$@"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@ - %@"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ - %@"
}
}
}
},
- "%@ and %@, ideal for your %@ reminder.": {
- "comment": "Full preview notification body combining temperature and sky condition",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$@ and %2$@, ideal for your %3$@ reminder."
+ "%@ (%@)" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$@ (%2$@)"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@ y %@, ideal para tu recordatorio de %@."
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ (%@)"
+ }
+ }
+ }
+ },
+ "%@ • Steady" : {
+ "comment" : "Wind detail line: cardinal direction and steady (no gusts)",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ • Constante"
}
}
}
},
- "%@ confidence, %lld of %lld days match": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$@ confidence, %2$lld of %3$lld days match"
+ "%@ and %@, ideal for your %@ reminder." : {
+ "comment" : "Full preview notification body combining temperature and sky condition",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$@ and %2$@, ideal for your %3$@ reminder."
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "confianza %@, %lld de %lld días coinciden"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ y %@, ideal para tu recordatorio de %@."
}
}
}
},
- "%@ hPa": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@ hPa"
+ "%@ confidence, %lld of %lld days match" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$@ confidence, %2$lld of %3$lld days match"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "confianza %@, %lld de %lld días coinciden"
}
}
}
},
- "%@ inHg": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@ inHg"
+ "%@ hPa" : {
+ "comment" : "Barometric pressure in hectopascals",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ hPa"
}
}
}
},
- "%@ km": {
- "comment": "Visibility distance in kilometers",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@ km"
+ "%@ inHg" : {
+ "comment" : "Barometric pressure in inches of mercury",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ inHg"
}
}
}
},
- "%@ km/h": {
- "comment": "Wind speed in kilometers per hour",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@ km/h"
+ "%@ km" : {
+ "comment" : "Visibility distance in kilometers",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ km"
}
}
}
},
- "%@ mi": {
- "comment": "Visibility distance in miles",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@ mi"
+ "%@ km/h" : {
+ "comment" : "Wind speed in kilometers per hour",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ km/h"
}
}
}
},
- "%@ mph": {
- "comment": "Wind speed in miles per hour",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@ mph"
+ "%@ mi" : {
+ "comment" : "Visibility distance in miles",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ mi"
}
}
}
},
- "%@ service is temporarily unavailable": {
- "comment": "Weather error naming the specific provider (e.g. Apple WeatherKit) that is down",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "El servicio de %@ no está disponible temporalmente"
+ "%@ mph" : {
+ "comment" : "Wind speed in miles per hour",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ mph"
}
}
}
},
- "%@ temperature": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Temperatura %@"
+ "%@ service is temporarily unavailable" : {
+ "comment" : "Weather error naming the specific provider (e.g. Apple WeatherKit) that is down",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "El servicio de %@ no está disponible temporalmente"
}
}
}
},
- "%@ time": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Hora de %@"
+ "%@ temperature" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Temperatura %@"
}
}
}
},
- "%@ to trigger %@": {
- "comment": "Live prediction description, e.g. 'Very likely to trigger in 2 days'",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$@ to trigger %2$@"
- }
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@, se activa %@"
+ "%@ time" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hora de %@"
}
}
}
},
- "%@ • Steady": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@ • Constante"
+ "%@ to trigger %@" : {
+ "comment" : "Live prediction description, e.g. 'Very likely to trigger in 2 days'",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$@ to trigger %2$@"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@, se activa %@"
}
}
}
},
- "%@%.0f°": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$@%2$.0f°"
+ "%@, %@" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$@, %2$@"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@%.0f°"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@, %@"
}
}
}
},
- "%@, %@": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$@, %2$@"
+ "%@, %@, %@" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$@, %2$@, %3$@"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@, %@"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@, %@, %@"
}
}
}
},
- "%@, %@, %@": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$@, %2$@, %3$@"
+ "%@, %@, high %lld degrees, low %lld degrees" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$@, %2$@, high %3$lld degrees, low %4$lld degrees"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@, %@, %@"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@, %@, máxima %lld grados, mínima %lld grados"
}
}
}
},
- "%@, %@, high %lld degrees, low %lld degrees": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$@, %2$@, high %3$lld degrees, low %4$lld degrees"
+ "%@: %@, %@" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$@: %2$@, %3$@"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@, %@, máxima %lld grados, mínima %lld grados"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@: %@, %@"
}
}
}
},
- "%@: %@, %@": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$@: %2$@, %3$@"
+ "%@: %@, high %lld, low %lld degrees" : {
+ "comment" : "Accessibility label for a weekly forecast row, e.g. 'Monday: Sunny, high 75, low 60 degrees'",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$@: %2$@, high %3$lld, low %4$lld degrees"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@: %@, %@"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@: %@, máxima %lld, mínima %lld grados"
}
}
}
},
- "%@: %@, high %lld, low %lld degrees": {
- "comment": "Accessibility label for a weekly forecast row, e.g. 'Monday: Sunny, high 75, low 60 degrees'",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$@: %2$@, high %3$lld, low %4$lld degrees"
+ "%@: current temperature is %@ than %@" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$@: current temperature is %2$@ than %3$@"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@: %@, máxima %lld, mínima %lld grados"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@: la temperatura actual es %@ que %@"
}
}
}
},
- "%@: current temperature is %@ than %@": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$@: current temperature is %2$@ than %3$@"
+ "%@%.0f°" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$@%2$.0f°"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@: la temperatura actual es %@ que %@"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@%.0f°"
}
}
}
},
- "%@°": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@°"
+ "%@°" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@°"
}
}
}
},
- "%@° cooler": {
- "comment": "Feels-like temperature is cooler than the actual temperature",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@° más fresco"
+ "%@° cooler" : {
+ "comment" : "Feels-like temperature is cooler than the actual temperature",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@° más fresco"
}
}
}
},
- "%@° warmer": {
- "comment": "Feels-like temperature is warmer than the actual temperature",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%@° más cálido"
+ "%@° warmer" : {
+ "comment" : "Feels-like temperature is warmer than the actual temperature",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@° más cálido"
}
}
}
},
- "%lld": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%lld"
+ "%lld" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld"
}
}
}
},
- "%lld active task%@": {
- "comment": "Dashboard subtitle showing the count of active weather tasks being watched",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$lld active task%2$@"
+ "%lld active task%@" : {
+ "comment" : "Dashboard subtitle showing the count of active weather tasks being watched",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$lld active task%2$@"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%lld tarea%@ en curso"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld tarea%@ en curso"
}
}
}
},
- "%lld degrees Fahrenheit": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%lld grados Fahrenheit"
+ "%lld degrees Fahrenheit" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld grados Fahrenheit"
}
}
}
},
- "%lld found": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%lld encontrados"
+ "%lld found" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld encontrados"
}
}
}
},
- "%lld hours": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%lld horas"
+ "%lld hours" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld horas"
}
}
}
},
- "%lld of %lld days": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$lld of %2$lld days"
+ "%lld of %lld days" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$lld of %2$lld days"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%lld de %lld días"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld de %lld días"
}
}
}
},
- "%lld watching • %lld paused": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$lld watching • %2$lld paused"
+ "%lld watching • %lld paused" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$lld watching • %2$lld paused"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%lld en seguimiento • %lld en pausa"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld en seguimiento • %lld en pausa"
+ }
+ }
+ }
+ },
+ "%lld%%" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld %%"
}
}
}
},
- "%lld%%": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%lld %%"
+ "%lld°" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld°"
}
}
}
},
- "%lldh %lldm daylight": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$lldh %2$lldm daylight"
+ "%lld° - %lld°" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$lld° - %2$lld°"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%lldh %lldm de luz solar"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld° - %lld°"
}
}
}
},
- "%lld°": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%lld°"
+ "%lld° - %lld°F" : {
+ "comment" : "Temperature range shown for a reminder, e.g. '65° - 75°F'",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$lld° - %2$lld°F"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld° - %lld°F"
}
}
}
},
- "%lld° - %lld°": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$lld° - %2$lld°"
+ "%lld°%@" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$lld°%2$@"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%lld° - %lld°"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld°%@"
+ }
+ }
+ }
+ },
+ "%lld°F" : {
+ "comment" : "Exact target temperature shown for a reminder, e.g. '70°F'",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld°F"
}
}
}
},
- "%lld° - %lld°F": {
- "comment": "Temperature range shown for a reminder, e.g. '65° - 75°F'",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$lld° - %2$lld°F"
+ "%lldh %lldm daylight" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$lldh %2$lldm daylight"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%lld° - %lld°F"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lldh %lldm de luz solar"
}
}
}
},
- "%lld°%@": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "%1$lld°%2$@"
+ "°" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "°"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%lld°%@"
+ }
+ }
+ },
+ "© 2026 SunHat" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "© 2026 SunHat"
}
}
}
},
- "%lld°F": {
- "comment": "Exact target temperature shown for a reminder, e.g. '70°F'",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "%lld°F"
+ "© 2026 SunHat. All rights reserved." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "© 2026 SunHat. Todos los derechos reservados."
}
}
}
},
- ", %lld percent chance of precipitation": {
- "comment": "Accessibility label suffix noting the chance of precipitation for a weekly forecast row",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": ", %lld por ciento de probabilidad de precipitación"
+ "±%lld°" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "±%lld°"
}
}
}
},
- "1 day before": {
- "comment": "Notification timing option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "1 día antes"
+ "1 day before" : {
+ "comment" : "Notification timing option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "1 día antes"
}
}
}
},
- "1 hour before": {
- "comment": "Notification timing option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "1 hora antes"
+ "1 hour before" : {
+ "comment" : "Notification timing option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "1 hora antes"
}
}
}
},
- "100°F": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "100°F"
+ "2 hours before" : {
+ "comment" : "Notification timing option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "2 horas antes"
}
}
}
},
- "12 hours before": {
- "comment": "Notification timing option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "12 horas antes"
+ "4 hours before" : {
+ "comment" : "Notification timing option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "4 horas antes"
}
}
}
},
- "15 minutes before": {
- "comment": "Notification timing option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "15 minutos antes"
+ "6 hours before" : {
+ "comment" : "Notification timing option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "6 horas antes"
}
}
}
},
- "2 hours before": {
- "comment": "Notification timing option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "2 horas antes"
+ "7 Days" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "7 días"
}
}
}
},
- "24 Hours": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "24 horas"
+ "12 hours before" : {
+ "comment" : "Notification timing option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "12 horas antes"
}
}
}
},
- "30 minutes before": {
- "comment": "Notification timing option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "30 minutos antes"
+ "15 minutes before" : {
+ "comment" : "Notification timing option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "15 minutos antes"
}
}
}
},
- "32°F": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "32°F"
+ "24 Hours" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "24 horas"
}
}
}
},
- "4 hours before": {
- "comment": "Notification timing option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "4 horas antes"
+ "30 minutes before" : {
+ "comment" : "Notification timing option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "30 minutos antes"
}
}
}
},
- "6 hours before": {
- "comment": "Notification timing option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "6 horas antes"
+ "32°F" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "32°F"
}
}
}
},
- "7 Days": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "7 días"
+ "100°F" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "100°F"
}
}
}
},
- "A quick answer helps us decide whether to ask for a review or collect feedback.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Una respuesta rápida nos ayuda a decidir si pedirte una reseña o recopilar tus comentarios."
+ "A quick answer helps us decide whether to ask for a review or collect feedback." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Una respuesta rápida nos ayuda a decidir si pedirte una reseña o recopilar tus comentarios."
}
}
}
},
- "A timely heads-up": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Un aviso oportuno"
+ "A renewal payment failed. Ads stay off while Apple retries — update your billing details to keep Ad-Free." : {
+ "comment" : "Settings footer during the billing grace period",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "A renewal payment failed. Ads stay off while Apple retries — update your billing details to keep Ad-Free."
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Falló un pago de renovación. Los anuncios seguirán desactivados mientras Apple lo reintenta; actualiza tus datos de pago para conservar la versión sin anuncios."
}
}
}
},
- "API quota exceeded for this period": {
- "comment": "Weather error when the provider's usage quota has been used up",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Se superó la cuota de la API para este período"
+ "A timely heads-up" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Un aviso oportuno"
}
}
}
},
- "AQI": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "ICA"
+ "About" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Acerca de"
}
}
}
},
- "About": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Acerca de"
+ "About SunHat" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Acerca de SunHat"
}
}
}
},
- "About SunHat": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Acerca de SunHat"
+ "above" : {
+ "comment" : "Temperature comparison word, e.g. 'temperature is above 70°'",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "por encima de"
}
}
}
},
- "Access My Data": {
- "comment": "Privacy inquiry type",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Acceder a mis datos"
+ "Access My Data" : {
+ "comment" : "Privacy inquiry type",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Acceder a mis datos"
}
}
}
},
- "Acknowledge": {
- "comment": "Notification action button",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Confirmar"
+ "Acknowledge" : {
+ "comment" : "Notification action button",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Confirmar"
}
}
}
},
- "Acknowledgments": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Agradecimientos"
+ "Acknowledgments" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Agradecimientos"
}
}
}
},
- "Active": {
- "comment": "Accessibility label clause: the reminder is active\nReminder list filter: show only active reminders\nReminder status",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Activo"
+ "Active" : {
+ "comment" : "Accessibility label clause: the reminder is active\nReminder list filter: show only active reminders\nReminder status",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Activo"
}
}
}
},
- "Activity": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Actividad"
+ "Activity" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Actividad"
}
}
}
},
- "Add Location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Agregar ubicación"
+ "Ad Privacy Options" : {
+ "comment" : "Settings row that opens Google's ad consent privacy options form",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ad Privacy Options"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Opciones de privacidad de anuncios"
}
}
}
},
- "Add a location or enable Location Services to load accurate weather for this reminder.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Añade una ubicación o activa los Servicios de Localización para cargar el clima exacto de este recordatorio."
+ "Ad-Free" : {
+ "comment" : "Fallback plan name when the specific Ad-Free plan is unknown",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ad-Free"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sin anuncios"
}
}
}
},
- "Add to Favorites": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Agregar a favoritos"
+ "Add a location or enable Location Services to load accurate weather for this reminder." : {
+ "comment" : "Error shown on the reminder detail screen when no location is available for a weather load",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Añade una ubicación o activa los Servicios de Localización para cargar el clima exacto de este recordatorio."
}
}
}
},
- "Advisory": {
- "comment": "Weather alert severity level",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Aviso"
+ "Add Location" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Agregar ubicación"
}
}
}
},
- "Afternoon": {
- "comment": "Time-of-day preference option label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tarde"
+ "Add to Favorites" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Agregar a favoritos"
}
}
}
},
- "Air quality index: %lld, %@": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Air quality index: %1$lld, %2$@"
+ "Ads are off. Cancel anytime — you keep Ad-Free until the end of the period you already paid for." : {
+ "comment" : "Settings footer while the Ad-Free subscription is active",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ads are off. Cancel anytime — you keep Ad-Free until the end of the period you already paid for."
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Índice de calidad del aire: %lld, %@"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Los anuncios están desactivados. Cancela cuando quieras: conservarás la versión sin anuncios hasta el final del periodo que ya pagaste."
}
}
}
},
- "Alert": {
- "comment": "Notification sound option: alert\nVibration pattern option: urgent alert pattern",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Alerta"
+ "Advertisement" : {
+ "comment" : "Accessibility label for the banner advertisement",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Anuncio"
}
}
}
},
- "All": {
- "comment": "Reminder list filter: show all reminders",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Todos"
+ "Advertising" : { "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Publicidad"
}
}
}
+
+
},
- "All Day": {
- "comment": "Time-of-day preference option label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Todo el día"
+ "Advisory" : {
+ "comment" : "Weather alert severity level",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aviso"
}
}
}
},
- "All composite conditions met: %@": {
- "comment": "Notification body summarizing every satisfied condition of a multi-condition reminder; metList is a comma-separated list of already-localized clauses like 'temperature 75° above target 70°'",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Se cumplieron todas las condiciones combinadas: %@"
+ "Afternoon" : {
+ "comment" : "Time-of-day preference option label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tarde"
}
}
}
},
- "All data has been successfully deleted": {
- "comment": "Data & Privacy screen confirmation after delete-all completes",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Todos los datos se eliminaron correctamente"
+ "Air quality index: %lld, %@" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Air quality index: %1$lld, %2$@"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Índice de calidad del aire: %lld, %@"
}
}
}
},
- "All weather conditions are normal.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Todas las condiciones climáticas son normales."
+ "Alert" : {
+ "comment" : "Notification sound option: alert\nVibration pattern option: urgent alert pattern",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Alerta"
}
}
}
},
- "All weather services are currently unavailable": {
- "comment": "Weather error when every configured provider failed",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Todos los servicios meteorológicos no están disponibles en este momento"
+ "All" : {
+ "comment" : "Reminder list filter: show all reminders",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Todos"
}
}
}
},
- "Allow Location Access": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Permitir acceso a la ubicación"
+ "All composite conditions met: %@" : {
+ "comment" : "Notification body summarizing every satisfied condition of a multi-condition reminder; metList is a comma-separated list of already-localized clauses like 'temperature 75° above target 70°'",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Se cumplieron todas las condiciones combinadas: %@"
}
}
}
},
- "Allow location access in Settings for local forecasts, or enter a city manually.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Permite el acceso a la ubicación en Ajustes para pronósticos locales, o escribe una ciudad manualmente."
+ "All data has been successfully deleted" : {
+ "comment" : "Data & Privacy screen confirmation after delete-all completes",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Todos los datos se eliminaron correctamente"
}
}
}
},
- "Allow notifications in Settings to receive weather reminders, or continue without them.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Permite las notificaciones en Ajustes para recibir recordatorios del clima, o continúa sin ellas."
+ "All Day" : {
+ "comment" : "Time-of-day preference option label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Todo el día"
}
}
}
},
- "Allow notifications so SunHat can tell you when the weather matches one of your tasks. You can change this later in Settings.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Permite las notificaciones para que SunHat te avise cuando el clima coincida con uno de tus recordatorios. Puedes cambiar esto luego en Ajustes."
+ "All weather conditions are normal." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Todas las condiciones climáticas son normales."
}
}
}
},
- "An unexpected error occurred": {
- "comment": "Location error failure reason fallback",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ocurrió un error inesperado"
+ "All weather services are currently unavailable" : {
+ "comment" : "Weather error when every configured provider failed",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Todos los servicios meteorológicos no están disponibles en este momento"
}
}
}
},
- "Analytics": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Análisis"
+ "Allow Location Access" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Permitir acceso a la ubicación"
}
}
}
},
- "Any weather": {
- "comment": "Sky condition summary when no specific sky conditions are selected",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cualquier clima"
+ "Allow location access in Settings for local forecasts, or enter a city manually." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Permite el acceso a la ubicación en Ajustes para pronósticos locales, o escribe una ciudad manualmente."
}
}
}
},
- "Apple WeatherKit": {
- "comment": "Weather data provider name; 'WeatherKit' is an Apple product name and should not be translated",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Apple WeatherKit"
+ "Allow notifications in Settings to receive weather reminders, or continue without them." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Permite las notificaciones en Ajustes para recibir recordatorios del clima, o continúa sin ellas."
}
}
}
},
- "At %@: %@ degrees, %@": {
- "comment": "VoiceOver label for one hour in the hourly forecast",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "At %1$@: %2$@ degrees, %3$@"
+ "Allow notifications so SunHat can tell you when the weather matches one of your tasks. You can change this later in Settings." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Permite las notificaciones para que SunHat te avise cuando el clima coincida con uno de tus recordatorios. Puedes cambiar esto luego en Ajustes."
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "A las %@: %@ grados, %@"
+ }
+ }
+ },
+ "An unexpected error occurred" : {
+ "comment" : "Location error failure reason fallback",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ocurrió un error inesperado"
+ }
+ }
+ }
+ },
+ "Analytics" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Análisis"
}
}
}
},
- "At %@: %@ degrees, %@, %lld percent chance of precipitation": {
- "comment": "VoiceOver label for one hour in the hourly forecast, including precipitation chance",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "At %1$@: %2$@ degrees, %3$@, %4$lld percent chance of precipitation"
+ "Annual" : {
+ "comment" : "Name of the annual Ad-Free plan shown in Settings",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Annual"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "A las %@: %@ grados, %@, %lld por ciento de probabilidad de precipitación"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Anual"
}
}
}
},
- "Average": {
- "comment": "Trigger type name: average temperature",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Promedio"
+ "Any weather" : {
+ "comment" : "Sky condition summary when no specific sky conditions are selected",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cualquier clima"
}
}
}
},
- "Average temperature %@° over %lld days differs from target by %@°": {
- "comment": "Notification body: average temperature over N days misses the target",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Average temperature %1$@° over %2$lld days differs from target by %3$@°"
+ "API quota exceeded for this period" : {
+ "comment" : "Weather error when the provider's usage quota has been used up",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Se superó la cuota de la API para este período"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura promedio de %@° en %lld días difiere del objetivo en %@°"
+ }
+ }
+ },
+ "Apple WeatherKit" : {
+ "comment" : "Weather data provider name; 'WeatherKit' is an Apple product name and should not be translated",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Apple WeatherKit"
+ }
+ }
+ }
+ },
+ "AQI" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "ICA"
}
}
}
},
- "Average temperature %@° over %lld days is above target %@°": {
- "comment": "Notification body: average temperature over N days exceeded the target",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Average temperature %1$@° over %2$lld days is above target %3$@°"
+ "At %@: %@ degrees, %@" : {
+ "comment" : "VoiceOver label for one hour in the hourly forecast",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "At %1$@: %2$@ degrees, %3$@"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura promedio de %@° en %lld días está por encima del objetivo de %@°"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "A las %@: %@ grados, %@"
}
}
}
},
- "Average temperature %@° over %lld days is below target %@°": {
- "comment": "Notification body: average temperature over N days is below the target",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Average temperature %1$@° over %2$lld days is below target %3$@°"
+ "At %@: %@ degrees, %@, %lld percent chance of precipitation" : {
+ "comment" : "VoiceOver label for one hour in the hourly forecast, including precipitation chance",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "At %1$@: %2$@ degrees, %3$@, %4$lld percent chance of precipitation"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura promedio de %@° en %lld días está por debajo del objetivo de %@°"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "A las %@: %@ grados, %@, %lld por ciento de probabilidad de precipitación"
+ }
+ }
+ }
+ },
+ "Average" : {
+ "comment" : "Trigger type name: average temperature",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Promedio"
}
}
}
},
- "Average temperature %@° over %lld days is outside range %@° - %@°": {
- "comment": "Notification body: average temperature over N days falls outside the configured range",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Average temperature %1$@° over %2$lld days is outside range %3$@° - %4$@°"
+ "average %lld° near %lld°" : {
+ "comment" : "Short clause describing a forecast day's average temperature versus target, used inside a comma-separated list",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "average %1$lld° near %2$lld°"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura promedio de %@° en %lld días está fuera del rango %@° - %@°"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "promedio %lld° cerca de %lld°"
}
}
}
},
- "Average temperature %@° over %lld days is within range %@° - %@°": {
- "comment": "Notification body: average temperature over N days falls inside the configured range",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Average temperature %1$@° over %2$lld days is within range %3$@° - %4$@°"
+ "Average temperature %@° over %lld days differs from target by %@°" : {
+ "comment" : "Notification body: average temperature over N days misses the target",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Average temperature %1$@° over %2$lld days differs from target by %3$@°"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura promedio de %@° en %lld días está dentro del rango %@° - %@°"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura promedio de %@° en %lld días difiere del objetivo en %@°"
}
}
}
},
- "Average temperature %@° over %lld days matches target %@° (±%@°)": {
- "comment": "Notification body: average temperature over N days matches the target within tolerance",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Average temperature %1$@° over %2$lld days matches target %3$@° (±%4$@°)"
+ "Average temperature %@° over %lld days is above target %@°" : {
+ "comment" : "Notification body: average temperature over N days exceeded the target",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Average temperature %1$@° over %2$lld days is above target %3$@°"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura promedio de %@° en %lld días coincide con el objetivo de %@° (±%@°)"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura promedio de %@° en %lld días está por encima del objetivo de %@°"
}
}
}
},
- "Avoid notifications during sleep": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Evita notificaciones durante el sueño"
+ "Average temperature %@° over %lld days is below target %@°" : {
+ "comment" : "Notification body: average temperature over N days is below the target",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Average temperature %1$@° over %2$lld days is below target %3$@°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura promedio de %@° en %lld días está por debajo del objetivo de %@°"
}
}
}
},
- "Back": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Atrás"
+ "Average temperature %@° over %lld days is outside range %@° - %@°" : {
+ "comment" : "Notification body: average temperature over N days falls outside the configured range",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Average temperature %1$@° over %2$lld days is outside range %3$@° - %4$@°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura promedio de %@° en %lld días está fuera del rango %@° - %@°"
}
}
}
},
- "Background refresh is disabled": {
- "comment": "Weather error when background updates are turned off",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La actualización en segundo plano está desactivada"
+ "Average temperature %@° over %lld days is within range %@° - %@°" : {
+ "comment" : "Notification body: average temperature over N days falls inside the configured range",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Average temperature %1$@° over %2$lld days is within range %3$@° - %4$@°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura promedio de %@° en %lld días está dentro del rango %@° - %@°"
}
}
}
},
- "Badge": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Insignia"
+ "Average temperature %@° over %lld days matches target %@° (±%@°)" : {
+ "comment" : "Notification body: average temperature over N days matches the target within tolerance",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Average temperature %1$@° over %2$lld days matches target %3$@° (±%4$@°)"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura promedio de %@° en %lld días coincide con el objetivo de %@° (±%@°)"
}
}
}
},
- "Begin setting up weather-based reminders": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Comienza a configurar recordatorios basados en el clima"
+ "Avoid notifications during sleep" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Evita notificaciones durante el sueño"
}
}
}
},
- "Bell": {
- "comment": "Notification sound option: bell",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Campana"
+ "Back" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Atrás"
}
}
}
},
- "Bike rides and cycling adventures": {
- "comment": "Description of cycling activity interest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Paseos en bicicleta y aventuras de ciclismo"
+ "Background refresh is disabled" : {
+ "comment" : "Weather error when background updates are turned off",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La actualización en segundo plano está desactivada"
}
}
}
},
- "Bird": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Pájaro"
+ "Badge" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Insignia"
}
}
}
},
- "Blue": {
- "comment": "Reminder color swatch name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Azul"
+ "Begin setting up weather-based reminders" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Comienza a configurar recordatorios basados en el clima"
}
}
}
},
- "Brown": {
- "comment": "Reminder color swatch name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Marrón"
+ "Bell" : {
+ "comment" : "Notification sound option: bell",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Campana"
}
}
}
},
- "Built With": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Creado con"
+ "below" : {
+ "comment" : "Temperature comparison word, e.g. 'temperature is below 70°'",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "por debajo de"
}
}
}
},
- "Bus": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Autobús"
+ "between" : {
+ "comment" : "Temperature comparison word, e.g. 'temperature is between 60° and 70°'",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "entre"
}
}
}
},
- "Cached weather data has expired": {
- "comment": "Weather error when only stale cached data is available",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Los datos meteorológicos en caché han caducado"
+ "Bike rides and cycling adventures" : {
+ "comment" : "Description of cycling activity interest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Paseos en bicicleta y aventuras de ciclismo"
}
}
}
},
- "Calculating prediction...": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Calculando predicción..."
+ "Billing Issue Found" : {
+ "comment" : "Alert title after a restore that found a subscription paused by a failed payment",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Billing Issue Found"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Problema de pago detectado"
}
}
}
},
- "Can I set reminders for multiple locations?": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¿Puedo crear recordatorios para varias ubicaciones?"
+ "Bird" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pájaro"
}
}
}
},
- "Can I use the app without location access?": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¿Puedo usar la app sin acceso a la ubicación?"
+ "Blue" : {
+ "comment" : "Reminder color swatch name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Azul"
}
}
}
},
- "Cancel": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cancelar"
+ "Brown" : {
+ "comment" : "Reminder color swatch name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Marrón"
}
}
}
},
- "Car Washing": {
- "comment": "Activity interest option: car washing",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Lavado de auto"
+ "Built With" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Creado con"
}
}
}
},
- "Cat": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Gato"
+ "Bus" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Autobús"
}
}
}
},
- "Celsius": {
- "comment": "Temperature unit name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Celsius"
+ "Cached weather data has expired" : {
+ "comment" : "Weather error when only stale cached data is available",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Los datos meteorológicos en caché han caducado"
}
}
}
},
- "Check Today's Weather Reminders": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Consultar los recordatorios del clima de hoy"
+ "Calculating prediction..." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Calculando predicción..."
}
}
}
},
- "Check your internet connection and try again": {
- "comment": "Location error recovery suggestion\nWeather error failure reason",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Verifica tu conexión a internet e inténtalo de nuevo"
+ "Can I set reminders for multiple locations?" : {
+ "comment" : "FAQ question",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¿Puedo crear recordatorios para varias ubicaciones?"
}
}
}
},
- "Chime": {
- "comment": "Notification sound option: chime",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Timbre"
+ "Can I use the app without location access?" : {
+ "comment" : "FAQ question",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¿Puedo usar la app sin acceso a la ubicación?"
}
}
}
},
- "Choose City Manually…": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Elegir ciudad manualmente…"
+ "Cancel" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cancelar"
}
}
}
},
- "Choose an Icon": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Elegir un ícono"
+ "Car Washing" : {
+ "comment" : "Activity interest option: car washing",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Lavado de auto"
}
}
}
},
- "Choose how you'd like to contact us about your privacy inquiry.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Elige cómo te gustaría contactarnos sobre tu solicitud de privacidad."
+ "Cat" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gato"
}
}
}
},
- "Choose where SunHat checks the weather": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Elige dónde consulta SunHat el clima"
+ "Celsius" : {
+ "comment" : "Temperature unit name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Celsius"
}
}
}
},
- "Choose your temperature unit": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Elige tu unidad de temperatura"
+ "Check Today's Weather Reminders" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Consultar los recordatorios del clima de hoy"
}
}
}
},
- "City name (e.g., San Francisco)": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Nombre de la ciudad (p. ej., San Francisco)"
+ "Check your internet connection and try again" : {
+ "comment" : "Location error recovery suggestion\nWeather error failure reason",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Verifica tu conexión a internet e inténtalo de nuevo"
}
}
}
},
- "City search field": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Campo de búsqueda de ciudad"
+ "Chime" : {
+ "comment" : "Notification sound option: chime",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Timbre"
}
}
}
},
- "City, neighborhood, or place": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ciudad, barrio o lugar"
+ "Choose an Icon" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Elegir un ícono"
}
}
}
},
- "Cleaning": {
- "comment": "Activity interest option: cleaning",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Limpieza"
+ "Choose City Manually…" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Elegir ciudad manualmente…"
}
}
}
},
- "Clear": {
- "comment": "Placeholder weather condition shown before weather data has loaded\nWeather condition name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Despejado"
+ "Choose how you'd like to contact us about your privacy inquiry." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Elige cómo te gustaría contactarnos sobre tu solicitud de privacidad."
}
}
}
},
- "Clear search": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Borrar búsqueda"
+ "Choose where SunHat checks the weather" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Elige dónde consulta SunHat el clima"
}
}
}
},
- "Cloudy": {
- "comment": "Sky condition option label\nWeather condition name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Nublado"
+ "Choose your temperature unit" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Elige tu unidad de temperatura"
}
}
}
},
- "Coffee": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Café"
+ "City name (e.g., San Francisco)" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Nombre de la ciudad (p. ej., San Francisco)"
}
}
}
},
- "Color": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Color"
+ "City search field" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Campo de búsqueda de ciudad"
}
}
}
},
- "Comfortable": {
- "comment": "Dew point comfort description\nHumidity level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Agradable"
+ "City, neighborhood, or place" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ciudad, barrio o lugar"
}
}
}
},
- "Compare to past years": {
- "comment": "Trigger type description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Comparar con años anteriores"
+ "Cleaning" : {
+ "comment" : "Activity interest option: cleaning",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Limpieza"
}
}
}
},
- "Comparison": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Comparación"
+ "Clear" : {
+ "comment" : "Placeholder weather condition shown before weather data has loaded\nWeather condition name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Despejado"
}
}
}
},
- "Completed": {
- "comment": "Reminder history action label\nReminder status",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Completado"
+ "Clear search" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Borrar búsqueda"
}
}
}
},
- "Complex": {
- "comment": "Trigger type name: multiple combined conditions",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Complejo"
+ "Cloudy" : {
+ "comment" : "Sky condition option label\nWeather condition name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Nublado"
}
}
}
},
- "Composite conditions not fully met. Met: [%@]. Failed: [%@]": {
- "comment": "Notification body summarizing which conditions of a multi-condition reminder passed and which failed; metList/failedList are comma-separated lists of already-localized clauses",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Composite conditions not fully met. Met: [%1$@]. Failed: [%2$@]"
- }
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se cumplieron todas las condiciones combinadas. Cumplidas: [%@]. No cumplidas: [%@]"
+ "Coffee" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Café"
}
}
}
},
- "Computer": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Computadora"
+ "Color" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Color"
}
}
}
},
- "Condition Met": {
- "comment": "Reminder history action label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Condición cumplida"
+ "Comfortable" : {
+ "comment" : "Dew point comfort description\nHumidity level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Agradable"
}
}
}
},
- "Condition Mode": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Modo de condición"
+ "Compare to past years" : {
+ "comment" : "Trigger type description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Comparar con años anteriores"
}
}
}
},
- "Condition No Longer Met": {
- "comment": "Reminder history action label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La condición ya no se cumple"
+ "Comparison" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Comparación"
}
}
}
},
- "Condition Type": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tipo de condición"
+ "Completed" : {
+ "comment" : "Reminder history action label\nReminder status",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Completado"
}
}
}
},
- "Conditions met!": {
- "comment": "Reminder detail status when the trigger condition currently matches",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¡Condiciones cumplidas!"
+ "Complex" : {
+ "comment" : "Trigger type name: multiple combined conditions",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Complejo"
}
}
}
},
- "Connect to Wi-Fi or cellular data and try again": {
- "comment": "Weather error recovery suggestion",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Conéctate a Wi-Fi o datos móviles e inténtalo de nuevo"
+ "Composite conditions not fully met. Met: [%@]. Failed: [%@]" : {
+ "comment" : "Notification body summarizing which conditions of a multi-condition reminder passed and which failed; metList/failedList are comma-separated lists of already-localized clauses",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Composite conditions not fully met. Met: [%1$@]. Failed: [%2$@]"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se cumplieron todas las condiciones combinadas. Cumplidas: [%@]. No cumplidas: [%@]"
}
}
}
},
- "Contact About Privacy": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Contactar sobre privacidad"
+ "Computer" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Computadora"
}
}
}
},
- "Contact Support": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Contactar soporte"
+ "Condition Met" : {
+ "comment" : "Reminder history action label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Condición cumplida"
}
}
}
},
- "Contact Us": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Contáctanos"
+ "Condition Mode" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Modo de condición"
}
}
}
},
- "Contact support to resolve API configuration": {
- "comment": "Weather error failure reason",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Contacta a soporte para resolver la configuración de la API"
+ "Condition No Longer Met" : {
+ "comment" : "Reminder history action label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La condición ya no se cumple"
}
}
}
},
- "Continue Without Notifications": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Continuar sin notificaciones"
+ "Condition Type" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tipo de condición"
}
}
}
},
- "Cooldown Period": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Período de espera"
+ "Conditions met!" : {
+ "comment" : "Reminder detail status when the trigger condition currently matches",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¡Condiciones cumplidas!"
}
}
}
},
- "Cooldown:": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Espera:"
+ "Connect to Wi-Fi or cellular data and try again" : {
+ "comment" : "Weather error recovery suggestion",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Conéctate a Wi-Fi o datos móviles e inténtalo de nuevo"
}
}
}
},
- "Copied": {
- "comment": "Alert title after copying the email template to the clipboard",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Copiado"
+ "Contact About Privacy" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Contactar sobre privacidad"
}
}
}
},
- "Copy Email Template": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Copiar plantilla de correo"
+ "Contact Support" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Contactar soporte"
}
}
}
},
- "Copy with Different Temperature": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Copiar con otra temperatura"
+ "Contact support to resolve API configuration" : {
+ "comment" : "Weather error failure reason",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Contacta a soporte para resolver la configuración de la API"
}
}
}
},
- "Copy with New Location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Copiar con nueva ubicación"
+ "Contact Us" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Contáctanos"
}
}
}
},
- "Correct My Data": {
- "comment": "Privacy inquiry type",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Corregir mis datos"
+ "Continue Without Notifications" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Continuar sin notificaciones"
}
}
}
},
- "Couldn't Complete Request": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo completar la solicitud"
+ "Cooldown Period" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Período de espera"
}
}
}
},
- "Couldn't Delete Task": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo eliminar la tarea"
+ "Cooldown:" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Espera:"
}
}
}
},
- "Couldn't Open": {
- "comment": "Alert title when opening the privacy policy link fails",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo abrir"
+ "Copied" : {
+ "comment" : "Alert title after copying the email template to the clipboard",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Copiado"
}
}
}
},
- "Couldn't Open Mail": {
- "comment": "Alert title",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo abrir Mail"
+ "Copy Email Template" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Copiar plantilla de correo"
}
}
}
},
- "Couldn't Save": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo guardar"
+ "Copy with Different Temperature" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Copiar con otra temperatura"
}
}
}
},
- "Couldn't open Mail. Set up a mail account, or email %@ directly.": {
- "comment": "Error shown when deep-linking to the Mail app fails",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo abrir Mail. Configura una cuenta de correo o escribe directamente a %@."
+ "Copy with New Location" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Copiar con nueva ubicación"
}
}
}
},
- "Couldn't open Settings. Open the Settings app manually to change SunHat's notification permissions.": {
- "comment": "Error shown when deep-linking to the iOS Settings app fails",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo abrir Ajustes. Abre la app Ajustes manualmente para cambiar los permisos de notificación de SunHat."
+ "Correct My Data" : {
+ "comment" : "Privacy inquiry type",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Corregir mis datos"
}
}
}
},
- "Couldn't open Settings. Open the Settings app manually to change SunHat's permissions.": {
- "comment": "Error shown when deep-linking to the iOS Settings app fails",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo abrir Ajustes. Abre la app Ajustes manualmente para cambiar los permisos de SunHat."
+ "Could not reach the App Store. Check your connection and try again." : {
+ "comment" : "Alert message when restore fails",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Could not reach the App Store. Check your connection and try again."
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo conectar con App Store. Comprueba tu conexión e inténtalo de nuevo."
}
}
}
},
- "Couldn't open the Terms of Service. Visit %@ in a browser.": {
- "comment": "Error shown when opening the Terms of Service link fails",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudieron abrir los Términos de servicio. Visita %@ en un navegador."
+ "Couldn't Complete Request" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo completar la solicitud"
}
}
}
},
- "Couldn't open the share sheet. Please try again.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo abrir la hoja para compartir. Inténtalo de nuevo."
+ "Couldn't Delete Task" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo eliminar la tarea"
}
}
}
},
- "Couldn't save changes. Please try again.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudieron guardar los cambios. Inténtalo de nuevo."
+ "Couldn't Open" : {
+ "comment" : "Settings alert title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo abrir"
}
}
}
},
- "Couldn't save your reminder. Please try again.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo guardar tu recordatorio. Inténtalo de nuevo."
+ "Couldn't Open Mail" : {
+ "comment" : "Alert title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo abrir Mail"
}
}
}
},
- "Create": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Crear"
+ "Couldn't open Mail. Set up a mail account, or email %@ directly." : {
+ "comment" : "Error shown when deep-linking to the Mail app fails",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo abrir Mail. Configura una cuenta de correo o escribe directamente a %@."
}
}
}
},
- "Create Reminder": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Crear recordatorio"
+ "Couldn't open Settings. Open the Settings app manually to change SunHat's notification permissions." : {
+ "comment" : "Error shown when deep-linking to the iOS Settings app fails",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo abrir Ajustes. Abre la app Ajustes manualmente para cambiar los permisos de notificación de SunHat."
}
}
}
},
- "Create Weather Reminder": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Crear recordatorio del clima"
+ "Couldn't open Settings. Open the Settings app manually to change SunHat's permissions." : {
+ "comment" : "Error shown when deep-linking to the iOS Settings app fails",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo abrir Ajustes. Abre la app Ajustes manualmente para cambiar los permisos de SunHat."
}
}
}
},
- "Create a Task": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Crear una tarea"
+ "Couldn't open the share sheet. Please try again." : {
+ "comment" : "Data & Privacy screen error when the export share sheet can't be presented",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo abrir la hoja para compartir. Inténtalo de nuevo."
}
}
}
},
- "Create a copy of this reminder with modifications": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Crea una copia de este recordatorio con modificaciones"
+ "Couldn't open the Terms of Service. Visit %@ in a browser." : {
+ "comment" : "Error shown when opening the Terms of Service link fails",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudieron abrir los Términos de servicio. Visita %@ en un navegador."
}
}
}
},
- "Create a weather task and SunHat will watch for matching conditions.": {
- "comment": "Empty state message when the user has no active weather tasks\nEmpty state message when the user has no weather tasks",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Crea un recordatorio del clima y SunHat vigilará las condiciones correspondientes."
+ "Couldn't Save" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo guardar"
}
}
}
},
- "Create weather-triggered reminders to see predictions here": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Crea recordatorios activados por el clima para ver predicciones aquí"
+ "Couldn't save changes. Please try again." : {
+ "comment" : "Error shown when a reminder state change can't be persisted",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudieron guardar los cambios. Inténtalo de nuevo."
}
}
}
},
- "Create your first weather-triggered task to start watching the weather.": {
- "comment": "Empty state message when the user has no weather tasks",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Crea tu primer recordatorio activado por el clima para empezar a vigilarlo."
+ "Couldn't save your reminder. Please try again." : {
+ "comment" : "Error shown when creating a reminder fails to persist",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo guardar tu recordatorio. Inténtalo de nuevo."
}
}
}
},
- "Created": {
- "comment": "Reminder history action label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Creado"
+ "Create" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Crear"
}
}
}
},
- "Current Location": {
- "comment": "Displayed in place of an exact location when using GPS\nLocation label shown when using the device's current location",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ubicación actual"
+ "Create a copy of this reminder with modifications" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Crea una copia de este recordatorio con modificaciones"
}
}
}
},
- "Current UV index (%lld) is above SunHat's advisory threshold of 8.": {
- "comment": "In-app weather advisory description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "El índice UV actual (%lld) supera el umbral de aviso de SunHat de 8."
+ "Create a Task" : {
+ "comment" : "Button on the empty reminders list that opens the creation screen",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Crear una tarea"
}
}
}
},
- "Current conditions: %@ degrees, %@, feels like %@ degrees": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Current conditions: %1$@ degrees, %2$@, feels like %3$@ degrees"
+ "Create a weather task and SunHat will watch for matching conditions." : {
+ "comment" : "Empty state message when the user has no active weather tasks\nEmpty state message when the user has no weather tasks",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Crea un recordatorio del clima y SunHat vigilará las condiciones correspondientes."
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Condiciones actuales: %@ grados, %@, sensación térmica de %@ grados"
+ }
+ }
+ },
+ "Create Reminder" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Crear recordatorio"
}
}
}
},
- "Current location is unavailable": {
- "comment": "Location error title",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La ubicación actual no está disponible"
+ "Create Weather Reminder" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Crear recordatorio del clima"
}
}
}
},
- "Current temperature %@ degrees": {
- "comment": "Accessibility label clause stating the current temperature",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Temperatura actual %@ grados"
+ "Create weather-triggered reminders to see predictions here" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Crea recordatorios activados por el clima para ver predicciones aquí"
}
}
}
},
- "Current temperature %@° differs from historical average by %@° (tolerance: %@°)": {
- "comment": "Notification body: current temperature misses the historical average by some amount",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Current temperature %1$@° differs from historical average by %2$@° (tolerance: %3$@°)"
+ "Create your first weather-triggered task to start watching the weather." : {
+ "comment" : "Empty state message when the user has no weather tasks",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Crea tu primer recordatorio activado por el clima para empezar a vigilarlo."
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura actual de %@° difiere del promedio histórico en %@° (tolerancia: %@°)"
+ }
+ }
+ },
+ "Created" : {
+ "comment" : "Reminder history action label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Creado"
}
}
}
},
- "Current temperature %@° is %@° cooler than historical average %@° for this date": {
- "comment": "Notification body comparing current temperature to the historical average for today's date",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Current temperature %1$@° is %2$@° cooler than historical average %3$@° for this date"
+ "Current conditions: %@ degrees, %@, feels like %@ degrees" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Current conditions: %1$@ degrees, %2$@, feels like %3$@ degrees"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura actual de %@° es %@° más fresca que el promedio histórico de %@° para esta fecha"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Condiciones actuales: %@ grados, %@, sensación térmica de %@ grados"
}
}
}
},
- "Current temperature %@° is %@° warmer than historical average %@° for this date": {
- "comment": "Notification body comparing current temperature to the historical average for today's date",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Current temperature %1$@° is %2$@° warmer than historical average %3$@° for this date"
+ "Current Location" : {
+ "comment" : "Displayed in place of an exact location when using GPS\nLocation label shown when using the device's current location",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ubicación actual"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura actual de %@° es %@° más cálida que el promedio histórico de %@° para esta fecha"
+ }
+ }
+ },
+ "Current location is unavailable" : {
+ "comment" : "Location error title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La ubicación actual no está disponible"
}
}
}
},
- "Current temperature %@° matches historical average %@° within %@°": {
- "comment": "Notification body: current temperature matches the historical average within tolerance",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Current temperature %1$@° matches historical average %2$@° within %3$@°"
+ "Current Plan" : {
+ "comment" : "Settings row showing which Ad-Free plan is active",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Current Plan"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura actual de %@° coincide con el promedio histórico de %@° dentro de %@°"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Plan actual"
}
}
}
},
- "Current temperature is above SunHat's 95°F advisory threshold.": {
- "comment": "In-app weather advisory description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura actual supera el umbral de aviso de SunHat de 95 °F."
+ "Current temperature %@ degrees" : {
+ "comment" : "Accessibility label clause stating the current temperature",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Temperatura actual %@ grados"
}
}
}
},
- "Current temperature is outside typical range for this date (percentile: %@-%@%%)": {
- "comment": "Notification body: current temperature is outside the typical historical percentile range",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Current temperature is outside typical range for this date (percentile: %1$@-%2$@%%)"
+ "Current temperature %@° differs from historical average by %@° (tolerance: %@°)" : {
+ "comment" : "Notification body: current temperature misses the historical average by some amount",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Current temperature %1$@° differs from historical average by %2$@° (tolerance: %3$@°)"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura actual está fuera del rango típico para esta fecha (percentil: %@-%@%%)"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura actual de %@° difiere del promedio histórico en %@° (tolerancia: %@°)"
}
}
}
},
- "Current temperature is within typical range for this date (percentile: %@-%@%%)": {
- "comment": "Notification body: current temperature is within the typical historical percentile range",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Current temperature is within typical range for this date (percentile: %1$@-%2$@%%)"
+ "Current temperature %@° is %@° cooler than historical average %@° for this date" : {
+ "comment" : "Notification body comparing current temperature to the historical average for today's date",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Current temperature %1$@° is %2$@° cooler than historical average %3$@° for this date"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura actual está dentro del rango típico para esta fecha (percentil: %@-%@%%)"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura actual de %@° es %@° más fresca que el promedio histórico de %@° para esta fecha"
}
}
}
},
- "Current weather for %@: %@ degrees, feels like %@ degrees, %@": {
- "comment": "Accessibility label summarizing the current weather for the dashboard's weather card",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Current weather for %1$@: %2$@ degrees, feels like %3$@ degrees, %4$@"
+ "Current temperature %@° is %@° warmer than historical average %@° for this date" : {
+ "comment" : "Notification body comparing current temperature to the historical average for today's date",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Current temperature %1$@° is %2$@° warmer than historical average %3$@° for this date"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Clima actual en %@: %@ grados, sensación térmica de %@ grados, %@"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura actual de %@° es %@° más cálida que el promedio histórico de %@° para esta fecha"
}
}
}
},
- "Current weather: %@ degrees, feels like %@ degrees, %@. High %@, low %@ degrees": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Current weather: %1$@ degrees, feels like %2$@ degrees, %3$@. High %4$@, low %5$@ degrees"
+ "Current temperature %@° matches historical average %@° within %@°" : {
+ "comment" : "Notification body: current temperature matches the historical average within tolerance",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Current temperature %1$@° matches historical average %2$@° within %3$@°"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Clima actual: %@ grados, sensación térmica de %@ grados, %@. Máxima %@, mínima %@ grados"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura actual de %@° coincide con el promedio histórico de %@° dentro de %@°"
}
}
}
},
- "Custom": {
- "comment": "Reminder category name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Personalizado"
+ "Current temperature is above SunHat's 95°F advisory threshold." : {
+ "comment" : "In-app weather advisory description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura actual supera el umbral de aviso de SunHat de 95 °F."
}
}
}
},
- "Cyan": {
- "comment": "Reminder color swatch name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cian"
+ "Current temperature is outside typical range for this date (percentile: %@-%@%%)" : {
+ "comment" : "Notification body: current temperature is outside the typical historical percentile range",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Current temperature is outside typical range for this date (percentile: %1$@-%2$@%%)"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura actual está fuera del rango típico para esta fecha (percentil: %@-%@%%)"
}
}
}
},
- "Cycling": {
- "comment": "Activity interest option: cycling\nReminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ciclismo"
+ "Current temperature is within typical range for this date (percentile: %@-%@%%)" : {
+ "comment" : "Notification body: current temperature is within the typical historical percentile range",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Current temperature is within typical range for this date (percentile: %1$@-%2$@%%)"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura actual está dentro del rango típico para esta fecha (percentil: %@-%@%%)"
}
}
}
},
- "Daily Limit": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Límite diario"
+ "Current UV index (%lld) is above SunHat's advisory threshold of 8." : {
+ "comment" : "In-app weather advisory description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "El índice UV actual (%lld) supera el umbral de aviso de SunHat de 8."
}
}
}
},
- "Daily walks, dog walking, and casual strolls": {
- "comment": "Description of walking activity interest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Caminatas diarias, paseos con el perro y salidas casuales"
+ "Current weather for %@: %@ degrees, feels like %@ degrees, %@" : {
+ "comment" : "Accessibility label summarizing the current weather for the dashboard's weather card",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Current weather for %1$@: %2$@ degrees, feels like %3$@ degrees, %4$@"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Clima actual en %@: %@ grados, sensación térmica de %@ grados, %@"
}
}
}
},
- "Dark": {
- "comment": "Appearance mode option: always dark",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Oscuro"
+ "Current weather: %@ degrees, feels like %@ degrees, %@. High %@, low %@ degrees" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Current weather: %1$@ degrees, feels like %2$@ degrees, %3$@. High %4$@, low %5$@ degrees"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Clima actual: %@ grados, sensación térmica de %@ grados, %@. Máxima %@, mínima %@ grados"
}
}
}
},
- "Data & Privacy": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Datos y privacidad"
+ "Custom" : {
+ "comment" : "Reminder category name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Personalizado"
}
}
}
},
- "Data Portability": {
- "comment": "Privacy inquiry type",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Portabilidad de datos"
+ "Cyan" : {
+ "comment" : "Reminder color swatch name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cian"
}
}
}
},
- "Data Processing Inquiry": {
- "comment": "Privacy inquiry type",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Consulta sobre procesamiento de datos"
+ "Cycling" : {
+ "comment" : "Activity interest option: cycling\nReminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ciclismo"
}
}
}
},
- "Data is stored locally on this device.": {
- "comment": "Data & Privacy screen sync status",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Los datos se almacenan localmente en este dispositivo."
+ "Daily Limit" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Límite diario"
}
}
}
},
- "Data recovery notice. %@": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Aviso de recuperación de datos. %@"
+ "Daily walks, dog walking, and casual strolls" : {
+ "comment" : "Description of walking activity interest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Caminatas diarias, paseos con el perro y salidas casuales"
}
}
}
},
- "Default": {
- "comment": "Notification sound option: system default\nVibration pattern option: system default",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Predeterminado"
+ "Dark" : {
+ "comment" : "Appearance mode option: always dark",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Oscuro"
}
}
}
},
- "Default notification settings": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ajustes de notificación predeterminados"
+ "Data & Privacy" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Datos y privacidad"
}
}
}
},
- "Delete": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Eliminar"
+ "Data is stored locally on this device." : {
+ "comment" : "Data & Privacy screen sync status",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Los datos se almacenan localmente en este dispositivo."
}
}
}
},
- "Delete All Data": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Eliminar todos los datos"
+ "Data Portability" : {
+ "comment" : "Privacy inquiry type",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Portabilidad de datos"
}
}
}
},
- "Delete Everything": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Eliminar todo"
+ "Data Processing Inquiry" : {
+ "comment" : "Privacy inquiry type",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Consulta sobre procesamiento de datos"
}
}
}
},
- "Delete My Data": {
- "comment": "Privacy inquiry type",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Eliminar mis datos"
+ "Data recovery notice. %@" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aviso de recuperación de datos. %@"
}
}
}
},
- "Delete Reminder": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Eliminar recordatorio"
+ "Default" : {
+ "comment" : "Notification sound option: system default\nVibration pattern option: system default",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Predeterminado"
}
}
}
},
- "Deleted": {
- "comment": "Reminder history action label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Eliminado"
+ "Default notification settings" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ajustes de notificación predeterminados"
}
}
}
},
- "Deletion cannot be undone. System permission choices remain managed in the Settings app.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La eliminación no se puede deshacer. Los permisos del sistema se siguen gestionando en la app Ajustes."
+ "Delete" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Eliminar"
}
}
}
},
- "Describe your inquiry...": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Describe tu solicitud..."
+ "Delete All Data" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Eliminar todos los datos"
}
}
}
},
- "Description (optional)": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Descripción (opcional)"
+ "Delete Everything" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Eliminar todo"
}
}
}
},
- "Disabled": {
- "comment": "Quiet hours state when the user has turned them off\nQuiet hours status: feature turned off",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Desactivado"
+ "Delete My Data" : {
+ "comment" : "Privacy inquiry type",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Eliminar mis datos"
}
}
}
},
- "Dog": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Perro"
+ "Delete Reminder" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Eliminar recordatorio"
}
}
}
},
- "Done": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Listo"
+ "Deleted" : {
+ "comment" : "Reminder history action label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Eliminado"
}
}
}
},
- "Double tap to change icon": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Doble toque para cambiar el ícono"
+ "Deletion cannot be undone. System permission choices remain managed in the Settings app." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La eliminación no se puede deshacer. Los permisos del sistema se siguen gestionando en la app Ajustes."
}
}
}
},
- "Double tap to change location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Doble toque para cambiar la ubicación"
+ "Describe your inquiry..." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Describe tu solicitud..."
}
}
}
},
- "Driving": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Conducir"
+ "Description (optional)" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Descripción (opcional)"
}
}
}
},
- "Drizzle": {
- "comment": "Weather condition name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Llovizna"
+ "Disabled" : {
+ "comment" : "Quiet hours state when the user has turned them off\nQuiet hours status: feature turned off",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Desactivado"
}
}
}
},
- "Dry": {
- "comment": "Dew point comfort description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Seco"
+ "Dog" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Perro"
}
}
}
},
- "Duplicate": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Duplicar"
+ "Done" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Listo"
}
}
}
},
- "Duplicate Reminder": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Duplicar recordatorio"
+ "Double tap to change icon" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Doble toque para cambiar el ícono"
}
}
}
},
- "E": {
- "comment": "Wind direction abbreviation: East",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "E"
+ "Double tap to change location" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Doble toque para cambiar la ubicación"
}
}
}
},
- "ENE": {
- "comment": "Wind direction abbreviation: East-northeast",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "ENE"
+ "Driving" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Conducir"
}
}
}
},
- "ESE": {
- "comment": "Wind direction abbreviation: East-southeast",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "ESE"
+ "Drizzle" : {
+ "comment" : "Weather condition name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Llovizna"
}
}
}
},
- "Edit reminder": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Editar recordatorio"
+ "Dry" : {
+ "comment" : "Dew point comfort description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Seco"
}
}
}
},
- "Email SunHat about data access, correction, deletion, export, or another privacy question.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Escribe a SunHat sobre acceso, corrección, eliminación o exportación de datos, u otra pregunta de privacidad."
+ "dry conditions expected" : {
+ "comment" : "Short clause listing one satisfied dry-weather condition of a multi-condition reminder",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "se esperan condiciones secas"
}
}
}
},
- "Emergency Preparedness": {
- "comment": "Reminder category name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Preparación para emergencias"
+ "Duplicate" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Duplicar"
}
}
}
},
- "Emergency SOS pattern": {
- "comment": "Description of SOS vibration pattern option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Patrón de SOS de emergencia"
+ "Duplicate Reminder" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Duplicar recordatorio"
}
}
}
},
- "Enable Location Services in Settings > Privacy & Security > Location Services": {
- "comment": "Location error recovery suggestion; \">\" separates iOS Settings menu levels and should stay as-is",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Activa los Servicios de ubicación en Ajustes > Privacidad y seguridad > Localización"
+ "E" : {
+ "comment" : "Wind direction abbreviation: East",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "E"
}
}
}
},
- "Enable Notifications": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Activar notificaciones"
+ "e.g., -122.4194" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "p. ej., -122.4194"
}
}
}
},
- "Enable location services in Settings": {
- "comment": "Weather error failure reason",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Activa los servicios de ubicación en Ajustes"
+ "e.g., 37.7749" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "p. ej., 37.7749"
}
}
}
},
- "Enjoying SunHat?": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¿Te está gustando SunHat?"
+ "Edit reminder" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Editar recordatorio"
}
}
}
},
- "Enter City Manually": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Escribir ciudad manualmente"
+ "Email SunHat about data access, correction, deletion, export, or another privacy question." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Escribe a SunHat sobre acceso, corrección, eliminación o exportación de datos, u otra pregunta de privacidad."
}
}
}
},
- "Enter Coordinates": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Escribir coordenadas"
+ "Emergency Preparedness" : {
+ "comment" : "Reminder category name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Preparación para emergencias"
}
}
}
},
- "Enter a city name, address, or point of interest to add it to your saved locations": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Escribe el nombre de una ciudad, dirección o punto de interés para agregarlo a tus ubicaciones guardadas"
+ "Emergency SOS pattern" : {
+ "comment" : "Description of SOS vibration pattern option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Patrón de SOS de emergencia"
}
}
}
},
- "Enter coordinates for a custom location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Escribe las coordenadas de una ubicación personalizada"
+ "Enable location services in Settings" : {
+ "comment" : "Weather error failure reason",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Activa los servicios de ubicación en Ajustes"
}
}
}
},
- "Enter notification message": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Escribe el mensaje de la notificación"
+ "Enable Location Services in Settings > Privacy & Security > Location Services" : {
+ "comment" : "Location error recovery suggestion; \">\" separates iOS Settings menu levels and should stay as-is",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Activa los Servicios de ubicación en Ajustes > Privacidad y seguridad > Localización"
}
}
}
},
- "Enter notification title": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Escribe el título de la notificación"
+ "Enable Notifications" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Activar notificaciones"
}
}
}
},
- "Enter your city name to get accurate weather data": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Escribe el nombre de tu ciudad para obtener datos meteorológicos precisos"
+ "ENE" : {
+ "comment" : "Wind direction abbreviation: East-northeast",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "ENE"
}
}
}
},
- "Estimated size": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tamaño estimado"
+ "Enjoying SunHat?" : {
+ "comment" : "Lifecycle prompt alert title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¿Te está gustando SunHat?"
}
}
}
},
- "Evening": {
- "comment": "Time-of-day preference option label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Noche"
+ "Enter a city name, address, or point of interest to add it to your saved locations" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Escribe el nombre de una ciudad, dirección o punto de interés para agregarlo a tus ubicaciones guardadas"
}
}
}
},
- "Exact": {
- "comment": "Trigger type name: exact temperature match",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Exacta"
+ "Enter City Manually" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Escribir ciudad manualmente"
}
}
}
},
- "Exact Copy": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Copia exacta"
+ "Enter Coordinates" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Escribir coordenadas"
}
}
}
},
- "Exact Temperature": {
- "comment": "Temperature condition type option label\nWeather condition type option label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Temperatura exacta"
+ "Enter coordinates for a custom location" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Escribe las coordenadas de una ubicación personalizada"
}
}
}
},
- "Example notification. Gardening conditions match. It is 72 degrees and sunny at your selected location.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Notificación de ejemplo. Las condiciones para jardinería coinciden. Hace 72 grados y está soleado en tu ubicación seleccionada."
+ "Enter notification message" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Escribe el mensaje de la notificación"
}
}
}
},
- "Example reminder: Perfect gardening weather. It’s 72 degrees and sunny. A good time to get outside.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorio de ejemplo: clima perfecto para jardinería. Hace 72 grados y está soleado. Un buen momento para salir."
+ "Enter notification title" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Escribe el título de la notificación"
}
}
}
},
- "Examples": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ejemplos"
+ "Enter your city name to get accurate weather data" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Escribe el nombre de tu ciudad para obtener datos meteorológicos precisos"
}
}
}
},
- "Excellent": {
- "comment": "Visibility level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Excelente"
+ "ESE" : {
+ "comment" : "Wind direction abbreviation: East-southeast",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "ESE"
}
}
}
},
- "Exclude": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Excluir"
+ "Estimated size" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tamaño estimado"
}
}
}
},
- "Exercise": {
- "comment": "Activity interest option: exercise",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ejercicio"
+ "Evening" : {
+ "comment" : "Time-of-day preference option label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Noche"
}
}
}
},
- "Exercise & Fitness": {
- "comment": "Reminder category name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ejercicio y fitness"
+ "Exact" : {
+ "comment" : "Trigger type name: exact temperature match",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Exacta"
}
}
}
},
- "Expected %@": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Se espera %@"
+ "Exact Copy" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Copia exacta"
}
}
}
},
- "Export Data": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Exportar datos"
+ "Exact Temperature" : {
+ "comment" : "Temperature condition type option label\nWeather condition type option label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Temperatura exacta"
}
}
}
},
- "Export Failed": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Error al exportar"
+ "exactly" : {
+ "comment" : "Temperature comparison word, e.g. 'temperature is exactly 70°'",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "exactamente en"
}
}
}
},
- "Export Format": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Formato de exportación"
+ "Example notification. Gardening conditions match. It is 72 degrees and sunny at your selected location." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Notificación de ejemplo. Las condiciones para jardinería coinciden. Hace 72 grados y está soleado en tu ubicación seleccionada."
}
}
}
},
- "Export My Data": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Exportar mis datos"
+ "Example reminder: Perfect gardening weather. It’s 72 degrees and sunny. A good time to get outside." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorio de ejemplo: clima perfecto para jardinería. Hace 72 grados y está soleado. Un buen momento para salir."
}
}
}
},
- "Export Settings": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Exportar ajustes"
+ "Examples" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ejemplos"
}
}
}
},
- "Export your data in a machine-readable format": {
- "comment": "Privacy inquiry type description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Exporta tus datos en un formato legible por máquina"
+ "Excellent" : {
+ "comment" : "Visibility level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Excelente"
}
}
}
},
- "Exports include reminders, preferences, locations, notification settings, and reminder history.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Las exportaciones incluyen recordatorios, preferencias, ubicaciones, ajustes de notificación e historial de recordatorios."
+ "Exclude" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Excluir"
}
}
}
},
- "Extreme": {
- "comment": "UV index level description\nWeather alert severity level",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Extremo"
+ "Exercise" : {
+ "comment" : "Activity interest option: exercise",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ejercicio"
}
}
}
},
- "Fahrenheit": {
- "comment": "Temperature unit name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Fahrenheit"
+ "Exercise & Fitness" : {
+ "comment" : "Reminder category name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ejercicio y fitness"
}
}
}
},
- "Failed to delete data": {
- "comment": "Data & Privacy error",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudieron eliminar los datos"
+ "Expected %@" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Se espera %@"
}
}
}
},
- "Failed to delete data: %@": {
- "comment": "Data & Privacy screen error",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudieron eliminar los datos: %@"
+ "Expires" : {
+ "comment" : "Settings row label when the Ad-Free subscription will not auto-renew",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Expires"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Vence"
}
}
}
},
- "Failed to export CSV: %@": {
- "comment": "Data & Privacy screen error",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo exportar el CSV: %@"
+ "Export Data" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Exportar datos"
}
}
}
},
- "Failed to export data": {
- "comment": "Data & Privacy error",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudieron exportar los datos"
+ "Export Failed" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Error al exportar"
}
}
}
},
- "Failed to export data: %@": {
- "comment": "Data & Privacy screen error",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudieron exportar los datos: %@"
+ "Export Format" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Formato de exportación"
}
}
}
},
- "Failed to load data summary: %@": {
- "comment": "Data & Privacy screen error",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo cargar el resumen de datos: %@"
+ "Export My Data" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Exportar mis datos"
}
}
}
},
- "Failed to load history": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo cargar el historial"
+ "Export Settings" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Exportar ajustes"
}
}
}
},
- "Failed to load notification settings: %@": {
- "comment": "Error shown in Settings when saved notification preferences could not be read",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudieron cargar los ajustes de notificación: %@"
+ "Export your data in a machine-readable format" : {
+ "comment" : "Privacy inquiry type description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Exporta tus datos en un formato legible por máquina"
}
}
}
},
- "Failed to parse weather data": {
- "comment": "Weather error when the provider's response could not be decoded",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudieron procesar los datos meteorológicos"
+ "Exports include reminders, preferences, locations, notification settings, and reminder history." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Las exportaciones incluyen recordatorios, preferencias, ubicaciones, ajustes de notificación e historial de recordatorios."
}
}
}
},
- "Failed to request notification permissions: %@": {
- "comment": "Error shown when the system permission request itself fails",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudieron solicitar los permisos de notificación: %@"
+ "Extreme" : {
+ "comment" : "UV index level description\nWeather alert severity level",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Extremo"
}
}
}
},
- "Failed to save changes": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudieron guardar los cambios"
+ "Fahrenheit" : {
+ "comment" : "Temperature unit name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Fahrenheit"
}
}
}
},
- "Failed to save export file: %@": {
- "comment": "Data & Privacy screen error",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo guardar el archivo exportado: %@"
+ "Failed to delete data" : {
+ "comment" : "Data & Privacy error",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudieron eliminar los datos"
}
}
}
},
- "Failed to save notification settings: %@": {
- "comment": "Error shown in Settings when notification preferences could not be saved",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudieron guardar los ajustes de notificación: %@"
+ "Failed to delete data: %@" : {
+ "comment" : "Data & Privacy screen error",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudieron eliminar los datos: %@"
}
}
}
},
- "Failed to send test notification: %@": {
- "comment": "Error shown when the sample notification could not be scheduled",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo enviar la notificación de prueba: %@"
+ "Failed to export CSV: %@" : {
+ "comment" : "Data & Privacy screen error",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo exportar el CSV: %@"
}
}
}
},
- "Favorite": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Favorito"
+ "Failed to export data" : {
+ "comment" : "Data & Privacy error",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudieron exportar los datos"
}
}
}
},
- "Feedback": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Comentarios"
+ "Failed to export data: %@" : {
+ "comment" : "Data & Privacy screen error",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudieron exportar los datos: %@"
}
}
}
},
- "Feels Like": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sensación térmica"
+ "Failed to load data summary: %@" : {
+ "comment" : "Data & Privacy screen error",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo cargar el resumen de datos: %@"
}
}
}
},
- "Feels like %@°": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sensación térmica de %@°"
+ "Failed to load history" : {
+ "comment" : "Error shown when the reminder's history timeline can't be loaded",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo cargar el historial"
}
}
}
},
- "Feels like %lld°": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sensación térmica de %lld°"
+ "Failed to load notification settings: %@" : {
+ "comment" : "Error shown in Settings when saved notification preferences could not be read",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudieron cargar los ajustes de notificación: %@"
}
}
}
},
- "Final onboarding step, step 4 of 4": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Paso final de configuración, paso 4 de 4"
+ "Failed to parse weather data" : {
+ "comment" : "Weather error when the provider's response could not be decoded",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudieron procesar los datos meteorológicos"
}
}
}
},
- "Find Any Forecast": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Consulta cualquier pronóstico"
+ "Failed to request notification permissions: %@" : {
+ "comment" : "Error shown when the system permission request itself fails",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudieron solicitar los permisos de notificación: %@"
}
}
}
},
- "Find answers to common questions about using SunHat.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Encuentra respuestas a preguntas frecuentes sobre el uso de SunHat."
+ "Failed to save changes" : {
+ "comment" : "Error shown when edits to a reminder can't be saved",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudieron guardar los cambios"
}
}
}
},
- "Finish Setup": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Finalizar configuración"
+ "Failed to save export file: %@" : {
+ "comment" : "Data & Privacy screen error",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo guardar el archivo exportado: %@"
}
}
}
},
- "Finish setup": {
- "comment": "Accessibility label for the final onboarding button",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Finalizar configuración"
+ "Failed to save notification settings: %@" : {
+ "comment" : "Error shown in Settings when notification preferences could not be saved",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudieron guardar los ajustes de notificación: %@"
}
}
}
},
- "Fire": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Fuego"
+ "Failed to send test notification: %@" : {
+ "comment" : "Error shown when the sample notification could not be scheduled",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo enviar la notificación de prueba: %@"
}
}
}
},
- "Fish": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Pez"
+ "Favorite" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Favorito"
}
}
}
},
- "Fitness": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Fitness"
+ "Feedback" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Comentarios"
}
}
}
},
- "Flag": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Bandera"
+ "Feels Like" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sensación térmica"
}
}
}
},
- "Flight": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Vuelo"
+ "Feels like %@°" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sensación térmica de %@°"
}
}
}
},
- "Focus search field to try again": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Enfoca el campo de búsqueda para intentarlo de nuevo"
+ "Feels like %lld°" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sensación térmica de %lld°"
}
}
}
},
- "Fog": {
- "comment": "Weather condition name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Niebla"
+ "Final onboarding step, step 4 of 4" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Paso final de configuración, paso 4 de 4"
}
}
}
},
- "Forecast": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Pronóstico"
+ "Find answers to common questions about using SunHat." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Encuentra respuestas a preguntas frecuentes sobre el uso de SunHat."
}
}
}
},
- "Forecast Unavailable": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Pronóstico no disponible"
+ "Find Any Forecast" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Consulta cualquier pronóstico"
}
}
}
},
- "Forecast does not match on %@: %@": {
- "comment": "Notification body: a specific upcoming forecast day fails some conditions; dateLabel is a short localized date like 'Jun 12', failedList is a comma-separated list of already-localized clauses",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Forecast does not match on %1$@: %2$@"
- }
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "El pronóstico no coincide el %@: %@"
+ "Finish setup" : {
+ "comment" : "Accessibility label for the final onboarding button",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Finalizar configuración"
}
}
}
},
- "Forecast matches on %@: %@": {
- "comment": "Notification body: a specific upcoming forecast day satisfies every condition; dateLabel is a short localized date like 'Jun 12', checkList is a comma-separated list of already-localized clauses",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Forecast matches on %1$@: %2$@"
- }
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "El pronóstico coincide el %@: %@"
+ "Finish Setup" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Finalizar configuración"
}
}
}
},
- "Forecast provider": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Proveedor de pronósticos"
+ "Fire" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Fuego"
}
}
}
},
- "Format": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Formato"
+ "Fish" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pez"
}
}
}
},
- "Freezing rain": {
- "comment": "Precipitation description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Lluvia helada"
+ "Fitness" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Fitness"
}
}
}
},
- "Frequently Asked Questions": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Preguntas frecuentes"
+ "Fix Billing Issue" : {
+ "comment" : "Settings row that opens subscription management when a renewal payment failed",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Fix Billing Issue"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Resolver problema de pago"
}
}
}
},
- "GDPR Article 15 (Right of Access), CCPA Section 1798.110": {
- "comment": "Legal citation; keep 'GDPR Article 15' and 'CCPA Section 1798.110' untranslated, translate only '(Right of Access)'",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "GDPR Article 15 (derecho de acceso), CCPA Section 1798.110"
+ "Flag" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bandera"
}
}
}
},
- "GDPR Article 16 (Right to Rectification)": {
- "comment": "Legal citation; keep 'GDPR Article 16' untranslated, translate only '(Right to Rectification)'",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "GDPR Article 16 (derecho de rectificación)"
+ "Flight" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Vuelo"
}
}
}
},
- "GDPR Article 17 (Right to Erasure), CCPA Section 1798.105": {
- "comment": "Legal citation; keep 'GDPR Article 17' and 'CCPA Section 1798.105' untranslated, translate only '(Right to Erasure)'",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "GDPR Article 17 (derecho de supresión), CCPA Section 1798.105"
+ "Focus search field to try again" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Enfoca el campo de búsqueda para intentarlo de nuevo"
}
}
}
},
- "GDPR Article 20 (Right to Data Portability)": {
- "comment": "Legal citation; keep 'GDPR Article 20' untranslated, translate only '(Right to Data Portability)'",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "GDPR Article 20 (derecho a la portabilidad de datos)"
+ "Fog" : {
+ "comment" : "Weather condition name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Niebla"
}
}
}
},
- "GPS": {
- "comment": "Location source name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "GPS"
+ "Forecast" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pronóstico"
}
}
}
},
- "Garden · Trail run · Pool day": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Jardín · Carrera en sendero · Día de piscina"
+ "Forecast does not match on %@: %@" : {
+ "comment" : "Notification body: a specific upcoming forecast day fails some conditions; dateLabel is a short localized date like 'Jun 12', failedList is a comma-separated list of already-localized clauses",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Forecast does not match on %1$@: %2$@"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "El pronóstico no coincide el %@: %@"
}
}
}
},
- "Gardening": {
- "comment": "Activity interest option: gardening\nReminder category name\nReminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Jardinería"
+ "forecast doesn't cover the full %lldh dry window" : {
+ "comment" : "Short clause: not enough forecast data exists to evaluate the N-hour dry window condition",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "el pronóstico no cubre toda la ventana seca de %lldh"
}
}
}
},
- "Gardening conditions match": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Las condiciones para jardinería coinciden"
+ "Forecast matches on %@: %@" : {
+ "comment" : "Notification body: a specific upcoming forecast day satisfies every condition; dateLabel is a short localized date like 'Jun 12', checkList is a comma-separated list of already-localized clauses",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Forecast matches on %1$@: %2$@"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "El pronóstico coincide el %@: %@"
}
}
}
},
- "General": {
- "comment": "Reminder category name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "General"
+ "forecast overlaps %lld°-%lld°" : {
+ "comment" : "Short clause describing a forecast day's temperature range overlap with the target range, used inside a comma-separated list",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "forecast overlaps %1$lld°-%2$lld°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "el pronóstico se superpone con %lld°-%lld°"
}
}
}
},
- "General Privacy Question": {
- "comment": "Privacy inquiry type",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Pregunta general de privacidad"
+ "Forecast provider" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Proveedor de pronósticos"
}
}
}
},
- "Gentle": {
- "comment": "Notification sound option: gentle",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Suave"
+ "Forecast Unavailable" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pronóstico no disponible"
}
}
}
},
- "Get Started": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Comenzar"
+ "Format" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Formato"
}
}
}
},
- "Go directly to the app": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ir directamente a la app"
+ "Freezing rain" : {
+ "comment" : "Precipitation description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Lluvia helada"
}
}
}
},
- "Go to Settings > Privacy & Security > Location Services to enable location access": {
- "comment": "Location error recovery suggestion; \">\" separates iOS Settings menu levels and should stay as-is",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ve a Ajustes > Privacidad y seguridad > Localización para activar el acceso a la ubicación"
+ "Frequently Asked Questions" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Preguntas frecuentes"
}
}
}
},
- "Go to Settings > Privacy > Location Services and enable for this app": {
- "comment": "Weather error recovery suggestion; \">\" separates iOS Settings menu levels and should stay as-is",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ve a Ajustes > Privacidad > Localización y actívalo para esta app"
+ "Garden · Trail run · Pool day" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Jardín · Carrera en sendero · Día de piscina"
}
}
}
},
- "Golf": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Golf"
+ "Gardening" : {
+ "comment" : "Activity interest option: gardening\nReminder category name\nReminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Jardinería"
}
}
}
},
- "Good": {
- "comment": "Air quality index level description\nVisibility level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Buena"
+ "Gardening conditions match" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Las condiciones para jardinería coinciden"
}
}
}
},
- "Good chance": {
- "comment": "Trigger likelihood: 51-75% chance the reminder's conditions will occur",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Buena probabilidad"
+ "GDPR Article 15 (Right of Access), CCPA Section 1798.110" : {
+ "comment" : "Legal citation; keep 'GDPR Article 15' and 'CCPA Section 1798.110' untranslated, translate only '(Right of Access)'",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "GDPR Article 15 (derecho de acceso), CCPA Section 1798.110"
}
}
}
},
- "Green": {
- "comment": "Reminder color swatch name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Verde"
+ "GDPR Article 16 (Right to Rectification)" : {
+ "comment" : "Legal citation; keep 'GDPR Article 16' untranslated, translate only '(Right to Rectification)'",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "GDPR Article 16 (derecho de rectificación)"
}
}
}
},
- "Group by Location": {
- "comment": "Notification grouping option: group by location",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Agrupar por ubicación"
+ "GDPR Article 17 (Right to Erasure), CCPA Section 1798.105" : {
+ "comment" : "Legal citation; keep 'GDPR Article 17' and 'CCPA Section 1798.105' untranslated, translate only '(Right to Erasure)'",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "GDPR Article 17 (derecho de supresión), CCPA Section 1798.105"
}
}
}
},
- "Group by Time": {
- "comment": "Notification grouping option: group by delivery time",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Agrupar por hora"
+ "GDPR Article 20 (Right to Data Portability)" : {
+ "comment" : "Legal citation; keep 'GDPR Article 20' untranslated, translate only '(Right to Data Portability)'",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "GDPR Article 20 (derecho a la portabilidad de datos)"
}
}
}
},
- "Group by Type": {
- "comment": "Notification grouping option: group by reminder category",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Agrupar por tipo"
+ "General" : {
+ "comment" : "Reminder category name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "General"
}
}
}
},
- "Group notifications by delivery time": {
- "comment": "Description of group-by-time notification option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Agrupa las notificaciones por hora de entrega"
+ "General Privacy Question" : {
+ "comment" : "Privacy inquiry type",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pregunta general de privacidad"
}
}
}
},
- "Group notifications by location": {
- "comment": "Description of group-by-location notification option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Agrupa las notificaciones por ubicación"
+ "Gentle" : {
+ "comment" : "Notification sound option: gentle",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Suave"
}
}
}
},
- "Group notifications by reminder category": {
- "comment": "Description of group-by-type notification option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Agrupa las notificaciones por categoría de recordatorio"
+ "Get Ad-Free" : {
+ "comment" : "Settings row that opens the Ad-Free subscription paywall",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Get Ad-Free"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Obtener sin anuncios"
}
}
}
},
- "Gusts %@ km/h": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Rachas de %@ km/h"
+ "Get Started" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Comenzar"
}
}
}
},
- "Gusts %@ mph": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Rachas de %@ mph"
+ "Go directly to the app" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ir directamente a la app"
}
}
}
},
- "H %.0f°": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Máx %.0f°"
+ "Go to Settings > Privacy & Security > Location Services to enable location access" : {
+ "comment" : "Location error recovery suggestion; \">\" separates iOS Settings menu levels and should stay as-is",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ve a Ajustes > Privacidad y seguridad > Localización para activar el acceso a la ubicación"
}
}
}
},
- "Hail": {
- "comment": "Precipitation description\nWeather condition name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Granizo"
+ "Go to Settings > Privacy > Location Services and enable for this app" : {
+ "comment" : "Weather error recovery suggestion; \">\" separates iOS Settings menu levels and should stay as-is",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ve a Ajustes > Privacidad > Localización y actívalo para esta app"
}
}
}
},
- "Hazardous": {
- "comment": "Air quality index level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Peligrosa"
+ "Golf" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Golf"
}
}
}
},
- "Health & Wellness": {
- "comment": "Reminder category name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Salud y bienestar"
+ "Good" : {
+ "comment" : "Air quality index level description\nVisibility level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Buena"
}
}
}
},
- "Heart": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Corazón"
+ "Good chance" : {
+ "comment" : "Trigger likelihood: 51-75% chance the reminder's conditions will occur",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Buena probabilidad"
}
}
}
},
- "Heartbeat": {
- "comment": "Vibration pattern option: rhythmic pulse pattern",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Latido"
+ "GPS" : {
+ "comment" : "Location source name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "GPS"
}
}
}
},
- "Hello,\n\nI am contacting you regarding my privacy rights under GDPR/CCPA.\n\nMy request:\n\n\nThank you,": {
- "comment": "Pre-filled body of the privacy-request email the app composes; GDPR/CCPA are legal acronyms and should not be translated",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Hola,\n\nMe pongo en contacto con ustedes respecto a mis derechos de privacidad bajo el GDPR/CCPA.\n\nMi solicitud:\n\n\nGracias,"
+ "Green" : {
+ "comment" : "Reminder color swatch name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Verde"
}
}
}
},
- "Help & FAQ": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ayuda y preguntas frecuentes"
+ "Group by Location" : {
+ "comment" : "Notification grouping option: group by location",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Agrupar por ubicación"
}
}
}
},
- "Hide Details": {
- "comment": "Lock screen behavior option: hide notification content details",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ocultar detalles"
+ "Group by Time" : {
+ "comment" : "Notification grouping option: group by delivery time",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Agrupar por hora"
}
}
}
},
- "Hide Notifications": {
- "comment": "Lock screen behavior option: hide notifications entirely",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ocultar notificaciones"
+ "Group by Type" : {
+ "comment" : "Notification grouping option: group by reminder category",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Agrupar por tipo"
}
}
}
},
- "Hide forecast details": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ocultar detalles del pronóstico"
+ "Group notifications by delivery time" : {
+ "comment" : "Description of group-by-time notification option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Agrupa las notificaciones por hora de entrega"
}
}
}
},
- "Hide notifications completely on lock screen": {
- "comment": "Description of hide-notifications lock screen option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Oculta por completo las notificaciones en la pantalla de bloqueo"
+ "Group notifications by location" : {
+ "comment" : "Description of group-by-location notification option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Agrupa las notificaciones por ubicación"
}
}
}
},
- "High": {
- "comment": "Reminder priority level\nUV index level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Alta"
+ "Group notifications by reminder category" : {
+ "comment" : "Description of group-by-type notification option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Agrupa las notificaciones por categoría de recordatorio"
}
}
}
},
- "High %@ degrees, low %@ degrees, wind %@": {
- "comment": "Accessibility value with high/low temperature and wind speed for the dashboard's weather card",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "High %1$@ degrees, low %2$@ degrees, wind %3$@"
+ "Gusts %@ km/h" : {
+ "comment" : "Wind gust speed in kilometers per hour",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Rachas de %@ km/h"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Máxima %@ grados, mínima %@ grados, viento %@"
+ }
+ }
+ },
+ "Gusts %@ mph" : {
+ "comment" : "Wind gust speed in miles per hour",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Rachas de %@ mph"
}
}
}
},
- "High pressure": {
- "comment": "Barometric pressure level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Presión alta"
+ "H %.0f°" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Máx %.0f°"
}
}
}
},
- "Hiking": {
- "comment": "Activity interest option: hiking\nReminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Senderismo"
+ "Hail" : {
+ "comment" : "Precipitation description\nWeather condition name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Granizo"
}
}
}
},
- "Historical": {
- "comment": "Trigger type name: historical comparison",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Histórico"
+ "Hazardous" : {
+ "comment" : "Air quality index level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Peligrosa"
}
}
}
},
- "History will appear here when the reminder triggers": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "El historial aparecerá aquí cuando se active el recordatorio"
+ "Health & Wellness" : {
+ "comment" : "Reminder category name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Salud y bienestar"
}
}
}
},
- "Home": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Casa"
+ "Heart" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Corazón"
}
}
}
},
- "Home Maintenance": {
- "comment": "Activity interest option: home maintenance\nReminder category name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mantenimiento del hogar"
+ "Heartbeat" : {
+ "comment" : "Vibration pattern option: rhythmic pulse pattern",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Latido"
}
}
}
},
- "Home repairs, painting, and outdoor maintenance tasks": {
- "comment": "Description of home maintenance activity interest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Reparaciones del hogar, pintura y tareas de mantenimiento exterior"
+ "Hello,\n\nI am contacting you regarding my privacy rights under GDPR/CCPA.\n\nMy request:\n\n\nThank you," : {
+ "comment" : "Pre-filled body of the privacy-request email the app composes; GDPR/CCPA are legal acronyms and should not be translated",
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hola,\n\nMe pongo en contacto con ustedes respecto a mis derechos de privacidad bajo el GDPR/CCPA.\n\nMi solicitud:\n\n\nGracias,"
}
}
}
},
- "Hourly forecast is unavailable right now. Pull to refresh to try again.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "El pronóstico por hora no está disponible en este momento. Desliza para actualizar e inténtalo de nuevo."
+ "Help & FAQ" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ayuda y preguntas frecuentes"
}
}
}
},
- "How SunHat Uses Data": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cómo usa SunHat los datos"
+ "Hide Details" : {
+ "comment" : "Lock screen behavior option: hide notification content details",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ocultar detalles"
}
}
}
},
- "How accurate is the weather data?": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¿Qué tan precisos son los datos del clima?"
+ "Hide forecast details" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ocultar detalles del pronóstico"
}
}
}
},
- "How do I delete a reminder?": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¿Cómo elimino un recordatorio?"
+ "Hide Notifications" : {
+ "comment" : "Lock screen behavior option: hide notifications entirely",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ocultar notificaciones"
}
}
}
},
- "How do weather-triggered reminders work?": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¿Cómo funcionan los recordatorios activados por el clima?"
+ "Hide notifications completely on lock screen" : {
+ "comment" : "Description of hide-notifications lock screen option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Oculta por completo las notificaciones en la pantalla de bloqueo"
}
}
}
},
- "Humid": {
- "comment": "Dew point comfort description\nHumidity level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Húmedo"
+ "High" : {
+ "comment" : "Reminder priority level\nUV index level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Alta"
}
}
}
},
- "I have feedback about SunHat.": {
- "comment": "Default body of the feedback email when the user submits without typing anything",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tengo comentarios sobre SunHat."
+ "High %@ degrees, low %@ degrees, wind %@" : {
+ "comment" : "Accessibility value with high/low temperature and wind speed for the dashboard's weather card",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "High %1$@ degrees, low %2$@ degrees, wind %3$@"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Máxima %@ grados, mínima %@ grados, viento %@"
}
}
}
},
- "Icon, currently %@": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ícono, actualmente %@"
+ "high %lld° above %lld°" : {
+ "comment" : "Short clause describing a forecast day's high temperature versus target, used inside a comma-separated list",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "high %1$lld° above %2$lld°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "máxima %lld° por encima de %lld°"
}
}
}
},
- "Idea": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Idea"
+ "High pressure" : {
+ "comment" : "Barometric pressure level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Presión alta"
}
}
}
},
- "Ideal": {
- "comment": "Humidity level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ideal"
+ "Hiking" : {
+ "comment" : "Activity interest option: hiking\nReminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Senderismo"
}
}
}
},
- "If you have any questions about this privacy policy, please contact us at %@": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Si tienes alguna pregunta sobre esta política de privacidad, contáctanos en %@"
+ "Historical" : {
+ "comment" : "Trigger type name: historical comparison",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Histórico"
}
}
}
},
- "Immediately": {
- "comment": "Notification timing option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Inmediatamente"
+ "History will appear here when the reminder triggers" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "El historial aparecerá aquí cuando se active el recordatorio"
}
}
}
},
- "Imported": {
- "comment": "Location source name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Importado"
+ "Home" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Casa"
}
}
}
},
- "Inactive": {
- "comment": "Reminder list filter: show only inactive reminders\nReminder status",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Inactivo"
+ "Home Maintenance" : {
+ "comment" : "Activity interest option: home maintenance\nReminder category name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mantenimiento del hogar"
}
}
}
},
- "Include": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Incluir"
+ "Home repairs, painting, and outdoor maintenance tasks" : {
+ "comment" : "Description of home maintenance activity interest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Reparaciones del hogar, pintura y tareas de mantenimiento exterior"
}
}
}
},
- "Included Data": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Datos incluidos"
+ "Hourly forecast is unavailable right now. Pull to refresh to try again." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "El pronóstico por hora no está disponible en este momento. Desliza para actualizar e inténtalo de nuevo."
}
}
}
},
- "Indigo": {
- "comment": "Reminder color swatch name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Índigo"
+ "How accurate is the weather data?" : {
+ "comment" : "FAQ question",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¿Qué tan precisos son los datos del clima?"
}
}
}
},
- "Inquiry Type": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tipo de solicitud"
+ "How do I delete a reminder?" : {
+ "comment" : "FAQ question",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¿Cómo elimino un recordatorio?"
}
}
}
},
- "Introduces smart weather-based reminders with comparison examples": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Presenta recordatorios inteligentes basados en el clima con ejemplos comparativos"
+ "How do weather-triggered reminders work?" : {
+ "comment" : "FAQ question",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¿Cómo funcionan los recordatorios activados por el clima?"
}
}
}
},
- "Invalid Coordinates": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Coordenadas no válidas"
+ "How SunHat Uses Data" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cómo usa SunHat los datos"
}
}
}
},
- "Invalid location provided": {
- "comment": "Weather error shown when a location cannot be used to fetch weather",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La ubicación proporcionada no es válida"
+ "Humid" : {
+ "comment" : "Dew point comfort description\nHumidity level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Húmedo"
}
}
}
},
- "Invalid notification settings": {
- "comment": "Notification error for malformed preferences",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ajustes de notificación no válidos"
+ "humidity %lld%% outside %lld%% ±%lld%%" : {
+ "comment" : "Short clause listing one unsatisfied humidity condition of a multi-condition reminder",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "humidity %1$lld%% outside %2$lld%% ±%3$lld%%"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "humedad %lld %% fuera de %lld %% ±%lld %%"
}
}
}
},
- "Invalid response from weather service": {
- "comment": "Weather error when the provider's response could not be understood",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Respuesta no válida del servicio meteorológico"
+ "humidity %lld%% within %lld%% ±%lld%%" : {
+ "comment" : "Short clause listing one satisfied humidity condition of a multi-condition reminder",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "humidity %1$lld%% within %2$lld%% ±%3$lld%%"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "humedad %lld %% dentro de %lld %% ±%lld %%"
}
}
}
},
- "Invalid temperature range - missing min or max temperature": {
- "comment": "Notification body fallback when a range trigger is missing its bounds",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Rango de temperatura no válido: falta la temperatura mínima o máxima"
+ "humidity target is missing" : {
+ "comment" : "Short clause for a misconfigured humidity condition",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "falta el objetivo de humedad"
}
}
}
},
- "Invalid temperature range configuration": {
- "comment": "Notification body fallback when a range trigger is misconfigured",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Configuración de rango de temperatura no válida"
+ "I have feedback about SunHat." : {
+ "comment" : "Default body of the feedback email when the user submits without typing anything",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tengo comentarios sobre SunHat."
}
}
}
},
- "Is my data synced to iCloud?": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¿Mis datos se sincronizan con iCloud?"
+ "iCloud is not available" : {
+ "comment" : "Data & Privacy error",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "iCloud no está disponible"
}
}
}
},
- "It is 72°F and sunny at your selected location.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Hace 72 °F y está soleado en tu ubicación seleccionada."
+ "iCloud sync will be available in a future update." : {
+ "comment" : "Data & Privacy screen sync status",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La sincronización con iCloud estará disponible en una futura actualización."
}
}
}
},
- "It's %lld°F": {
- "comment": "Preview notification body temperature clause for a temperature range reminder\nPreview notification body temperature clause for an exact temperature reminder",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Está a %lld°F"
+ "Icon, currently %@" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ícono, actualmente %@"
}
}
}
},
- "It’s 72°F and sunny. A good time to get outside.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Está a 72 °F y soleado. Un buen momento para salir."
+ "Idea" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Idea"
}
}
}
},
- "Keychain operation failed with status %d": {
- "comment": "Low-level Keychain error surfaced through the weather API key error path",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La operación del llavero falló con estado %d"
+ "Ideal" : {
+ "comment" : "Humidity level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ideal"
}
}
}
},
- "Know when conditions are right": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Entérate cuando las condiciones sean las adecuadas"
+ "If you have any questions about this privacy policy, please contact us at %@" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Si tienes alguna pregunta sobre esta política de privacidad, contáctanos en %@"
}
}
}
},
- "L %.0f°": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mín %.0f°"
+ "Immediately" : {
+ "comment" : "Notification timing option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Inmediatamente"
}
}
}
},
- "Laptop": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Laptop"
+ "Imported" : {
+ "comment" : "Location source name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Importado"
}
}
}
},
- "Last updated: August 28, 2026": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Última actualización: 28 de agosto de 2026"
+ "Inactive" : {
+ "comment" : "Reminder list filter: show only inactive reminders\nReminder status",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Inactivo"
}
}
}
},
- "Last updated: January 30, 2026": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Última actualización: 30 de enero de 2026"
+ "Include" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Incluir"
}
}
}
},
- "Latitude": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Latitud"
+ "Included Data" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Datos incluidos"
}
}
}
},
- "Latitude must be between -90 and 90 degrees.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La latitud debe estar entre -90 y 90 grados."
+ "Indigo" : {
+ "comment" : "Reminder color swatch name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Índigo"
}
}
}
},
- "Latitude ranges from -90 to 90. Longitude ranges from -180 to 180.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La latitud va de -90 a 90. La longitud va de -180 a 180."
+ "Inquiry Type" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tipo de solicitud"
}
}
}
},
- "Legal Basis": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Base legal"
+ "inside notification time window" : {
+ "comment" : "Short clause listing one satisfied time-of-day condition of a multi-condition reminder",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "dentro de la ventana horaria de notificación"
}
}
}
},
- "Light": {
- "comment": "Appearance mode option: always light",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Claro"
+ "Introduces smart weather-based reminders with comparison examples" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Presenta recordatorios inteligentes basados en el clima con ejemplos comparativos"
}
}
}
},
- "Light rain": {
- "comment": "Precipitation description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Lluvia ligera"
+ "Invalid Coordinates" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Coordenadas no válidas"
}
}
}
},
- "Light snow": {
- "comment": "Precipitation description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Nieve ligera"
+ "invalid forecast temperature range" : {
+ "comment" : "Short clause for a misconfigured forecast temperature range condition",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "rango de temperatura del pronóstico no válido"
}
}
}
},
- "Light, gentle vibration": {
- "comment": "Description of subtle vibration pattern option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Vibración suave y ligera"
+ "Invalid location provided" : {
+ "comment" : "Weather error shown when a location cannot be used to fetch weather",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La ubicación proporcionada no es válida"
}
}
}
},
- "Likely": {
- "comment": "Prediction confidence level for when a reminder will next trigger",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Probable"
+ "Invalid notification settings" : {
+ "comment" : "Notification error for malformed preferences",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ajustes de notificación no válidos"
}
}
}
},
- "Limited": {
- "comment": "Visibility level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Limitada"
+ "Invalid response from weather service" : {
+ "comment" : "Weather error when the provider's response could not be understood",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Respuesta no válida del servicio meteorológico"
}
}
}
},
- "Loading current weather...": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cargando clima actual..."
+ "Invalid temperature range - missing min or max temperature" : {
+ "comment" : "Notification body fallback when a range trigger is missing its bounds",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Rango de temperatura no válido: falta la temperatura mínima o máxima"
}
}
}
},
- "Loading data summary": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cargando resumen de datos"
+ "Invalid temperature range configuration" : {
+ "comment" : "Notification body fallback when a range trigger is misconfigured",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Configuración de rango de temperatura no válida"
}
}
}
},
- "Loading location...": {
- "comment": "Placeholder shown before the user's location is resolved",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cargando ubicación..."
+ "Is my data synced to iCloud?" : {
+ "comment" : "FAQ question",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¿Mis datos se sincronizan con iCloud?"
}
}
}
},
- "Loading weather...": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cargando el clima..."
+ "It is 72°F and sunny at your selected location." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hace 72 °F y está soleado en tu ubicación seleccionada."
}
}
}
},
- "Loading...": {
- "comment": "Placeholder shown before weather data has loaded",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cargando..."
+ "It's %lld°F" : {
+ "comment" : "Preview notification body temperature clause for a temperature range reminder\nPreview notification body temperature clause for an exact temperature reminder",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Está a %lld°F"
}
}
}
},
- "Location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ubicación"
+ "It’s 72°F and sunny. A good time to get outside." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Está a 72 °F y soleado. Un buen momento para salir."
}
}
}
},
- "Location Access": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Acceso a la ubicación"
+ "Keychain operation failed with status %d" : {
+ "comment" : "Low-level Keychain error surfaced through the weather API key error path",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La operación del llavero falló con estado %d"
}
}
}
},
- "Location Actions": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Acciones de ubicación"
+ "Know when conditions are right" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Entérate cuando las condiciones sean las adecuadas"
}
}
}
},
- "Location Data": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Datos de ubicación"
+ "L %.0f°" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mín %.0f°"
}
}
}
},
- "Location Details": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Detalles de la ubicación"
+ "Laptop" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Laptop"
}
}
}
},
- "Location Name": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Nombre de la ubicación"
+ "Last updated: August 28, 2026" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Última actualización: 28 de agosto de 2026"
}
}
}
},
- "Location Search Failed": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Error en la búsqueda de ubicación"
+ "Last updated: August 31, 2026" : { "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Última actualización: 31 de agosto de 2026"
}
}
}
+
+
},
- "Location Unavailable": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ubicación no disponible"
+ "Last updated: January 30, 2026" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Última actualización: 30 de enero de 2026"
}
}
}
},
- "Location access is required for current-location weather reminders. You can also save manual locations. Coordinates are sent only to enabled weather providers to fetch forecasts.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "El acceso a la ubicación es necesario para los recordatorios basados en tu ubicación actual. También puedes guardar ubicaciones manuales. Las coordenadas se envían solo a los proveedores de clima activados para obtener pronósticos."
+ "Latitude" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Latitud"
}
}
}
},
- "Location accuracy is too low": {
- "comment": "Location error title",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La precisión de la ubicación es demasiado baja"
+ "Latitude must be between -90 and 90 degrees." : {
+ "comment" : "Validation error in the manual location entry form",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La latitud debe estar entre -90 y 90 grados."
}
}
}
},
- "Location actions": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Acciones de ubicación"
+ "Latitude ranges from -90 to 90. Longitude ranges from -180 to 180." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La latitud va de -90 a 90. La longitud va de -180 a 180."
}
}
}
},
- "Location data is too old": {
- "comment": "Location error title",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Los datos de ubicación son demasiado antiguos"
+ "Legal Basis" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Base legal"
}
}
}
},
- "Location permission denied": {
- "comment": "Location error title",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Permiso de ubicación denegado"
+ "Light" : {
+ "comment" : "Appearance mode option: always light",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Claro"
}
}
}
},
- "Location permission is required for weather data": {
- "comment": "Weather error when location access has not been granted",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Se requiere el permiso de ubicación para los datos meteorológicos"
+ "Light rain" : {
+ "comment" : "Precipitation description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Lluvia ligera"
}
}
}
},
- "Location request timed out": {
- "comment": "Location error title",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Se agotó el tiempo de espera de la solicitud de ubicación"
+ "Light snow" : {
+ "comment" : "Precipitation description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Nieve ligera"
}
}
}
},
- "Location request took too long to complete": {
- "comment": "Location error failure reason",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La solicitud de ubicación tardó demasiado en completarse"
+ "Light, gentle vibration" : {
+ "comment" : "Description of subtle vibration pattern option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Vibración suave y ligera"
}
}
}
},
- "Location services are disabled": {
- "comment": "Location error title",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Los servicios de ubicación están desactivados"
+ "Likely" : {
+ "comment" : "Prediction confidence level for when a reminder will next trigger",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Probable"
}
}
}
},
- "Location services are turned off in device settings": {
- "comment": "Location error failure reason",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Los servicios de ubicación están desactivados en los ajustes del dispositivo"
+ "Limited" : {
+ "comment" : "Visibility level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Limitada"
}
}
}
},
- "Location setup": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Configuración de ubicación"
+ "Loading current weather..." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cargando clima actual..."
}
}
}
},
- "Locations": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ubicaciones"
+ "Loading data summary" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cargando resumen de datos"
}
}
}
},
- "Locations Map": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mapa de ubicaciones"
+ "Loading location..." : {
+ "comment" : "Placeholder shown before the user's location is resolved",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cargando ubicación..."
}
}
}
},
- "Longitude": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Longitud"
+ "Loading weather..." : {
+ "comment" : "Progress label while the weather tab loads",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cargando el clima..."
}
}
}
},
- "Longitude must be between -180 and 180 degrees.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La longitud debe estar entre -180 y 180 grados."
+ "Loading..." : {
+ "comment" : "Placeholder shown before weather data has loaded",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cargando..."
}
}
}
},
- "Low": {
- "comment": "Reminder priority level\nUV index level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Baja"
+ "Location" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ubicación"
}
}
}
},
- "Low %lld°": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mín %lld°"
+ "Location Access" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Acceso a la ubicación"
}
}
}
},
- "Low chance": {
- "comment": "Trigger likelihood: 1-25% chance the reminder's conditions will occur",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Poca probabilidad"
+ "Location access is required for current-location weather reminders. You can also save manual locations. Coordinates are sent only to enabled weather providers to fetch forecasts." : {
+ "comment" : "FAQ answer",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "El acceso a la ubicación es necesario para los recordatorios basados en tu ubicación actual. También puedes guardar ubicaciones manuales. Las coordenadas se envían solo a los proveedores de clima activados para obtener pronósticos."
}
}
}
},
- "Low pressure": {
- "comment": "Barometric pressure level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Presión baja"
+ "Location accuracy is too low" : {
+ "comment" : "Location error title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La precisión de la ubicación es demasiado baja"
}
}
}
},
- "Mail is not configured on this device. You can copy the message and send it from another email app.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mail no está configurado en este dispositivo. Puedes copiar el mensaje y enviarlo desde otra app de correo."
+ "Location actions" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Acciones de ubicación"
}
}
}
},
- "Maintenance": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mantenimiento"
+ "Location Actions" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Acciones de ubicación"
}
}
}
},
- "Make sure notifications are enabled in Settings, your reminder is active, and the weather conditions match your trigger settings. Also check that you're not in quiet hours and haven't reached your daily notification limit.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Asegúrate de que las notificaciones estén activadas en Ajustes, de que tu recordatorio esté activo y de que las condiciones del clima coincidan con tu configuración. Comprueba también que no estés en horas de silencio y que no hayas alcanzado tu límite diario de notificaciones."
+ "Location Data" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Datos de ubicación"
}
}
}
},
- "Manage Reminders": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Administrar recordatorios"
+ "Location data is too old" : {
+ "comment" : "Location error title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Los datos de ubicación son demasiado antiguos"
}
}
}
},
- "Manual Entry": {
- "comment": "Location source name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Entrada manual"
+ "Location Details" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Detalles de la ubicación"
}
}
}
},
- "Manual Location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ubicación manual"
+ "Location Name" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Nombre de la ubicación"
}
}
}
},
- "Manual Location Entry": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Entrada manual de ubicación"
+ "Location permission denied" : {
+ "comment" : "Location error title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Permiso de ubicación denegado"
}
}
}
},
- "Mark Complete": {
- "comment": "Default notification action button title",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Marcar como completado"
+ "Location permission is required for weather data" : {
+ "comment" : "Weather error when location access has not been granted",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Se requiere el permiso de ubicación para los datos meteorológicos"
}
}
}
},
- "Mark Done": {
- "comment": "Notification action button",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Marcar como hecho"
+ "Location request timed out" : {
+ "comment" : "Location error title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Se agotó el tiempo de espera de la solicitud de ubicación"
}
}
}
},
- "Marketing Opt-Out": {
- "comment": "Privacy inquiry type",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Exclusión de marketing"
+ "Location request took too long to complete" : {
+ "comment" : "Location error failure reason",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La solicitud de ubicación tardó demasiado en completarse"
}
}
}
},
- "Message": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mensaje"
+ "Location Search Failed" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Error en la búsqueda de ubicación"
}
}
}
},
- "Minor": {
- "comment": "Weather alert severity level",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Menor"
+ "Location services are disabled" : {
+ "comment" : "Location error title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Los servicios de ubicación están desactivados"
}
}
}
},
- "Mint": {
- "comment": "Reminder color swatch name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Menta"
+ "Location services are turned off in device settings" : {
+ "comment" : "Location error failure reason",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Los servicios de ubicación están desactivados en los ajustes del dispositivo"
}
}
}
},
- "Mixed precipitation": {
- "comment": "Precipitation description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Precipitación mixta"
+ "Location setup" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Configuración de ubicación"
}
}
}
},
- "Moderate": {
- "comment": "Air quality index level description\nUV index level description\nWeather alert severity level",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Moderada"
+ "Location Unavailable" : {
+ "comment" : "Dashboard location label when no location can be determined",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ubicación no disponible"
}
}
}
},
- "Moderate chance": {
- "comment": "Trigger likelihood: 26-50% chance the reminder's conditions will occur",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Probabilidad moderada"
+ "Locations" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ubicaciones"
}
}
}
},
- "Modified": {
- "comment": "Reminder history action label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Modificado"
+ "Locations Map" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mapa de ubicaciones"
}
}
}
},
- "Monitor weather at your device location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Vigila el clima en la ubicación de tu dispositivo"
+ "Longitude" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Longitud"
}
}
}
},
- "Monitor weather at your device's current GPS location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Vigila el clima en la ubicación GPS actual de tu dispositivo"
+ "Longitude must be between -180 and 180 degrees." : {
+ "comment" : "Validation error in the manual location entry form",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La longitud debe estar entre -180 y 180 grados."
}
}
}
},
- "Monitoring": {
- "comment": "Status text shown on a reminder card while actively watching the weather",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Vigilando"
+ "Low" : {
+ "comment" : "Reminder priority level\nUV index level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Baja"
}
}
}
},
- "More options": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Más opciones"
+ "Low %lld°" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mín %lld°"
}
}
}
},
- "Morning": {
- "comment": "Time-of-day preference option label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mañana"
+ "low %lld° below %lld°" : {
+ "comment" : "Short clause describing a forecast day's low temperature versus target, used inside a comma-separated list",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "low %1$lld° below %2$lld°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "mínima %lld° por debajo de %lld°"
}
}
}
},
- "Move to an open area away from buildings and try again": {
- "comment": "Location error recovery suggestion",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Muévete a un área abierta lejos de edificios e inténtalo de nuevo"
+ "Low chance" : {
+ "comment" : "Trigger likelihood: 1-25% chance the reminder's conditions will occur",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Poca probabilidad"
}
}
}
},
- "Multiple conditions required": {
- "comment": "Trigger type description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Se requieren varias condiciones"
+ "Low pressure" : {
+ "comment" : "Barometric pressure level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Presión baja"
}
}
}
},
- "Music": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Música"
+ "Mail is not configured on this device. You can copy the message and send it from another email app." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mail no está configurado en este dispositivo. Puedes copiar el mensaje y enviarlo desde otra app de correo."
}
}
}
},
- "N": {
- "comment": "Wind direction abbreviation: North",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "N"
+ "Maintenance" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mantenimiento"
}
}
}
},
- "NE": {
- "comment": "Wind direction abbreviation: Northeast",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "NE"
+ "Make sure notifications are enabled in Settings, your reminder is active, and the weather conditions match your trigger settings. Also check that you're not in quiet hours and haven't reached your daily notification limit." : {
+ "comment" : "FAQ answer",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Asegúrate de que las notificaciones estén activadas en Ajustes, de que tu recordatorio esté activo y de que las condiciones del clima coincidan con tu configuración. Comprueba también que no estés en horas de silencio y que no hayas alcanzado tu límite diario de notificaciones."
}
}
}
},
- "NNE": {
- "comment": "Wind direction abbreviation: North-northeast",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "NNE"
+ "Manage Reminders" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Administrar recordatorios"
}
}
}
},
- "NNW": {
- "comment": "Wind direction abbreviation: North-northwest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "NNO"
+ "Manage Subscription" : {
+ "comment" : "Settings row that opens Apple's subscription management sheet",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Manage Subscription"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gestionar suscripción"
}
}
}
},
- "NW": {
- "comment": "Wind direction abbreviation: Northwest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "NO"
+ "Manual Entry" : {
+ "comment" : "Location source name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Entrada manual"
}
}
}
},
- "Nature": {
- "comment": "Notification sound option: nature-themed",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Naturaleza"
+ "Manual Location" : {
+ "comment" : "Dashboard location label for an unnamed manually entered location",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ubicación manual"
}
}
}
},
- "Network connection unavailable": {
- "comment": "Weather error shown when the device has no network connection",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Conexión de red no disponible"
+ "Manual Location Entry" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Entrada manual de ubicación"
}
}
}
},
- "Network error while getting location": {
- "comment": "Location error title",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Error de red al obtener la ubicación"
+ "Mark Complete" : {
+ "comment" : "Default notification action button title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Marcar como completado"
}
}
}
},
- "New Reminder": {
- "comment": "Fallback reminder title shown when the user hasn't entered one",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Nuevo recordatorio"
+ "Mark Done" : {
+ "comment" : "Notification action button",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Marcar como hecho"
}
}
}
},
- "New Task": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Nueva tarea"
+ "Marketing Opt-Out" : {
+ "comment" : "Privacy inquiry type",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Exclusión de marketing"
}
}
}
},
- "Next Ready": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Próximo listo"
+ "Message" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mensaje"
}
}
}
},
- "Next Trigger": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Próxima activación"
+ "Minor" : {
+ "comment" : "Weather alert severity level",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Menor"
}
}
}
},
- "Next ready reminder, %@": {
- "comment": "Accessibility label announcing the next ready reminder by name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Próximo recordatorio listo, %@"
+ "Mint" : {
+ "comment" : "Reminder color swatch name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Menta"
}
}
}
},
- "Next trigger, %@": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Próxima activación, %@"
+ "Mixed precipitation" : {
+ "comment" : "Precipitation description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Precipitación mixta"
}
}
}
},
- "No Active Alerts": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sin alertas activas"
+ "Moderate" : {
+ "comment" : "Air quality index level description\nUV index level description\nWeather alert severity level",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Moderada"
}
}
}
},
- "No Active Tasks": {
- "comment": "Empty state title when the user has no active weather tasks",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sin tareas activas"
+ "Moderate chance" : {
+ "comment" : "Trigger likelihood: 26-50% chance the reminder's conditions will occur",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Probabilidad moderada"
}
}
}
},
- "No Grouping": {
- "comment": "Notification grouping option: don't group notifications",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sin agrupar"
+ "Modified" : {
+ "comment" : "Reminder history action label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Modificado"
}
}
}
},
- "No Location Set": {
- "comment": "Displayed when the user has neither GPS nor a manual location configured",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sin ubicación establecida"
+ "Monitor weather at your device location" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Vigila el clima en la ubicación de tu dispositivo"
}
}
}
},
- "No Matches Yet": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Aún sin coincidencias"
+ "Monitor weather at your device's current GPS location" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Vigila el clima en la ubicación GPS actual de tu dispositivo"
}
}
}
},
- "No Rain": {
- "comment": "Weather condition type option label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sin lluvia"
+ "Monitoring" : {
+ "comment" : "Status text shown on a reminder card while actively watching the weather",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Vigilando"
}
}
}
},
- "No Ready Reminder": {
- "comment": "Compact surface title when no reminder is currently ready",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sin recordatorio listo"
+ "Monthly" : {
+ "comment" : "Name of the monthly Ad-Free plan shown in Settings",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Monthly"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mensual"
}
}
}
},
- "No Tasks Found": {
- "comment": "Empty state title when a search or filter matches no weather tasks",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se encontraron tareas"
+ "More options" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Más opciones"
}
}
}
},
- "No Tasks Yet": {
- "comment": "Empty state title when the user has no weather tasks",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Aún no hay tareas"
+ "Morning" : {
+ "comment" : "Time-of-day preference option label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mañana"
}
}
}
},
- "No active reminders": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sin recordatorios activos"
+ "Move to an open area away from buildings and try again" : {
+ "comment" : "Location error recovery suggestion",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Muévete a un área abierta lejos de edificios e inténtalo de nuevo"
}
}
}
},
- "No cities found": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se encontraron ciudades"
+ "Multiple conditions required" : {
+ "comment" : "Trigger type description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Se requieren varias condiciones"
}
}
}
},
- "No cities found. Try searching with a different spelling or include the state or country.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se encontraron ciudades. Intenta buscar con otra ortografía o incluye el estado o país."
+ "Music" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Música"
}
}
}
},
- "No condition set": {
- "comment": "Reminder detail status when no trigger condition is configured\nTrigger prediction summary when a reminder has no condition configured",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sin condición establecida"
+ "N" : {
+ "comment" : "Wind direction abbreviation: North",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "N"
}
}
}
},
- "No forecast day matches this reminder condition": {
- "comment": "Notification body fallback when no upcoming forecast day satisfies the reminder's condition",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ningún día del pronóstico coincide con la condición de este recordatorio"
+ "Nature" : {
+ "comment" : "Notification sound option: nature-themed",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Naturaleza"
}
}
}
},
- "No precipitation": {
- "comment": "Precipitation description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sin precipitación"
+ "NE" : {
+ "comment" : "Wind direction abbreviation: Northeast",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "NE"
}
}
}
},
- "No recent activity": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sin actividad reciente"
+ "Network connection unavailable" : {
+ "comment" : "Weather error shown when the device has no network connection",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Conexión de red no disponible"
}
}
}
},
- "No trigger condition set": {
- "comment": "Accessibility label clause when a reminder has no trigger condition configured",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sin condición de activación establecida"
+ "Network error while getting location" : {
+ "comment" : "Location error title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Error de red al obtener la ubicación"
}
}
}
},
- "No triggers expected in the next 7 days": {
- "comment": "Live prediction description shown when no trigger is expected in the forecast window",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se esperan activaciones en los próximos 7 días"
+ "New Reminder" : {
+ "comment" : "Fallback reminder title shown when the user hasn't entered one",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Nuevo recordatorio"
}
}
}
},
- "No vibration": {
- "comment": "Description of no-vibration pattern option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sin vibración"
+ "New Task" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Nueva tarea"
}
}
}
},
- "None": {
- "comment": "Notification sound option: no sound\nVibration pattern option: no vibration",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ninguno"
+ "Next Ready" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Próximo listo"
}
}
}
},
- "Normal": {
- "comment": "Barometric pressure level description\nReminder priority level",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Normal"
+ "Next ready reminder, %@" : {
+ "comment" : "Accessibility label announcing the next ready reminder by name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Próximo recordatorio listo, %@"
}
}
}
},
- "Not %@": {
- "comment": "Sky condition summary when excluding the listed conditions, e.g. 'Not rainy'",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No %@"
+ "Next Trigger" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Próxima activación"
}
}
}
},
- "Not Now": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ahora no"
+ "Next trigger, %@" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Próxima activación, %@"
}
}
}
},
- "Not Really": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "En realidad no"
+ "NNE" : {
+ "comment" : "Wind direction abbreviation: North-northeast",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "NNE"
}
}
}
},
- "Not enough history yet. Comparisons appear once SunHat has stored weather for this location.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Aún no hay suficiente historial. Las comparaciones aparecen una vez que SunHat haya almacenado datos del clima para esta ubicación."
+ "NNW" : {
+ "comment" : "Wind direction abbreviation: North-northwest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "NNO"
}
}
}
},
- "Not selected": {
- "comment": "Accessibility value for a toggled filter chip\nAccessibility value for the current-location card when not chosen",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No seleccionado"
+ "No active Ad-Free subscription is associated with your App Store account." : {
+ "comment" : "Alert message when restore finds nothing",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No active Ad-Free subscription is associated with your App Store account."
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No hay ninguna suscripción sin anuncios activa asociada a tu cuenta de App Store."
}
}
}
},
- "Not yet. Your reminders and preferences are stored locally on this device. iCloud sync is planned for a future update.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Todavía no. Tus recordatorios y preferencias se almacenan localmente en este dispositivo. La sincronización con iCloud está prevista para una futura actualización."
+ "No Active Alerts" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sin alertas activas"
}
}
}
},
- "Notes": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Notas"
+ "No active reminders" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sin recordatorios activos"
}
}
}
},
- "Notes (optional)": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Notas (opcional)"
+ "No Active Tasks" : {
+ "comment" : "Empty state title when the user has no active weather tasks",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sin tareas activas"
}
}
}
},
- "Nothing Ready Right Now": {
- "comment": "Empty state title when no active tasks currently match the weather",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Nada listo por ahora"
+ "No banner ads, anywhere in the app" : {
+ "comment" : "Paywall benefit row",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No banner ads, anywhere in the app"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sin anuncios de banner en ninguna parte de la app"
}
}
}
},
- "Notification Access": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Acceso a notificaciones"
+ "No cities found" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se encontraron ciudades"
}
}
}
},
- "Notification Failed": {
- "comment": "Reminder history action label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Notificación fallida"
+ "No cities found. Try searching with a different spelling or include the state or country." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se encontraron ciudades. Intenta buscar con otra ortografía o incluye el estado o país."
}
}
}
},
- "Notification Sent": {
- "comment": "Reminder history action label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Notificación enviada"
+ "No condition set" : {
+ "comment" : "Reminder detail status when no trigger condition is configured\nTrigger prediction summary when a reminder has no condition configured",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sin condición establecida"
}
}
}
},
- "Notification Title": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Título de la notificación"
+ "No forecast day matches this reminder condition" : {
+ "comment" : "Notification body fallback when no upcoming forecast day satisfies the reminder's condition",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ningún día del pronóstico coincide con la condición de este recordatorio"
}
}
}
},
- "Notification permissions are required for weather alerts": {
- "comment": "Error shown when the user denies notification permission needed for reminders",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Se requieren permisos de notificación para las alertas del clima"
+ "No Grouping" : {
+ "comment" : "Notification grouping option: don't group notifications",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sin agrupar"
}
}
}
},
- "Notification permissions were denied": {
- "comment": "Notification error when the system permission was denied",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Se denegaron los permisos de notificación"
+ "No Location Set" : {
+ "comment" : "Displayed when the user has neither GPS nor a manual location configured",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sin ubicación establecida"
}
}
}
},
- "Notification setup": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Configuración de notificaciones"
+ "No Matches Yet" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aún sin coincidencias"
}
}
}
},
- "Notifications": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Notificaciones"
+ "No precipitation" : {
+ "comment" : "Precipitation description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sin precipitación"
}
}
}
},
- "Notifications Are Off": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Las notificaciones están desactivadas"
+ "no precipitation expected" : {
+ "comment" : "Short clause listing one unsatisfied precipitation condition of a multi-condition reminder",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "no se espera precipitación"
}
}
}
},
- "Now": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ahora"
+ "no precipitation expected for the next %lldh" : {
+ "comment" : "Short clause: the N-hour dry window condition is satisfied",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "no se espera precipitación en las próximas %lldh"
}
}
}
},
- "Now: %@ • %@": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ahora: %@ • %@"
+ "no precipitation requirement" : {
+ "comment" : "Short clause for a reminder with no precipitation condition",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "sin requisito de precipitación"
}
}
}
},
- "OK": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Aceptar"
+ "No Purchases Found" : {
+ "comment" : "Alert title after a restore that found no Ad-Free subscription",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No Purchases Found"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se encontraron compras"
}
}
}
},
- "Off": {
- "comment": "Location access status",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Desactivado"
+ "No Rain" : {
+ "comment" : "Weather condition type option label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sin lluvia"
}
}
}
},
- "Open ${section} in SunHat": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Abrir ${section} en SunHat"
+ "No Ready Reminder" : {
+ "comment" : "Compact surface title when no reminder is currently ready",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sin recordatorio listo"
}
}
}
},
- "Open Settings": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Abrir Ajustes"
+ "No recent activity" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sin actividad reciente"
}
}
}
},
- "Open SunHat": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Abrir SunHat"
+ "no sky condition filter" : {
+ "comment" : "Short clause for a reminder with no sky condition filter",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "sin filtro de condición del cielo"
}
}
}
},
- "Open SunHat settings to manage weather reminder notifications.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Abre los ajustes de SunHat para administrar las notificaciones de recordatorios del clima."
+ "No Tasks Found" : {
+ "comment" : "Empty state title when a search or filter matches no weather tasks",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se encontraron tareas"
}
}
}
},
- "Open SunHat to a specific section.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Abre SunHat en una sección específica."
+ "No Tasks Yet" : {
+ "comment" : "Empty state title when the user has no weather tasks",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aún no hay tareas"
}
}
}
},
- "Open SunHat to create a new weather-triggered reminder.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Abre SunHat para crear un nuevo recordatorio activado por el clima."
+ "no time window" : {
+ "comment" : "Short clause for a reminder with no time-of-day condition",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "sin ventana horaria"
}
}
}
},
- "Open SunHat to review today's weather reminders.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Abre SunHat para revisar los recordatorios del clima de hoy."
+ "No trigger condition set" : {
+ "comment" : "Accessibility label clause when a reminder has no trigger condition configured",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sin condición de activación establecida"
}
}
}
},
- "Open SunHat to the next reminder that is ready or closest to being ready.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Abre SunHat en el próximo recordatorio que esté listo o más cerca de estarlo."
+ "No triggers expected in the next 7 days" : {
+ "comment" : "Live prediction description shown when no trigger is expected in the forecast window",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se esperan activaciones en los próximos 7 días"
}
}
}
},
- "OpenWeatherMap": {
- "comment": "Weather data provider name; product name, should not be translated",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "OpenWeatherMap"
+ "No vibration" : {
+ "comment" : "Description of no-vibration pattern option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sin vibración"
}
}
}
},
- "Opens task details.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Abre los detalles de la tarea."
+ "None" : {
+ "comment" : "Notification sound option: no sound\nVibration pattern option: no vibration",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ninguno"
}
}
}
},
- "Opt out of marketing communications": {
- "comment": "Privacy inquiry type description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Excluirme de las comunicaciones de marketing"
+ "Normal" : {
+ "comment" : "Barometric pressure level description\nReminder priority level",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Normal"
}
}
}
},
- "Optional backup provider": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Proveedor de respaldo opcional"
+ "Not %@" : {
+ "comment" : "Sky condition summary when excluding the listed conditions, e.g. 'Not rainy'",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No %@"
}
}
}
},
- "Orange": {
- "comment": "Reminder color swatch name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Naranja"
+ "Not enough history yet. Comparisons appear once SunHat has stored weather for this location." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aún no hay suficiente historial. Las comparaciones aparecen una vez que SunHat haya almacenado datos del clima para esta ubicación."
}
}
}
},
- "Other privacy-related questions": {
- "comment": "Privacy inquiry type description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Otras preguntas relacionadas con la privacidad"
+ "Not Now" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ahora no"
}
}
}
},
- "Outdoor Activities": {
- "comment": "Reminder category name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Actividades al aire libre"
+ "Not Really" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "En realidad no"
}
}
}
},
- "Outdoor Dining": {
- "comment": "Activity interest option: outdoor dining",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Comer al aire libre"
+ "Not selected" : {
+ "comment" : "Accessibility value for a toggled filter chip\nAccessibility value for the current-location card when not chosen",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No seleccionado"
}
}
}
},
- "Outdoor cleaning and washing tasks": {
- "comment": "Description of cleaning activity interest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tareas de limpieza y lavado al aire libre"
+ "Not yet. Your reminders and preferences are stored locally on this device. iCloud sync is planned for a future update." : {
+ "comment" : "FAQ answer",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Todavía no. Tus recordatorios y preferencias se almacenan localmente en este dispositivo. La sincronización con iCloud está prevista para una futura actualización."
}
}
}
},
- "Outdoor photo sessions and landscape photography": {
- "comment": "Description of photography activity interest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sesiones de fotos al aire libre y fotografía de paisajes"
+ "Notes" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Notas"
}
}
}
},
- "Outdoor sports and recreational activities": {
- "comment": "Description of sports activity interest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Deportes y actividades recreativas al aire libre"
+ "Notes (optional)" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Notas (opcional)"
}
}
}
},
- "Outdoor workouts, running, and fitness activities": {
- "comment": "Description of exercise activity interest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Entrenamientos al aire libre, correr y actividades de fitness"
+ "Nothing Ready Right Now" : {
+ "comment" : "Empty state title when no active tasks currently match the weather",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Nada listo por ahora"
}
}
}
},
- "Overcast": {
- "comment": "Weather condition name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cubierto"
+ "Notification Access" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Acceso a notificaciones"
}
}
}
},
- "PM2.5: %.1f μg/m³": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "PM2.5: %.1f μg/m³"
+ "Notification Failed" : {
+ "comment" : "Reminder history action label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Notificación fallida"
}
}
}
},
- "Painting": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Pintura"
+ "Notification permissions are required for weather alerts" : {
+ "comment" : "Error shown when the user denies notification permission needed for reminders",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Se requieren permisos de notificación para las alertas del clima"
}
}
}
},
- "Partly Cloudy": {
- "comment": "Sky condition option label\nWeather condition name\nWeather condition type option label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Parcialmente nublado"
+ "Notification permissions were denied" : {
+ "comment" : "Notification error when the system permission was denied",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Se denegaron los permisos de notificación"
}
}
}
},
- "Pause": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Pausar"
+ "Notification Sent" : {
+ "comment" : "Reminder history action label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Notificación enviada"
}
}
}
},
- "Pause This Reminder": {
- "comment": "Notification action button",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Pausar este recordatorio"
+ "Notification setup" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Configuración de notificaciones"
}
}
}
},
- "Pause Weather Reminders": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Pausar recordatorios del clima"
+ "Notification Title" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Título de la notificación"
}
}
}
},
- "Paused": {
- "comment": "Accessibility label clause: the reminder is paused\nReminder history action label\nReminder status",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "En pausa"
+ "Notifications" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Notificaciones"
}
}
}
},
- "Perfect gardening weather": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Clima perfecto para jardinería"
+ "Notifications Are Off" : {
+ "comment" : "Settings alert title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Las notificaciones están desactivadas"
}
}
}
},
- "Perfect weather for %@! ✨": {
- "comment": "Preview notification title shown while creating a reminder",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¡Clima perfecto para %@! ✨"
+ "now" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "ahora"
}
}
}
},
- "Pet Care": {
- "comment": "Activity interest option: pet care",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cuidado de mascotas"
+ "Now" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ahora"
}
}
}
},
- "Pet grooming and outdoor pet activities": {
- "comment": "Description of pet care activity interest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Aseo de mascotas y actividades al aire libre con mascotas"
+ "Now: %@ • %@" : {
+ "comment" : "Weather summary line appended to a reminder notification, e.g. 'Now: 72°F • Sunny'",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ahora: %@ • %@"
}
}
}
},
- "Photography": {
- "comment": "Activity interest option: photography\nReminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Fotografía"
+ "NW" : {
+ "comment" : "Wind direction abbreviation: Northwest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "NO"
}
}
}
},
- "Picnic": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Picnic"
+ "Off" : {
+ "comment" : "Location access status",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Desactivado"
}
}
}
},
- "Picnics, barbecues, and outdoor meal planning": {
- "comment": "Description of outdoor dining activity interest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Picnics, parrilladas y planificación de comidas al aire libre"
+ "OK" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aceptar"
}
}
}
},
- "Pink": {
- "comment": "Reminder color swatch name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Rosa"
+ "Open ${section} in SunHat" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Abrir ${section} en SunHat"
}
}
}
},
- "Plant care, yard work, and outdoor gardening activities": {
- "comment": "Description of gardening activity interest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cuidado de plantas, trabajo de jardín y actividades de jardinería al aire libre"
+ "Open Settings" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Abrir Ajustes"
}
}
}
},
- "Play sound": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Reproducir sonido"
+ "Open SunHat" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Abrir SunHat"
}
}
}
},
- "Please enter a location name.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Introduce un nombre de ubicación."
+ "Open SunHat settings to manage weather reminder notifications." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Abre los ajustes de SunHat para administrar las notificaciones de recordatorios del clima."
}
}
}
},
- "Please enter valid numeric coordinates.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Introduce coordenadas numéricas válidas."
+ "Open SunHat to a specific section." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Abre SunHat en una sección específica."
}
}
}
},
- "Please provide details about your privacy request. Include any specific data or timeframes if relevant.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Proporciona detalles sobre tu solicitud de privacidad. Incluye datos o plazos específicos si son relevantes."
+ "Open SunHat to create a new weather-triggered reminder." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Abre SunHat para crear un nuevo recordatorio activado por el clima."
}
}
}
},
- "Poor": {
- "comment": "Visibility level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mala"
+ "Open SunHat to review today's weather reminders." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Abre SunHat para revisar los recordatorios del clima de hoy."
}
}
}
},
- "Popular Cities": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ciudades populares"
+ "Open SunHat to the next reminder that is ready or closest to being ready." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Abre SunHat en el próximo recordatorio que esté listo o más cerca de estarlo."
}
}
}
},
- "Possibly": {
- "comment": "Prediction confidence level for when a reminder will next trigger",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Posiblemente"
+ "Opens task details." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Abre los detalles de la tarea."
}
}
}
},
- "Postpone": {
- "comment": "Notification action button",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Posponer"
+ "OpenWeatherMap" : {
+ "comment" : "Weather data provider name; product name, should not be translated",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "OpenWeatherMap"
}
}
}
},
- "Postpone Reminder": {
- "comment": "Notification action button",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Posponer recordatorio"
+ "Opt out of marketing communications" : {
+ "comment" : "Privacy inquiry type description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Excluirme de las comunicaciones de marketing"
}
}
}
},
- "Preferences and notification settings": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Preferencias y ajustes de notificación"
+ "Optional backup provider" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Proveedor de respaldo opcional"
}
}
}
},
- "Preparing export": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Preparando exportación"
+ "Orange" : {
+ "comment" : "Reminder color swatch name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Naranja"
}
}
}
},
- "Primary forecast provider": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Proveedor principal de pronósticos"
+ "Other privacy-related questions" : {
+ "comment" : "Privacy inquiry type description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Otras preguntas relacionadas con la privacidad"
}
}
}
},
- "Privacy": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Privacidad"
+ "Outdoor Activities" : {
+ "comment" : "Reminder category name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Actividades al aire libre"
}
}
}
},
- "Privacy Information": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Información de privacidad"
+ "Outdoor cleaning and washing tasks" : {
+ "comment" : "Description of cleaning activity interest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tareas de limpieza y lavado al aire libre"
}
}
}
},
- "Privacy Inquiry - %@": {
- "comment": "Pre-filled subject line of the privacy-request email the app composes",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Consulta de privacidad - %@"
+ "Outdoor Dining" : {
+ "comment" : "Activity interest option: outdoor dining",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Comer al aire libre"
}
}
}
},
- "Privacy Inquiry - SunHat App": {
- "comment": "Pre-filled subject line of the privacy-request email the app composes",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Consulta de privacidad - App SunHat"
+ "Outdoor photo sessions and landscape photography" : {
+ "comment" : "Description of photography activity interest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sesiones de fotos al aire libre y fotografía de paisajes"
}
}
}
},
- "Privacy Inquiry Type: %@\n%@\nYour Inquiry:\n%@\n\nContact Information:\nEmail: %@\n\nApp Information:\n- App: SunHat\n- Version: %@ (%@)\n- Platform: iOS\n- Date: %@\n\nThank you for contacting our Data Protection Officer. We will respond within 30 days as required by law.": {
- "comment": "Pre-filled body of the GDPR/CCPA privacy-request email the app composes; keep the field labels (Privacy Inquiry Type, Your Inquiry, Contact Information, Email, App Information, App, Version, Platform, Date) and the '- ' bullet formatting",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Privacy Inquiry Type: %1$@\n%2$@\nYour Inquiry:\n%3$@\n\nContact Information:\nEmail: %4$@\n\nApp Information:\n- App: SunHat\n- Version: %5$@ (%6$@)\n- Platform: iOS\n- Date: %7$@\n\nThank you for contacting our Data Protection Officer. We will respond within 30 days as required by law."
+ "Outdoor sports and recreational activities" : {
+ "comment" : "Description of sports activity interest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Deportes y actividades recreativas al aire libre"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tipo de consulta de privacidad: %@\n%@\nTu consulta:\n%@\n\nInformación de contacto:\nCorreo: %@\n\nInformación de la app:\n- App: SunHat\n- Versión: %@ (%@)\n- Plataforma: iOS\n- Fecha: %@\n\nGracias por contactar a nuestro Delegado de Protección de Datos. Responderemos dentro de 30 días según lo exige la ley."
+ }
+ }
+ },
+ "Outdoor workouts, running, and fitness activities" : {
+ "comment" : "Description of exercise activity interest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Entrenamientos al aire libre, correr y actividades de fitness"
}
}
}
},
- "Privacy Policy": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Política de privacidad"
+ "outside notification time window" : {
+ "comment" : "Short clause listing one unsatisfied time-of-day condition of a multi-condition reminder",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "fuera de la ventana horaria de notificación"
}
}
}
},
- "Progress, final step, step 4 of 4": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Progreso, paso final, paso 4 de 4"
+ "Overcast" : {
+ "comment" : "Weather condition name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cubierto"
}
}
}
},
- "Progress, step 2 of 4": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Progreso, paso 2 de 4"
+ "Painting" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pintura"
}
}
}
},
- "Progress, step 3 of 4": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Progreso, paso 3 de 4"
+ "Partly Cloudy" : {
+ "comment" : "Sky condition option label\nWeather condition name\nWeather condition type option label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Parcialmente nublado"
}
}
}
},
- "Projects": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Proyectos"
+ "Pause" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pausar"
}
}
}
},
- "Prominent": {
- "comment": "Vibration pattern option: strong noticeable vibration",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Pronunciada"
+ "Pause This Reminder" : {
+ "comment" : "Notification action button",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pausar este recordatorio"
}
}
}
},
- "Providing your email helps us respond more efficiently. This is optional but recommended for faster resolution.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Proporcionar tu correo nos ayuda a responder con más eficiencia. Es opcional, pero se recomienda para una resolución más rápida."
+ "Pause Weather Reminders" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pausar recordatorios del clima"
}
}
}
},
- "Pull down to refresh when weather access is available": {
- "comment": "Accessibility value hint shown when weather data isn't available yet",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Desliza hacia abajo para actualizar cuando el acceso al clima esté disponible"
+ "Paused" : {
+ "comment" : "Accessibility label clause: the reminder is paused\nReminder history action label\nReminder status",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "En pausa"
}
}
}
},
- "Pull down to refresh when weather access is available.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Desliza hacia abajo para actualizar cuando el acceso al clima esté disponible."
+ "Perfect gardening weather" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Clima perfecto para jardinería"
}
}
}
},
- "Pull to refresh to get updated weather data": {
- "comment": "Weather error recovery suggestion",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Desliza para actualizar y obtener datos meteorológicos actualizados"
+ "Perfect weather for %@! ✨" : {
+ "comment" : "Preview notification title shown while creating a reminder",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¡Clima perfecto para %@! ✨"
}
}
}
},
- "Purple": {
- "comment": "Reminder color swatch name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Morado"
+ "Pet Care" : {
+ "comment" : "Activity interest option: pet care",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cuidado de mascotas"
}
}
}
},
- "Questions about how we process your data": {
- "comment": "Privacy inquiry type description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Preguntas sobre cómo procesamos tus datos"
+ "Pet grooming and outdoor pet activities" : {
+ "comment" : "Description of pet care activity interest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aseo de mascotas y actividades al aire libre con mascotas"
}
}
}
},
- "Quiet Hours": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Horas de silencio"
+ "Photography" : {
+ "comment" : "Activity interest option: photography\nReminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Fotografía"
}
}
}
},
- "Rain": {
- "comment": "Precipitation description\nWeather condition name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Lluvia"
+ "Picnic" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Picnic"
}
}
}
},
- "Rainy": {
- "comment": "Sky condition option label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Lluvioso"
+ "Picnics, barbecues, and outdoor meal planning" : {
+ "comment" : "Description of outdoor dining activity interest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Picnics, parrilladas y planificación de comidas al aire libre"
}
}
}
},
- "Range": {
- "comment": "Trigger type name: temperature range",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Rango"
+ "Pink" : {
+ "comment" : "Reminder color swatch name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Rosa"
}
}
}
},
- "Range:": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Rango:"
+ "Plant care, yard work, and outdoor gardening activities" : {
+ "comment" : "Description of gardening activity interest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cuidado de plantas, trabajo de jardín y actividades de jardinería al aire libre"
}
}
}
},
- "Rate limit exceeded. Please try again later": {
- "comment": "Weather error when no retry countdown is available",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Se superó el límite de solicitudes. Inténtalo de nuevo más tarde"
+ "Play sound" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Reproducir sonido"
}
}
}
},
- "Rate limit exceeded. Try again in %lld seconds": {
- "comment": "Weather error with a countdown before the next request is allowed",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Se superó el límite de solicitudes. Inténtalo de nuevo en %lld segundos"
+ "Please enter a location name." : {
+ "comment" : "Validation error in the manual location entry form",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Introduce un nombre de ubicación."
}
}
}
},
- "Reading": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Lectura"
+ "Please enter valid numeric coordinates." : {
+ "comment" : "Validation error in the manual location entry form",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Introduce coordenadas numéricas válidas."
}
}
}
},
- "Ready Now": {
- "comment": "Dashboard section title for tasks matching the current forecast",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Listo ahora"
+ "Please provide details about your privacy request. Include any specific data or timeframes if relevant." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Proporciona detalles sobre tu solicitud de privacidad. Incluye datos o plazos específicos si son relevantes."
}
}
}
},
- "Ready when the forecast is right.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Listo cuando el pronóstico sea el adecuado."
+ "PM2.5: %.1f μg/m³" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "PM2.5: %.1f μg/m³"
}
}
}
},
- "Ready when the weather matches.": {
- "comment": "Compact surface summary when a reminder has no specific trigger condition",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Listo cuando el clima coincida."
+ "Poor" : {
+ "comment" : "Visibility level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mala"
}
}
}
},
- "Red": {
- "comment": "Reminder color swatch name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Rojo"
+ "Popular Cities" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ciudades populares"
}
}
}
},
- "Remind Later": {
- "comment": "Notification action button",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordar más tarde"
+ "Possibly" : {
+ "comment" : "Prediction confidence level for when a reminder will next trigger",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Posiblemente"
}
}
}
},
- "Remind me unless it's any of these:": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Avísame a menos que sea alguna de estas:"
+ "Postpone" : {
+ "comment" : "Notification action button",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Posponer"
}
}
}
},
- "Remind me when it's any of these:": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Avísame cuando sea alguna de estas:"
+ "Postpone Reminder" : {
+ "comment" : "Notification action button",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Posponer recordatorio"
}
}
}
},
- "Reminder": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorio"
+ "precipitation expected" : {
+ "comment" : "Short clause listing one satisfied precipitation condition of a multi-condition reminder\nShort clause listing one unsatisfied dry-weather condition of a multi-condition reminder",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "se espera precipitación"
}
}
}
},
- "Reminder %@: %@ likelihood to trigger": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Reminder %1$@: %2$@ likelihood to trigger"
+ "precipitation expected within the %lldh window" : {
+ "comment" : "Short clause: rain/snow is forecast within the N-hour dry window, so the condition fails",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "se espera precipitación dentro de la ventana de %lldh"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorio %@: %@ de probabilidad de activarse"
+ }
+ }
+ },
+ "precipitation now, %lldh dry requirement not met" : {
+ "comment" : "Short clause: it is currently raining/snowing so the N-hour dry window condition fails",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "hay precipitación ahora, no se cumple el requisito seco de %lldh"
}
}
}
},
- "Reminder history and related records": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Historial de recordatorios y registros relacionados"
+ "Preferences and notification settings" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Preferencias y ajustes de notificación"
}
}
}
},
- "Reminder marked as complete": {
- "comment": "Reminder history timeline entry",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorio marcado como completado"
+ "Preferred Delivery Time" : { "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hora de entrega preferida"
}
}
}
+
+
},
- "Reminder notes": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Notas del recordatorio"
+ "Preparing export" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Preparando exportación"
}
}
}
},
- "Reminder paused": {
- "comment": "Reminder history timeline entry",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorio en pausa"
+ "Primary forecast provider" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Proveedor principal de pronósticos"
}
}
}
},
- "Reminder resumed": {
- "comment": "Reminder history timeline entry",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorio reanudado"
+ "Privacy" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Privacidad"
}
}
}
},
- "Reminder skipped by user": {
- "comment": "Reminder history timeline entry",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorio omitido por el usuario"
+ "Privacy Information" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Información de privacidad"
}
}
}
},
- "Reminder title": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Título del recordatorio"
+ "Privacy Inquiry - %@" : {
+ "comment" : "Pre-filled subject line of the privacy-request email the app composes",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Consulta de privacidad - %@"
}
}
}
},
- "Reminder triggered": {
- "comment": "Reminder history timeline entry",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorio activado"
+ "Privacy Inquiry - SunHat App" : {
+ "comment" : "Pre-filled subject line of the privacy-request email the app composes",
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Consulta de privacidad - App SunHat"
}
}
}
},
- "Reminder updated": {
- "comment": "Reminder history timeline entry",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "translated",
- "value": "Reminder updated"
+ "Privacy Inquiry Type: %@\n%@\nYour Inquiry:\n%@\n\nContact Information:\nEmail: %@\n\nApp Information:\n- App: SunHat\n- Version: %@ (%@)\n- Platform: iOS\n- Date: %@\n\nThank you for contacting our Data Protection Officer. We will respond within 30 days as required by law." : {
+ "comment" : "Pre-filled body of the GDPR/CCPA privacy-request email the app composes; keep the field labels (Privacy Inquiry Type, Your Inquiry, Contact Information, Email, App Information, App, Version, Platform, Date) and the '- ' bullet formatting",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Privacy Inquiry Type: %1$@\n%2$@\nYour Inquiry:\n%3$@\n\nContact Information:\nEmail: %4$@\n\nApp Information:\n- App: SunHat\n- Version: %5$@ (%6$@)\n- Platform: iOS\n- Date: %7$@\n\nThank you for contacting our Data Protection Officer. We will respond within 30 days as required by law."
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorio actualizado"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tipo de consulta de privacidad: %@\n%@\nTu consulta:\n%@\n\nInformación de contacto:\nCorreo: %@\n\nInformación de la app:\n- App: SunHat\n- Versión: %@ (%@)\n- Plataforma: iOS\n- Fecha: %@\n\nGracias por contactar a nuestro Delegado de Protección de Datos. Responderemos dentro de 30 días según lo exige la ley."
}
}
}
},
- "Reminders": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorios"
+ "Privacy Policy" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Política de privacidad"
}
}
}
},
- "Reminders & Preferences": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorios y preferencias"
+ "Progress, final step, step 4 of 4" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Progreso, paso final, paso 4 de 4"
}
}
}
},
- "Reminders and trigger conditions": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorios y condiciones de activación"
+ "Progress, step 2 of 4" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Progreso, paso 2 de 4"
}
}
}
},
- "Remove from Favorites": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Quitar de favoritos"
+ "Progress, step 3 of 4" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Progreso, paso 3 de 4"
}
}
}
},
- "Rename": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cambiar nombre"
+ "Projects" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Proyectos"
}
}
}
},
- "Report a security or privacy concern": {
- "comment": "Privacy inquiry type description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Reportar un problema de seguridad o privacidad"
+ "Prominent" : {
+ "comment" : "Vibration pattern option: strong noticeable vibration",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pronunciada"
}
}
}
},
- "Request a copy of all data we have about you": {
- "comment": "Privacy inquiry type description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Solicitar una copia de todos los datos que tenemos sobre ti"
+ "Providing your email helps us respond more efficiently. This is optional but recommended for faster resolution." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Proporcionar tu correo nos ayuda a responder con más eficiencia. Es opcional, pero se recomienda para una resolución más rápida."
}
}
}
},
- "Request deletion of all your personal data": {
- "comment": "Privacy inquiry type description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Solicitar la eliminación de todos tus datos personales"
+ "Pull down to refresh when weather access is available" : {
+ "comment" : "Accessibility value hint shown when weather data isn't available yet",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Desliza hacia abajo para actualizar cuando el acceso al clima esté disponible"
}
}
}
},
- "Requesting Location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Solicitando ubicación"
+ "Pull down to refresh when weather access is available." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Desliza hacia abajo para actualizar cuando el acceso al clima esté disponible."
}
}
}
},
- "Requesting Permission": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Solicitando permiso"
+ "Pull to refresh to get updated weather data" : {
+ "comment" : "Weather error recovery suggestion",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Desliza para actualizar y obtener datos meteorológicos actualizados"
}
}
}
},
- "Reschedule": {
- "comment": "Notification action button",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Reprogramar"
+ "Purchases Restored" : {
+ "comment" : "Alert title after a successful restore that found an Ad-Free subscription",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Purchases Restored"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Compras restauradas"
}
}
}
},
- "Reset": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Restablecer"
+ "Purple" : {
+ "comment" : "Reminder color swatch name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Morado"
}
}
}
},
- "Reset All Settings": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Restablecer todos los ajustes"
+ "Questions about how we process your data" : {
+ "comment" : "Privacy inquiry type description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Preguntas sobre cómo procesamos tus datos"
}
}
}
},
- "Reset Settings": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Restablecer ajustes"
+ "Quiet Hours" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Horas de silencio"
}
}
}
},
- "Respect Quiet Hours": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Respetar horas de silencio"
+ "Quiet hours respected" : { "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Se respetan las horas de silencio"
}
}
}
+
+
},
- "Response Time": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tiempo de respuesta"
+ "Rain" : {
+ "comment" : "Precipitation description\nWeather condition name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Lluvia"
}
}
}
},
- "Resume": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Reanudar"
+ "rain expected" : {
+ "comment" : "Short clause listing one satisfied rain condition of a multi-condition reminder",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "se espera lluvia"
}
}
}
},
- "Resumed": {
- "comment": "Reminder history action label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Reanudado"
+ "rain not expected" : {
+ "comment" : "Short clause listing one unsatisfied rain condition of a multi-condition reminder",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "no se espera lluvia"
}
}
}
},
- "Retry": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Reintentar"
+ "Rainy" : {
+ "comment" : "Sky condition option label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Lluvioso"
}
}
}
},
- "Review SunHat": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Calificar SunHat"
+ "Range" : {
+ "comment" : "Trigger type name: temperature range",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Rango"
}
}
}
},
- "Review SunHat?": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¿Calificar SunHat?"
+ "Range:" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Rango:"
}
}
}
},
- "Reviews help more people discover weather-triggered reminders.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Las reseñas ayudan a más personas a descubrir los recordatorios activados por el clima."
+ "Rate limit exceeded. Please try again later" : {
+ "comment" : "Weather error when no retry countdown is available",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Se superó el límite de solicitudes. Inténtalo de nuevo más tarde"
}
}
}
},
- "Rhythmic pulse pattern": {
- "comment": "Description of heartbeat vibration pattern option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Patrón de pulso rítmico"
+ "Rate limit exceeded. Try again in %lld seconds" : {
+ "comment" : "Weather error with a countdown before the next request is allowed",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Se superó el límite de solicitudes. Inténtalo de nuevo en %lld segundos"
}
}
}
},
- "Running": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Correr"
+ "Reading" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Lectura"
}
}
}
},
- "S": {
- "comment": "Wind direction abbreviation: South",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "S"
+ "Ready Now" : {
+ "comment" : "Dashboard section title for tasks matching the current forecast",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Listo ahora"
}
}
}
},
- "SE": {
- "comment": "Wind direction abbreviation: Southeast",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SE"
+ "Ready when the forecast is right." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Listo cuando el pronóstico sea el adecuado."
}
}
}
},
- "SOS": {
- "comment": "Vibration pattern option: emergency SOS pattern",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SOS"
+ "Ready when the weather matches." : {
+ "comment" : "Compact surface summary when a reminder has no specific trigger condition",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Listo cuando el clima coincida."
}
}
}
},
- "SSE": {
- "comment": "Wind direction abbreviation: South-southeast",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SSE"
+ "Red" : {
+ "comment" : "Reminder color swatch name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Rojo"
}
}
}
},
- "SSW": {
- "comment": "Wind direction abbreviation: South-southwest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SSO"
+ "Remind Later" : {
+ "comment" : "Notification action button",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordar más tarde"
}
}
}
},
- "SW": {
- "comment": "Wind direction abbreviation: Southwest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SO"
+ "Remind me unless it's any of these:" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Avísame a menos que sea alguna de estas:"
}
}
}
},
- "Sailing": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Navegar"
+ "Remind me when it's any of these:" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Avísame cuando sea alguna de estas:"
}
}
}
},
- "Save": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Guardar"
+ "Reminder" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorio"
}
}
}
},
- "Saved locations": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ubicaciones guardadas"
+ "Reminder %@: %@ likelihood to trigger" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Reminder %1$@: %2$@ likelihood to trigger"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorio %@: %@ de probabilidad de activarse"
}
}
}
},
- "Saving": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Guardando"
+ "Reminder history and related records" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Historial de recordatorios y registros relacionados"
}
}
}
},
- "Saving preferences": {
- "comment": "Accessibility label while onboarding preferences are being saved",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Guardando preferencias"
+ "Reminder marked as complete" : {
+ "comment" : "Reminder history timeline entry",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorio marcado como completado"
}
}
}
},
- "Search": {
- "comment": "Location source name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Buscar"
+ "Reminder notes" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Notas del recordatorio"
}
}
}
},
- "Search Error": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Error de búsqueda"
+ "Reminder paused" : {
+ "comment" : "Reminder history timeline entry",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorio en pausa"
}
}
}
},
- "Search Results": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Resultados de búsqueda"
+ "Reminder resumed" : {
+ "comment" : "Reminder history timeline entry",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorio reanudado"
}
}
}
},
- "Search by city, neighborhood, airport, or landmark.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Busca por ciudad, barrio, aeropuerto o punto de referencia."
+ "Reminder skipped by user" : {
+ "comment" : "Reminder history timeline entry",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorio omitido por el usuario"
}
}
}
},
- "Search for Your City": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Busca tu ciudad"
+ "Reminder title" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Título del recordatorio"
}
}
}
},
- "Search for a location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Buscar una ubicación"
+ "Reminder triggered" : {
+ "comment" : "Reminder history timeline entry",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorio activado"
}
}
}
},
- "Search for locations": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Buscar ubicaciones"
+ "Reminder updated" : {
+ "comment" : "Reminder history timeline entry",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Reminder updated"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorio actualizado"
}
}
}
},
- "Search for this city": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Buscar esta ciudad"
+ "Reminders" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorios"
}
}
}
},
- "Search locations": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Buscar ubicaciones"
+ "Reminders & Preferences" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorios y preferencias"
}
}
}
},
- "Search tasks": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Buscar tareas"
+ "Reminders and trigger conditions" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorios y condiciones de activación"
}
}
}
},
- "Search tasks...": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Buscar tareas..."
+"Remove every ad from SunHat with a monthly or annual plan. Already subscribed on another device or a previous install? Restore Purchases brings Ad-Free back." : {
+ "comment" : "Settings footer shown to non-subscribers",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Remove every ad from SunHat with a monthly or annual plan. Already subscribed on another device or a previous install? Restore Purchases brings Ad-Free back."
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Elimina todos los anuncios de SunHat con un plan mensual o anual. ¿Ya te suscribiste en otro dispositivo o en una instalación anterior? Restaurar compras recupera la versión sin anuncios."
}
}
}
},
- "Search tips": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Consejos de búsqueda"
+ "Remove from Favorites" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Quitar de favoritos"
}
}
}
},
- "Search tips: Include state or country for better results. Try Portland, OR instead of just Portland. Use common city names in English.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Consejos de búsqueda: incluye el estado o país para obtener mejores resultados. Prueba \"Portland, OR\" en lugar de solo \"Portland\". Usa nombres de ciudades comunes."
+ "Removes all ads from SunHat." : {
+ "comment" : "Paywall subtitle",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Removes all ads from SunHat."
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Elimina todos los anuncios de SunHat."
}
}
}
},
- "Searching": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Buscando"
+ "Rename" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cambiar nombre"
}
}
}
},
- "Searching for cities": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Buscando ciudades"
+ "Renews" : {
+ "comment" : "Settings row label for the next Ad-Free renewal date",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renews"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Se renueva"
}
}
}
},
- "Searching for cities...": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Buscando ciudades..."
+ "Report a security or privacy concern" : {
+ "comment" : "Privacy inquiry type description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Reportar un problema de seguridad o privacidad"
}
}
}
},
- "Searching...": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Buscando..."
+ "Request a copy of all data we have about you" : {
+ "comment" : "Privacy inquiry type description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Solicitar una copia de todos los datos que tenemos sobre ti"
}
}
}
},
- "Seasonal": {
- "comment": "Trigger type name: seasonal marker",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Estacional"
+ "Request deletion of all your personal data" : {
+ "comment" : "Privacy inquiry type description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Solicitar la eliminación de todos tus datos personales"
}
}
}
},
- "Seasonal Tasks": {
- "comment": "Reminder category name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tareas estacionales"
+ "Requesting Location" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Solicitando ubicación"
}
}
}
},
- "Seasonal marker evaluation not yet fully implemented with ModelActor": {
- "comment": "Internal placeholder notification body for a trigger type that is not yet implemented",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La evaluación de marcadores estacionales aún no está implementada por completo"
+ "Requesting Permission" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Solicitando permiso"
}
}
}
},
- "Section": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sección"
+ "Reschedule" : {
+ "comment" : "Notification action button",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Reprogramar"
}
}
}
},
- "Security Concern": {
- "comment": "Privacy inquiry type",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Problema de seguridad"
+ "Reset" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Restablecer"
}
}
}
},
- "Select Location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Seleccionar ubicación"
+ "Reset All Settings" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Restablecer todos los ajustes"
}
}
}
},
- "Select Your City": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Selecciona tu ciudad"
+ "Reset Settings" : {
+ "comment" : "Settings alert title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Restablecer ajustes"
}
}
}
},
- "Select location, currently %@": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Seleccionar ubicación, actualmente %@"
+ "Respect Quiet Hours" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Respetar horas de silencio"
}
}
}
},
- "Select the type of privacy inquiry or request you'd like to make.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Selecciona el tipo de consulta o solicitud de privacidad que deseas hacer."
+ "Response Time" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tiempo de respuesta"
}
}
}
},
- "Select this city for weather data": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Selecciona esta ciudad para obtener datos meteorológicos"
+ "Restore Failed" : {
+ "comment" : "Alert title when restoring purchases fails",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Restore Failed"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Error al restaurar"
}
}
}
},
- "Selected": {
- "comment": "Accessibility value for a toggled filter chip\nAccessibility value for the current-location card when chosen",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Seleccionado"
+ "Restore Purchases" : {
+ "comment" : "Settings row that restores a previous Ad-Free purchase",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Restore Purchases"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Restaurar compras"
}
}
}
},
- "Send": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Enviar"
+ "Resume" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Reanudar"
}
}
}
},
- "Send Email Inquiry": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Enviar consulta por correo"
+ "Resumed" : {
+ "comment" : "Reminder history action label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Reanudado"
}
}
}
},
- "Send Feedback": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Enviar comentarios"
+ "Retry" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Reintentar"
}
}
}
},
- "Set up a mail account, or email %@ directly.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Configura una cuenta de correo o escribe directamente a %@."
+ "Review SunHat" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Calificar SunHat"
}
}
}
},
- "Set up a mail account, or email %@ directly. You can also use Copy Email Template.": {
- "comment": "Alert message",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Configura una cuenta de correo o escribe directamente a %@. También puedes usar Copiar plantilla de correo."
+ "Review SunHat?" : {
+ "comment" : "Lifecycle prompt alert title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¿Calificar SunHat?"
}
}
}
},
- "Settings": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ajustes"
+ "Reviews help more people discover weather-triggered reminders." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Las reseñas ayudan a más personas a descubrir los recordatorios activados por el clima."
}
}
}
},
- "Severe": {
- "comment": "Weather alert severity level",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Severo"
+ "Rhythmic pulse pattern" : {
+ "comment" : "Description of heartbeat vibration pattern option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Patrón de pulso rítmico"
}
}
}
},
- "Share": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Compartir"
+ "Running" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Correr"
}
}
}
},
- "Share Reminder": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Compartir recordatorio"
+ "S" : {
+ "comment" : "Wind direction abbreviation: South",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "S"
}
}
}
},
- "Share as Text": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Compartir como texto"
+ "Sailing" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Navegar"
}
}
}
},
- "Shopping": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Compras"
+ "Same forecasts, reminders & alerts" : {
+ "comment" : "Paywall benefit row",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Same forecasts, reminders & alerts"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Los mismos pronósticos, recordatorios y alertas"
}
}
}
},
- "Show Next Ready Reminder": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mostrar próximo recordatorio listo"
+ "Save" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Guardar"
}
}
}
},
- "Show Previews": {
- "comment": "Lock screen behavior option: show full notification previews",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mostrar vistas previas"
+ "Saved locations" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ubicaciones guardadas"
}
}
}
},
- "Show badge": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mostrar insignia"
+ "Saving" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Guardando"
}
}
}
},
- "Show each notification separately": {
- "comment": "Description of no-grouping notification option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mostrar cada notificación por separado"
+ "Saving preferences" : {
+ "comment" : "Accessibility label while onboarding preferences are being saved",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Guardando preferencias"
}
}
}
},
- "Show forecast details": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mostrar detalles del pronóstico"
+ "SE" : {
+ "comment" : "Wind direction abbreviation: Southeast",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SE"
}
}
}
},
- "Show full notification content on lock screen": {
- "comment": "Description of show-previews lock screen option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mostrar el contenido completo de la notificación en la pantalla de bloqueo"
+ "Search" : {
+ "comment" : "Location source name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Buscar"
}
}
}
},
- "Show notification but hide content details": {
- "comment": "Description of hide-details lock screen option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mostrar la notificación pero ocultar los detalles del contenido"
+ "Search by city, neighborhood, airport, or landmark." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Busca por ciudad, barrio, aeropuerto o punto de referencia."
}
}
}
},
- "Silent Window": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ventana silenciosa"
+ "Search Error" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Error de búsqueda"
}
}
}
},
- "Skip Today": {
- "comment": "Notification action button",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Omitir hoy"
+ "Search for a location" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Buscar una ubicación"
}
}
}
},
- "Skip for now": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Omitir por ahora"
+ "Search for locations" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Buscar ubicaciones"
}
}
}
},
- "Skip onboarding": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Omitir configuración inicial"
+ "Search for this city" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Buscar esta ciudad"
}
}
}
},
- "Skipped": {
- "comment": "Reminder history action label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Omitido"
+ "Search for Your City" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Busca tu ciudad"
}
}
}
},
- "Sky Conditions": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Condiciones del cielo"
+ "Search locations" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Buscar ubicaciones"
}
}
}
},
- "Sleet": {
- "comment": "Precipitation description\nWeather condition name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Aguanieve"
+ "Search Results" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Resultados de búsqueda"
}
}
}
},
- "Snooze": {
- "comment": "Default notification action button title",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Posponer"
+ "Search tasks" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Buscar tareas"
}
}
}
},
- "Snooze 2h": {
- "comment": "Notification action button; 'h' is an abbreviation for hours",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Posponer 2h"
+ "Search tasks..." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Buscar tareas..."
}
}
}
},
- "Snoozed": {
- "comment": "Reminder history action label\nReminder status",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Pospuesto"
+ "Search tips" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Consejos de búsqueda"
}
}
}
},
- "Snoozed for %lld hours": {
- "comment": "Reminder history timeline entry",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Pospuesto por %lld horas"
+ "Search tips: Include state or country for better results. Try Portland, OR instead of just Portland. Use common city names in English." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Consejos de búsqueda: incluye el estado o país para obtener mejores resultados. Prueba \"Portland, OR\" en lugar de solo \"Portland\". Usa nombres de ciudades comunes."
}
}
}
},
- "Snow": {
- "comment": "Precipitation description\nReminder icon activity name\nWeather condition name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Nieve"
+ "Searching" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Buscando"
}
}
}
},
- "Snowy": {
- "comment": "Sky condition option label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Nevado"
+ "Searching for cities" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Buscando ciudades"
}
}
}
},
- "Sound": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sonido"
+ "Searching for cities..." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Buscando ciudades..."
}
}
}
},
- "Sports": {
- "comment": "Activity interest option: sports\nReminder category name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Deportes"
+ "Searching..." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Buscando..."
}
}
}
},
- "Standard system vibration": {
- "comment": "Description of default vibration pattern option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Vibración estándar del sistema"
+ "Seasonal" : {
+ "comment" : "Trigger type name: seasonal marker",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Estacional"
}
}
}
},
- "Start Task": {
- "comment": "Notification action button",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Iniciar tarea"
+ "Seasonal marker evaluation not yet fully implemented with ModelActor" : {
+ "comment" : "Internal placeholder notification body for a trigger type that is not yet implemented",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La evaluación de marcadores estacionales aún no está implementada por completo"
}
}
}
},
- "Start Workout": {
- "comment": "Notification action button",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Iniciar entrenamiento"
+ "Seasonal Tasks" : {
+ "comment" : "Reminder category name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tareas estacionales"
}
}
}
},
- "Start onboarding": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Iniciar configuración"
+ "Section" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sección"
}
}
}
},
- "Stay hydrated and limit time outdoors": {
- "comment": "In-app weather advisory instructions",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mantente hidratado y limita el tiempo al aire libre"
+ "Security Concern" : {
+ "comment" : "Privacy inquiry type",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Problema de seguridad"
}
}
}
},
- "Step 2 of 4": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Paso 2 de 4"
+ "Select Location" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Seleccionar ubicación"
}
}
}
},
- "Step 3 of 4": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Paso 3 de 4"
+ "Select location, currently %@" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Seleccionar ubicación, actualmente %@"
}
}
}
},
- "Step 4 of 4": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Paso 4 de 4"
+ "Select the type of privacy inquiry or request you'd like to make." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Selecciona el tipo de consulta o solicitud de privacidad que deseas hacer."
}
}
}
},
- "Still need help?": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¿Aún necesitas ayuda?"
+ "Select this city for weather data" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Selecciona esta ciudad para obtener datos meteorológicos"
}
}
}
},
- "Stored on This Device": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Almacenado en este dispositivo"
+ "Select Your City" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Selecciona tu ciudad"
}
}
}
},
- "Strong, noticeable vibration": {
- "comment": "Description of prominent vibration pattern option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Vibración fuerte y notoria"
+ "Selected" : {
+ "comment" : "Accessibility value for a toggled filter chip\nAccessibility value for the current-location card when chosen",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Seleccionado"
}
}
}
},
- "Subtle": {
- "comment": "Vibration pattern option: light gentle vibration",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sutil"
+ "Send" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Enviar"
}
}
}
},
- "Sun": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sol"
+ "Send Email Inquiry" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Enviar consulta por correo"
}
}
}
},
- "SunHat": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat"
+ "Send Feedback" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Enviar comentarios"
}
}
}
},
- "SunHat Advisories": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Avisos de SunHat"
+ "Set up a mail account, or email %@ directly." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Configura una cuenta de correo o escribe directamente a %@."
}
}
}
},
- "SunHat Feedback": {
- "comment": "Pre-filled subject line of the feedback email the app composes; 'SunHat' is the app name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Comentarios de SunHat"
+ "Set up a mail account, or email %@ directly. You can also use Copy Email Template." : {
+ "comment" : "Alert message",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Configura una cuenta de correo o escribe directamente a %@. También puedes usar Copiar plantilla de correo."
}
}
}
},
- "SunHat Heat Advisory": {
- "comment": "Title of an in-app weather advisory; 'SunHat' is the app name and should not be translated",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Aviso de calor de SunHat"
+ "Settings" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ajustes"
}
}
}
},
- "SunHat Section": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sección de SunHat"
+ "Severe" : {
+ "comment" : "Weather alert severity level",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Severo"
}
}
}
},
- "SunHat Support Request": {
- "comment": "Pre-filled subject line of the support email the app composes; 'SunHat' is the app name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Solicitud de soporte de SunHat"
+ "Share" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Compartir"
}
}
}
},
- "SunHat UV Advisory": {
- "comment": "Title of an in-app weather advisory; 'SunHat' is the app name and should not be translated",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Aviso UV de SunHat"
+ "Share as Text" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Compartir como texto"
}
}
}
},
- "SunHat can only alert you when weather matches your tasks if notifications are enabled.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat solo puede avisarte cuando el clima coincida con tus tareas si las notificaciones están activadas."
+ "Share Reminder" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Compartir recordatorio"
}
}
}
},
- "SunHat checks the weather, your location, and the time, then reminds you when your plans make sense.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat consulta el clima, tu ubicación y la hora, y te avisa cuando tus planes tengan sentido."
+ "Shopping" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Compras"
}
}
}
},
- "SunHat couldn't determine your current location. Pick a location for this reminder, or try again once location is available.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat no pudo determinar tu ubicación actual. Elige una ubicación para este recordatorio o inténtalo de nuevo cuando la ubicación esté disponible."
+ "Show badge" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mostrar insignia"
}
}
}
},
- "SunHat couldn't load weather for this location. Check your connection and location settings, then try again.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat no pudo cargar el clima para esta ubicación. Comprueba tu conexión y los ajustes de ubicación, e inténtalo de nuevo."
+ "Show each notification separately" : {
+ "comment" : "Description of no-grouping notification option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mostrar cada notificación por separado"
}
}
}
},
- "SunHat is built with Swift and Apple's iOS frameworks.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat está creado con Swift y los frameworks de iOS de Apple."
+ "Show forecast details" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mostrar detalles del pronóstico"
}
}
}
},
- "SunHat is designed with your privacy in mind. Here's how we handle your data:": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat está diseñado pensando en tu privacidad. Así es como manejamos tus datos:"
+ "Show full notification content on lock screen" : {
+ "comment" : "Description of show-previews lock screen option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mostrar el contenido completo de la notificación en la pantalla de bloqueo"
}
}
}
},
- "SunHat is running in temporary recovery mode, changes made now will NOT be saved after you close the app. Contact support@sunhat.app for help recovering your data.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat está en modo de recuperación temporal; los cambios realizados ahora NO se guardarán al cerrar la app. Escribe a support@sunhat.app para recuperar tus datos."
+ "Show Next Ready Reminder" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mostrar próximo recordatorio listo"
}
}
}
},
- "SunHat is still watching your active tasks and will notify you when the weather matches.": {
- "comment": "Empty state message when no active tasks currently match the weather",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat sigue vigilando tus tareas activas y te avisará cuando el clima coincida."
+ "Show notification but hide content details" : {
+ "comment" : "Description of hide-details lock screen option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mostrar la notificación pero ocultar los detalles del contenido"
}
}
}
},
- "SunHat is watching your weather tasks.": {
- "comment": "Compact surface subtitle when no reminder is currently ready",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat está vigilando tus tareas del clima."
+ "Show Previews" : {
+ "comment" : "Lock screen behavior option: show full notification previews",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mostrar vistas previas"
}
}
}
},
- "SunHat monitors weather conditions and sends you notifications when the temperature, humidity, or other conditions match your preferences. For example, you can set a reminder to water your garden when it hasn't rained and the temperature is above 70°F.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat vigila las condiciones del clima y te envía notificaciones cuando la temperatura, la humedad u otras condiciones coinciden con tus preferencias. Por ejemplo, puedes crear un recordatorio para regar el jardín cuando no haya llovido y la temperatura supere los 70°F."
+ "Silent Window" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ventana silenciosa"
}
}
}
},
- "SunHat repaired a local data store issue and preserved the previous store for recovery.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat reparó un problema en el almacén de datos local y conservó el almacén anterior para su recuperación."
+ "Skip for now" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Omitir por ahora"
}
}
}
},
- "SunHat stores your reminders and preferences on this device. Location coordinates are sent to enabled weather providers only when needed to retrieve weather data.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat almacena tus recordatorios y preferencias en este dispositivo. Las coordenadas de ubicación se envían a los proveedores meteorológicos habilitados solo cuando es necesario para obtener datos del clima."
+ "Skip onboarding" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Omitir configuración inicial"
}
}
}
},
- "SunHat uses your location to:\n• Provide accurate weather data for your area\n• Send timely reminders based on local conditions\n• Ensure weather triggers work for your specific location\n\nYour location data:\n• Stays on your device\n• Is only used for weather updates\n• Is sent only to enabled weather providers to fetch forecasts\n• Can be changed to manual entry at any time": {
- "comment": "Multi-paragraph explanation of location data usage shown in a privacy info sheet; keep the bullet ('•') formatting",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat usa tu ubicación para:\n• Proporcionar datos meteorológicos precisos de tu área\n• Enviar recordatorios oportunos según las condiciones locales\n• Garantizar que las activaciones del clima funcionen para tu ubicación específica\n\nTus datos de ubicación:\n• Permanecen en tu dispositivo\n• Solo se usan para actualizaciones del clima\n• Solo se envían a los proveedores meteorológicos habilitados para obtener pronósticos\n• Pueden cambiarse a entrada manual en cualquier momento"
+ "Skip Today" : {
+ "comment" : "Notification action button",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Omitir hoy"
}
}
}
},
- "SunHat watches the forecast for conditions you choose, then reminds you when it may be a good time for an activity.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat vigila el pronóstico según las condiciones que elijas y te avisa cuando pueda ser un buen momento para una actividad."
+ "Skipped" : {
+ "comment" : "Reminder history action label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Omitido"
}
}
}
},
- "SunHat welcome screen": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Pantalla de bienvenida de SunHat"
+ "sky condition %@ does not match" : {
+ "comment" : "Short clause listing one unsatisfied sky-condition filter of a multi-condition reminder",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "la condición del cielo %@ no coincide"
}
}
}
},
- "SunHat won't send weather reminders while notifications are off.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "SunHat no enviará recordatorios del clima mientras las notificaciones estén desactivadas."
+ "sky condition %@ matches" : {
+ "comment" : "Short clause listing one satisfied sky-condition filter of a multi-condition reminder",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "la condición del cielo %@ coincide"
}
}
}
},
- "Sunny": {
- "comment": "Sky condition option label\nWeather condition type option label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Soleado"
+ "Sky Conditions" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Condiciones del cielo"
}
}
}
},
- "Sunrise and sunset times": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Horarios de amanecer y atardecer"
+ "Sleet" : {
+ "comment" : "Precipitation description\nWeather condition name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aguanieve"
}
}
}
},
- "Support": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Soporte"
+ "Snooze" : {
+ "comment" : "Default notification action button title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Posponer"
}
}
}
},
- "Swipe left on any reminder in the list, or open the reminder and choose Delete from its detail screen.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Desliza hacia la izquierda sobre cualquier recordatorio de la lista, o abre el recordatorio y elige Eliminar en su pantalla de detalles."
+ "Snooze 2h" : {
+ "comment" : "Notification action button; 'h' is an abbreviation for hours",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Posponer 2h"
}
}
}
},
- "Sync is currently unavailable. Data is stored locally.": {
- "comment": "Data & Privacy screen sync status",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La sincronización no está disponible en este momento. Los datos se almacenan localmente."
+ "Snoozed" : {
+ "comment" : "Reminder history action label\nReminder status",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pospuesto"
}
}
}
},
- "System": {
- "comment": "Appearance mode option: follow the system light/dark setting",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sistema"
+ "Snoozed for %lld hours" : {
+ "comment" : "Reminder history timeline entry",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pospuesto por %lld horas"
}
}
}
},
- "Takeout": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Comida para llevar"
+ "Snow" : {
+ "comment" : "Precipitation description\nReminder icon activity name\nWeather condition name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Nieve"
}
}
}
},
- "Tap a city below or search for your location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Toca una ciudad abajo o busca tu ubicación"
+ "snow expected" : {
+ "comment" : "Short clause listing one satisfied snow condition of a multi-condition reminder",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "se espera nieve"
}
}
}
},
- "Target": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Objetivo"
+ "snow not expected" : {
+ "comment" : "Short clause listing one unsatisfied snow condition of a multi-condition reminder",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "no se espera nieve"
}
}
}
},
- "Target Temperature": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Temperatura objetivo"
+ "Snowy" : {
+ "comment" : "Sky condition option label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Nevado"
}
}
}
},
- "Task Unavailable": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tarea no disponible"
+ "SOS" : {
+ "comment" : "Vibration pattern option: emergency SOS pattern",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SOS"
}
}
}
},
- "Tasks matching the current forecast": {
- "comment": "Dashboard section subtitle for the Ready Now card",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tareas que coinciden con el pronóstico actual"
+ "Sound" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sonido"
}
}
}
},
- "Teal": {
- "comment": "Reminder color swatch name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Verde azulado"
+ "Sports" : {
+ "comment" : "Activity interest option: sports\nReminder category name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Deportes"
}
}
}
},
- "Temperature": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Temperatura"
+ "SSE" : {
+ "comment" : "Wind direction abbreviation: South-southeast",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SSE"
}
}
}
},
- "Temperature %@": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Temperatura %@"
+ "SSW" : {
+ "comment" : "Wind direction abbreviation: South-southwest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SSO"
}
}
}
},
- "Temperature %@° differs from target %@° by %@°": {
- "comment": "Notification body: current temperature misses the target by some amount",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Temperature %1$@° differs from target %2$@° by %3$@°"
- }
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura de %@° difiere del objetivo de %@° en %@°"
+ "Standard system vibration" : {
+ "comment" : "Description of default vibration pattern option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Vibración estándar del sistema"
}
}
}
},
- "Temperature %@° is above target %@°": {
- "comment": "Notification body: current temperature exceeded the target",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Temperature %1$@° is above target %2$@°"
+ "Start onboarding" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Iniciar configuración"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura de %@° está por encima del objetivo de %@°"
+ }
+ }
+ },
+ "Start Task" : {
+ "comment" : "Notification action button",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Iniciar tarea"
}
}
}
},
- "Temperature %@° is below target %@°": {
- "comment": "Notification body: current temperature is below the target",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Temperature %1$@° is below target %2$@°"
+ "Start Workout" : {
+ "comment" : "Notification action button",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Iniciar entrenamiento"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura de %@° está por debajo del objetivo de %@°"
+ }
+ }
+ },
+ "Stay hydrated and limit time outdoors" : {
+ "comment" : "In-app weather advisory instructions",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mantente hidratado y limita el tiempo al aire libre"
}
}
}
},
- "Temperature %@° is outside range %@° - %@°": {
- "comment": "Notification body: current temperature falls outside the configured range",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Temperature %1$@° is outside range %2$@° - %3$@°"
+ "Step 2 of 4" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Paso 2 de 4"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura de %@° está fuera del rango %@° - %@°"
+ }
+ }
+ },
+ "Step 3 of 4" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Paso 3 de 4"
}
}
}
},
- "Temperature %@° is within range %@° - %@°": {
- "comment": "Notification body: current temperature falls inside the configured range",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Temperature %1$@° is within range %2$@° - %3$@°"
+ "Step 4 of 4" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Paso 4 de 4"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura de %@° está dentro del rango %@° - %@°"
+ }
+ }
+ },
+ "Still need help?" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¿Aún necesitas ayuda?"
}
}
}
},
- "Temperature %@° matches target %@° (±%@°)": {
- "comment": "Notification body: current temperature matches the target within tolerance",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Temperature %1$@° matches target %2$@° (±%3$@°)"
+ "Stored on This Device" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Almacenado en este dispositivo"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura de %@° coincide con el objetivo de %@° (±%@°)"
+ }
+ }
+ },
+ "Strong, noticeable vibration" : {
+ "comment" : "Description of prominent vibration pattern option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Vibración fuerte y notoria"
}
}
}
},
- "Temperature Range": {
- "comment": "Temperature condition type option label\nWeather condition type option label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Rango de temperatura"
+ "Subtle" : {
+ "comment" : "Vibration pattern option: light gentle vibration",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sutil"
}
}
}
},
- "Temperature has been above %@° for %lld consecutive days (required: %lld)": {
- "comment": "Notification body: consecutive-days-above-target trigger satisfied",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Temperature has been above %1$@° for %2$lld consecutive days (required: %3$lld)"
+ "Sun" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sol"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura ha estado por encima de %@° durante %lld días consecutivos (requeridos: %lld)"
+ }
+ }
+ },
+ "SunHat" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat"
}
}
}
},
- "Temperature has been above %@° for only %lld consecutive days (required: %lld)": {
- "comment": "Notification body: consecutive-days-above-target trigger not yet satisfied",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Temperature has been above %1$@° for only %2$lld consecutive days (required: %3$lld)"
+ "SunHat Ad-Free" : {
+ "comment" : "Name of the ad-free subscription, used as the Settings section header and paywall title",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat Ad-Free"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura solo ha estado por encima de %@° durante %lld días consecutivos (requeridos: %lld)"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat sin anuncios"
}
}
}
},
- "Temperature has been below %@° for %lld consecutive days (required: %lld)": {
- "comment": "Notification body: consecutive-days-below-target trigger satisfied",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Temperature has been below %1$@° for %2$lld consecutive days (required: %3$lld)"
+ "SunHat Advisories" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Avisos de SunHat"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura ha estado por debajo de %@° durante %lld días consecutivos (requeridos: %lld)"
+ }
+ }
+ },
+ "SunHat can only alert you when weather matches your tasks if notifications are enabled." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat solo puede avisarte cuando el clima coincida con tus tareas si las notificaciones están activadas."
}
}
}
},
- "Temperature has been below %@° for only %lld consecutive days (required: %lld)": {
- "comment": "Notification body: consecutive-days-below-target trigger not yet satisfied",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Temperature has been below %1$@° for only %2$lld consecutive days (required: %3$lld)"
+ "SunHat can't save changes right now because your data couldn't be loaded. Restart SunHat to try again." : {
+ "comment" : "Error shown when trying to edit a reminder while the app is in temporary in-memory recovery mode",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat no puede guardar cambios ahora mismo porque no se pudieron cargar tus datos. Reinicia SunHat para intentarlo de nuevo."
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura solo ha estado por debajo de %@° durante %lld días consecutivos (requeridos: %lld)"
+ }
+ }
+ },
+ "SunHat can't save new reminders right now because your data couldn't be loaded. Restart SunHat to try again." : {
+ "comment" : "Error shown when trying to create a reminder while the app is in temporary in-memory recovery mode",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat no puede guardar nuevos recordatorios ahora mismo porque no se pudieron cargar tus datos. Reinicia SunHat para intentarlo de nuevo."
}
}
}
},
- "Temperature has been in target range for %lld consecutive days (required: %lld)": {
- "comment": "Notification body: consecutive-days-in-range trigger satisfied",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Temperature has been in target range for %1$lld consecutive days (required: %2$lld)"
+ "SunHat checks the weather, your location, and the time, then reminds you when your plans make sense." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat consulta el clima, tu ubicación y la hora, y te avisa cuando tus planes tengan sentido."
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura ha estado dentro del rango objetivo durante %lld días consecutivos (requeridos: %lld)"
+ }
+ }
+ },
+ "SunHat couldn't determine your current location. Pick a location for this reminder, or try again once location is available." : {
+ "comment" : "Error shown when saving a reminder set to current location while no location fix is available",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat no pudo determinar tu ubicación actual. Elige una ubicación para este recordatorio o inténtalo de nuevo cuando la ubicación esté disponible."
}
}
}
},
- "Temperature has been in target range for only %lld consecutive days (required: %lld)": {
- "comment": "Notification body: consecutive-days-in-range trigger not yet satisfied",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Temperature has been in target range for only %1$lld consecutive days (required: %2$lld)"
+ "SunHat couldn't load weather for this location. Check your connection and location settings, then try again." : {
+ "comment" : "Description of the weather tab's unavailable state",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat no pudo cargar el clima para esta ubicación. Comprueba tu conexión y los ajustes de ubicación, e inténtalo de nuevo."
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura solo ha estado dentro del rango objetivo durante %lld días consecutivos (requeridos: %lld)"
+ }
+ }
+ },
+ "SunHat couldn't load your data and can't save changes right now. Restart SunHat to try again." : {
+ "comment" : "Notice shown while editing a reminder when the app is in temporary in-memory recovery mode",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat no pudo cargar tus datos y no puede guardar cambios ahora mismo. Reinicia SunHat para intentarlo de nuevo."
}
}
}
},
- "Temperature is the actual air temperature, while 'feels like' accounts for humidity and wind chill to represent what the temperature actually feels like to your body.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura es la temperatura real del aire, mientras que la sensación térmica tiene en cuenta la humedad y el viento para representar lo que realmente se siente."
+ "SunHat couldn't load your data and can't save new reminders right now. Restart SunHat to try again." : {
+ "comment" : "Notice shown on the creation screen when the app is in temporary in-memory recovery mode",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat no pudo cargar tus datos y no puede guardar nuevos recordatorios ahora mismo. Reinicia SunHat para intentarlo de nuevo."
}
}
}
},
- "Temperature matched but sky conditions didn't (%@: %@)": {
- "comment": "Notification body explaining a reminder did not fire because sky conditions failed even though temperature matched",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Temperature matched but sky conditions didn't (%1$@: %2$@)"
+ "SunHat couldn't load your data and is running in temporary recovery mode. Creating and editing reminders is disabled until this is fixed. Restart SunHat to try again, or contact support@sunhat.app for help." : {
+ "comment" : "Banner shown when the app is running on a temporary in-memory database",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat no pudo cargar tus datos y está en modo de recuperación temporal. La creación y edición de recordatorios están desactivadas hasta que esto se solucione. Reinicia SunHat para intentarlo de nuevo, o escribe a support@sunhat.app para obtener ayuda."
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La temperatura coincidió, pero las condiciones del cielo no (%@: %@)"
+ }
+ }
+ },
+ "SunHat Feedback" : {
+ "comment" : "Pre-filled subject line of the feedback email the app composes; 'SunHat' is the app name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Comentarios de SunHat"
}
}
}
},
- "Temperature unit": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Unidad de temperatura"
+ "SunHat Heat Advisory" : {
+ "comment" : "Title of an in-app weather advisory; 'SunHat' is the app name and should not be translated",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aviso de calor de SunHat"
}
}
}
},
- "Temperature unit setup": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Configuración de la unidad de temperatura"
+ "SunHat is built with Swift and Apple's iOS frameworks." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat está creado con Swift y los frameworks de iOS de Apple."
}
}
}
},
- "Temperature: %@": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Temperatura: %@"
+ "SunHat is designed with your privacy in mind. Here's how we handle your data:" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat está diseñado pensando en tu privacidad. Así es como manejamos tus datos:"
}
}
}
},
- "Temperature: %lld°F": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Temperatura: %lld°F"
+ "SunHat is running in temporary recovery mode, changes made now will NOT be saved after you close the app. Contact support@sunhat.app for help recovering your data." : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat está en modo de recuperación temporal; los cambios realizados ahora NO se guardarán al cerrar la app. Escribe a support@sunhat.app para recuperar tus datos."
}
}
}
},
- "Tennis": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tenis"
+ "SunHat is still watching your active tasks and will notify you when the weather matches." : {
+ "comment" : "Empty state message when no active tasks currently match the weather",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat sigue vigilando tus tareas activas y te avisará cuando el clima coincida."
}
}
}
},
- "Terms of Service": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Términos de servicio"
+ "SunHat is watching your weather tasks." : {
+ "comment" : "Compact surface subtitle when no reminder is currently ready",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat está vigilando tus tareas del clima."
}
}
}
},
- "Test Notification": {
- "comment": "Title of a sample notification the user triggers to preview notification style",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Notificación de prueba"
+ "SunHat itself does not collect personal analytics. Your reminders, preferences, and weather history stay on your device." : { "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat no recopila datos analíticos personales. Tus recordatorios, preferencias e historial meteorológico se quedan en tu dispositivo."
}
}
}
+
+
},
- "The GPS signal is too weak for accurate positioning": {
- "comment": "Location error failure reason",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La señal GPS es demasiado débil para un posicionamiento preciso"
+ "SunHat monitors weather conditions and sends you notifications when the temperature, humidity, or other conditions match your preferences. For example, you can set a reminder to water your garden when it hasn't rained and the temperature is above 70°F." : {
+ "comment" : "FAQ answer",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat vigila las condiciones del clima y te envía notificaciones cuando la temperatura, la humedad u otras condiciones coinciden con tus preferencias. Por ejemplo, puedes crear un recordatorio para regar el jardín cuando no haya llovido y la temperatura supere los 70°F."
}
}
}
},
- "The app does not have permission to access your location": {
- "comment": "Location error failure reason",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La app no tiene permiso para acceder a tu ubicación"
+ "SunHat repaired a local data store issue and preserved the previous store for recovery." : {
+ "comment" : "Banner shown after the local database was quarantined and recreated",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat reparó un problema en el almacén de datos local y conservó el almacén anterior para su recuperación."
}
}
}
},
- "The email template was copied to your clipboard. Paste it into any email to %@.": {
- "comment": "Alert message",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La plantilla de correo se copió al portapapeles. Pégala en cualquier correo dirigido a %@."
+ "SunHat Section" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sección de SunHat"
}
}
}
},
- "The exported file may contain personal information. Store and share it carefully.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "El archivo exportado puede contener información personal. Guárdalo y compártelo con cuidado."
+ "SunHat stores your reminders and preferences on this device. Location coordinates are sent to enabled weather providers only when needed to retrieve weather data." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat almacena tus recordatorios y preferencias en este dispositivo. Las coordenadas de ubicación se envían a los proveedores meteorológicos habilitados solo cuando es necesario para obtener datos del clima."
}
}
}
},
- "The last known location is too old to be useful": {
- "comment": "Location error failure reason",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La última ubicación conocida es demasiado antigua para ser útil"
+ "SunHat Support Request" : {
+ "comment" : "Pre-filled subject line of the support email the app composes; 'SunHat' is the app name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Solicitud de soporte de SunHat"
}
}
}
},
- "The weather service is experiencing temporary issues": {
- "comment": "Weather error failure reason",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "El servicio meteorológico está teniendo problemas temporales"
+ "SunHat uses your location to:\n• Provide accurate weather data for your area\n• Send timely reminders based on local conditions\n• Ensure weather triggers work for your specific location\n\nYour location data:\n• Stays on your device\n• Is only used for weather updates\n• Is sent only to enabled weather providers to fetch forecasts\n• Can be changed to manual entry at any time" : {
+ "comment" : "Multi-paragraph explanation of location data usage shown in a privacy info sheet; keep the bullet ('•') formatting",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat usa tu ubicación para:\n• Proporcionar datos meteorológicos precisos de tu área\n• Enviar recordatorios oportunos según las condiciones locales\n• Garantizar que las activaciones del clima funcionen para tu ubicación específica\n\nTus datos de ubicación:\n• Permanecen en tu dispositivo\n• Solo se usan para actualizaciones del clima\n• Solo se envían a los proveedores meteorológicos habilitados para obtener pronósticos\n• Pueden cambiarse a entrada manual en cualquier momento"
}
}
}
},
- "Third-Party Services": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Servicios de terceros"
+ "SunHat UV Advisory" : {
+ "comment" : "Title of an in-app weather advisory; 'SunHat' is the app name and should not be translated",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aviso UV de SunHat"
}
}
}
},
- "This action cannot be undone. The reminder and all its history will be permanently deleted.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Esta acción no se puede deshacer. El recordatorio y todo su historial se eliminarán permanentemente."
+ "SunHat watches the forecast for conditions you choose, then reminds you when it may be a good time for an activity." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat vigila el pronóstico según las condiciones que elijas y te avisa cuando pueda ser un buen momento para una actividad."
}
}
}
},
- "This controls how forecasts and weather conditions appear. You can change it later in Settings.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Esto controla cómo aparecen los pronósticos y las condiciones climáticas. Puedes cambiarlo luego en Ajustes."
+ "SunHat welcome screen" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pantalla de bienvenida de SunHat"
}
}
}
},
- "This export is provided to comply with GDPR Article 20 (Right to Data Portability) and CCPA requirements.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Esta exportación se proporciona para cumplir con el GDPR Article 20 (derecho a la portabilidad de datos) y los requisitos del CCPA."
+ "SunHat won't send weather reminders while notifications are off." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SunHat no enviará recordatorios del clima mientras las notificaciones estén desactivadas."
}
}
}
},
- "This is how your %@ notifications will appear": {
- "comment": "Body of a sample notification; 'type' is a reminder category name such as Exercise or Gardening",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Así se verán tus notificaciones de %@"
+ "Sunny" : {
+ "comment" : "Sky condition option label\nWeather condition type option label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Soleado"
}
}
}
},
- "This permanently removes your reminders, preferences, saved locations, and cached weather data from this device.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Esto elimina permanentemente tus recordatorios, preferencias, ubicaciones guardadas y datos meteorológicos en caché de este dispositivo."
+ "Sunrise and sunset times" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Horarios de amanecer y atardecer"
}
}
}
},
- "This weather task may have been deleted.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Es posible que esta tarea del clima haya sido eliminada."
+ "Support" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Soporte"
}
}
}
},
- "This will reset all app settings to their defaults. Your reminders will not be affected.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Esto restablecerá todos los ajustes de la app a sus valores predeterminados. Tus recordatorios no se verán afectados."
+ "Supports an independent weather app" : {
+ "comment" : "Paywall benefit row",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Supports an independent weather app"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Apoya a una app del tiempo independiente"
}
}
}
},
- "Thunderstorm": {
- "comment": "Weather condition name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tormenta eléctrica"
+ "SW" : {
+ "comment" : "Wind direction abbreviation: Southwest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SO"
}
}
}
},
- "Title": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Título"
+ "Swipe left on any reminder in the list, or open the reminder and choose Delete from its detail screen." : {
+ "comment" : "FAQ answer",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Desliza hacia la izquierda sobre cualquier recordatorio de la lista, o abre el recordatorio y elige Eliminar en su pantalla de detalles."
}
}
}
},
- "To get weather reminders, allow notifications for SunHat in Settings.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Para recibir recordatorios del clima, permite las notificaciones de SunHat en Ajustes."
+ "Sync is currently unavailable. Data is stored locally." : {
+ "comment" : "Data & Privacy screen sync status",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La sincronización no está disponible en este momento. Los datos se almacenan localmente."
}
}
}
},
- "Today": {
- "comment": "Relative day label in the weekly forecast",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Hoy"
+ "System" : {
+ "comment" : "Appearance mode option: follow the system light/dark setting",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sistema"
}
}
}
},
- "Today's Reminders": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorios de hoy"
+ "Takeout" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Comida para llevar"
}
}
}
},
- "Tolerance:": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tolerancia:"
+ "Tap a city below or search for your location" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Toca una ciudad abajo o busca tu ubicación"
}
}
}
},
- "Tomorrow": {
- "comment": "Relative day label in the weekly forecast",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Mañana"
+ "Target" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Objetivo"
}
}
}
},
- "Too many requests made in a short period": {
- "comment": "Weather error failure reason",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Se hicieron demasiadas solicitudes en poco tiempo"
+ "Target Temperature" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Temperatura objetivo"
}
}
}
},
- "Trail hiking and nature exploration": {
- "comment": "Description of hiking activity interest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Senderismo y exploración de la naturaleza"
+ "Task Unavailable" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tarea no disponible"
}
}
}
},
- "Travel": {
- "comment": "Reminder category name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Viajes"
+ "Tasks matching the current forecast" : {
+ "comment" : "Dashboard section subtitle for the Ready Now card",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tareas que coinciden con el pronóstico actual"
}
}
}
},
- "Trees": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Árboles"
+ "Teal" : {
+ "comment" : "Reminder color swatch name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Verde azulado"
}
}
}
},
- "Trend": {
- "comment": "Trigger type name: consecutive days trend",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tendencia"
+ "Temperature" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Temperatura"
}
}
}
},
- "Trigger Type": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tipo de activación"
+ "Temperature %@" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Temperatura %@"
}
}
}
},
- "Trigger at specific temperature": {
- "comment": "Trigger type description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Activar a una temperatura específica"
+ "temperature %@° above target %@°" : {
+ "comment" : "Short clause listing one satisfied condition of a multi-condition reminder\nShort clause listing one unsatisfied condition of a multi-condition reminder",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "temperature %1$@° above target %2$@°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "temperatura %@° por encima del objetivo %@°"
}
}
}
},
- "Trigger on average over time": {
- "comment": "Trigger type description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Activar según el promedio a lo largo del tiempo"
+ "temperature %@° below target %@°" : {
+ "comment" : "Short clause listing one satisfied condition of a multi-condition reminder\nShort clause listing one unsatisfied condition of a multi-condition reminder",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "temperature %1$@° below target %2$@°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "temperatura %@° por debajo del objetivo %@°"
}
}
}
},
- "Trigger on seasonal changes": {
- "comment": "Trigger type description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Activar según cambios estacionales"
+ "temperature %@° differs from target %@° (±%@°)" : {
+ "comment" : "Short clause listing one unsatisfied condition of a multi-condition reminder",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "temperature %1$@° differs from target %2$@° (±%3$@°)"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "temperatura %@° distinta del objetivo %@° (±%@°)"
}
}
}
},
- "Trigger on temperature trends": {
- "comment": "Trigger type description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Activar según tendencias de temperatura"
+ "Temperature %@° differs from target %@° by %@°" : {
+ "comment" : "Notification body: current temperature misses the target by some amount",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Temperature %1$@° differs from target %2$@° by %3$@°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura de %@° difiere del objetivo de %@° en %@°"
}
}
}
},
- "Trigger when temperature is %@ %@ degrees": {
- "comment": "Accessibility label clause describing a reminder's temperature trigger condition",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Trigger when temperature is %1$@ %2$@ degrees"
+ "Temperature %@° is above target %@°" : {
+ "comment" : "Notification body: current temperature exceeded the target",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Temperature %1$@° is above target %2$@°"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Se activa cuando la temperatura está %@ %@ grados"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura de %@° está por encima del objetivo de %@°"
}
}
}
},
- "Trigger within temperature range": {
- "comment": "Trigger type description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Activar dentro de un rango de temperatura"
+ "Temperature %@° is below target %@°" : {
+ "comment" : "Notification body: current temperature is below the target",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Temperature %1$@° is below target %2$@°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura de %@° está por debajo del objetivo de %@°"
}
}
}
},
- "Trigger: No condition set": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Activación: sin condición establecida"
+ "Temperature %@° is outside range %@° - %@°" : {
+ "comment" : "Notification body: current temperature falls outside the configured range",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Temperature %1$@° is outside range %2$@° - %3$@°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura de %@° está fuera del rango %@° - %@°"
}
}
}
},
- "Trigger: When temperature is %@ %.1f°": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Trigger: When temperature is %1$@ %2$.1f°"
+ "Temperature %@° is within range %@° - %@°" : {
+ "comment" : "Notification body: current temperature falls inside the configured range",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Temperature %1$@° is within range %2$@° - %3$@°"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Activación: cuando la temperatura está %@ %.1f°"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura de %@° está dentro del rango %@° - %@°"
}
}
}
},
- "Triggered": {
- "comment": "Reminder history action label",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Activado"
+ "temperature %@° matches target %@° (±%@°)" : {
+ "comment" : "Short clause listing one satisfied condition of a multi-condition reminder",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "temperature %1$@° matches target %2$@° (±%3$@°)"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "temperatura %@° coincide con el objetivo %@° (±%@°)"
}
}
}
},
- "Triggers when temperature is %@ %lld°F": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "Triggers when temperature is %1$@ %2$lld°F"
+ "Temperature %@° matches target %@° (±%@°)" : {
+ "comment" : "Notification body: current temperature matches the target within tolerance",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Temperature %1$@° matches target %2$@° (±%3$@°)"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Se activa cuando la temperatura está %@ %lld°F"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura de %@° coincide con el objetivo de %@° (±%@°)"
}
}
}
},
- "Try Again": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Intentar de nuevo"
+ "temperature %@° outside range %@°-%@°" : {
+ "comment" : "Short clause listing one unsatisfied condition of a multi-condition reminder",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "temperature %1$@° outside range %2$@°-%3$@°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "temperatura %@° fuera del rango %@°-%@°"
}
}
}
},
- "Try a broader city or landmark name.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Prueba con un nombre de ciudad o punto de referencia más amplio."
+ "temperature %@° within range %@°-%@°" : {
+ "comment" : "Short clause listing one satisfied condition of a multi-condition reminder",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "temperature %1$@° within range %2$@°-%3$@°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "temperatura %@° dentro del rango %@°-%@°"
}
}
}
},
- "Try a different search or switch back to all tasks.": {
- "comment": "Empty state message when a search or filter matches no weather tasks",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Prueba otra búsqueda o vuelve a mostrar todas las tareas."
+ "Temperature has been above %@° for %lld consecutive days (required: %lld)" : {
+ "comment" : "Notification body: consecutive-days-above-target trigger satisfied",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Temperature has been above %1$@° for %2$lld consecutive days (required: %3$lld)"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura ha estado por encima de %@° durante %lld días consecutivos (requeridos: %lld)"
}
}
}
},
- "Try again in a few minutes or check your internet connection": {
- "comment": "Weather error recovery suggestion",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Inténtalo de nuevo en unos minutos o verifica tu conexión a internet"
+ "Temperature has been above %@° for only %lld consecutive days (required: %lld)" : {
+ "comment" : "Notification body: consecutive-days-above-target trigger not yet satisfied",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Temperature has been above %1$@° for only %2$lld consecutive days (required: %3$lld)"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura solo ha estado por encima de %@° durante %lld días consecutivos (requeridos: %lld)"
}
}
}
},
- "Try again later": {
- "comment": "Weather error generic recovery suggestion",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Inténtalo de nuevo más tarde"
+ "Temperature has been below %@° for %lld consecutive days (required: %lld)" : {
+ "comment" : "Notification body: consecutive-days-below-target trigger satisfied",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Temperature has been below %1$@° for %2$lld consecutive days (required: %3$lld)"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura ha estado por debajo de %@° durante %lld días consecutivos (requeridos: %lld)"
}
}
}
},
- "Try again or enter your city manually": {
- "comment": "Location error recovery suggestion fallback",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Inténtalo de nuevo o escribe tu ciudad manualmente"
+ "Temperature has been below %@° for only %lld consecutive days (required: %lld)" : {
+ "comment" : "Notification body: consecutive-days-below-target trigger not yet satisfied",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Temperature has been below %1$@° for only %2$lld consecutive days (required: %3$lld)"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura solo ha estado por debajo de %@° durante %lld días consecutivos (requeridos: %lld)"
}
}
}
},
- "Try entering your city manually instead": {
- "comment": "Location error recovery suggestion",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Intenta escribir tu ciudad manualmente"
+ "Temperature has been in target range for %lld consecutive days (required: %lld)" : {
+ "comment" : "Notification body: consecutive-days-in-range trigger satisfied",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Temperature has been in target range for %1$lld consecutive days (required: %2$lld)"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura ha estado dentro del rango objetivo durante %lld días consecutivos (requeridos: %lld)"
}
}
}
},
- "Try getting your location again": {
- "comment": "Location error recovery suggestion",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Intenta obtener tu ubicación de nuevo"
+ "Temperature has been in target range for only %lld consecutive days (required: %lld)" : {
+ "comment" : "Notification body: consecutive-days-in-range trigger not yet satisfied",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Temperature has been in target range for only %1$lld consecutive days (required: %2$lld)"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura solo ha estado dentro del rango objetivo durante %lld días consecutivos (requeridos: %lld)"
}
}
}
},
- "Try moving to an area with better GPS reception": {
- "comment": "Location error recovery suggestion",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Intenta moverte a un área con mejor recepción GPS"
+ "Temperature is the actual air temperature, while 'feels like' accounts for humidity and wind chill to represent what the temperature actually feels like to your body." : {
+ "comment" : "FAQ answer",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura es la temperatura real del aire, mientras que la sensación térmica tiene en cuenta la humedad y el viento para representar lo que realmente se siente."
}
}
}
},
- "Try searching with a different spelling or include the state/country": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Intenta buscar con otra ortografía o incluye el estado o país"
+ "Temperature matched but sky conditions didn't (%@: %@)" : {
+ "comment" : "Notification body explaining a reminder did not fire because sky conditions failed even though temperature matched",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Temperature matched but sky conditions didn't (%1$@: %2$@)"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La temperatura coincidió, pero las condiciones del cielo no (%@: %@)"
}
}
}
},
- "Turn On Weather Reminders?": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¿Activar recordatorios del clima?"
+ "Temperature Range" : {
+ "comment" : "Temperature condition type option label\nWeather condition type option label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Rango de temperatura"
}
}
}
},
- "Type DELETE to confirm": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Escribe ELIMINAR para confirmar"
+ "Temperature unit" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Unidad de temperatura"
}
}
}
},
- "Type of Inquiry": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tipo de solicitud"
+ "Temperature unit setup" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Configuración de la unidad de temperatura"
}
}
}
},
- "Unable to access app data": {
- "comment": "Data & Privacy error\nNotification error when the local data store is unavailable",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo acceder a los datos de la app"
+ "Temperature: %@" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Temperatura: %@"
}
}
}
},
- "Unable to connect to location services": {
- "comment": "Location error failure reason",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo conectar a los servicios de ubicación"
+ "Temperature: %lld°F" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Temperatura: %lld°F"
}
}
}
},
- "Unable to determine your location. Please check location permissions in Settings.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudo determinar tu ubicación. Comprueba los permisos de ubicación en Ajustes."
+ "Tennis" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tenis"
}
}
}
},
- "Unable to fetch forecast data: %@": {
- "comment": "Notification body fallback when the forecast could not be fetched for a prediction-based trigger",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se pudieron obtener los datos del pronóstico: %@"
+ "Terms of Service" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Términos de servicio"
}
}
}
},
- "Under GDPR and CCPA, you have specific rights regarding your personal data. We're committed to honoring these rights.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Según el GDPR y el CCPA, tienes derechos específicos respecto a tus datos personales. Nos comprometemos a respetar estos derechos."
+ "Test Notification" : {
+ "comment" : "Title of a sample notification the user triggers to preview notification style",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Notificación de prueba"
}
}
}
},
- "Unhealthy": {
- "comment": "Air quality index level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No saludable"
+ "The app does not have permission to access your location" : {
+ "comment" : "Location error failure reason",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La app no tiene permiso para acceder a tu ubicación"
}
}
}
},
- "Unhealthy for sensitive": {
- "comment": "Air quality index level description, short for 'unhealthy for sensitive groups'",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No saludable para sensibles"
+ "The App Store could not verify this purchase." : {
+ "comment" : "Error shown when a StoreKit transaction fails local verification",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "The App Store could not verify this purchase."
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "App Store no pudo verificar esta compra."
}
}
}
},
- "Unknown": {
- "comment": "Fallback app version when it cannot be read from the bundle\nFallback build number when it cannot be read from the bundle\nTrigger likelihood: percentage outside the expected 0-100 range\nWeather condition name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Desconocido"
+ "The email template was copied to your clipboard. Paste it into any email to %@." : {
+ "comment" : "Alert message",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La plantilla de correo se copió al portapapeles. Pégala en cualquier correo dirigido a %@."
}
}
}
},
- "Unknown Location": {
- "comment": "Fallback name when reverse geocoding returns no place name\nPlaceholder shown before the user's location is resolved",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ubicación desconocida"
+ "The exported file may contain personal information. Store and share it carefully." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "El archivo exportado puede contener información personal. Guárdalo y compártelo con cuidado."
}
}
}
},
- "Unknown error: %@": {
- "comment": "Weather error fallback wrapping an unexpected underlying error",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Error desconocido: %@"
+ "The free version of SunHat shows banner ads provided by Google AdMob. Google may collect device data (such as an advertising identifier) to serve and measure ads; iOS asks for your permission before any cross-app tracking, and you can decline. The SunHat Ad-Free subscription removes all ads, and no ad requests are made for subscribers. See Google's privacy policy for details on how Google handles ad data." : { "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La versión gratuita de SunHat muestra anuncios en banner proporcionados por Google AdMob. Google puede recopilar datos del dispositivo (como el identificador publicitario) para servir y medir anuncios; iOS te pide permiso antes de cualquier seguimiento entre aplicaciones, y puedes rechazarlo. La suscripción SunHat Ad-Free elimina todos los anuncios, y los suscriptores no generan ninguna solicitud de anuncios. Consulta la política de privacidad de Google para saber cómo trata Google los datos de los anuncios."
}
}
}
+
+
},
- "Unknown location error": {
- "comment": "Location error title fallback",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Error de ubicación desconocido"
+ "The GPS signal is too weak for accurate positioning" : {
+ "comment" : "Location error failure reason",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La señal GPS es demasiado débil para un posicionamiento preciso"
}
}
}
},
- "Unlikely": {
- "comment": "Trigger likelihood: 0% chance the reminder's conditions will occur",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Poco probable"
+ "The last known location is too old to be useful" : {
+ "comment" : "Location error failure reason",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La última ubicación conocida es demasiado antigua para ser útil"
}
}
}
},
- "Untitled Reminder": {
- "comment": "Fallback title for a reminder with no title, shown on compact surfaces\nFallback title when reminder has no title",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorio sin título"
+ "The weather service is experiencing temporary issues" : {
+ "comment" : "Weather error failure reason",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "El servicio meteorológico está teniendo problemas temporales"
}
}
}
},
- "Update or correct your personal information": {
- "comment": "Privacy inquiry type description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Actualizar o corregir tu información personal"
+ "Third-Party Services" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Servicios de terceros"
}
}
}
},
- "Updated %@ ago": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Actualizado hace %@"
+ "This action cannot be undone. The reminder and all its history will be permanently deleted." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Esta acción no se puede deshacer. El recordatorio y todo su historial se eliminarán permanentemente."
}
}
}
},
- "Updating weather": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Actualizando clima"
+ "This controls how forecasts and weather conditions appear. You can change it later in Settings." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Esto controla cómo aparecen los pronósticos y las condiciones climáticas. Puedes cambiarlo luego en Ajustes."
}
}
}
},
- "Updating weather…": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Actualizando clima…"
+ "This export is provided to comply with GDPR Article 20 (Right to Data Portability) and CCPA requirements." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Esta exportación se proporciona para cumplir con el GDPR Article 20 (derecho a la portabilidad de datos) y los requisitos del CCPA."
}
}
}
},
- "Urgent": {
- "comment": "Notification sound option: urgent\nReminder priority level",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Urgente"
+ "This is how your %@ notifications will appear" : {
+ "comment" : "Body of a sample notification; 'type' is a reminder category name such as Exercise or Gardening",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Así se verán tus notificaciones de %@"
}
}
}
},
- "Urgent alert pattern": {
- "comment": "Description of alert vibration pattern option",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Patrón de alerta urgente"
+ "This permanently removes your reminders, preferences, saved locations, and cached weather data from this device." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Esto elimina permanentemente tus recordatorios, preferencias, ubicaciones guardadas y datos meteorológicos en caché de este dispositivo."
}
}
}
},
- "Use 'feels like' temperature": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Usar temperatura de \"sensación térmica\""
+ "This weather task may have been deleted." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Es posible que esta tarea del clima haya sido eliminada."
}
}
}
},
- "Use Current Location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Usar ubicación actual"
+ "This will reset all app settings to their defaults. Your reminders will not be affected." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Esto restablecerá todos los ajustes de la app a sus valores predeterminados. Tus recordatorios no se verán afectados."
}
}
}
},
- "Use current location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Usar ubicación actual"
+ "Thunderstorm" : {
+ "comment" : "Weather condition name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tormenta eléctrica"
}
}
}
},
- "Use sunscreen and seek shade midday": {
- "comment": "In-app weather advisory instructions",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Usa protector solar y busca sombra al mediodía"
+ "Title" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Título"
}
}
}
},
- "Use the device location for live local weather.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Usa la ubicación del dispositivo para el clima local en vivo."
+ "To get weather reminders, allow notifications for SunHat in Settings." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Para recibir recordatorios del clima, permite las notificaciones de SunHat en Ajustes."
}
}
}
},
- "Use your current location for local forecasts, or enter a city manually.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Usa tu ubicación actual para pronósticos locales, o escribe una ciudad manualmente."
+ "Today" : {
+ "comment" : "Relative day label in the weekly forecast",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hoy"
}
}
}
},
- "Used %lld times": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Usado %lld veces"
+ "Today's Reminders" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorios de hoy"
}
}
}
},
- "Uses the device location for live local weather.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Usa la ubicación del dispositivo para el clima local en vivo."
+ "Tolerance:" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tolerancia:"
}
}
}
},
- "Uses your device location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Usa la ubicación de tu dispositivo"
+ "Tomorrow" : {
+ "comment" : "Relative day label in the weekly forecast",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mañana"
}
}
}
},
- "Using device location": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Usando la ubicación del dispositivo"
+ "Too many requests made in a short period" : {
+ "comment" : "Weather error failure reason",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Se hicieron demasiadas solicitudes en poco tiempo"
}
}
}
},
- "Vehicle washing and detailing": {
- "comment": "Description of car washing activity interest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Lavado y detallado de vehículos"
+ "Trail hiking and nature exploration" : {
+ "comment" : "Description of hiking activity interest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Senderismo y exploración de la naturaleza"
}
}
}
},
- "Version": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Versión"
+ "Travel" : {
+ "comment" : "Reminder category name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Viajes"
}
}
}
},
- "Very dry": {
- "comment": "Humidity level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Muy seco"
+ "Trees" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Árboles"
}
}
}
},
- "Very high": {
- "comment": "UV index level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Muy alto"
+ "Trend" : {
+ "comment" : "Trigger type name: consecutive days trend",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tendencia"
}
}
}
},
- "Very humid": {
- "comment": "Dew point comfort description\nHumidity level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Muy húmedo"
+ "Trigger at specific temperature" : {
+ "comment" : "Trigger type description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Activar a una temperatura específica"
}
}
}
},
- "Very likely": {
- "comment": "Prediction confidence level for when a reminder will next trigger\nTrigger likelihood: 76-100% chance the reminder's conditions will occur",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Muy probable"
+ "Trigger on average over time" : {
+ "comment" : "Trigger type description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Activar según el promedio a lo largo del tiempo"
}
}
}
},
- "Very unhealthy": {
- "comment": "Air quality index level description",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Muy no saludable"
+ "Trigger on seasonal changes" : {
+ "comment" : "Trigger type description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Activar según cambios estacionales"
}
}
}
},
- "View Alert": {
- "comment": "Notification action button",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ver alerta"
+ "Trigger on temperature trends" : {
+ "comment" : "Trigger type description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Activar según tendencias de temperatura"
}
}
}
},
- "View All %lld Tasks": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ver las %lld tareas"
+ "Trigger Type" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tipo de activación"
}
}
}
},
- "View Details": {
- "comment": "Notification action button",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ver detalles"
+ "Trigger when temperature is %@ %@ degrees" : {
+ "comment" : "Accessibility label clause describing a reminder's temperature trigger condition",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Trigger when temperature is %1$@ %2$@ degrees"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Se activa cuando la temperatura está %@ %@ grados"
}
}
}
},
- "View Forecast": {
- "comment": "Notification action button",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ver pronóstico"
+ "Trigger within temperature range" : {
+ "comment" : "Trigger type description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Activar dentro de un rango de temperatura"
}
}
}
},
- "Visit %@ in a browser.": {
- "comment": "Alert message",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Visita %@ en un navegador."
+ "Trigger: No condition set" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Activación: sin condición establecida"
}
}
}
},
- "W": {
- "comment": "Wind direction abbreviation: West",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "O"
+ "Trigger: When temperature is %@ %.1f°" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Trigger: When temperature is %1$@ %2$.1f°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Activación: cuando la temperatura está %@ %.1f°"
}
}
}
},
- "WNW": {
- "comment": "Wind direction abbreviation: West-northwest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "ONO"
+ "Triggered" : {
+ "comment" : "Reminder history action label",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Activado"
}
}
}
},
- "WSW": {
- "comment": "Wind direction abbreviation: West-southwest",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "OSO"
+ "Triggers when temperature is %@ %lld°F" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Triggers when temperature is %1$@ %2$lld°F"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Se activa cuando la temperatura está %@ %lld°F"
}
}
}
},
- "Wait a few minutes before making another request": {
- "comment": "Weather error recovery suggestion",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Espera unos minutos antes de hacer otra solicitud"
+ "Try a broader city or landmark name." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Prueba con un nombre de ciudad o punto de referencia más amplio."
}
}
}
},
- "Waiting for conditions": {
- "comment": "Reminder detail status when the trigger condition doesn't currently match",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Esperando condiciones"
+ "Try a different search or switch back to all tasks." : {
+ "comment" : "Empty state message when a search or filter matches no weather tasks",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Prueba otra búsqueda o vuelve a mostrar todas las tareas."
}
}
}
},
- "Waiting for weather data": {
- "comment": "Status text shown on a reminder card while weather data hasn't loaded yet",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Esperando datos meteorológicos"
+ "Try Again" : {
+ "comment" : "Retry button in the weather tab's unavailable state",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Intentar de nuevo"
}
}
}
},
- "Walking": {
- "comment": "Activity interest option: walking\nReminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Caminar"
+ "Try again in a few minutes or check your internet connection" : {
+ "comment" : "Weather error recovery suggestion",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Inténtalo de nuevo en unos minutos o verifica tu conexión a internet"
}
}
}
},
- "Warning": {
- "comment": "Weather alert severity level",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Advertencia"
+ "Try again later" : {
+ "comment" : "Weather error generic recovery suggestion",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Inténtalo de nuevo más tarde"
}
}
}
},
- "Watch": {
- "comment": "Weather alert severity level",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Vigilancia"
+ "Try again or enter your city manually" : {
+ "comment" : "Location error recovery suggestion fallback",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Inténtalo de nuevo o escribe tu ciudad manualmente"
}
}
}
},
- "Watching": {
- "comment": "Dashboard section title for the list of active weather tasks",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Vigilando"
+ "Try entering your city manually instead" : {
+ "comment" : "Location error recovery suggestion",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Intenta escribir tu ciudad manualmente"
}
}
}
},
- "Water": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Agua"
+ "Try getting your location again" : {
+ "comment" : "Location error recovery suggestion",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Intenta obtener tu ubicación de nuevo"
}
}
}
},
- "Water Plants": {
- "comment": "Notification action button",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Regar plantas"
+ "Try moving to an area with better GPS reception" : {
+ "comment" : "Location error recovery suggestion",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Intenta moverte a un área con mejor recepción GPS"
}
}
}
},
- "We do not collect any personal analytics or tracking data. The app works entirely on your device.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No recopilamos datos personales de análisis ni de seguimiento. La app funciona íntegramente en tu dispositivo."
+ "Try searching with a different spelling or include the state/country" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Intenta buscar con otra ortografía o incluye el estado o país"
}
}
}
},
- "We only access your location to provide relevant weather information. Coordinates are sent only to enabled weather providers to fetch forecasts for your area.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Solo accedemos a tu ubicación para ofrecerte información del clima relevante. Las coordenadas se envían únicamente a los proveedores de clima activados para obtener pronósticos de tu zona."
+ "Turn On Weather Reminders?" : {
+ "comment" : "Lifecycle prompt alert title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¿Activar recordatorios del clima?"
}
}
}
},
- "We respond to privacy inquiries within 30 days, as required by GDPR and CCPA.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Respondemos las consultas de privacidad dentro de 30 días, según lo exigen el GDPR y el CCPA."
+ "Type DELETE to confirm" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Escribe ELIMINAR para confirmar"
}
}
}
},
- "We use Apple WeatherKit as our data source. Weather data is typically accurate for the next 24-48 hours, with longer forecasts being less precise.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Usamos Apple WeatherKit como fuente de datos. Los datos del clima suelen ser precisos para las próximas 24-48 horas; los pronósticos a más largo plazo son menos exactos."
+ "Type of Inquiry" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tipo de solicitud"
}
}
}
},
- "Weather": {
- "comment": "Notification sound option: weather-themed",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Clima"
+ "Unable to access app data" : {
+ "comment" : "Data & Privacy error\nNotification error when the local data store is unavailable",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo acceder a los datos de la app"
}
}
}
},
- "Weather API key is invalid": {
- "comment": "Weather error shown when the configured API key was rejected",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La clave de la API meteorológica no es válida"
+ "Unable to connect to location services" : {
+ "comment" : "Location error failure reason",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo conectar a los servicios de ubicación"
}
}
}
},
- "Weather API key not configured": {
- "comment": "Weather error shown when no API key is configured for the backup provider",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La clave de la API meteorológica no está configurada"
+ "Unable to determine your location. Please check location permissions in Settings." : {
+ "comment" : "Dashboard error when no location can be resolved for a weather refresh",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudo determinar tu ubicación. Comprueba los permisos de ubicación en Ajustes."
}
}
}
},
- "Weather Condition Met": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Condición del clima cumplida"
+ "Unable to fetch forecast data: %@" : {
+ "comment" : "Notification body fallback when the forecast could not be fetched for a prediction-based trigger",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se pudieron obtener los datos del pronóstico: %@"
}
}
}
},
- "Weather Data": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Datos del clima"
+ "Under GDPR and CCPA, you have specific rights regarding your personal data. We're committed to honoring these rights." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Según el GDPR y el CCPA, tienes derechos específicos respecto a tus datos personales. Nos comprometemos a respetar estos derechos."
}
}
}
},
- "Weather Providers": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Proveedores meteorológicos"
+ "Unhealthy" : {
+ "comment" : "Air quality index level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No saludable"
}
}
}
},
- "Weather Reminder": {
- "comment": "Default notification title",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorio del clima"
+ "Unhealthy for sensitive" : {
+ "comment" : "Air quality index level description, short for 'unhealthy for sensitive groups'",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No saludable para sensibles"
}
}
}
},
- "Weather Tasks": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tareas del clima"
+ "Unknown" : {
+ "comment" : "Fallback app version when it cannot be read from the bundle\nFallback build number when it cannot be read from the bundle\nTrigger likelihood: percentage outside the expected 0-100 range\nWeather condition name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Desconocido"
}
}
}
},
- "Weather Timeframe": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Período del clima"
+ "Unknown error: %@" : {
+ "comment" : "Weather error fallback wrapping an unexpected underlying error",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Error desconocido: %@"
}
}
}
},
- "Weather Unavailable": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Clima no disponible"
+ "Unknown Location" : {
+ "comment" : "Fallback name when reverse geocoding returns no place name\nPlaceholder shown before the user's location is resolved",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ubicación desconocida"
}
}
}
},
- "Weather data for this reminder is not available right now.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Los datos meteorológicos de este recordatorio no están disponibles en este momento."
+ "Unknown location error" : {
+ "comment" : "Location error title fallback",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Error de ubicación desconocido"
}
}
}
},
- "Weather data is provided by Apple WeatherKit. Please review Apple's privacy policy for information on how it handles data.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Los datos del clima los proporciona Apple WeatherKit. Consulta la política de privacidad de Apple para saber cómo trata los datos."
+ "Unlikely" : {
+ "comment" : "Trigger likelihood: 0% chance the reminder's conditions will occur",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Poco probable"
}
}
}
},
- "Weather data provider": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Proveedor de datos del clima"
+ "Untitled Reminder" : {
+ "comment" : "Fallback title for a reminder with no title, shown on compact surfaces\nFallback title when reminder has no title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorio sin título"
}
}
}
},
- "Weather data unavailable: %@": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Datos del clima no disponibles: %@"
+ "Update or correct your personal information" : {
+ "comment" : "Privacy inquiry type description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Actualizar o corregir tu información personal"
}
}
}
},
- "Weather information is fetched from reliable sources and cached locally on your device for better performance. This data is not transmitted to our servers.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La información del clima se obtiene de fuentes fiables y se guarda en caché localmente en tu dispositivo para mejorar el rendimiento. Estos datos no se transmiten a nuestros servidores."
+ "Updated %@ ago" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Actualizado hace %@"
}
}
}
},
- "Weather records": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Registros meteorológicos"
+ "Updating weather" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Actualizando clima"
}
}
}
},
- "Weather reminder": {
- "comment": "Fallback short description for a reminder",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorio del clima"
+ "Updating weather…" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Actualizando clima…"
}
}
}
},
- "Weather reminders for the plans that matter": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorios del clima para los planes que importan"
+ "Urgent" : {
+ "comment" : "Notification sound option: urgent\nReminder priority level",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Urgente"
}
}
}
},
- "Weather reminders pause between these times.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Los recordatorios del clima se pausan entre estos horarios."
+ "Urgent alert pattern" : {
+ "comment" : "Description of alert vibration pattern option",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Patrón de alerta urgente"
}
}
}
},
- "Weather unavailable": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Clima no disponible"
+ "Use 'feels like' temperature" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Usar temperatura de \"sensación térmica\""
}
}
}
},
- "Weather unavailable for %@": {
- "comment": "Accessibility label when weather data could not be loaded for the current location",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Clima no disponible para %@"
+ "Use current location" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Usar ubicación actual"
}
}
}
},
- "Weather-aware reminders": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Recordatorios sensibles al clima"
+ "Use Current Location" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Usar ubicación actual"
}
}
}
},
- "Welcome to SunHat": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Bienvenido a SunHat"
+ "Use sunscreen and seek shade midday" : {
+ "comment" : "In-app weather advisory instructions",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Usa protector solar y busca sombra al mediodía"
}
}
}
},
- "What could make SunHat better?": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¿Qué podría mejorar SunHat?"
+ "Use the device location for live local weather." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Usa la ubicación del dispositivo para el clima local en vivo."
}
}
}
},
- "What's the difference between temperature and 'feels like'?": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¿Cuál es la diferencia entre la temperatura y la sensación térmica?"
+ "Use your current location for local forecasts, or enter a city manually." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Usa tu ubicación actual para pronósticos locales, o escribe una ciudad manualmente."
}
}
}
},
- "When temp < %@": {
- "comment": "Short trigger prediction summary; keep the '<' comparison symbol",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cuando la temp. < %@"
+ "Used %lld times" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Usado %lld veces"
}
}
}
},
- "When temp = %@": {
- "comment": "Short trigger prediction summary; keep the '=' comparison symbol",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cuando la temp. = %@"
+ "Uses the device location for live local weather." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Usa la ubicación del dispositivo para el clima local en vivo."
}
}
}
},
- "When temp > %@": {
- "comment": "Short trigger prediction summary; keep the '>' comparison symbol",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cuando la temp. > %@"
+ "Uses your device location" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Usa la ubicación de tu dispositivo"
}
}
}
},
- "When temp between %@ and %@": {
- "comment": "Short trigger prediction summary for a temperature range",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "When temp between %1$@ and %2$@"
+ "Using device location" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Usando la ubicación del dispositivo"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cuando la temp. está entre %@ y %@"
+ }
+ }
+ },
+ "Vehicle washing and detailing" : {
+ "comment" : "Description of car washing activity interest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Lavado y detallado de vehículos"
}
}
}
},
- "When temperature is %@ %lf°": {
- "comment": "Short description of a reminder's temperature trigger",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "When temperature is %1$@ %2$lf°"
+ "Version" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Versión"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cuando la temperatura está %@ %lf°"
+ }
+ }
+ },
+ "Very dry" : {
+ "comment" : "Humidity level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Muy seco"
}
}
}
},
- "When temperature is %@ %lld degrees": {
- "comment": "Accessibility label clause describing a reminder's temperature trigger condition",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "When temperature is %1$@ %2$lld degrees"
+ "Very high" : {
+ "comment" : "UV index level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Muy alto"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cuando la temperatura está %@ %lld grados"
+ }
+ }
+ },
+ "Very humid" : {
+ "comment" : "Dew point comfort description\nHumidity level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Muy húmedo"
}
}
}
},
- "When temperature is %@ %lld°": {
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "When temperature is %1$@ %2$lld°"
+ "Very likely" : {
+ "comment" : "Prediction confidence level for when a reminder will next trigger\nTrigger likelihood: 76-100% chance the reminder's conditions will occur",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Muy probable"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Cuando la temperatura está %@ %lld°"
+ }
+ }
+ },
+ "Very unhealthy" : {
+ "comment" : "Air quality index level description",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Muy no saludable"
}
}
}
},
- "While Using": {
- "comment": "Location access status",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Al usar la app"
+ "View Alert" : {
+ "comment" : "Notification action button",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ver alerta"
}
}
}
},
- "Why isn't my reminder triggering?": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¿Por qué no se activa mi recordatorio?"
+ "View All %lld Tasks" : {
+ "comment" : "Button under the dashboard's truncated task list; opens the Reminders tab",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ver las %lld tareas"
}
}
}
},
- "Windy": {
- "comment": "Weather condition name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Ventoso"
+ "View Details" : {
+ "comment" : "Notification action button",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ver detalles"
}
}
}
},
- "Work": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Trabajo"
+ "View Forecast" : {
+ "comment" : "Notification action button",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ver pronóstico"
}
}
}
},
- "Work Related": {
- "comment": "Reminder category name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Relacionado con el trabajo"
+ "Visit %@ in a browser." : {
+ "comment" : "Alert message",
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Visita %@ en un navegador."
}
}
}
},
- "Writing": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Escritura"
+ "W" : {
+ "comment" : "Wind direction abbreviation: West",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "O"
}
}
}
},
- "Yellow": {
- "comment": "Reminder color swatch name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Amarillo"
+ "Wait a few minutes before making another request" : {
+ "comment" : "Weather error recovery suggestion",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Espera unos minutos antes de hacer otra solicitud"
}
}
}
},
- "Yes": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sí"
+ "Waiting for conditions" : {
+ "comment" : "Reminder detail status when the trigger condition doesn't currently match",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Esperando condiciones"
}
}
}
},
- "Yes. Each reminder stores its own location: use your current location or pick a city when creating or editing a reminder.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Sí. Cada recordatorio guarda su propia ubicación: usa tu ubicación actual o elige una ciudad al crear o editar un recordatorio."
+ "Waiting for weather data" : {
+ "comment" : "Status text shown on a reminder card while weather data hasn't loaded yet",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Esperando datos meteorológicos"
}
}
}
},
- "Yoga": {
- "comment": "Reminder icon activity name",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Yoga"
+ "Walking" : {
+ "comment" : "Activity interest option: walking\nReminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Caminar"
}
}
}
},
- "Your Data": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tus datos"
+ "Warning" : {
+ "comment" : "Weather alert severity level",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Advertencia"
}
}
}
},
- "Your Email (optional)": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tu correo (opcional)"
+ "Watch" : {
+ "comment" : "Weather alert severity level",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Vigilancia"
}
}
}
},
- "Your Information": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tu información"
+ "Watching" : {
+ "comment" : "Dashboard section title for the list of active weather tasks",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Vigilando"
}
}
}
},
- "Your Inquiry": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tu consulta"
+ "Water" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Agua"
}
}
}
},
- "Your Privacy Matters": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tu privacidad importa"
+ "Water Plants" : {
+ "comment" : "Notification action button",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Regar plantas"
}
}
}
},
- "Your Privacy Rights": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tus derechos de privacidad"
+ "We do not collect any personal analytics or tracking data. The app works entirely on your device." : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No recopilamos datos personales de análisis ni de seguimiento. La app funciona íntegramente en tu dispositivo."
}
}
}
},
- "Your current location cannot be determined": {
- "comment": "Location error failure reason",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "No se puede determinar tu ubicación actual"
+ "We only access your location to provide relevant weather information. Coordinates are sent only to enabled weather providers to fetch forecasts for your area." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Solo accedemos a tu ubicación para ofrecerte información del clima relevante. Las coordenadas se envían únicamente a los proveedores de clima activados para obtener pronósticos de tu zona."
}
}
}
},
- "Your data is stored locally on this device. iCloud sync will be available in a future update.": {
- "comment": "Data & Privacy screen sync status",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tus datos se almacenan localmente en este dispositivo. La sincronización con iCloud estará disponible en una futura actualización."
+ "We respond to privacy inquiries within 30 days, as required by GDPR and CCPA." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Respondemos las consultas de privacidad dentro de 30 días, según lo exigen el GDPR y el CCPA."
}
}
}
},
- "Your feedback opens in Mail so you can review it before sending.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tus comentarios se abren en Mail para que puedas revisarlos antes de enviarlos."
+ "We use Apple WeatherKit as our data source. Weather data is typically accurate for the next 24-48 hours, with longer forecasts being less precise." : {
+ "comment" : "FAQ answer",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Usamos Apple WeatherKit como fuente de datos. Los datos del clima suelen ser precisos para las próximas 24-48 horas; los pronósticos a más largo plazo son menos exactos."
}
}
}
},
- "Your location is used only to fetch forecasts from enabled weather providers for your reminders.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tu ubicación se usa solo para obtener pronósticos de los proveedores meteorológicos habilitados para tus recordatorios."
+ "Weather" : {
+ "comment" : "Notification sound option: weather-themed",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Clima"
}
}
}
},
- "Your reminders and app preferences are stored locally on your device. We never have access to your personal reminder content.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tus recordatorios y preferencias de la app se almacenan localmente en tu dispositivo. Nunca tenemos acceso al contenido personal de tus recordatorios."
+ "Weather API key is invalid" : {
+ "comment" : "Weather error shown when the configured API key was rejected",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La clave de la API meteorológica no es válida"
}
}
}
},
- "Your saved location stays on this device. Coordinates are sent only to enabled weather providers when fetching a forecast.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "Tu ubicación guardada permanece en este dispositivo. Las coordenadas solo se envían a los proveedores meteorológicos habilitados al obtener un pronóstico."
+ "Weather API key not configured" : {
+ "comment" : "Weather error shown when no API key is configured for the backup provider",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La clave de la API meteorológica no está configurada"
}
}
}
},
- "Your weather condition has been met!": {
- "comment": "Default notification body",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "¡Se cumplió tu condición del clima!"
+ "Weather Condition Met" : {
+ "comment" : "Default weather reminder notification title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Condición del clima cumplida"
}
}
}
},
- "[Please describe your privacy request]": {
- "comment": "Placeholder in the privacy-request email template when the user left the inquiry field empty",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "[Describe tu solicitud de privacidad]"
+ "Weather Data" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Datos del clima"
}
}
}
},
- "[Your email address]": {
- "comment": "Placeholder in the privacy-request email template when the user left the email field empty",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "[Tu dirección de correo]"
+ "Weather data for this reminder is not available right now." : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Los datos meteorológicos de este recordatorio no están disponibles en este momento."
}
}
}
},
- "above": {
- "comment": "Temperature comparison word, e.g. 'temperature is above 70°'",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "por encima de"
+ "Weather data is provided by Apple WeatherKit and advertising by Google AdMob. Please review Apple's and Google's privacy policies for information on how they handle data." : { "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Los datos meteorológicos los proporciona Apple WeatherKit y la publicidad Google AdMob. Revisa las políticas de privacidad de Apple y Google para saber cómo tratan los datos."
}
}
}
+
+
},
- "average %lld° near %lld°": {
- "comment": "Short clause describing a forecast day's average temperature versus target, used inside a comma-separated list",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "average %1$lld° near %2$lld°"
+ "Weather data is provided by Apple WeatherKit. Please review Apple's privacy policy for information on how it handles data." : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Los datos del clima los proporciona Apple WeatherKit. Consulta la política de privacidad de Apple para saber cómo trata los datos."
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "promedio %lld° cerca de %lld°"
+ }
+ }
+ },
+ "Weather data provider" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Proveedor de datos del clima"
}
}
}
},
- "below": {
- "comment": "Temperature comparison word, e.g. 'temperature is below 70°'",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "por debajo de"
+ "Weather data unavailable: %@" : {
+ "comment" : "Dashboard error banner; the placeholder is the underlying error message",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Datos del clima no disponibles: %@"
}
}
}
},
- "between": {
- "comment": "Temperature comparison word, e.g. 'temperature is between 60° and 70°'",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "entre"
+ "Weather information is fetched from reliable sources and cached locally on your device for better performance. This data is not transmitted to our servers." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "La información del clima se obtiene de fuentes fiables y se guarda en caché localmente en tu dispositivo para mejorar el rendimiento. Estos datos no se transmiten a nuestros servidores."
}
}
}
},
- "dry conditions expected": {
- "comment": "Short clause listing one satisfied dry-weather condition of a multi-condition reminder",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "se esperan condiciones secas"
+ "Weather Providers" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Proveedores meteorológicos"
}
}
}
},
- "e.g., -122.4194": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "p. ej., -122.4194"
+ "Weather records" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Registros meteorológicos"
}
}
}
},
- "e.g., 37.7749": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "p. ej., 37.7749"
+ "Weather reminder" : {
+ "comment" : "Fallback short description for a reminder",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorio del clima"
}
}
}
},
- "exactly": {
- "comment": "Temperature comparison word, e.g. 'temperature is exactly 70°'",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "exactamente en"
+ "Weather Reminder" : {
+ "comment" : "Default notification title",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorio del clima"
}
}
}
},
- "forecast doesn't cover the full %lldh dry window": {
- "comment": "Short clause: not enough forecast data exists to evaluate the N-hour dry window condition",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "el pronóstico no cubre toda la ventana seca de %lldh"
+ "Weather reminders for the plans that matter" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorios del clima para los planes que importan"
}
}
}
},
- "forecast overlaps %lld°-%lld°": {
- "comment": "Short clause describing a forecast day's temperature range overlap with the target range, used inside a comma-separated list",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "forecast overlaps %1$lld°-%2$lld°"
+ "Weather reminders pause between these times." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Los recordatorios del clima se pausan entre estos horarios."
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "el pronóstico se superpone con %lld°-%lld°"
+ }
+ }
+ },
+ "Weather Tasks" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tareas del clima"
}
}
}
},
- "high %lld° above %lld°": {
- "comment": "Short clause describing a forecast day's high temperature versus target, used inside a comma-separated list",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "high %1$lld° above %2$lld°"
+ "Weather Timeframe" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Período del clima"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "máxima %lld° por encima de %lld°"
+ }
+ }
+ },
+ "Weather unavailable" : {
+ "comment" : "Dashboard placeholder when no weather data could be loaded",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Clima no disponible"
}
}
}
},
- "humidity %lld%% outside %lld%% ±%lld%%": {
- "comment": "Short clause listing one unsatisfied humidity condition of a multi-condition reminder",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "humidity %1$lld%% outside %2$lld%% ±%3$lld%%"
+ "Weather Unavailable" : {
+ "comment" : "Title of the weather tab's unavailable state",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Clima no disponible"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "humedad %lld %% fuera de %lld %% ±%lld %%"
+ }
+ }
+ },
+ "Weather unavailable for %@" : {
+ "comment" : "Accessibility label when weather data could not be loaded for the current location",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Clima no disponible para %@"
}
}
}
},
- "humidity %lld%% within %lld%% ±%lld%%": {
- "comment": "Short clause listing one satisfied humidity condition of a multi-condition reminder",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "humidity %1$lld%% within %2$lld%% ±%3$lld%%"
+ "Weather-aware reminders" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recordatorios sensibles al clima"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "humedad %lld %% dentro de %lld %% ±%lld %%"
+ }
+ }
+ },
+ "Welcome to SunHat" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bienvenido a SunHat"
}
}
}
},
- "humidity target is missing": {
- "comment": "Short clause for a misconfigured humidity condition",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "falta el objetivo de humedad"
+ "What could make SunHat better?" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¿Qué podría mejorar SunHat?"
}
}
}
},
- "iCloud is not available": {
- "comment": "Data & Privacy error",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "iCloud no está disponible"
+ "What's the difference between temperature and 'feels like'?" : {
+ "comment" : "FAQ question",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¿Cuál es la diferencia entre la temperatura y la sensación térmica?"
}
}
}
},
- "iCloud sync will be available in a future update.": {
- "comment": "Data & Privacy screen sync status",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "La sincronización con iCloud estará disponible en una futura actualización."
+ "When temp < %@" : {
+ "comment" : "Short trigger prediction summary; keep the '<' comparison symbol",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cuando la temp. < %@"
}
}
}
},
- "inside notification time window": {
- "comment": "Short clause listing one satisfied time-of-day condition of a multi-condition reminder",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "dentro de la ventana horaria de notificación"
+ "When temp = %@" : {
+ "comment" : "Short trigger prediction summary; keep the '=' comparison symbol",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cuando la temp. = %@"
}
}
}
},
- "invalid forecast temperature range": {
- "comment": "Short clause for a misconfigured forecast temperature range condition",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "rango de temperatura del pronóstico no válido"
+ "When temp > %@" : {
+ "comment" : "Short trigger prediction summary; keep the '>' comparison symbol",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cuando la temp. > %@"
}
}
}
},
- "low %lld° below %lld°": {
- "comment": "Short clause describing a forecast day's low temperature versus target, used inside a comma-separated list",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "low %1$lld° below %2$lld°"
+ "When temp between %@ and %@" : {
+ "comment" : "Short trigger prediction summary for a temperature range",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "When temp between %1$@ and %2$@"
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "mínima %lld° por debajo de %lld°"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cuando la temp. está entre %@ y %@"
}
}
}
},
- "no precipitation expected": {
- "comment": "Short clause listing one unsatisfied precipitation condition of a multi-condition reminder",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "no se espera precipitación"
+ "When temperature is %@ %lf°" : {
+ "comment" : "Short description of a reminder's temperature trigger",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "When temperature is %1$@ %2$lf°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cuando la temperatura está %@ %lf°"
}
}
}
},
- "no precipitation expected for the next %lldh": {
- "comment": "Short clause: the N-hour dry window condition is satisfied",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "no se espera precipitación en las próximas %lldh"
+ "When temperature is %@ %lld degrees" : {
+ "comment" : "Accessibility label clause describing a reminder's temperature trigger condition",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "When temperature is %1$@ %2$lld degrees"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cuando la temperatura está %@ %lld grados"
}
}
}
},
- "no precipitation requirement": {
- "comment": "Short clause for a reminder with no precipitation condition",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "sin requisito de precipitación"
+ "When temperature is %@ %lld°" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "When temperature is %1$@ %2$lld°"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cuando la temperatura está %@ %lld°"
}
}
}
},
- "no sky condition filter": {
- "comment": "Short clause for a reminder with no sky condition filter",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "sin filtro de condición del cielo"
+ "While Using" : {
+ "comment" : "Location access status",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Al usar la app"
}
}
}
},
- "no time window": {
- "comment": "Short clause for a reminder with no time-of-day condition",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "sin ventana horaria"
+ "Why isn't my reminder triggering?" : {
+ "comment" : "FAQ question",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¿Por qué no se activa mi recordatorio?"
}
}
}
},
- "now": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "ahora"
+ "wind %lld mph above %lld mph" : {
+ "comment" : "Short clause listing one unsatisfied wind condition of a multi-condition reminder",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "wind %1$lld mph above %2$lld mph"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "viento %lld mph por encima de %lld mph"
}
}
}
},
- "outside notification time window": {
- "comment": "Short clause listing one unsatisfied time-of-day condition of a multi-condition reminder",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "fuera de la ventana horaria de notificación"
+ "wind %lld mph at or below %lld mph" : {
+ "comment" : "Short clause listing one satisfied wind condition of a multi-condition reminder",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "wind %1$lld mph at or below %2$lld mph"
+ }
+ },
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "viento %lld mph igual o por debajo de %lld mph"
}
}
}
},
- "precipitation expected": {
- "comment": "Short clause listing one satisfied precipitation condition of a multi-condition reminder\nShort clause listing one unsatisfied dry-weather condition of a multi-condition reminder",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "se espera precipitación"
+ "wind limit is missing" : {
+ "comment" : "Short clause for a misconfigured wind condition",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "falta el límite de viento"
}
}
}
},
- "precipitation expected within the %lldh window": {
- "comment": "Short clause: rain/snow is forecast within the N-hour dry window, so the condition fails",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "se espera precipitación dentro de la ventana de %lldh"
+ "Windy" : {
+ "comment" : "Weather condition name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ventoso"
}
}
}
},
- "precipitation now, %lldh dry requirement not met": {
- "comment": "Short clause: it is currently raining/snowing so the N-hour dry window condition fails",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "hay precipitación ahora, no se cumple el requisito seco de %lldh"
+ "WNW" : {
+ "comment" : "Wind direction abbreviation: West-northwest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "ONO"
}
}
}
},
- "rain expected": {
- "comment": "Short clause listing one satisfied rain condition of a multi-condition reminder",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "se espera lluvia"
+ "Work" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Trabajo"
}
}
}
},
- "rain not expected": {
- "comment": "Short clause listing one unsatisfied rain condition of a multi-condition reminder",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "no se espera lluvia"
+ "Work Related" : {
+ "comment" : "Reminder category name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Relacionado con el trabajo"
}
}
}
},
- "sky condition %@ does not match": {
- "comment": "Short clause listing one unsatisfied sky-condition filter of a multi-condition reminder",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "la condición del cielo %@ no coincide"
+ "Writing" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Escritura"
}
}
}
},
- "sky condition %@ matches": {
- "comment": "Short clause listing one satisfied sky-condition filter of a multi-condition reminder",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "la condición del cielo %@ coincide"
+ "WSW" : {
+ "comment" : "Wind direction abbreviation: West-southwest",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "OSO"
}
}
}
},
- "snow expected": {
- "comment": "Short clause listing one satisfied snow condition of a multi-condition reminder",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "se espera nieve"
+ "Yellow" : {
+ "comment" : "Reminder color swatch name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Amarillo"
}
}
}
},
- "snow not expected": {
- "comment": "Short clause listing one unsatisfied snow condition of a multi-condition reminder",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "no se espera nieve"
+ "Yes" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sí"
}
}
}
},
- "temperature %@° above target %@°": {
- "comment": "Short clause listing one satisfied condition of a multi-condition reminder\nShort clause listing one unsatisfied condition of a multi-condition reminder",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "temperature %1$@° above target %2$@°"
- }
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "temperatura %@° por encima del objetivo %@°"
+ "Yes. Each reminder stores its own location: use your current location or pick a city when creating or editing a reminder." : {
+ "comment" : "FAQ answer",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sí. Cada recordatorio guarda su propia ubicación: usa tu ubicación actual o elige una ciudad al crear o editar un recordatorio."
}
}
}
},
- "temperature %@° below target %@°": {
- "comment": "Short clause listing one satisfied condition of a multi-condition reminder\nShort clause listing one unsatisfied condition of a multi-condition reminder",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "temperature %1$@° below target %2$@°"
- }
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "temperatura %@° por debajo del objetivo %@°"
+ "Yoga" : {
+ "comment" : "Reminder icon activity name",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yoga"
}
}
}
},
- "temperature %@° differs from target %@° (±%@°)": {
- "comment": "Short clause listing one unsatisfied condition of a multi-condition reminder",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "temperature %1$@° differs from target %2$@° (±%3$@°)"
+ "Your Ad-Free subscription is back. Ads are now off." : {
+ "comment" : "Alert message after a successful restore",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Your Ad-Free subscription is back. Ads are now off."
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "temperatura %@° distinta del objetivo %@° (±%@°)"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tu suscripción sin anuncios está activa de nuevo. Los anuncios están desactivados."
}
}
}
},
- "temperature %@° matches target %@° (±%@°)": {
- "comment": "Short clause listing one satisfied condition of a multi-condition reminder",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "temperature %1$@° matches target %2$@° (±%3$@°)"
+ "Your Ad-Free subscription is paused because a renewal payment failed. Update your billing details to restore it." : {
+ "comment" : "Alert message when restore finds a subscription in billing retry",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Your Ad-Free subscription is paused because a renewal payment failed. Update your billing details to restore it."
}
},
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "temperatura %@° coincide con el objetivo %@° (±%@°)"
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tu suscripción sin anuncios está en pausa porque falló un pago de renovación. Actualiza tus datos de pago para restaurarla."
}
}
}
},
- "temperature %@° outside range %@°-%@°": {
- "comment": "Short clause listing one unsatisfied condition of a multi-condition reminder",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "temperature %1$@° outside range %2$@°-%3$@°"
+ "Your current location cannot be determined" : {
+ "comment" : "Location error failure reason",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No se puede determinar tu ubicación actual"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "temperatura %@° fuera del rango %@°-%@°"
+ }
+ }
+ },
+ "Your Data" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tus datos"
}
}
}
},
- "temperature %@° within range %@°-%@°": {
- "comment": "Short clause listing one satisfied condition of a multi-condition reminder",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "temperature %1$@° within range %2$@°-%3$@°"
+ "Your data is stored locally on this device. iCloud sync will be available in a future update." : {
+ "comment" : "Data & Privacy screen sync status",
+ "extractionState" : "stale",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tus datos se almacenan localmente en este dispositivo. La sincronización con iCloud estará disponible en una futura actualización."
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "temperatura %@° dentro del rango %@°-%@°"
+ }
+ }
+ },
+ "Your Email (optional)" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tu correo (opcional)"
}
}
}
},
- "wind %lld mph above %lld mph": {
- "comment": "Short clause listing one unsatisfied wind condition of a multi-condition reminder",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "wind %1$lld mph above %2$lld mph"
+ "Your feedback opens in Mail so you can review it before sending." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tus comentarios se abren en Mail para que puedas revisarlos antes de enviarlos."
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "viento %lld mph por encima de %lld mph"
+ }
+ }
+ },
+ "Your Information" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tu información"
}
}
}
},
- "wind %lld mph at or below %lld mph": {
- "comment": "Short clause listing one satisfied wind condition of a multi-condition reminder",
- "localizations": {
- "en": {
- "stringUnit": {
- "state": "new",
- "value": "wind %1$lld mph at or below %2$lld mph"
+ "Your Inquiry" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tu consulta"
}
- },
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "viento %lld mph igual o por debajo de %lld mph"
+ }
+ }
+ },
+ "Your location is used only to fetch forecasts from enabled weather providers for your reminders." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tu ubicación se usa solo para obtener pronósticos de los proveedores meteorológicos habilitados para tus recordatorios."
}
}
}
},
- "wind limit is missing": {
- "comment": "Short clause for a misconfigured wind condition",
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "falta el límite de viento"
+ "Your Privacy Matters" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tu privacidad importa"
}
}
}
},
- "© 2026 SunHat": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "© 2026 SunHat"
+ "Your Privacy Rights" : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tus derechos de privacidad"
}
}
}
},
- "© 2026 SunHat. All rights reserved.": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "© 2026 SunHat. Todos los derechos reservados."
+ "Your reminders and app preferences are stored locally on your device. We never have access to your personal reminder content." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tus recordatorios y preferencias de la app se almacenan localmente en tu dispositivo. Nunca tenemos acceso al contenido personal de tus recordatorios."
}
}
}
},
- "°": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "°"
+ "Your saved location stays on this device. Coordinates are sent only to enabled weather providers when fetching a forecast." : {
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tu ubicación guardada permanece en este dispositivo. Las coordenadas solo se envían a los proveedores meteorológicos habilitados al obtener un pronóstico."
}
}
}
},
- "±%lld°": {
- "localizations": {
- "es": {
- "stringUnit": {
- "state": "translated",
- "value": "±%lld°"
+ "Your weather condition has been met!" : {
+ "comment" : "Default notification body",
+ "localizations" : {
+ "es" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "¡Se cumplió tu condición del clima!"
}
}
}
}
},
- "version": "1.0"
-}
+ "version" : "1.0"
+}
\ No newline at end of file
diff --git a/SunHat/Services/Ads/AdActivationPolicy.swift b/SunHat/Services/Ads/AdActivationPolicy.swift
new file mode 100644
index 0000000..97d4488
--- /dev/null
+++ b/SunHat/Services/Ads/AdActivationPolicy.swift
@@ -0,0 +1,68 @@
+//
+// AdActivationPolicy.swift
+// SunHat
+//
+// Pure decision logic for when the ad SDK may start and when the App
+// Tracking Transparency prompt may be shown. StoreKit-free and UIKit-free so
+// it is unit-testable; AdManager feeds it live values and applies the result.
+//
+// Rules encoded here:
+// - Nothing happens unless entitlement affirmatively allows ad requests
+// (unknown fails closed — a subscriber never triggers ad machinery).
+// - The ATT prompt is never shown during the app's first session, and only
+// while the scene is active (the system ignores it otherwise).
+// - The SDK may start without tracking authorization (ads simply serve
+// non-personalized); a deferred ATT request can still happen in a later
+// session after the SDK is already running.
+//
+
+import Foundation
+
+/// ATTrackingManager.AuthorizationStatus reduced to a framework-free value.
+nonisolated enum TrackingStatusKind: Sendable, Equatable {
+ case notDetermined
+ case restricted
+ case denied
+ case authorized
+}
+
+nonisolated struct AdActivationDecision: Sendable, Equatable {
+ var shouldRequestTracking: Bool
+ var shouldStartSDK: Bool
+
+ static let none = AdActivationDecision(shouldRequestTracking: false, shouldStartSDK: false)
+}
+
+nonisolated enum AdActivationPolicy {
+ /// The entitlement input for ad decisions. While the live state is still
+ /// `.unknown` (StoreKit hasn't answered this launch), fall back to the
+ /// UserDefaults-mirrored last-known state so a known non-subscriber's ad
+ /// slot exists from first layout instead of popping in mid-session
+ /// (Google policy: content must not shift around ads). Both unknown →
+ /// no ads: fail closed.
+ static func effectiveAllowsAdRequests(
+ live: AdFreeEntitlementState,
+ cached: AdFreeEntitlementState
+ ) -> Bool {
+ live == .unknown ? cached.allowsAdRequests : live.allowsAdRequests
+ }
+
+ static func decide(
+ allowsAdRequests: Bool,
+ sdkStarted: Bool,
+ trackingStatus: TrackingStatusKind,
+ appOpenCount: Int,
+ isSceneActive: Bool
+ ) -> AdActivationDecision {
+ guard allowsAdRequests else { return .none }
+
+ let shouldRequestTracking = trackingStatus == .notDetermined
+ && appOpenCount >= 2
+ && isSceneActive
+
+ return AdActivationDecision(
+ shouldRequestTracking: shouldRequestTracking,
+ shouldStartSDK: !sdkStarted
+ )
+ }
+}
diff --git a/SunHat/Services/Ads/AdManager.swift b/SunHat/Services/Ads/AdManager.swift
new file mode 100644
index 0000000..fc5fe56
--- /dev/null
+++ b/SunHat/Services/Ads/AdManager.swift
@@ -0,0 +1,289 @@
+//
+// AdManager.swift
+// SunHat
+//
+// Entitlement-gated wrapper around the Google Mobile Ads SDK. The SDK is
+// never started — no network activity, no ad requests — unless entitlement
+// allows ads: affirmatively non-entitled, or (only while live resolution is
+// still pending at launch) the UserDefaults-mirrored last-known state says
+// non-entitled. Info.plist sets GADDelayAppMeasurementInit so nothing runs
+// before start() either.
+//
+// Consent & tracking order per Google's guidance: UMP consent (EEA/UK form
+// when required) → App Tracking Transparency → SDK start, with the SDK
+// gated on ConsentInformation.canRequestAds. ATT is deferred past the first
+// session and only requested while the scene is active — see
+// AdActivationPolicy for the exact rules.
+//
+// Observes StoreManager's entitlement state directly, so a mid-session
+// purchase immediately flips shouldShowAdSlots and every banner slot
+// disappears.
+//
+
+import Foundation
+import AppTrackingTransparency
+import GoogleMobileAds
+import UserMessagingPlatform
+import UIKit
+import os
+
+@MainActor
+protocol TrackingAuthorizing: AnyObject {
+ var statusKind: TrackingStatusKind { get }
+ func requestAuthorization() async
+}
+
+@MainActor
+final class SystemTrackingAuthorizer: TrackingAuthorizing {
+ var statusKind: TrackingStatusKind {
+ switch ATTrackingManager.trackingAuthorizationStatus {
+ case .notDetermined: .notDetermined
+ case .restricted: .restricted
+ case .denied: .denied
+ case .authorized: .authorized
+ @unknown default: .denied
+ }
+ }
+
+ func requestAuthorization() async {
+ _ = await ATTrackingManager.requestTrackingAuthorization()
+ }
+}
+
+@MainActor
+@Observable
+final class AdManager {
+ static let shared = AdManager()
+
+ /// The SDK has been started this process. Never reset — stopping the SDK
+ /// isn't supported; ad-free is enforced by shouldShowAdSlots and by
+ /// banner slots not existing.
+ private(set) var isStarted = false
+
+ /// Mirrors the real scene phase; the ATT prompt is only ever attempted
+ /// while active (the system silently ignores it otherwise).
+ private(set) var isSceneActive = false
+
+ /// AppLifecyclePromptCoordinator increments this once per foreground
+ /// session; reusing it keeps "never on first launch" aligned with the
+ /// app's existing definition of a session.
+ private static let appOpenCountKey = "appLifecyclePrompt.appOpenCount"
+
+ /// QA seam: UI tests that exercise StoreKit purchase flows pass this to
+ /// keep the ad SDK entirely off — on the iOS 27 beta simulator, Google
+ /// Mobile Ads' storekitd interaction can wedge the StoreKit-Testing
+ /// storefront ("Subscription Unavailable"). Never set in production.
+ private static let isDisabledForUITesting =
+ ProcessInfo.processInfo.arguments.contains("-sunhatDisableAdSDK")
+
+ private let userDefaults: UserDefaults
+ private let tracking: TrackingAuthorizing
+ private let startSDK: () -> Void
+ private var isEvaluating = false
+ private let logger = Logger(subsystem: "org.wesley.sunhat", category: "AdManager")
+
+ convenience init() {
+ self.init(
+ userDefaults: .standard,
+ tracking: SystemTrackingAuthorizer(),
+ startSDK: { MobileAds.shared.start() }
+ )
+ }
+
+ /// Test seam; production uses `.shared`.
+ init(userDefaults: UserDefaults, tracking: TrackingAuthorizing, startSDK: @escaping () -> Void) {
+ self.userDefaults = userDefaults
+ self.tracking = tracking
+ self.startSDK = startSDK
+ }
+
+ /// Live entitlement when resolved; last-known mirror while it's pending.
+ private var effectiveAllowsAdRequests: Bool {
+ AdActivationPolicy.effectiveAllowsAdRequests(
+ live: StoreManager.shared.entitlementState,
+ cached: StoreManager.cachedEntitlementState(userDefaults: userDefaults)
+ )
+ }
+
+ /// Gates every ad slot on Google's consent framework, and **fails
+ /// closed**: it is seeded from UMP's persisted verdict (readable
+ /// synchronously, no network) so a fresh install or a non-consenting EEA
+ /// user blocks ad requests from the very first layout. `false` only ever
+ /// means UMP affirmatively says ads may be requested. Google's EU User
+ /// Consent Policy forbids requesting *any* ad before consent, so this
+ /// must never default open.
+ private(set) var consentBlocksAds = !ConsentInformation.shared.canRequestAds
+
+ /// Single source of truth for every banner slot. Reactive through both
+ /// this manager's and StoreManager's observation.
+ var shouldShowAdSlots: Bool {
+ isStarted && effectiveAllowsAdRequests && !consentBlocksAds
+ }
+
+ /// True when Google's consent framework requires a reachable
+ /// privacy-options entry point (EEA/UK users); Settings shows a row.
+ /// Stored rather than computed so SwiftUI actually observes it — the
+ /// underlying UMP property carries no Observation instrumentation, so a
+ /// computed passthrough would never trigger a view update.
+ private(set) var privacyOptionsRequired =
+ ConsentInformation.shared.privacyOptionsRequirementStatus == .required
+
+ /// Re-reads UMP's persisted verdicts. Cheap and synchronous; call when a
+ /// surface that shows consent state appears (e.g. Settings).
+ func refreshConsentState() {
+ privacyOptionsRequired =
+ ConsentInformation.shared.privacyOptionsRequirementStatus == .required
+ consentBlocksAds = !ConsentInformation.shared.canRequestAds
+ }
+
+ /// Idempotent launch hook: applies the current entitlement and re-arms
+ /// observation so later entitlement changes (purchase, restore, expiry)
+ /// re-evaluate automatically. The scene isn't active yet during App.init,
+ /// so ATT is never requested from here — the foreground hook handles it.
+ func activate() {
+ evaluateActivation()
+ observeEntitlement()
+ }
+
+ /// Called from the scenePhase hook on every phase change.
+ func sceneDidChange(active: Bool) {
+ isSceneActive = active
+ if active {
+ evaluateActivation()
+ // A consent form that couldn't present earlier (e.g. a system
+ // permission alert was up) gets another chance each foreground.
+ if isStarted && consentBlocksAds {
+ Task { await gatherConsentConcurrently() }
+ }
+ }
+ }
+
+ func evaluateActivation() {
+ guard !Self.isDisabledForUITesting else { return }
+ guard !isEvaluating else { return }
+ isEvaluating = true
+
+ let decision = AdActivationPolicy.decide(
+ allowsAdRequests: effectiveAllowsAdRequests,
+ sdkStarted: isStarted,
+ trackingStatus: tracking.statusKind,
+ appOpenCount: userDefaults.integer(forKey: Self.appOpenCountKey),
+ isSceneActive: isSceneActive
+ )
+
+ guard decision != .none else {
+ isEvaluating = false
+ return
+ }
+
+ // This Task must contain NO network awaits: a hung endpoint would
+ // wedge isEvaluating for the whole session. ATT is a local system
+ // prompt; consent gathering runs concurrently and gates the ad SLOTS
+ // via consentBlocksAds when it answers.
+ Task {
+ defer { isEvaluating = false }
+
+ if decision.shouldRequestTracking {
+ await tracking.requestAuthorization()
+ }
+ // Re-check entitlement after the prompt: the user may have become
+ // Ad-Free while it was up.
+ if decision.shouldStartSDK, effectiveAllowsAdRequests {
+ startSDK()
+ isStarted = true
+ logger.info("Google Mobile Ads SDK started (user is not Ad-Free)")
+ Task { await self.gatherConsentConcurrently() }
+ }
+ }
+ }
+
+ /// Presents Google's privacy-options form (consent revocation) for users
+ /// in regions where it's required.
+ func presentPrivacyOptions() {
+ guard let rootViewController = Self.rootViewController() else { return }
+ // UMP invokes this completion on a nonisolated context, so hop back to
+ // the main actor rather than touching @Observable state from it.
+ ConsentForm.presentPrivacyOptionsForm(from: rootViewController) { error in
+ Task { @MainActor in
+ if let error {
+ AdManager.shared.logger.error("Privacy options form failed: \(error.localizedDescription)")
+ }
+ // The user may have just revoked consent — re-gate the ad slots.
+ AdManager.shared.refreshConsentState()
+ }
+ }
+ }
+
+ /// Google EU User Consent Policy: a certified CMP must gather consent
+ /// before ads serve to EEA/UK users. Elsewhere this resolves quickly with
+ /// canRequestAds == true and no form.
+ /// Runs concurrently with (never blocking) the activation pipeline.
+ /// When Google's consent framework answers, its verdict gates the ad
+ /// slots via consentBlocksAds and presents the EEA form when required.
+ private func gatherConsentConcurrently() async {
+ let parameters = RequestParameters()
+ #if DEBUG
+ // Simulator/dev runs use Google's sample app ID, which has no
+ // published consent message — UMP then reports consent "required"
+ // with no form to satisfy it, blocking every ad. Debug geography
+ // makes dev behave like a production non-EEA user; Release builds
+ // use real geography against the real AdMob app's consent message.
+ let debugSettings = DebugSettings()
+ // `.other` is UMP 3.x's replacement for the deprecated `.notEEA`.
+ debugSettings.geography = .other
+ parameters.debugSettings = debugSettings
+ #endif
+ do {
+ try await ConsentInformation.shared.requestConsentInfoUpdate(with: parameters)
+ } catch {
+ // Fail closed on UMP's stored verdict rather than leaving the gate
+ // wherever it happened to be: an EEA user on a flaky connection
+ // must not be served ads with no consent record.
+ refreshConsentState()
+ logger.error("Consent info update failed: \(error.localizedDescription); ad slots gated by last known consent")
+ return
+ }
+
+ refreshConsentState()
+ logger.info("Ad consent resolved: canRequestAds=\(ConsentInformation.shared.canRequestAds)")
+
+ // Only attempt presentation when a form is actually required and the
+ // UI can host it — loadAndPresentIfRequired can otherwise throw
+ // spurious presentation errors at cold launch.
+ guard ConsentInformation.shared.consentStatus == .required, isSceneActive else { return }
+ // Retry a few times: at launch a system permission alert (location,
+ // notifications) is often mid-presentation and UIKit refuses a second
+ // presented controller.
+ for attempt in 1...3 {
+ guard let rootViewController = Self.rootViewController() else { return }
+ do {
+ try await ConsentForm.loadAndPresentIfRequired(from: rootViewController)
+ refreshConsentState()
+ logger.info("Consent form flow finished: canRequestAds=\(ConsentInformation.shared.canRequestAds)")
+ return
+ } catch {
+ logger.error("Consent form presentation attempt \(attempt) failed: \(error.localizedDescription)")
+ try? await Task.sleep(for: .seconds(4))
+ }
+ }
+ }
+
+ private static func rootViewController() -> UIViewController? {
+ UIApplication.shared.connectedScenes
+ .compactMap { $0 as? UIWindowScene }
+ .flatMap(\.windows)
+ .first(where: \.isKeyWindow)?
+ .rootViewController
+ }
+
+ private func observeEntitlement() {
+ withObservationTracking {
+ _ = StoreManager.shared.entitlementState
+ } onChange: {
+ Task { @MainActor in
+ AdManager.shared.evaluateActivation()
+ AdManager.shared.observeEntitlement()
+ }
+ }
+ }
+}
diff --git a/SunHat/Services/Store/AdFreeEntitlement.swift b/SunHat/Services/Store/AdFreeEntitlement.swift
new file mode 100644
index 0000000..2cdf074
--- /dev/null
+++ b/SunHat/Services/Store/AdFreeEntitlement.swift
@@ -0,0 +1,90 @@
+//
+// AdFreeEntitlement.swift
+// SunHat
+//
+// Pure entitlement model for the "SunHat Ad-Free" subscription group.
+// The resolver is deliberately free of StoreKit types so the state mapping
+// (active / grace period / billing retry / expired) is unit-testable without
+// a StoreKit test session. StoreManager feeds it values derived from
+// Transaction.currentEntitlements and Product.SubscriptionInfo.status.
+//
+
+import Foundation
+
+// The app target compiles with default MainActor isolation
+// (SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor); these are pure Sendable value
+// types callable from any context, so they opt out explicitly.
+
+/// The renewal states StoreKit can report for a subscription in the group,
+/// reduced to a StoreKit-free value type.
+nonisolated enum SubscriptionRenewalStateKind: Sendable, Equatable {
+ case subscribed
+ case expired
+ case inBillingRetryPeriod
+ case inGracePeriod
+ case revoked
+}
+
+/// The single source of truth every ad placement and the paywall read from.
+nonisolated enum AdFreeEntitlementState: String, Sendable, Equatable, Codable {
+ /// Not yet resolved this launch (StoreKit hasn't answered).
+ case unknown
+ /// Affirmatively not subscribed (never bought, or fully expired).
+ case notEntitled
+ /// Subscribed and in good standing.
+ case active
+ /// A renewal payment failed but Apple's billing grace period is active —
+ /// the user keeps paid access while Apple retries the charge.
+ case gracePeriod
+ /// Grace exhausted; Apple is still retrying billing but paid access has
+ /// lapsed. Settings surfaces a fix-billing prompt in this state.
+ case billingRetry
+
+ /// True when the user has paid access, so ads must be off.
+ var isAdFree: Bool {
+ self == .active || self == .gracePeriod
+ }
+
+ /// Ads may be requested only on affirmative non-entitlement. `.unknown`
+ /// fails closed (no ad request) so a subscriber never sees an ad flash
+ /// while entitlements are still resolving at launch.
+ var allowsAdRequests: Bool {
+ self == .notEntitled || self == .billingRetry
+ }
+}
+
+nonisolated enum AdFreeEntitlementResolver {
+ /// Maps what StoreKit reported to the app-facing entitlement state.
+ ///
+ /// - Parameters:
+ /// - hasVerifiedEntitlement: a verified, non-revoked transaction for an
+ /// Ad-Free product appeared in `Transaction.currentEntitlements`.
+ /// - renewalStates: subscription-group statuses, when they could be
+ /// fetched (empty when offline or products failed to load).
+ static func resolve(
+ hasVerifiedEntitlement: Bool,
+ renewalStates: [SubscriptionRenewalStateKind]
+ ) -> AdFreeEntitlementState {
+ if renewalStates.contains(.subscribed) {
+ return .active
+ }
+ if renewalStates.contains(.inGracePeriod) {
+ return .gracePeriod
+ }
+ if renewalStates.contains(.inBillingRetryPeriod) {
+ return .billingRetry
+ }
+ // An affirmative, verified terminal status outranks the entitlement
+ // snapshot: the snapshot is captured before the status fetch, so after
+ // a refund or expiry the status is the fresher signal.
+ if renewalStates.contains(.revoked) || renewalStates.contains(.expired) {
+ return .notEntitled
+ }
+ // No status information at all (offline, products not loaded): the
+ // verified entitlement snapshot is authoritative — favor the paying user.
+ if hasVerifiedEntitlement {
+ return .active
+ }
+ return .notEntitled
+ }
+}
diff --git a/SunHat/Services/Store/StoreManager.swift b/SunHat/Services/Store/StoreManager.swift
new file mode 100644
index 0000000..7d65557
--- /dev/null
+++ b/SunHat/Services/Store/StoreManager.swift
@@ -0,0 +1,358 @@
+//
+// StoreManager.swift
+// SunHat
+//
+// StoreKit 2 manager for the "SunHat Ad-Free" subscription group: loads the
+// two products, derives the app-wide entitlement state from
+// Transaction.currentEntitlements + subscription status, listens to
+// Transaction.updates so renewals/refunds/Ask-to-Buy approvals land without a
+// relaunch, and handles purchase + restore.
+//
+// Shared like the other services (`.shared`), and additionally injected via
+// `.environment(StoreManager.shared)` at the app root so views read it with
+// `@Environment(StoreManager.self)`.
+//
+
+import Foundation
+import StoreKit
+import os
+
+@MainActor
+@Observable
+final class StoreManager {
+ static let shared = StoreManager()
+
+ /// Product identifiers are developer-chosen, so these exact IDs can be
+ /// created verbatim in App Store Connect later — no code change needed.
+ nonisolated enum ProductID {
+ static let monthly = "org.wesley.sunhat.adfree.monthly"
+ static let yearly = "org.wesley.sunhat.adfree.yearly"
+ static let all: [String] = [monthly, yearly]
+ }
+
+ enum PurchaseOutcome: Sendable, Equatable {
+ case success
+ /// Deferred (e.g. Ask to Buy) — entitlement arrives via Transaction.updates.
+ case pending
+ case cancelled
+ }
+
+ enum StoreError: LocalizedError {
+ case failedVerification
+
+ var errorDescription: String? {
+ String(localized: "The App Store could not verify this purchase.", comment: "Error shown when a StoreKit transaction fails local verification")
+ }
+ }
+
+ /// How cryptographic verification failures from StoreKit are treated.
+ enum TransactionTrustPolicy: Sendable, Equatable {
+ /// Production: only transactions Apple's verifier accepts count.
+ case verifiedOnly
+ /// StoreKit-Testing seam: xcodebuild-run unit tests drive an
+ /// SKTestSession whose transactions are signed with a local test
+ /// certificate this process doesn't trust, so they arrive as
+ /// .unverified(_, .invalidSignature). Accepting exactly that failure
+ /// keeps the integration tests meaningful. Only the test target passes
+ /// this; every production path uses the default `.verifiedOnly`.
+ case acceptTestSignatures
+ }
+
+ /// Last-known entitlement, mirrored to UserDefaults. Consulted only to
+ /// decide whether ad-SDK startup may proceed before the first StoreKit
+ /// resolution of a launch; never trusted to grant paid access.
+ nonisolated static let cachedEntitlementStateKey = "entitlement.adFree.state"
+
+ /// Test-only entitlement override, so gate tests can pin a state without
+ /// driving the App Store. Never set in production.
+ private var entitlementOverrideForTesting: AdFreeEntitlementState?
+
+ func overrideEntitlementStateForTesting(_ state: AdFreeEntitlementState) {
+ entitlementOverrideForTesting = state
+ entitlementState = state
+ }
+
+ func clearEntitlementOverrideForTesting() {
+ entitlementOverrideForTesting = nil
+ entitlementState = .unknown
+ }
+
+ private(set) var products: [Product] = []
+ private(set) var productsLoadFailed = false
+ private(set) var entitlementState: AdFreeEntitlementState = .unknown
+ private(set) var activeProductID: String?
+ private(set) var expirationDate: Date?
+ private(set) var willAutoRenew: Bool?
+
+ var isAdFree: Bool { entitlementState.isAdFree }
+ var allowsAdRequests: Bool { entitlementState.allowsAdRequests }
+
+ var activePlanDisplayName: String? {
+ switch activeProductID {
+ case ProductID.monthly:
+ String(localized: "Monthly", comment: "Name of the monthly Ad-Free plan shown in Settings")
+ case ProductID.yearly:
+ String(localized: "Annual", comment: "Name of the annual Ad-Free plan shown in Settings")
+ default:
+ nil
+ }
+ }
+
+ private let userDefaults: UserDefaults
+ private var updatesTask: Task?
+ private var statusUpdatesTask: Task?
+ /// Bumped at each refreshEntitlement entry; a run that was overtaken while
+ /// suspended discards its result instead of clobbering the newer one.
+ private var refreshGeneration = 0
+ private let logger = Logger(subsystem: "org.wesley.sunhat", category: "StoreManager")
+
+ private let trustPolicy: TransactionTrustPolicy
+
+ convenience init() {
+ self.init(userDefaults: .standard)
+ }
+
+ /// Test seam; production uses `.shared`.
+ init(userDefaults: UserDefaults, trustPolicy: TransactionTrustPolicy = .verifiedOnly) {
+ self.userDefaults = userDefaults
+ self.trustPolicy = trustPolicy
+ }
+
+ /// Reads the mirrored entitlement without touching StoreKit, for launch-time
+ /// gating before the first resolution completes. Missing/invalid → `.unknown`,
+ /// which fails closed (no ad requests). UserDefaults is thread-safe, so this
+ /// is callable from any context.
+ nonisolated static func cachedEntitlementState(userDefaults: UserDefaults = .standard) -> AdFreeEntitlementState {
+ guard let raw = userDefaults.string(forKey: cachedEntitlementStateKey),
+ let state = AdFreeEntitlementState(rawValue: raw) else {
+ return .unknown
+ }
+ return state
+ }
+
+ /// Idempotent launch hook: starts the Transaction.updates listener and
+ /// kicks off the initial product load + entitlement resolution.
+ func start() {
+ guard updatesTask == nil else { return }
+
+ updatesTask = Task { [weak self] in
+ for await update in Transaction.updates {
+ guard let self else { return }
+ // Finish trusted transactions so StoreKit stops redelivering
+ // them; never finish untrusted ones.
+ if let transaction = self.trusted(update) {
+ await transaction.finish()
+ }
+ await self.refreshEntitlement()
+ }
+ }
+
+ // Transaction.updates only fires when a new transaction exists.
+ // Grace-period entry, billing-retry entry, auto-renew cancellation,
+ // and natural expiry are delivered on the status stream instead.
+ statusUpdatesTask = Task { [weak self] in
+ for await _ in Product.SubscriptionInfo.Status.updates {
+ guard let self else { return }
+ await self.refreshEntitlement()
+ }
+ }
+
+ Task { [weak self] in
+ // The storefront can attach a beat after process start (notably
+ // under a local StoreKit configuration); querying StoreKit before
+ // it does can wedge the whole session's store connection. In
+ // production Storefront.current resolves immediately.
+ for _ in 0..<50 {
+ if await Storefront.current != nil { break }
+ try? await Task.sleep(for: .milliseconds(200))
+ }
+ guard let self else { return }
+ // Deliberately parallel: entitlement resolution must never wait
+ // on the product catalog (Product.products can stall for minutes
+ // on a cold store), and the paywall's products must not wait on
+ // entitlement either.
+ Task { await self.loadProducts() }
+ Task { await self.refreshEntitlement() }
+ }
+ }
+
+ private var productsLoadInFlight = false
+
+ func loadProducts() async {
+ guard !productsLoadInFlight else { return }
+ productsLoadInFlight = true
+ defer { productsLoadInFlight = false }
+ let hadProducts = !products.isEmpty
+ do {
+ let loaded = try await Product.products(for: ProductID.all)
+ products = loaded.sorted { $0.price < $1.price }
+ productsLoadFailed = false
+ } catch {
+ productsLoadFailed = true
+ logger.error("Failed to load Ad-Free products: \(error)")
+ }
+
+ // Entitlement resolution refines its answer with subscription-group
+ // status, which needs a loaded product. currentEntitlements is served
+ // from the local cache while Product.products is a network round
+ // trip, so the first resolution of a launch almost always runs
+ // without status — re-resolve once the catalog lands, or grace-period
+ // and billing-retry states would be unreachable until the next
+ // foreground.
+ if !hadProducts, !products.isEmpty {
+ await refreshEntitlement()
+ }
+ }
+
+ /// Re-derives the entitlement state from StoreKit. Safe to call at any
+ /// time; also invoked by the transaction and status listeners. Concurrent
+ /// calls are safe: only the newest run commits its result.
+ @discardableResult
+ func refreshEntitlement() async -> AdFreeEntitlementState {
+ refreshGeneration += 1
+ let generation = refreshGeneration
+
+ // Retry a failed catalog load in the background — but NEVER await it
+ // here: entitlement resolution is the ad/paywall gate and must not
+ // serialize behind Product.products, which can stall on a cold store.
+ // Without products the status refinement below is skipped and the
+ // resolver falls back to currentEntitlements alone (grace period then
+ // reads as .active until a later refresh adds status detail).
+ if products.isEmpty {
+ Task { [weak self] in
+ await self?.loadProducts()
+ }
+ }
+
+ var hasVerifiedEntitlement = false
+ var entitledProductID: String?
+ var entitledExpiration: Date?
+
+ for await result in Transaction.currentEntitlements {
+ guard let transaction = trusted(result),
+ ProductID.all.contains(transaction.productID),
+ transaction.revocationDate == nil else {
+ continue
+ }
+ hasVerifiedEntitlement = true
+ entitledProductID = transaction.productID
+ entitledExpiration = transaction.expirationDate
+ }
+
+ // Subscription-group status distinguishes grace period from billing
+ // retry. Status is group-scoped, so any loaded product's subscription
+ // works. When it can't be fetched (offline, products not loaded), the
+ // resolver falls back to currentEntitlements alone. Only statuses whose
+ // backing transaction verifies are trusted.
+ var renewalStates: [SubscriptionRenewalStateKind] = []
+ var autoRenews: Bool?
+ var statusProductID: String?
+ var statusExpiration: Date?
+ if let subscription = products.compactMap(\.subscription).first,
+ let statuses = try? await subscription.status {
+ for status in statuses {
+ guard let statusTransaction = trusted(status.transaction) else { continue }
+ if let kind = Self.renewalStateKind(status.state) {
+ renewalStates.append(kind)
+ }
+ if let renewalInfo = trusted(status.renewalInfo) {
+ autoRenews = renewalInfo.willAutoRenew
+ }
+ // The status store can update before currentEntitlements does
+ // (e.g. right after a purchase or crossgrade); keep its
+ // transaction as a display-info fallback for that window.
+ if statusProductID == nil, ProductID.all.contains(statusTransaction.productID) {
+ statusProductID = statusTransaction.productID
+ statusExpiration = statusTransaction.expirationDate
+ }
+ }
+ }
+
+ let resolved = entitlementOverrideForTesting
+ ?? AdFreeEntitlementResolver.resolve(
+ hasVerifiedEntitlement: hasVerifiedEntitlement,
+ renewalStates: renewalStates
+ )
+
+ // Overtaken while suspended above — a newer refresh has fresher data,
+ // so don't commit. The value is still returned: this run did read
+ // post-await entitlements, so it's a correct answer for its own
+ // caller (restorePurchases relies on that read-after-write).
+ guard generation == refreshGeneration else { return resolved }
+
+ logger.info("Ad-Free entitlement resolved: \(resolved.rawValue, privacy: .public)")
+ entitlementState = resolved
+ activeProductID = resolved.isAdFree ? (entitledProductID ?? statusProductID) : nil
+ expirationDate = resolved.isAdFree ? (entitledExpiration ?? statusExpiration) : nil
+ willAutoRenew = resolved.isAdFree ? autoRenews : nil
+ userDefaults.set(resolved.rawValue, forKey: Self.cachedEntitlementStateKey)
+ return resolved
+ }
+
+ @discardableResult
+ func purchase(_ product: Product) async throws -> PurchaseOutcome {
+ try await handlePurchaseResult(product.purchase())
+ }
+
+ /// Shared endpoint for purchase results, whether the purchase ran through
+ /// `purchase(_:)` or a StoreKit view (the paywall's SubscriptionStoreView
+ /// delivers its result via onInAppPurchaseCompletion).
+ @discardableResult
+ func handlePurchaseResult(_ result: Product.PurchaseResult) async throws -> PurchaseOutcome {
+ switch result {
+ case .success(let verification):
+ guard let transaction = trusted(verification) else {
+ throw StoreError.failedVerification
+ }
+ await transaction.finish()
+ await refreshEntitlement()
+ return .success
+ case .pending:
+ return .pending
+ case .userCancelled:
+ return .cancelled
+ @unknown default:
+ return .cancelled
+ }
+ }
+
+ /// Apple-required restore path. AppStore.sync forces a sync with the App
+ /// Store (prompts for credentials on a fresh install), then entitlements
+ /// are re-derived.
+ /// Returns the entitlement this restore itself resolved. Callers must use
+ /// the returned value rather than re-reading `entitlementState`: the
+ /// sync can make the Transaction.updates listener fire its own refresh,
+ /// which would overtake this one and leave the shared property momentarily
+ /// behind what the user just restored.
+ @discardableResult
+ func restorePurchases() async throws -> AdFreeEntitlementState {
+ try await AppStore.sync()
+ return await refreshEntitlement()
+ }
+
+ /// Unwraps a StoreKit verification result according to the trust policy.
+ private func trusted(_ result: VerificationResult) -> T? {
+ switch result {
+ case .verified(let value):
+ return value
+ case .unverified(let value, let error):
+ if trustPolicy == .acceptTestSignatures, case .invalidSignature = error {
+ return value
+ }
+ return nil
+ }
+ }
+
+ private static func renewalStateKind(
+ _ state: Product.SubscriptionInfo.RenewalState
+ ) -> SubscriptionRenewalStateKind? {
+ switch state {
+ case .subscribed: .subscribed
+ case .expired: .expired
+ case .inBillingRetryPeriod: .inBillingRetryPeriod
+ case .inGracePeriod: .inGracePeriod
+ case .revoked: .revoked
+ default: nil
+ }
+ }
+}
diff --git a/SunHat/ViewModels/Settings/DataPrivacyViewModel.swift b/SunHat/ViewModels/Settings/DataPrivacyViewModel.swift
index 82a7dac..c732223 100644
--- a/SunHat/ViewModels/Settings/DataPrivacyViewModel.swift
+++ b/SunHat/ViewModels/Settings/DataPrivacyViewModel.swift
@@ -426,6 +426,11 @@ final class DataPrivacyViewModel {
UserDefaults.standard.removeObject(forKey: "hasCreatedFirstReminder")
UserDefaults.standard.removeObject(forKey: "lastLaunchVersion")
+ // The mirrored Ad-Free entitlement is only a launch-time ad gate; it
+ // re-resolves from StoreKit, and while cleared ("unknown") no ads are
+ // requested, so a subscriber is never shown ads by this reset.
+ UserDefaults.standard.removeObject(forKey: StoreManager.cachedEntitlementStateKey)
+
// Manual coordinates are persisted outside SwiftData by the location
// service, so they need an explicit privacy-deletion boundary.
locationPermissionManager.clearStoredLocationData()
diff --git a/SunHat/Views/Ads/AdBannerSlot.swift b/SunHat/Views/Ads/AdBannerSlot.swift
new file mode 100644
index 0000000..255772b
--- /dev/null
+++ b/SunHat/Views/Ads/AdBannerSlot.swift
@@ -0,0 +1,37 @@
+//
+// AdBannerSlot.swift
+// SunHat
+//
+// The complete bottom banner slot for a screen: just the compact 320x50
+// banner with a small breathing gap, matching QuoteReaper's minimal
+// presentation — no divider, no "Remove Ads" affordance (the Ad-Free paywall
+// lives in Settings' "SunHat Ad-Free" section). The opaque background keeps
+// scrolling content visually separated from the ad while it scrolls past
+// (Google banner policy).
+//
+// Renders nothing at all for Ad-Free users or before entitlement resolves.
+//
+
+import SwiftUI
+
+struct AdBannerSlot: View {
+ let adUnitID: String
+
+ @State private var adManager = AdManager.shared
+
+ var body: some View {
+ // Gated here as well as inside BannerAdView so the slot collapses to
+ // a zero-height inset instead of reserving padding for a hidden
+ // banner.
+ Group {
+ if adManager.shouldShowAdSlots {
+ BannerAdView(adUnitID: adUnitID)
+ .padding(.top, 6)
+ .padding(.bottom, 2)
+ .background(Color(.systemBackground))
+ } else {
+ Color.clear.frame(height: 0)
+ }
+ }
+ }
+}
diff --git a/SunHat/Views/Ads/BannerAdView.swift b/SunHat/Views/Ads/BannerAdView.swift
new file mode 100644
index 0000000..4b3f454
--- /dev/null
+++ b/SunHat/Views/Ads/BannerAdView.swift
@@ -0,0 +1,97 @@
+//
+// BannerAdView.swift
+// SunHat
+//
+// Reusable banner slot using Google's smallest standard unit — the fixed
+// 320x50 banner — centered on its own reserved row. Renders nothing at all
+// (zero height, no ad request) unless AdManager.shouldShowAdSlots is true.
+// The 50pt height is reserved up front, so content never shifts when an ad
+// loads or fails — a Google banner-policy requirement. On load failure the
+// reserved space stays blank.
+//
+// Presentation mirrors QuoteReaper's AdBannerView: a bare transparent
+// banner (no painted chrome), explicit root view controller, and a static
+// "Advertisement" accessibility element.
+//
+
+import SwiftUI
+import UIKit
+import GoogleMobileAds
+
+/// Ad unit IDs, one constant per placement — Google's public iOS TEST units.
+/// Phase 8 (go-live) replaces each with a real per-placement unit ID from the
+/// AdMob console; the only other Google ID in the codebase is the sample
+/// GADApplicationIdentifier in Info.plist, which is swapped at the same time.
+enum AdConfig {
+ static let dashboardBannerUnitID = "ca-app-pub-3940256099942544/2435281174"
+ static let weatherBannerUnitID = "ca-app-pub-3940256099942544/2435281174"
+}
+
+struct BannerAdView: View {
+ let adUnitID: String
+
+ @State private var adManager = AdManager.shared
+
+ var body: some View {
+ // shouldShowAdSlots is reactive through both AdManager and
+ // StoreManager observation: a purchase mid-session removes the banner
+ // immediately.
+ if adManager.shouldShowAdSlots {
+ SmallBannerRepresentable(adUnitID: adUnitID)
+ .frame(width: 320, height: 50)
+ .frame(maxWidth: .infinity)
+ }
+ }
+}
+
+private struct SmallBannerRepresentable: UIViewRepresentable {
+ let adUnitID: String
+
+ func makeCoordinator() -> Coordinator {
+ Coordinator()
+ }
+
+ func makeUIView(context: Context) -> BannerView {
+ let banner = BannerView(adSize: AdSizeBanner)
+ banner.adUnitID = adUnitID
+ // QuoteReaper-style presentation: transparent banner on the slot's own
+ // background, no painted chrome of its own.
+ banner.backgroundColor = .clear
+ banner.rootViewController = Self.rootViewController
+ banner.isAccessibilityElement = true
+ banner.accessibilityLabel = String(
+ localized: "Advertisement",
+ comment: "Accessibility label for the banner advertisement"
+ )
+ banner.accessibilityIdentifier = "ad-banner"
+ banner.delegate = context.coordinator
+ banner.load(Request())
+ return banner
+ }
+
+ func updateUIView(_ banner: BannerView, context: Context) {
+ // The 320x50 size never changes; nothing to update.
+ }
+
+ private static var rootViewController: UIViewController? {
+ UIApplication.shared.connectedScenes
+ .compactMap { $0 as? UIWindowScene }
+ .flatMap(\.windows)
+ .first(where: \.isKeyWindow)?
+ .rootViewController
+ }
+
+ final class Coordinator: NSObject, BannerViewDelegate {
+ func bannerViewDidReceiveAd(_ bannerView: BannerView) {
+ bannerView.alpha = 0
+ UIView.animate(withDuration: 0.25) {
+ bannerView.alpha = 1
+ }
+ }
+
+ func bannerView(_ bannerView: BannerView, didFailToReceiveAdWithError error: Error) {
+ // Keep the reserved space (no layout shift); stay blank.
+ bannerView.alpha = 0
+ }
+ }
+}
diff --git a/SunHat/Views/Dashboard/ContentView.swift b/SunHat/Views/Dashboard/ContentView.swift
index 0b3d2dc..92f84d3 100644
--- a/SunHat/Views/Dashboard/ContentView.swift
+++ b/SunHat/Views/Dashboard/ContentView.swift
@@ -28,5 +28,6 @@ struct ContentView: View {
#Preview {
ContentView()
+ .environment(StoreManager.shared)
.modelContainer(for: WeatherReminder.self, inMemory: true)
}
diff --git a/SunHat/Views/Dashboard/DashboardView.swift b/SunHat/Views/Dashboard/DashboardView.swift
index 5e88ee4..b16580e 100644
--- a/SunHat/Views/Dashboard/DashboardView.swift
+++ b/SunHat/Views/Dashboard/DashboardView.swift
@@ -57,6 +57,9 @@ struct DashboardView: View {
.padding(.bottom, 132)
}
}
+ .safeAreaInset(edge: .bottom, spacing: 0) {
+ AdBannerSlot(adUnitID: AdConfig.dashboardBannerUnitID)
+ }
.navigationTitle("SunHat")
.navigationBarTitleDisplayMode(.large)
.onAppear {
diff --git a/SunHat/Views/Dashboard/MainTabView.swift b/SunHat/Views/Dashboard/MainTabView.swift
index 02978c0..726d5bf 100644
--- a/SunHat/Views/Dashboard/MainTabView.swift
+++ b/SunHat/Views/Dashboard/MainTabView.swift
@@ -246,6 +246,7 @@ private struct NotificationReminderDestination: View {
#Preview {
MainTabView()
.environmentObject(OnboardingCoordinator())
+ .environment(StoreManager.shared)
.modelContainer(for: [
WeatherReminder.self,
WeatherData.self,
diff --git a/SunHat/Views/Settings/AdFreeSettingsSection.swift b/SunHat/Views/Settings/AdFreeSettingsSection.swift
new file mode 100644
index 0000000..9f18b6e
--- /dev/null
+++ b/SunHat/Views/Settings/AdFreeSettingsSection.swift
@@ -0,0 +1,199 @@
+//
+// AdFreeSettingsSection.swift
+// SunHat
+//
+// The "SunHat Ad-Free" section of Settings: current plan, manage/cancel via
+// Apple's manage-subscriptions sheet, and Restore Purchases. The paywall
+// entry point for non-subscribers is added separately.
+//
+
+import SwiftUI
+import StoreKit
+
+struct AdFreeSettingsSection: View {
+ @Environment(StoreManager.self) private var storeManager
+
+ @State private var showingManageSubscriptions = false
+ @State private var showingPaywall = false
+ @State private var isRestoring = false
+ @State private var restoreResult: RestoreResult?
+
+ private enum RestoreResult: Identifiable {
+ case restored
+ case nothingToRestore
+ case billingIssue
+ case failed
+
+ var id: Self { self }
+
+ var title: String {
+ switch self {
+ case .restored:
+ String(localized: "Purchases Restored", comment: "Alert title after a successful restore that found an Ad-Free subscription")
+ case .nothingToRestore:
+ String(localized: "No Purchases Found", comment: "Alert title after a restore that found no Ad-Free subscription")
+ case .billingIssue:
+ String(localized: "Billing Issue Found", comment: "Alert title after a restore that found a subscription paused by a failed payment")
+ case .failed:
+ String(localized: "Restore Failed", comment: "Alert title when restoring purchases fails")
+ }
+ }
+
+ var message: String {
+ switch self {
+ case .restored:
+ String(localized: "Your Ad-Free subscription is back. Ads are now off.", comment: "Alert message after a successful restore")
+ case .nothingToRestore:
+ String(localized: "No active Ad-Free subscription is associated with your App Store account.", comment: "Alert message when restore finds nothing")
+ case .billingIssue:
+ String(localized: "Your Ad-Free subscription is paused because a renewal payment failed. Update your billing details to restore it.", comment: "Alert message when restore finds a subscription in billing retry")
+ case .failed:
+ String(localized: "Could not reach the App Store. Check your connection and try again.", comment: "Alert message when restore fails")
+ }
+ }
+ }
+
+ var body: some View {
+ Section {
+ switch storeManager.entitlementState {
+ case .active:
+ currentPlanRow
+
+ // The last paid period's end date. Meaningful only while the
+ // subscription is in good standing — in grace period it has
+ // already passed, so that branch omits it.
+ if let expirationDate = storeManager.expirationDate {
+ LabeledContent(
+ storeManager.willAutoRenew == false
+ ? String(localized: "Expires", comment: "Settings row label when the Ad-Free subscription will not auto-renew")
+ : String(localized: "Renews", comment: "Settings row label for the next Ad-Free renewal date"),
+ value: expirationDate.formatted(date: .abbreviated, time: .omitted)
+ )
+ }
+
+ manageSubscriptionButton(title: String(localized: "Manage Subscription", comment: "Settings row that opens Apple's subscription management sheet"))
+ case .gracePeriod:
+ currentPlanRow
+ manageSubscriptionButton(title: String(localized: "Fix Billing Issue", comment: "Settings row that opens subscription management when a renewal payment failed"))
+ case .billingRetry:
+ manageSubscriptionButton(title: String(localized: "Fix Billing Issue", comment: "Settings row that opens subscription management when a renewal payment failed"))
+ case .notEntitled:
+ Button {
+ showingPaywall = true
+ } label: {
+ SettingsIconLabel(title: String(localized: "Get Ad-Free", comment: "Settings row that opens the Ad-Free subscription paywall"), systemImage: "sparkles", color: .purple)
+ }
+ .foregroundStyle(.primary)
+ case .unknown:
+ // Entitlement not resolved yet — don't sell until we know.
+ EmptyView()
+ }
+
+ restoreButton
+ } header: {
+ Text("SunHat Ad-Free")
+ } footer: {
+ Text(footerText)
+ }
+ }
+
+ private var currentPlanRow: some View {
+ LabeledContent {
+ Text(storeManager.activePlanDisplayName ?? String(localized: "Ad-Free", comment: "Fallback plan name when the specific Ad-Free plan is unknown"))
+ } label: {
+ SettingsIconLabel(title: String(localized: "Current Plan", comment: "Settings row showing which Ad-Free plan is active"), systemImage: "sparkles", color: .purple)
+ }
+ }
+
+ private func manageSubscriptionButton(title: String) -> some View {
+ Button {
+ showingManageSubscriptions = true
+ } label: {
+ SettingsIconLabel(title: title, systemImage: "creditcard.fill", color: .blue)
+ }
+ .foregroundStyle(.primary)
+ }
+
+ // The manage sheet and restore alert are attached here because this row
+ // exists in every entitlement state: presenting them from a row inside the
+ // state switch would tear down the presenting modifier when the sheet's
+ // own effect (cancelling, fixing billing) changes the state mid-presentation.
+ private var restoreButton: some View {
+ Button {
+ restorePurchases()
+ } label: {
+ HStack {
+ SettingsIconLabel(title: String(localized: "Restore Purchases", comment: "Settings row that restores a previous Ad-Free purchase"), systemImage: "arrow.clockwise", color: .green)
+ if isRestoring {
+ Spacer()
+ ProgressView()
+ }
+ }
+ }
+ .foregroundStyle(.primary)
+ .disabled(isRestoring)
+ .manageSubscriptionsSheet(isPresented: $showingManageSubscriptions)
+ .sheet(isPresented: $showingPaywall) {
+ AdFreePaywallView()
+ }
+ .alert(
+ restoreResult?.title ?? "",
+ isPresented: Binding(
+ get: { restoreResult != nil },
+ set: { if !$0 { restoreResult = nil } }
+ ),
+ presenting: restoreResult
+ ) { _ in
+ Button("OK", role: .cancel) { }
+ } message: { result in
+ Text(result.message)
+ }
+ }
+
+ private var footerText: String {
+ switch storeManager.entitlementState {
+ case .active:
+ String(localized: "Ads are off. Cancel anytime — you keep Ad-Free until the end of the period you already paid for.", comment: "Settings footer while the Ad-Free subscription is active")
+ case .gracePeriod:
+ String(localized: "A renewal payment failed. Ads stay off while Apple retries — update your billing details to keep Ad-Free.", comment: "Settings footer during the billing grace period")
+ case .billingRetry:
+ String(localized: "Your Ad-Free subscription is paused because a renewal payment failed. Update your billing details to restore it.", comment: "Settings footer during billing retry after the grace period lapsed")
+ case .notEntitled, .unknown:
+ String(localized: "Remove every ad from SunHat with a monthly or annual plan. Already subscribed on another device or a previous install? Restore Purchases brings Ad-Free back.", comment: "Settings footer shown to non-subscribers")
+ }
+ }
+
+ private func restorePurchases() {
+ isRestoring = true
+ Task {
+ defer { isRestoring = false }
+ do {
+ // Use the state this restore resolved, not the shared
+ // property — a concurrent listener refresh can overtake ours.
+ let restored = try await storeManager.restorePurchases()
+ if restored.isAdFree {
+ restoreResult = .restored
+ } else if restored == .billingRetry {
+ restoreResult = .billingIssue
+ } else {
+ restoreResult = .nothingToRestore
+ }
+ } catch StoreKitError.userCancelled {
+ // The user dismissed the App Store sign-in prompt — not a failure.
+ } catch {
+ restoreResult = .failed
+ }
+ }
+ }
+}
+
+// MARK: - Preview
+
+#Preview {
+ NavigationStack {
+ Form {
+ AdFreeSettingsSection()
+ }
+ }
+ .environment(StoreManager.shared)
+}
diff --git a/SunHat/Views/Settings/PrivacyPolicyView.swift b/SunHat/Views/Settings/PrivacyPolicyView.swift
index 8bab697..c3341c8 100644
--- a/SunHat/Views/Settings/PrivacyPolicyView.swift
+++ b/SunHat/Views/Settings/PrivacyPolicyView.swift
@@ -48,12 +48,17 @@ struct PrivacyPolicyView: View {
privacySection(
title: "Analytics",
- content: "We do not collect any personal analytics or tracking data. The app works entirely on your device."
+ content: "SunHat itself does not collect personal analytics. Your reminders, preferences, and weather history stay on your device."
+ )
+
+ privacySection(
+ title: "Advertising",
+ content: "The free version of SunHat shows banner ads provided by Google AdMob. Google may collect device data (such as an advertising identifier) to serve and measure ads; iOS asks for your permission before any cross-app tracking, and you can decline. The SunHat Ad-Free subscription removes all ads, and no ad requests are made for subscribers. See Google's privacy policy for details on how Google handles ad data."
)
privacySection(
title: "Third-Party Services",
- content: "Weather data is provided by Apple WeatherKit. Please review Apple's privacy policy for information on how it handles data."
+ content: "Weather data is provided by Apple WeatherKit and advertising by Google AdMob. Please review Apple's and Google's privacy policies for information on how they handle data."
)
Text("Contact Us")
@@ -64,7 +69,7 @@ struct PrivacyPolicyView: View {
Text("If you have any questions about this privacy policy, please contact us at \(AppSupportLinks.privacyEmail)")
.font(.body)
- Text("Last updated: August 28, 2026")
+ Text("Last updated: August 31, 2026")
.font(.caption)
.foregroundStyle(.secondary)
.padding(.top, 20)
diff --git a/SunHat/Views/Settings/SettingsView.swift b/SunHat/Views/Settings/SettingsView.swift
index 125ff1b..5f28402 100644
--- a/SunHat/Views/Settings/SettingsView.swift
+++ b/SunHat/Views/Settings/SettingsView.swift
@@ -16,6 +16,7 @@ struct SettingsView: View {
@Environment(\.dismiss) private var dismiss
@State private var activeSheet: ActiveSheet?
+ @State private var adManager = AdManager.shared
@StateObject private var locationViewModel = LocationManagementViewModel()
private enum ActiveSheet: Identifiable {
@@ -33,6 +34,7 @@ struct SettingsView: View {
notificationsSection
locationSection
generalSection
+ AdFreeSettingsSection()
supportSection
privacySection
aboutSection
@@ -43,6 +45,10 @@ struct SettingsView: View {
.onAppear {
viewModel.configure(modelContext: modelContext)
locationViewModel.configure(modelContext: modelContext)
+ // UMP state isn't observable, so re-read it when Settings
+ // appears; Google requires the privacy-options entry point to
+ // be reachable whenever the framework says it's required.
+ adManager.refreshConsentState()
}
.alert(
viewModel.activeAlert?.title ?? "",
@@ -249,6 +255,15 @@ struct SettingsView: View {
viewModel.openTermsOfService()
}
.foregroundStyle(.primary)
+
+ // Google-required consent revocation entry point; only appears
+ // for users in regions where the consent framework mandates it.
+ if adManager.privacyOptionsRequired {
+ Button("Ad Privacy Options") {
+ adManager.presentPrivacyOptions()
+ }
+ .foregroundStyle(.primary)
+ }
}
}
@@ -304,6 +319,7 @@ enum AppearanceMode: String, CaseIterable {
#Preview {
SettingsView()
+ .environment(StoreManager.shared)
.modelContainer(for: [
UserPreferences.self,
WeatherReminder.self,
diff --git a/SunHat/Views/Store/AdFreePaywallView.swift b/SunHat/Views/Store/AdFreePaywallView.swift
new file mode 100644
index 0000000..f32d9b7
--- /dev/null
+++ b/SunHat/Views/Store/AdFreePaywallView.swift
@@ -0,0 +1,100 @@
+//
+// AdFreePaywallView.swift
+// SunHat
+//
+// The "SunHat Ad-Free" paywall — direction A ("System Store", chosen
+// 2026-08-30): a custom marketing header on top of Apple's
+// SubscriptionStoreView, which renders the tier picker, localized pricing,
+// auto-renewal disclosure, and buy button itself. Both plans are shown at
+// full price with no pre-selection tricks; Restore and the policy links are
+// system-provided.
+//
+
+import SwiftUI
+import StoreKit
+
+struct AdFreePaywallView: View {
+ @Environment(StoreManager.self) private var storeManager
+ @Environment(\.dismiss) private var dismiss
+
+ var body: some View {
+ SubscriptionStoreView(productIDs: StoreManager.ProductID.all) {
+ marketingHeader
+ .containerBackground(Color(.systemBackground), for: .subscriptionStore)
+ }
+ .subscriptionStoreButtonLabel(.multiline)
+ .storeButton(.visible, for: .restorePurchases)
+ .subscriptionStorePolicyDestination(url: AppSupportLinks.termsOfServiceURL, for: .termsOfService)
+ .subscriptionStorePolicyDestination(url: AppSupportLinks.privacyPolicyURL, for: .privacyPolicy)
+ .onInAppPurchaseCompletion { _, result in
+ guard case .success(let purchaseResult) = result else { return }
+ let outcome = try? await storeManager.handlePurchaseResult(purchaseResult)
+ if outcome == .success {
+ dismiss()
+ }
+ }
+ }
+
+ private var marketingHeader: some View {
+ VStack(spacing: 12) {
+ Image(systemName: "sun.max.fill")
+ .font(.system(size: 44))
+ .foregroundStyle(
+ LinearGradient(
+ colors: [.yellow, .orange],
+ startPoint: .top,
+ endPoint: .bottom
+ )
+ )
+ .padding(.top, 8)
+
+ Text("SunHat Ad-Free")
+ .font(.title2.bold())
+
+ Text("Removes all ads from SunHat.", comment: "Paywall subtitle")
+ .font(.subheadline)
+ .foregroundStyle(.secondary)
+
+ VStack(alignment: .leading, spacing: 12) {
+ benefitRow(
+ systemImage: "rectangle.slash.fill",
+ color: .orange,
+ text: String(localized: "No banner ads, anywhere in the app", comment: "Paywall benefit row")
+ )
+ benefitRow(
+ systemImage: "cloud.sun.fill",
+ color: .blue,
+ text: String(localized: "Same forecasts, reminders & alerts", comment: "Paywall benefit row")
+ )
+ benefitRow(
+ systemImage: "heart.fill",
+ color: .green,
+ text: String(localized: "Supports an independent weather app", comment: "Paywall benefit row")
+ )
+ }
+ .padding(.top, 8)
+ .padding(.horizontal, 4)
+ }
+ .padding(.horizontal, 16)
+ }
+
+ private func benefitRow(systemImage: String, color: Color, text: String) -> some View {
+ HStack(spacing: 12) {
+ Image(systemName: systemImage)
+ .font(.system(size: 13, weight: .semibold))
+ .foregroundStyle(.white)
+ .frame(width: 28, height: 28)
+ .background(color.gradient, in: .rect(cornerRadius: 6.5, style: .continuous))
+ Text(text)
+ .font(.subheadline)
+ Spacer(minLength: 0)
+ }
+ }
+}
+
+// MARK: - Preview
+
+#Preview {
+ AdFreePaywallView()
+ .environment(StoreManager.shared)
+}
diff --git a/SunHat/Views/Weather/WeatherView.swift b/SunHat/Views/Weather/WeatherView.swift
index 57adff7..8b2143a 100644
--- a/SunHat/Views/Weather/WeatherView.swift
+++ b/SunHat/Views/Weather/WeatherView.swift
@@ -82,6 +82,9 @@ struct WeatherView: View {
.padding(.bottom, 20)
}
}
+ .safeAreaInset(edge: .bottom, spacing: 0) {
+ AdBannerSlot(adUnitID: AdConfig.weatherBannerUnitID)
+ }
.navigationTitle("Weather")
.navigationBarTitleDisplayMode(.large)
.toolbar {
diff --git a/SunHat/sunhat.swift b/SunHat/sunhat.swift
index 0206721..8e2cf36 100644
--- a/SunHat/sunhat.swift
+++ b/SunHat/sunhat.swift
@@ -47,6 +47,7 @@ final class SunHatAppDelegate: NSObject, UIApplicationDelegate, UNUserNotificati
struct SunHatApp: App {
@UIApplicationDelegateAdaptor(SunHatAppDelegate.self) private var appDelegate
@StateObject private var storeRecoveryState = StoreRecoveryState.shared
+ @Environment(\.scenePhase) private var scenePhase
var sharedModelContainer: ModelContainer = {
Self.prepareSharedStoreDirectory()
@@ -88,6 +89,14 @@ struct SunHatApp: App {
}
}()
+ /// True only when XCTest is loaded INTO this process (unit tests hosted
+ /// in the app). Deliberately not an environment-variable check: UI-test
+ /// runs also inject XCTest* env vars into the app under test, which must
+ /// behave like production.
+ private static var isHostingUnitTests: Bool {
+ NSClassFromString("XCTestCase") != nil
+ }
+
private static func prepareSharedStoreDirectory() {
guard let appGroupURL = FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.org.wesley.sunhat"
@@ -149,6 +158,18 @@ struct SunHatApp: App {
let container = sharedModelContainer
BackgroundWeatherManager.shared.configure(modelContainer: container)
+ // Unit tests replace the App Store with a local SKTestSession; starting
+ // the live Transaction.updates listener at launch would bind this
+ // process to the real App Store environment before a test session can
+ // take over, so the tests drive StoreManager instances directly instead.
+ if !Self.isHostingUnitTests {
+ StoreManager.shared.start()
+ // Ad SDK activation is entitlement-gated inside AdManager: it only
+ // starts Google Mobile Ads once StoreKit affirmatively resolves
+ // that the user is not Ad-Free, and re-evaluates on changes.
+ AdManager.shared.activate()
+ }
+
Task {
// Configure the weather service before any background refresh can run,
// so BackgroundWeatherManager's refresh path never hits an unconfigured actor.
@@ -165,6 +186,29 @@ struct SunHatApp: App {
StoreRecoveryBanner(message: message)
}
}
+ .environment(StoreManager.shared)
+ .task {
+ // onChange(of: scenePhase) never fires for the INITIAL
+ // value, and the scene is usually already .active by the
+ // first body evaluation — capture it here or ATT/consent
+ // presentation would wait for a background/foreground
+ // round-trip.
+ guard !Self.isHostingUnitTests else { return }
+ AdManager.shared.sceneDidChange(active: scenePhase == .active)
+ }
+ .onChange(of: scenePhase) { _, newPhase in
+ guard !Self.isHostingUnitTests else { return }
+ // AdManager tracks real scene activity: ATT may only be
+ // requested while active.
+ AdManager.shared.sceneDidChange(active: newPhase == .active)
+ // Grace/billing/cancel transitions that land while the app
+ // is suspended produce no Transaction.updates event, so
+ // re-derive entitlement on each return to foreground; the
+ // entitlement change re-triggers ad evaluation on its own.
+ if newPhase == .active {
+ Task { await StoreManager.shared.refreshEntitlement() }
+ }
+ }
}
.modelContainer(sharedModelContainer)
}
diff --git a/SunHatTests/AdActivationPolicyTests.swift b/SunHatTests/AdActivationPolicyTests.swift
new file mode 100644
index 0000000..864920e
--- /dev/null
+++ b/SunHatTests/AdActivationPolicyTests.swift
@@ -0,0 +1,122 @@
+//
+// AdActivationPolicyTests.swift
+// SunHatTests
+//
+// Pins the ad-SDK activation and App Tracking Transparency timing rules:
+// nothing runs for Ad-Free or unresolved users, ATT is never requested in
+// the app's first session or while the scene is inactive, and the SDK can
+// start without tracking authorization (with ATT still requestable later).
+//
+
+import Foundation
+import Testing
+@testable import SunHat
+
+struct AdActivationPolicyTests {
+
+ @Test("Ad-Free or unresolved entitlement triggers nothing at all")
+ func entitledUserTriggersNothing() {
+ let decision = AdActivationPolicy.decide(
+ allowsAdRequests: false,
+ sdkStarted: false,
+ trackingStatus: .notDetermined,
+ appOpenCount: 10,
+ isSceneActive: true
+ )
+ #expect(decision == .none)
+ #expect(!decision.shouldRequestTracking)
+ #expect(!decision.shouldStartSDK)
+ }
+
+ @Test("First session starts the SDK without an ATT prompt")
+ func firstSessionSkipsTracking() {
+ let decision = AdActivationPolicy.decide(
+ allowsAdRequests: true,
+ sdkStarted: false,
+ trackingStatus: .notDetermined,
+ appOpenCount: 1,
+ isSceneActive: true
+ )
+ #expect(!decision.shouldRequestTracking)
+ #expect(decision.shouldStartSDK)
+ }
+
+ @Test("Second session with an active scene requests ATT and starts the SDK")
+ func secondSessionRequestsTracking() {
+ let decision = AdActivationPolicy.decide(
+ allowsAdRequests: true,
+ sdkStarted: false,
+ trackingStatus: .notDetermined,
+ appOpenCount: 2,
+ isSceneActive: true
+ )
+ #expect(decision.shouldRequestTracking)
+ #expect(decision.shouldStartSDK)
+ }
+
+ @Test("ATT is never requested while the scene is inactive")
+ func inactiveSceneDefersTracking() {
+ let decision = AdActivationPolicy.decide(
+ allowsAdRequests: true,
+ sdkStarted: false,
+ trackingStatus: .notDetermined,
+ appOpenCount: 5,
+ isSceneActive: false
+ )
+ #expect(!decision.shouldRequestTracking)
+ #expect(decision.shouldStartSDK)
+ }
+
+ @Test("A determined ATT status is never re-prompted",
+ arguments: [TrackingStatusKind.denied, .authorized, .restricted])
+ func determinedStatusNeverPrompts(status: TrackingStatusKind) {
+ let decision = AdActivationPolicy.decide(
+ allowsAdRequests: true,
+ sdkStarted: false,
+ trackingStatus: status,
+ appOpenCount: 5,
+ isSceneActive: true
+ )
+ #expect(!decision.shouldRequestTracking)
+ #expect(decision.shouldStartSDK)
+ }
+
+ @Test("A running SDK can still pick up a deferred ATT request")
+ func deferredTrackingAfterSDKStart() {
+ let decision = AdActivationPolicy.decide(
+ allowsAdRequests: true,
+ sdkStarted: true,
+ trackingStatus: .notDetermined,
+ appOpenCount: 3,
+ isSceneActive: true
+ )
+ #expect(decision.shouldRequestTracking)
+ #expect(!decision.shouldStartSDK)
+ }
+
+ @Test("While live entitlement is unknown, the cached mirror decides ad eligibility",
+ arguments: [
+ (AdFreeEntitlementState.unknown, AdFreeEntitlementState.notEntitled, true),
+ (.unknown, .active, false),
+ (.unknown, .unknown, false),
+ (.unknown, .billingRetry, true),
+ (.active, .notEntitled, false),
+ (.notEntitled, .active, true),
+ (.gracePeriod, .notEntitled, false)
+ ])
+ func cachedMirrorGatesLaunch(live: AdFreeEntitlementState, cached: AdFreeEntitlementState, expected: Bool) {
+ #expect(AdActivationPolicy.effectiveAllowsAdRequests(live: live, cached: cached) == expected)
+ }
+
+ @Test("A running SDK with determined ATT has nothing left to do")
+ func steadyStateIsNone() {
+ let decision = AdActivationPolicy.decide(
+ allowsAdRequests: true,
+ sdkStarted: true,
+ trackingStatus: .authorized,
+ appOpenCount: 3,
+ isSceneActive: true
+ )
+ #expect(decision == .none)
+ }
+}
diff --git a/SunHatTests/AdFreeEntitlementResolverTests.swift b/SunHatTests/AdFreeEntitlementResolverTests.swift
new file mode 100644
index 0000000..7e99439
--- /dev/null
+++ b/SunHatTests/AdFreeEntitlementResolverTests.swift
@@ -0,0 +1,167 @@
+//
+// AdFreeEntitlementResolverTests.swift
+// SunHatTests
+//
+// Exhaustive coverage of the StoreKit-free entitlement mapping: which
+// combinations of currentEntitlements presence and subscription renewal
+// states produce active / grace / billing-retry / not-entitled, and the two
+// derived gates (isAdFree, allowsAdRequests). The launch-safety invariant —
+// ads are only ever requested on affirmative non-entitlement — is pinned here.
+//
+
+import Foundation
+import Testing
+@testable import SunHat
+
+struct AdFreeEntitlementResolverTests {
+
+ // MARK: - State resolution
+
+ @Test("Subscribed status resolves to active")
+ func subscribedResolvesActive() {
+ let state = AdFreeEntitlementResolver.resolve(
+ hasVerifiedEntitlement: true,
+ renewalStates: [.subscribed]
+ )
+ #expect(state == .active)
+ }
+
+ @Test("Grace period keeps the user entitled but is labeled gracePeriod")
+ func gracePeriodResolves() {
+ let state = AdFreeEntitlementResolver.resolve(
+ hasVerifiedEntitlement: true,
+ renewalStates: [.inGracePeriod]
+ )
+ #expect(state == .gracePeriod)
+ #expect(state.isAdFree)
+ #expect(!state.allowsAdRequests)
+ }
+
+ @Test("Billing retry without a current entitlement is not ad-free")
+ func billingRetryResolves() {
+ let state = AdFreeEntitlementResolver.resolve(
+ hasVerifiedEntitlement: false,
+ renewalStates: [.inBillingRetryPeriod]
+ )
+ #expect(state == .billingRetry)
+ #expect(!state.isAdFree)
+ #expect(state.allowsAdRequests)
+ }
+
+ @Test("Expired subscription with no entitlement resolves to notEntitled")
+ func expiredResolvesNotEntitled() {
+ let state = AdFreeEntitlementResolver.resolve(
+ hasVerifiedEntitlement: false,
+ renewalStates: [.expired]
+ )
+ #expect(state == .notEntitled)
+ #expect(state.allowsAdRequests)
+ }
+
+ @Test("Revoked subscription with no entitlement resolves to notEntitled")
+ func revokedResolvesNotEntitled() {
+ let state = AdFreeEntitlementResolver.resolve(
+ hasVerifiedEntitlement: false,
+ renewalStates: [.revoked]
+ )
+ #expect(state == .notEntitled)
+ }
+
+ @Test("No StoreKit data at all resolves to notEntitled")
+ func noDataResolvesNotEntitled() {
+ let state = AdFreeEntitlementResolver.resolve(
+ hasVerifiedEntitlement: false,
+ renewalStates: []
+ )
+ #expect(state == .notEntitled)
+ }
+
+ @Test("Entitlement with unavailable status (offline) favors the paying user")
+ func entitlementWithoutStatusResolvesActive() {
+ let state = AdFreeEntitlementResolver.resolve(
+ hasVerifiedEntitlement: true,
+ renewalStates: []
+ )
+ #expect(state == .active)
+ #expect(state.isAdFree)
+ }
+
+ @Test("An affirmative expired status outranks a stale entitlement snapshot")
+ func expiredStatusOutranksStaleSnapshot() {
+ // The entitlement snapshot is captured before the status fetch, so a
+ // terminal status is always the fresher signal (refund/expiry race).
+ let state = AdFreeEntitlementResolver.resolve(
+ hasVerifiedEntitlement: true,
+ renewalStates: [.expired]
+ )
+ #expect(state == .notEntitled)
+ }
+
+ @Test("An affirmative revoked status outranks a stale entitlement snapshot")
+ func revokedStatusOutranksStaleSnapshot() {
+ let state = AdFreeEntitlementResolver.resolve(
+ hasVerifiedEntitlement: true,
+ renewalStates: [.revoked]
+ )
+ #expect(state == .notEntitled)
+ }
+
+ @Test("Billing retry outranks a stale entitlement snapshot")
+ func billingRetryOutranksStaleSnapshot() {
+ let state = AdFreeEntitlementResolver.resolve(
+ hasVerifiedEntitlement: true,
+ renewalStates: [.inBillingRetryPeriod]
+ )
+ #expect(state == .billingRetry)
+ }
+
+ @Test("Subscribed outranks other states when both plans report (crossgrade)")
+ func subscribedOutranksExpired() {
+ let state = AdFreeEntitlementResolver.resolve(
+ hasVerifiedEntitlement: true,
+ renewalStates: [.expired, .subscribed]
+ )
+ #expect(state == .active)
+ }
+
+ // MARK: - Gate invariants
+
+ @Test("Unknown fails closed: no paid access claimed, but no ad requests either")
+ func unknownFailsClosed() {
+ let state = AdFreeEntitlementState.unknown
+ #expect(!state.isAdFree)
+ #expect(!state.allowsAdRequests)
+ }
+
+ @Test("Ads are only ever requested on affirmative non-entitlement",
+ arguments: [
+ AdFreeEntitlementState.unknown,
+ .notEntitled,
+ .active,
+ .gracePeriod,
+ .billingRetry
+ ])
+ func adRequestsRequireAffirmativeNonEntitlement(state: AdFreeEntitlementState) {
+ // A state never both grants ad-free and allows ad requests, and ad
+ // requests are allowed only for notEntitled / billingRetry.
+ #expect(!(state.isAdFree && state.allowsAdRequests))
+ #expect(state.allowsAdRequests == (state == .notEntitled || state == .billingRetry))
+ }
+
+ // MARK: - Cached-state launch gate
+
+ @Test("Cached entitlement round-trips through UserDefaults")
+ func cachedStateRoundTrip() async {
+ let defaults = UserDefaults(suiteName: "AdFreeEntitlementResolverTests")!
+ defer { defaults.removePersistentDomain(forName: "AdFreeEntitlementResolverTests") }
+
+ defaults.set(AdFreeEntitlementState.active.rawValue, forKey: StoreManager.cachedEntitlementStateKey)
+ #expect(StoreManager.cachedEntitlementState(userDefaults: defaults) == .active)
+
+ defaults.set("garbage-value", forKey: StoreManager.cachedEntitlementStateKey)
+ #expect(StoreManager.cachedEntitlementState(userDefaults: defaults) == .unknown)
+
+ defaults.removeObject(forKey: StoreManager.cachedEntitlementStateKey)
+ #expect(StoreManager.cachedEntitlementState(userDefaults: defaults) == .unknown)
+ }
+}
diff --git a/SunHatTests/AdManagerGateTests.swift b/SunHatTests/AdManagerGateTests.swift
new file mode 100644
index 0000000..ed8d4ce
--- /dev/null
+++ b/SunHatTests/AdManagerGateTests.swift
@@ -0,0 +1,153 @@
+//
+// AdManagerGateTests.swift
+// SunHatTests
+//
+// Covers the gate every banner slot actually reads — AdManager's decision to
+// start the Google Mobile Ads SDK — using the manager's injection seam so no
+// real SDK call is made. The pure timing rules live in
+// AdActivationPolicyTests; this pins the wiring around them, including the
+// consent gate's fail-closed default.
+//
+
+import Foundation
+import Testing
+@testable import SunHat
+
+@MainActor
+private final class StubTrackingAuthorizer: TrackingAuthorizing {
+ var statusKind: TrackingStatusKind
+ private(set) var requestCount = 0
+
+ init(statusKind: TrackingStatusKind) {
+ self.statusKind = statusKind
+ }
+
+ func requestAuthorization() async {
+ requestCount += 1
+ statusKind = .denied
+ }
+}
+
+@MainActor
+@Suite(.serialized)
+struct AdManagerGateTests {
+
+ private static let suiteName = "AdManagerGateTests"
+
+ private func makeManager(
+ appOpenCount: Int,
+ trackingStatus: TrackingStatusKind = .denied
+ ) -> (AdManager, StubTrackingAuthorizer, Box) {
+ let defaults = UserDefaults(suiteName: Self.suiteName)!
+ defaults.removePersistentDomain(forName: Self.suiteName)
+ defaults.set(appOpenCount, forKey: "appLifecyclePrompt.appOpenCount")
+
+ let tracking = StubTrackingAuthorizer(statusKind: trackingStatus)
+ let box = Box()
+ let manager = AdManager(
+ userDefaults: defaults,
+ tracking: tracking,
+ startSDK: { box.startCount += 1 }
+ )
+ return (manager, tracking, box)
+ }
+
+ final class Box {
+ var startCount = 0
+ }
+
+ /// Polls instead of assuming one yield: evaluateActivation commits its
+ /// work inside an unstructured Task.
+ private func settle(_ condition: () -> Bool, attempts: Int = 50) async {
+ for _ in 0.. 0 }
+
+ #expect(box.startCount == 0)
+ #expect(!manager.isStarted)
+ #expect(!manager.shouldShowAdSlots)
+ }
+
+ @Test("Unresolved entitlement fails closed — no SDK start, no slot")
+ func unknownEntitlementFailsClosed() async {
+ let (manager, _, box) = makeManager(appOpenCount: 5)
+ StoreManager.shared.overrideEntitlementStateForTesting(.unknown)
+ defer { StoreManager.shared.clearEntitlementOverrideForTesting() }
+
+ manager.sceneDidChange(active: true)
+ await settle { box.startCount > 0 }
+
+ #expect(box.startCount == 0)
+ #expect(!manager.shouldShowAdSlots)
+ }
+
+ @Test("A non-subscriber starts the SDK exactly once across repeated evaluations")
+ func nonSubscriberStartsSDKOnce() async {
+ let (manager, _, box) = makeManager(appOpenCount: 5)
+ StoreManager.shared.overrideEntitlementStateForTesting(.notEntitled)
+ defer { StoreManager.shared.clearEntitlementOverrideForTesting() }
+
+ manager.sceneDidChange(active: true)
+ await settle { manager.isStarted }
+ manager.sceneDidChange(active: true)
+ manager.evaluateActivation()
+ await settle { box.startCount > 1 }
+
+ #expect(box.startCount == 1)
+ #expect(manager.isStarted)
+ }
+
+ @Test("A started SDK still shows no slots while consent blocks ads")
+ func consentGateSuppressesSlots() async {
+ let (manager, _, _) = makeManager(appOpenCount: 5)
+ StoreManager.shared.overrideEntitlementStateForTesting(.notEntitled)
+ defer { StoreManager.shared.clearEntitlementOverrideForTesting() }
+
+ manager.sceneDidChange(active: true)
+ await settle { manager.isStarted }
+
+ // consentBlocksAds is seeded from UMP's persisted verdict; whatever it
+ // says, the invariant is that a blocking verdict hides every slot.
+ if manager.consentBlocksAds {
+ #expect(!manager.shouldShowAdSlots)
+ } else {
+ #expect(manager.shouldShowAdSlots)
+ }
+ }
+
+ @Test("ATT is never requested in the app's first session")
+ func firstSessionNeverRequestsTracking() async {
+ let (manager, tracking, _) = makeManager(appOpenCount: 1, trackingStatus: .notDetermined)
+ StoreManager.shared.overrideEntitlementStateForTesting(.notEntitled)
+ defer { StoreManager.shared.clearEntitlementOverrideForTesting() }
+
+ manager.sceneDidChange(active: true)
+ await settle { manager.isStarted }
+
+ #expect(tracking.requestCount == 0)
+ }
+
+ @Test("A later session with an active scene does request ATT")
+ func laterSessionRequestsTracking() async {
+ let (manager, tracking, _) = makeManager(appOpenCount: 3, trackingStatus: .notDetermined)
+ StoreManager.shared.overrideEntitlementStateForTesting(.notEntitled)
+ defer { StoreManager.shared.clearEntitlementOverrideForTesting() }
+
+ manager.sceneDidChange(active: true)
+ await settle { tracking.requestCount > 0 }
+
+ #expect(tracking.requestCount == 1)
+ }
+}
diff --git a/SunHatTests/DashboardViewTests.swift b/SunHatTests/DashboardViewTests.swift
index d844bb5..793aabc 100644
--- a/SunHatTests/DashboardViewTests.swift
+++ b/SunHatTests/DashboardViewTests.swift
@@ -44,12 +44,17 @@ struct DashboardViewModelTests {
@Test("ViewModel initializes with expected defaults")
func viewModelInitialState() {
- #expect(viewModel.currentTemperature == 0.0)
- #expect(viewModel.humidity == 0)
- #expect(viewModel.weatherDescription == String(localized: "Loading...", comment: "Placeholder shown before weather data has loaded"))
- #expect(viewModel.errorMessage == nil)
- #expect(viewModel.activeReminders.isEmpty)
- #expect(viewModel.forecastData.isEmpty)
+ // Assert on a freshly constructed VM: the suite-level `viewModel` has
+ // already been configure()d, which starts an async weather refresh
+ // whose location/network outcome is environment-dependent and can set
+ // errorMessage before this test runs (an intermittent CI flake).
+ let fresh = DashboardViewModel()
+ #expect(fresh.currentTemperature == 0.0)
+ #expect(fresh.humidity == 0)
+ #expect(fresh.weatherDescription == String(localized: "Loading...", comment: "Placeholder shown before weather data has loaded"))
+ #expect(fresh.errorMessage == nil)
+ #expect(fresh.activeReminders.isEmpty)
+ #expect(fresh.forecastData.isEmpty)
}
@Test("Loading completes after weather refresh")
diff --git a/SunHatTests/StoreManagerStoreKitTests.swift b/SunHatTests/StoreManagerStoreKitTests.swift
new file mode 100644
index 0000000..d68e99c
--- /dev/null
+++ b/SunHatTests/StoreManagerStoreKitTests.swift
@@ -0,0 +1,185 @@
+//
+// StoreManagerStoreKitTests.swift
+// SunHatTests
+//
+// End-to-end StoreKit 2 coverage against the repo's SunHat.storekit
+// configuration via SKTestSession: the config parses and exposes both
+// Ad-Free products, purchases of either tier produce the active entitlement,
+// expiration drops it, and the UserDefaults mirror tracks each resolution.
+// SKTestSession mutates process-global StoreKit state, so the suite is
+// serialized and every test builds a fresh session + StoreManager.
+//
+
+import Foundation
+import StoreKit
+import StoreKitTest
+import Testing
+@testable import SunHat
+
+@MainActor
+@Suite(.serialized)
+struct StoreManagerStoreKitTests {
+
+ private static let defaultsSuiteName = "StoreManagerStoreKitTests"
+
+ // #filePath bakes the builder's checkout path into the binary, so this
+ // only resolves when tests run on the machine that built them — true for
+ // this project's local xcodebuild flow. If a build/test split or path
+ // remapping is ever introduced, bundle the .storekit into SunHatTests
+ // and load it via Bundle instead.
+ private static var configurationURL: URL {
+ URL(fileURLWithPath: #filePath)
+ .deletingLastPathComponent() // SunHatTests/
+ .deletingLastPathComponent() // repo root
+ .appendingPathComponent("SunHat.storekit")
+ }
+
+ private func makeSession() throws -> SKTestSession {
+ let session = try SKTestSession(contentsOf: Self.configurationURL)
+ session.resetToDefaultState()
+ session.disableDialogs = true
+ session.clearTransactions()
+ return session
+ }
+
+ private func makeManager() -> (StoreManager, UserDefaults) {
+ let defaults = UserDefaults(suiteName: Self.defaultsSuiteName)!
+ defaults.removePersistentDomain(forName: Self.defaultsSuiteName)
+ // .acceptTestSignatures: SKTestSession transactions are signed with a
+ // local test certificate this process doesn't trust, so they arrive
+ // as .unverified(_, .invalidSignature). Production uses .verifiedOnly.
+ let manager = StoreManager(userDefaults: defaults, trustPolicy: .acceptTestSignatures)
+ return (manager, defaults)
+ }
+
+ /// StoreKit test-session mutations propagate asynchronously, so poll the
+ /// manager instead of assuming one refresh is enough (same pattern as
+ /// waitUntilLevelSettles in StoreRecoveryWriteGatingTests).
+ private func refreshUntil(
+ _ manager: StoreManager,
+ reaches expected: AdFreeEntitlementState,
+ maxAttempts: Int = 50
+ ) async {
+ for _ in 0.. org.wesley.sunhat
+//
+
+import XCTest
+
+final class AdFreeIntegrationUITests: XCTestCase {
+ override func setUpWithError() throws {
+ continueAfterFailure = false
+ }
+
+ // MARK: - Ads visible for a non-subscriber (SDK live)
+
+ @MainActor
+ func testBannerSlotAppearsForNonSubscriber() throws {
+ let app = XCUIApplication()
+ app.launchArguments += [
+ "-hasCompletedOnboarding", "YES",
+ "-hasCreatedFirstReminder", "YES"
+ ]
+ app.launch()
+
+ // First-run gauntlet: location alert, Google's GDPR consent form, the
+ // ATT explainer + system alert all chain in front of the ad slot.
+ // The banner exposes itself as a single accessibility element with
+ // identifier "ad-banner" (see BannerAdView), so it is present as soon
+ // as the slot exists, even before a creative fills it.
+ let adBanner = app.descendants(matching: .any)
+ .matching(identifier: "ad-banner")
+ .firstMatch
+ clearFirstRunPrompts(app: app, until: adBanner, timeout: 240)
+ if !adBanner.exists {
+ save(app.screenshot(), name: "diagnostic-no-ad-slot")
+ XCTFail("Banner slot should appear for a non-subscriber")
+ }
+ save(app.screenshot(), name: "dashboard-with-ad-slot")
+ }
+
+ // MARK: - Purchase unlocks Ad-Free and persists (SDK suppressed)
+
+ @MainActor
+ func testPurchaseUnlocksAdFreeAndPersists() throws {
+ let app = XCUIApplication()
+ app.launchArguments += [
+ "-hasCompletedOnboarding", "YES",
+ "-hasCreatedFirstReminder", "YES",
+ "-sunhatDisableAdSDK",
+ ]
+ app.launch()
+
+ // 1. Paywall via Settings' Get Ad-Free row.
+ XCTAssertTrue(app.tabBars.buttons["Settings"].waitForExistence(timeout: 15))
+ app.tabBars.buttons["Settings"].tap()
+ let getAdFree = app.buttons["Get Ad-Free"]
+ var settingsSwipes = 0
+ while !(getAdFree.exists && getAdFree.isHittable) && settingsSwipes < 8 {
+ app.swipeUp()
+ settingsSwipes += 1
+ _ = getAdFree.waitForExistence(timeout: 2)
+ }
+ XCTAssertTrue(getAdFree.exists, "Get Ad-Free row should exist for a non-subscriber")
+ getAdFree.tap()
+
+ // 2. Wait for the store to actually finish loading. The Subscribe
+ // button (not the tier text, which can match a not-yet-visible
+ // element) is the reliable "store is ready" signal.
+ // Match on LABEL, not identifier: SubscriptionStoreView's buy button
+ // carries the renewal disclosure in its label ("Subscribe\nPlan
+ // auto-renews for $1.00/month until canceled."), so an exact
+ // identifier lookup for "Subscribe" never matches it.
+ let subscribeButton = app.buttons
+ .matching(NSPredicate(format: "label BEGINSWITH[c] %@", "Subscribe"))
+ .firstMatch
+ if !subscribeButton.waitForExistence(timeout: 90) {
+ // The simulator's StoreKit-Testing storefront sometimes isn't
+ // attached yet on the first launch of a run ("Subscription
+ // Unavailable"); a relaunch reliably picks it up. Retry once for
+ // that transient, then fail loudly — never skip, which would mask
+ // a genuine store misconfiguration (and previously did).
+ save(app.screenshot(), name: "diagnostic-paywall-first-try")
+ app.terminate()
+ app.launch()
+ app.tabBars.buttons["Settings"].tap()
+ var retrySwipes = 0
+ while !(getAdFree.exists && getAdFree.isHittable) && retrySwipes < 8 {
+ app.swipeUp()
+ retrySwipes += 1
+ _ = getAdFree.waitForExistence(timeout: 2)
+ }
+ if getAdFree.exists { getAdFree.tap() }
+ if !subscribeButton.waitForExistence(timeout: 120) {
+ save(app.screenshot(), name: "diagnostic-paywall-state")
+ XCTFail("Paywall never finished loading after a retry. If it shows 'Subscription Unavailable', the StoreKit-Testing storefront never attached; verify the scheme's TestAction still carries its StoreKitConfigurationFileReference.")
+ }
+ }
+ save(app.screenshot(), name: "paywall-open")
+
+ // 3. Purchase the pre-selected monthly plan through the system store.
+ subscribeButton.tap()
+ confirmStoreKitPurchase(app: app)
+
+ // 4. Success dismisses the paywall; the section now shows the plan
+ // and the Get Ad-Free row is gone (entitlement flipped mid-session,
+ // the same signal that removes every ad slot).
+ let currentPlan = app.staticTexts["Current Plan"]
+ if !currentPlan.waitForExistence(timeout: 60) {
+ // Capture the screen: the usual cause is the StoreKit purchase
+ // sheet still being up (its confirm control's label varies by OS),
+ // which is invisible in a bare assertion failure.
+ save(app.screenshot(), name: "diagnostic-after-purchase")
+ XCTFail("Settings should show the active Ad-Free plan after purchase")
+ }
+ XCTAssertFalse(getAdFree.exists, "Get Ad-Free row must disappear once subscribed")
+ save(app.screenshot(), name: "settings-subscribed")
+
+ // 5. Cold relaunch: entitlement comes from the persisted transaction.
+ app.terminate()
+ app.launch()
+ XCTAssertTrue(app.tabBars.buttons["Settings"].waitForExistence(timeout: 15))
+ app.tabBars.buttons["Settings"].tap()
+ var relaunchSwipes = 0
+ while !currentPlan.exists && relaunchSwipes < 8 {
+ app.swipeUp()
+ relaunchSwipes += 1
+ _ = currentPlan.waitForExistence(timeout: 3)
+ }
+ XCTAssertTrue(currentPlan.exists, "Ad-Free must persist across a cold relaunch")
+ // Deliberately NOT asserting the absence of an ad slot here: this
+ // launch disables the ad SDK, so such an assertion could never fail
+ // and would be a false signal. The real gate (shouldShowAdSlots) is
+ // covered by AdManagerGateTests, and the visible-ad case by
+ // testBannerSlotAppearsForNonSubscriber.
+ }
+
+ // MARK: - Helpers
+
+ /// Answers every first-run prompt — the springboard location alert, the
+ /// ATT tracking alert, and Google's UMP consent dialogs — until `target`
+ /// exists or the timeout lapses.
+ @MainActor
+ private func clearFirstRunPrompts(app: XCUIApplication, until target: XCUIElement, timeout: TimeInterval) {
+ let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
+ // "Continue" = Google's ATT pre-prompt explainer; UMP renders its
+ // dialogs in webviews whose controls surface as buttons, links, or
+ // bare static text depending on the OS.
+ let labels = [
+ "Allow While Using App", // location alert
+ "Allow", // ATT system alert (personalized path)
+ "Consent", "Accept all", "Agree", "Continue", "OK",
+ ]
+
+ let deadline = Date().addingTimeInterval(timeout)
+ while !target.exists && Date() < deadline {
+ var tappedSomething = false
+ tapLoop: for label in labels {
+ let queries: [XCUIElementQuery] = [
+ springboard.buttons,
+ app.buttons,
+ app.links,
+ app.webViews.buttons,
+ app.webViews.links,
+ app.staticTexts,
+ ]
+ for query in queries {
+ let element = query[label]
+ if element.exists && element.isHittable {
+ element.tap()
+ tappedSomething = true
+ break tapLoop
+ }
+ }
+ }
+ if !tappedSomething {
+ // Google's ATT pre-prompt explainer exposes nothing to the
+ // accessibility tree; its "Continue" button sits centered at
+ // ~61% height. A blind tap there is harmless on the dashboard
+ // (non-interactive empty-state text) if no dialog is up.
+ app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.607)).tap()
+ }
+ _ = target.waitForExistence(timeout: 2)
+ }
+ }
+
+ /// The StoreKit-Testing purchase sheet's confirm control varies by OS;
+ /// try the known labels in both the app's and springboard's hierarchies.
+ @MainActor
+ private func confirmStoreKitPurchase(app: XCUIApplication) {
+ let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
+ let candidates: [XCUIElement] = [
+ springboard.buttons["Subscribe"],
+ springboard.buttons["Purchase"],
+ springboard.buttons["Confirm"],
+ app.buttons["Purchase"],
+ app.buttons["Confirm"],
+ ]
+ let deadline = Date().addingTimeInterval(20)
+ while Date() < deadline {
+ for candidate in candidates where candidate.exists && candidate.isHittable {
+ candidate.tap()
+ return
+ }
+ RunLoop.current.run(until: Date().addingTimeInterval(0.5))
+ }
+ // No confirmation sheet found — StoreKit Testing with dialogs
+ // disabled completes without one; the entitlement assertions that
+ // follow are the real check either way.
+ }
+
+ @MainActor
+ private func save(_ screenshot: XCUIScreenshot, name: String) {
+ let attachment = XCTAttachment(screenshot: screenshot)
+ attachment.name = name
+ attachment.lifetime = .keepAlways
+ add(attachment)
+
+ let directory = URL(fileURLWithPath: "/tmp/sunhat-shots", isDirectory: true)
+ try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
+ try? screenshot.pngRepresentation.write(to: directory.appendingPathComponent("\(name).png"))
+ }
+}
diff --git a/SunHatUITests/PaywallScreenshotTests.swift b/SunHatUITests/PaywallScreenshotTests.swift
new file mode 100644
index 0000000..5015070
--- /dev/null
+++ b/SunHatUITests/PaywallScreenshotTests.swift
@@ -0,0 +1,80 @@
+//
+// PaywallScreenshotTests.swift
+// SunHatUITests
+//
+// Drives the app to the Ad-Free section in Settings and into the paywall
+// sheet, capturing screenshots for design review (and reused by the Phase 7
+// integration pass). Screenshots are written to /tmp/sunhat-shots and also
+// attached to the xcresult.
+//
+
+import XCTest
+
+final class PaywallScreenshotTests: XCTestCase {
+ override func setUpWithError() throws {
+ continueAfterFailure = false
+ }
+
+ @MainActor
+ func testCaptureAdFreeSettingsAndPaywall() throws {
+ let app = XCUIApplication()
+ // Argument-domain defaults: skip onboarding deterministically.
+ app.launchArguments += [
+ "-hasCompletedOnboarding", "YES",
+ "-hasCreatedFirstReminder", "YES"
+ ]
+ app.launch()
+
+ let settingsTab = app.tabBars.buttons["Settings"]
+ XCTAssertTrue(settingsTab.waitForExistence(timeout: 10), "Settings tab should appear")
+ settingsTab.tap()
+
+ // AdFreeIntegrationUITests purchases in the persistent local StoreKit
+ // store; this test needs a non-subscribed state. Skip with guidance
+ // rather than failing confusingly.
+ if app.staticTexts["Current Plan"].waitForExistence(timeout: 3) {
+ throw XCTSkip("App is already subscribed in the local StoreKit store — run `xcrun simctl uninstall org.wesley.sunhat` first to reset.")
+ }
+
+ // The row only exists once (a) StoreManager resolves .notEntitled —
+ // which can take a while when the local StoreKit test store rebuilds —
+ // and (b) the lazily-realized Form row is scrolled near the viewport.
+ // Poll with swipes until both are true.
+ let getAdFree = app.buttons["Get Ad-Free"]
+ let deadline = Date().addingTimeInterval(60)
+ while !getAdFree.exists && Date() < deadline {
+ app.swipeUp()
+ if getAdFree.waitForExistence(timeout: 3) { break }
+ app.swipeDown()
+ _ = getAdFree.waitForExistence(timeout: 1)
+ }
+ XCTAssertTrue(getAdFree.exists, "Get Ad-Free row should exist for a non-subscriber")
+ var hittableSwipes = 0
+ while !getAdFree.isHittable && hittableSwipes < 8 {
+ app.swipeUp()
+ hittableSwipes += 1
+ }
+ save(app.screenshot(), name: "settings-adfree-section")
+
+ getAdFree.tap()
+ let paywallTitle = app.staticTexts["SunHat Ad-Free"]
+ XCTAssertTrue(paywallTitle.waitForExistence(timeout: 10), "Paywall marketing header should appear")
+ // Wait for the tiers themselves so the screenshot shows the loaded
+ // store, not the "Loading Subscription" spinner.
+ let annualTier = app.staticTexts["Ad-Free Annual"]
+ XCTAssertTrue(annualTier.waitForExistence(timeout: 20), "Subscription tiers should load from the local StoreKit configuration")
+ save(app.screenshot(), name: "paywall-sheet")
+ }
+
+ @MainActor
+ private func save(_ screenshot: XCUIScreenshot, name: String) {
+ let attachment = XCTAttachment(screenshot: screenshot)
+ attachment.name = name
+ attachment.lifetime = .keepAlways
+ add(attachment)
+
+ let directory = URL(fileURLWithPath: "/tmp/sunhat-shots", isDirectory: true)
+ try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
+ try? screenshot.pngRepresentation.write(to: directory.appendingPathComponent("\(name).png"))
+ }
+}