diff --git a/PIA VPN.xcodeproj/project.pbxproj b/PIA VPN.xcodeproj/project.pbxproj index 93ebd1515..1175ff58a 100644 --- a/PIA VPN.xcodeproj/project.pbxproj +++ b/PIA VPN.xcodeproj/project.pbxproj @@ -11,8 +11,6 @@ 0E0786DE1EFA7EAE00F77466 /* Components.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0E0786DD1EFA7EAE00F77466 /* Components.plist */; }; 0E1F318620176A5F00FC1000 /* Theme+DarkPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F318520176A5F00FC1000 /* Theme+DarkPalette.swift */; }; 0E1F318720176A6300FC1000 /* Theme+DarkPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F318520176A5F00FC1000 /* Theme+DarkPalette.swift */; }; - 0E2215C920084CD700F5FB4D /* SwiftGen+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2215C820084CD700F5FB4D /* SwiftGen+Strings.swift */; }; - 0E2215CA2008BA9100F5FB4D /* SwiftGen+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2215C820084CD700F5FB4D /* SwiftGen+Strings.swift */; }; 0E2215CC2008BF8300F5FB4D /* SwiftGen+Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2215CB2008BF8300F5FB4D /* SwiftGen+Assets.swift */; }; 0E2215CD2008C01D00F5FB4D /* SwiftGen+Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2215CB2008BF8300F5FB4D /* SwiftGen+Assets.swift */; }; 0E392DA61FE3283C0002160D /* TransientState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E392DA51FE3283C0002160D /* TransientState.swift */; }; @@ -167,11 +165,7 @@ 692483222AB05F37002A0407 /* PIACircleIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 692483212AB05F37002A0407 /* PIACircleIcon.swift */; }; 692483262AB05F85002A0407 /* PIAConnectionActivityWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 692483252AB05F85002A0407 /* PIAConnectionActivityWidget.swift */; }; 695BF81D2AC30EFB00D1139C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0E7EC043209326E30029811E /* Localizable.strings */; }; - 695BF81F2AC410E000D1139C /* SwiftGen+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2215C820084CD700F5FB4D /* SwiftGen+Strings.swift */; }; - 697C59AC2AFD0771008B6212 /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 697C59AB2AFD0770008B6212 /* PIALibrary */; }; - 697C59AE2AFD0786008B6212 /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 697C59AD2AFD0786008B6212 /* PIALibrary */; }; - 697C59B02AFD0791008B6212 /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 697C59AF2AFD0791008B6212 /* PIALibrary */; }; - 697C59B22AFD079B008B6212 /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 697C59B12AFD079B008B6212 /* PIALibrary */; }; + 695BF81F2AC410E000D1139C /* (null) in Sources */ = {isa = PBXBuildFile; }; 698F4F2B2AB8A2080010B2B0 /* PIAWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 8269A6D5251CB5E0000B4DBF /* PIAWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 698F4F2D2AB978BF0010B2B0 /* PIACircleImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698F4F2C2AB978BF0010B2B0 /* PIACircleImageView.swift */; }; 698F4F2E2AB97BAD0010B2B0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 291C6397183EBC210039EC03 /* Images.xcassets */; }; @@ -449,6 +443,91 @@ DDFCFA9521E892130081F235 /* RegionTile.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFCFA9321E892130081F235 /* RegionTile.swift */; }; DDFCFA9721E8921F0081F235 /* RegionTile.xib in Resources */ = {isa = PBXBuildFile; fileRef = DDFCFA9621E8921F0081F235 /* RegionTile.xib */; }; DDFCFA9821E8921F0081F235 /* RegionTile.xib in Resources */ = {isa = PBXBuildFile; fileRef = DDFCFA9621E8921F0081F235 /* RegionTile.xib */; }; + E501CBB32AE97EBA00515006 /* Signup.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E501CBB22AE97EBA00515006 /* Signup.storyboard */; }; + E501CBB42AE97EBA00515006 /* Signup.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E501CBB22AE97EBA00515006 /* Signup.storyboard */; }; + E501CBB62AE97EC800515006 /* Welcome.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E501CBB52AE97EC800515006 /* Welcome.storyboard */; }; + E501CBB72AE97EC800515006 /* Welcome.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E501CBB52AE97EC800515006 /* Welcome.storyboard */; }; + E501CBF52AE9806800515006 /* UI.strings in Resources */ = {isa = PBXBuildFile; fileRef = E501CBBA2AE9806800515006 /* UI.strings */; }; + E501CBF62AE9806800515006 /* UI.strings in Resources */ = {isa = PBXBuildFile; fileRef = E501CBBA2AE9806800515006 /* UI.strings */; }; + E501CBF72AE9806800515006 /* Signup.strings in Resources */ = {isa = PBXBuildFile; fileRef = E501CBCE2AE9806800515006 /* Signup.strings */; }; + E501CBF82AE9806800515006 /* Signup.strings in Resources */ = {isa = PBXBuildFile; fileRef = E501CBCE2AE9806800515006 /* Signup.strings */; }; + E501CBF92AE9806800515006 /* Welcome.strings in Resources */ = {isa = PBXBuildFile; fileRef = E501CBD02AE9806800515006 /* Welcome.strings */; }; + E501CBFA2AE9806800515006 /* Welcome.strings in Resources */ = {isa = PBXBuildFile; fileRef = E501CBD02AE9806800515006 /* Welcome.strings */; }; + E501CBFB2AE9806800515006 /* UI.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E501CBDC2AE9806800515006 /* UI.xcassets */; }; + E501CBFC2AE9806800515006 /* UI.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E501CBDC2AE9806800515006 /* UI.xcassets */; }; + E51693C62AFAD3550089FB8F /* UIDevice+WIFI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51693C52AFAD3550089FB8F /* UIDevice+WIFI.swift */; }; + E51693D12AFADCC20089FB8F /* SwiftGen+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51693D02AFADCC20089FB8F /* SwiftGen+Strings.swift */; }; + E51693D22AFADCC20089FB8F /* SwiftGen+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51693D02AFADCC20089FB8F /* SwiftGen+Strings.swift */; }; + E51693D32AFAE3670089FB8F /* SwiftGen+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51693D02AFADCC20089FB8F /* SwiftGen+Strings.swift */; }; + E51693D52AFBDB8A0089FB8F /* NavigationBar+Appearence..swift in Sources */ = {isa = PBXBuildFile; fileRef = E51693D42AFBDB8A0089FB8F /* NavigationBar+Appearence..swift */; }; + E51693D62AFBDB8A0089FB8F /* NavigationBar+Appearence..swift in Sources */ = {isa = PBXBuildFile; fileRef = E51693D42AFBDB8A0089FB8F /* NavigationBar+Appearence..swift */; }; + E51693D82AFBE5680089FB8F /* CAGradientLayer+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51693D72AFBE5680089FB8F /* CAGradientLayer+Image.swift */; }; + E51693D92AFBE5680089FB8F /* CAGradientLayer+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51693D72AFBE5680089FB8F /* CAGradientLayer+Image.swift */; }; + E5217F392AEA7B0E00123442 /* Macros+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F382AEA7B0E00123442 /* Macros+UI.swift */; }; + E5217F3A2AEA7B0E00123442 /* Macros+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F382AEA7B0E00123442 /* Macros+UI.swift */; }; + E5217F3C2AEA886000123442 /* BorderedTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F3B2AEA886000123442 /* BorderedTextField.swift */; }; + E5217F402AEAC7A900123442 /* Theme+LightPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F3E2AEAC7A900123442 /* Theme+LightPalette.swift */; }; + E5217F412AEAC7A900123442 /* Theme+LightPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F3E2AEAC7A900123442 /* Theme+LightPalette.swift */; }; + E5217F422AEAC7A900123442 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F3F2AEAC7A900123442 /* Theme.swift */; }; + E5217F432AEAC7A900123442 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F3F2AEAC7A900123442 /* Theme.swift */; }; + E5217F452AEACE0200123442 /* NavigationLogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F442AEACE0200123442 /* NavigationLogoView.swift */; }; + E5217F462AEACE0200123442 /* NavigationLogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F442AEACE0200123442 /* NavigationLogoView.swift */; }; + E5217F482AEAD28000123442 /* UIViewLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F472AEAD28000123442 /* UIViewLoading.swift */; }; + E5217F492AEAD28000123442 /* UIViewLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F472AEAD28000123442 /* UIViewLoading.swift */; }; + E5217F4B2AEAD29F00123442 /* PurchasePlanCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F4A2AEAD29F00123442 /* PurchasePlanCell.swift */; }; + E5217F4C2AEAD29F00123442 /* PurchasePlanCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F4A2AEAD29F00123442 /* PurchasePlanCell.swift */; }; + E5217F542AEAD54600123442 /* WalkthroughPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F532AEAD54600123442 /* WalkthroughPageView.swift */; }; + E5217F552AEAD54600123442 /* WalkthroughPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F532AEAD54600123442 /* WalkthroughPageView.swift */; }; + E5217F5D2AEAE01400123442 /* NotificationCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F5C2AEAE01400123442 /* NotificationCategory.swift */; }; + E5217F5E2AEAE01400123442 /* NotificationCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F5C2AEAE01400123442 /* NotificationCategory.swift */; }; + E5217F662AEAF94C00123442 /* SwiftGen+Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F652AEAF94C00123442 /* SwiftGen+Assets.swift */; }; + E5217F672AEAF94C00123442 /* SwiftGen+Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F652AEAF94C00123442 /* SwiftGen+Assets.swift */; }; + E5217F6F2AEB042800123442 /* ClientError+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F6E2AEB042800123442 /* ClientError+Localization.swift */; }; + E5217F702AEB042800123442 /* ClientError+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F6E2AEB042800123442 /* ClientError+Localization.swift */; }; + E5217F722AEB055D00123442 /* Client+Storyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F712AEB055D00123442 /* Client+Storyboard.swift */; }; + E5217F732AEB055D00123442 /* Client+Storyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F712AEB055D00123442 /* Client+Storyboard.swift */; }; + E59E8F942AEA7A29009278F5 /* ActivityButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F932AEA7A29009278F5 /* ActivityButton.swift */; }; + E59E8F952AEA7A29009278F5 /* ActivityButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F932AEA7A29009278F5 /* ActivityButton.swift */; }; + E59E8F972AEA7A5A009278F5 /* AutolayoutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F962AEA7A5A009278F5 /* AutolayoutViewController.swift */; }; + E59E8FA92AEA7A81009278F5 /* SignupInternetUnreachableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F992AEA7A7F009278F5 /* SignupInternetUnreachableViewController.swift */; }; + E59E8FAA2AEA7A81009278F5 /* SignupInternetUnreachableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F992AEA7A7F009278F5 /* SignupInternetUnreachableViewController.swift */; }; + E59E8FAB2AEA7A81009278F5 /* SignupSuccessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F9A2AEA7A7F009278F5 /* SignupSuccessViewController.swift */; }; + E59E8FAC2AEA7A81009278F5 /* SignupSuccessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F9A2AEA7A7F009278F5 /* SignupSuccessViewController.swift */; }; + E59E8FAD2AEA7A81009278F5 /* SignupInProgressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F9B2AEA7A7F009278F5 /* SignupInProgressViewController.swift */; }; + E59E8FAE2AEA7A81009278F5 /* SignupInProgressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F9B2AEA7A7F009278F5 /* SignupInProgressViewController.swift */; }; + E59E8FAF2AEA7A81009278F5 /* MagicLinkLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F9C2AEA7A80009278F5 /* MagicLinkLoginViewController.swift */; }; + E59E8FB02AEA7A81009278F5 /* MagicLinkLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F9C2AEA7A80009278F5 /* MagicLinkLoginViewController.swift */; }; + E59E8FB12AEA7A81009278F5 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F9D2AEA7A80009278F5 /* LoginViewController.swift */; }; + E59E8FB22AEA7A81009278F5 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F9D2AEA7A80009278F5 /* LoginViewController.swift */; }; + E59E8FB32AEA7A81009278F5 /* PIAWelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F9E2AEA7A80009278F5 /* PIAWelcomeViewController.swift */; }; + E59E8FB42AEA7A81009278F5 /* PIAWelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F9E2AEA7A80009278F5 /* PIAWelcomeViewController.swift */; }; + E59E8FB52AEA7A81009278F5 /* GetStartedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F9F2AEA7A80009278F5 /* GetStartedViewController.swift */; }; + E59E8FB62AEA7A81009278F5 /* GetStartedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F9F2AEA7A80009278F5 /* GetStartedViewController.swift */; }; + E59E8FB72AEA7A81009278F5 /* ConfirmVPNPlanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA02AEA7A80009278F5 /* ConfirmVPNPlanViewController.swift */; }; + E59E8FB82AEA7A81009278F5 /* ConfirmVPNPlanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA02AEA7A80009278F5 /* ConfirmVPNPlanViewController.swift */; }; + E59E8FB92AEA7A81009278F5 /* RestoreSignupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA12AEA7A80009278F5 /* RestoreSignupViewController.swift */; }; + E59E8FBA2AEA7A81009278F5 /* RestoreSignupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA12AEA7A80009278F5 /* RestoreSignupViewController.swift */; }; + E59E8FBB2AEA7A81009278F5 /* GDPRViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA22AEA7A80009278F5 /* GDPRViewController.swift */; }; + E59E8FBC2AEA7A81009278F5 /* GDPRViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA22AEA7A80009278F5 /* GDPRViewController.swift */; }; + E59E8FBD2AEA7A81009278F5 /* SignupFailureViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA32AEA7A80009278F5 /* SignupFailureViewController.swift */; }; + E59E8FBE2AEA7A81009278F5 /* SignupFailureViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA32AEA7A80009278F5 /* SignupFailureViewController.swift */; }; + E59E8FBF2AEA7A81009278F5 /* WelcomePageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA42AEA7A80009278F5 /* WelcomePageViewController.swift */; }; + E59E8FC02AEA7A81009278F5 /* WelcomePageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA42AEA7A80009278F5 /* WelcomePageViewController.swift */; }; + E59E8FC12AEA7A81009278F5 /* ShareDataInformationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA52AEA7A81009278F5 /* ShareDataInformationViewController.swift */; }; + E59E8FC22AEA7A81009278F5 /* ShareDataInformationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA52AEA7A81009278F5 /* ShareDataInformationViewController.swift */; }; + E59E8FC42AEA7A81009278F5 /* PurchaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA62AEA7A81009278F5 /* PurchaseViewController.swift */; }; + E59E8FC52AEA7A81009278F5 /* TermsAndConditionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA72AEA7A81009278F5 /* TermsAndConditionsViewController.swift */; }; + E59E8FC62AEA7A81009278F5 /* TermsAndConditionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA72AEA7A81009278F5 /* TermsAndConditionsViewController.swift */; }; + E59E8FC72AEA7A81009278F5 /* OptionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA82AEA7A81009278F5 /* OptionsViewController.swift */; }; + E59E8FC82AEA7A81009278F5 /* OptionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA82AEA7A81009278F5 /* OptionsViewController.swift */; }; + E5C507682B0D609300200A6A /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = E5C507672B0D609300200A6A /* PIALibrary */; }; + E5C5076A2B0D60A500200A6A /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = E5C507692B0D60A500200A6A /* PIALibrary */; }; + E5C5076C2B0D60AD00200A6A /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = E5C5076B2B0D60AD00200A6A /* PIALibrary */; }; + E5C5076E2B0D60B300200A6A /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = E5C5076D2B0D60B300200A6A /* PIALibrary */; }; + E5C5076F2B0D61B800200A6A /* PurchaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8FA62AEA7A81009278F5 /* PurchaseViewController.swift */; }; + E5C507702B0DEC2400200A6A /* AutolayoutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E59E8F962AEA7A5A009278F5 /* AutolayoutViewController.swift */; }; + E5C507712B0DEC3F00200A6A /* BorderedTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5217F3B2AEA886000123442 /* BorderedTextField.swift */; }; + E5C507722B0DEC7400200A6A /* UIDevice+WIFI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51693C52AFAD3550089FB8F /* UIDevice+WIFI.swift */; }; E5F52A182A8A5D5300828883 /* WifiNetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5F52A172A8A5D5300828883 /* WifiNetworkMonitor.swift */; }; E5F52A192A8A5D5300828883 /* WifiNetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5F52A172A8A5D5300828883 /* WifiNetworkMonitor.swift */; }; E5F52A1B2A8A5E1E00828883 /* IPv4Address.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5F52A1A2A8A5E1E00828883 /* IPv4Address.swift */; }; @@ -582,7 +661,6 @@ 0E0715E5201CBB7100D6F666 /* Flags-dev.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Flags-dev.plist"; sourceTree = ""; }; 0E0786DD1EFA7EAE00F77466 /* Components.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Components.plist; sourceTree = ""; }; 0E1F318520176A5F00FC1000 /* Theme+DarkPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Theme+DarkPalette.swift"; sourceTree = ""; }; - 0E2215C820084CD700F5FB4D /* SwiftGen+Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+Strings.swift"; sourceTree = ""; }; 0E2215CB2008BF8300F5FB4D /* SwiftGen+Assets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+Assets.swift"; sourceTree = ""; }; 0E257AC41DA45D2F0000D3C3 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; 0E325DA62093277F0020BEDB /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Main.storyboard; sourceTree = ""; }; @@ -871,6 +949,97 @@ DDFCFA8E21E892070081F235 /* RegionTileCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RegionTileCollectionViewCell.xib; sourceTree = ""; }; DDFCFA9321E892130081F235 /* RegionTile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionTile.swift; sourceTree = ""; }; DDFCFA9621E8921F0081F235 /* RegionTile.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RegionTile.xib; sourceTree = ""; }; + E501CBB22AE97EBA00515006 /* Signup.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Signup.storyboard; sourceTree = ""; }; + E501CBB52AE97EC800515006 /* Welcome.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Welcome.storyboard; sourceTree = ""; }; + E501CBBB2AE9806800515006 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/UI.strings; sourceTree = ""; }; + E501CBBC2AE9806800515006 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/UI.strings; sourceTree = ""; }; + E501CBBD2AE9806800515006 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/UI.strings"; sourceTree = ""; }; + E501CBBE2AE9806800515006 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/UI.strings; sourceTree = ""; }; + E501CBBF2AE9806800515006 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/UI.strings; sourceTree = ""; }; + E501CBC02AE9806800515006 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/UI.strings; sourceTree = ""; }; + E501CBC12AE9806800515006 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/UI.strings; sourceTree = ""; }; + E501CBC22AE9806800515006 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/UI.strings; sourceTree = ""; }; + E501CBC32AE9806800515006 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/UI.strings; sourceTree = ""; }; + E501CBC42AE9806800515006 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/UI.strings"; sourceTree = ""; }; + E501CBC52AE9806800515006 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/UI.strings; sourceTree = ""; }; + E501CBC62AE9806800515006 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/UI.strings; sourceTree = ""; }; + E501CBC72AE9806800515006 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/UI.strings"; sourceTree = ""; }; + E501CBC82AE9806800515006 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/UI.strings"; sourceTree = ""; }; + E501CBC92AE9806800515006 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/UI.strings; sourceTree = ""; }; + E501CBCA2AE9806800515006 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/UI.strings; sourceTree = ""; }; + E501CBCB2AE9806800515006 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/UI.strings; sourceTree = ""; }; + E501CBCC2AE9806800515006 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/UI.strings; sourceTree = ""; }; + E501CBCF2AE9806800515006 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Signup.strings; sourceTree = ""; }; + E501CBD12AE9806800515006 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Welcome.strings; sourceTree = ""; }; + E501CBD22AE9806800515006 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Signup.strings; sourceTree = ""; }; + E501CBD32AE9806800515006 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Welcome.strings; sourceTree = ""; }; + E501CBD42AE9806800515006 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Signup.strings"; sourceTree = ""; }; + E501CBD52AE9806800515006 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Welcome.strings"; sourceTree = ""; }; + E501CBD62AE9806800515006 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Signup.strings; sourceTree = ""; }; + E501CBD72AE9806800515006 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Welcome.strings; sourceTree = ""; }; + E501CBD82AE9806800515006 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Signup.strings; sourceTree = ""; }; + E501CBD92AE9806800515006 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Welcome.strings; sourceTree = ""; }; + E501CBDA2AE9806800515006 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Signup.strings; sourceTree = ""; }; + E501CBDB2AE9806800515006 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Welcome.strings; sourceTree = ""; }; + E501CBDC2AE9806800515006 /* UI.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = UI.xcassets; sourceTree = ""; }; + E501CBDD2AE9806800515006 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Signup.strings; sourceTree = ""; }; + E501CBDE2AE9806800515006 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Welcome.strings; sourceTree = ""; }; + E501CBDF2AE9806800515006 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Signup.strings; sourceTree = ""; }; + E501CBE02AE9806800515006 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Welcome.strings; sourceTree = ""; }; + E501CBE12AE9806800515006 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Signup.strings; sourceTree = ""; }; + E501CBE22AE9806800515006 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Welcome.strings; sourceTree = ""; }; + E501CBE32AE9806800515006 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Signup.strings"; sourceTree = ""; }; + E501CBE42AE9806800515006 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Welcome.strings"; sourceTree = ""; }; + E501CBE52AE9806800515006 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Signup.strings; sourceTree = ""; }; + E501CBE62AE9806800515006 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Welcome.strings; sourceTree = ""; }; + E501CBE72AE9806800515006 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Signup.strings; sourceTree = ""; }; + E501CBE82AE9806800515006 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Welcome.strings; sourceTree = ""; }; + E501CBE92AE9806800515006 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Signup.strings"; sourceTree = ""; }; + E501CBEA2AE9806800515006 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Welcome.strings"; sourceTree = ""; }; + E501CBEB2AE9806800515006 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/Signup.strings"; sourceTree = ""; }; + E501CBEC2AE9806800515006 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/Welcome.strings"; sourceTree = ""; }; + E501CBED2AE9806800515006 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Signup.strings; sourceTree = ""; }; + E501CBEE2AE9806800515006 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Welcome.strings; sourceTree = ""; }; + E501CBEF2AE9806800515006 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Signup.strings; sourceTree = ""; }; + E501CBF02AE9806800515006 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Welcome.strings; sourceTree = ""; }; + E501CBF12AE9806800515006 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Signup.strings; sourceTree = ""; }; + E501CBF22AE9806800515006 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Welcome.strings; sourceTree = ""; }; + E501CBF32AE9806800515006 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Signup.strings; sourceTree = ""; }; + E501CBF42AE9806800515006 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Welcome.strings; sourceTree = ""; }; + E51693C52AFAD3550089FB8F /* UIDevice+WIFI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+WIFI.swift"; sourceTree = ""; }; + E51693D02AFADCC20089FB8F /* SwiftGen+Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+Strings.swift"; sourceTree = ""; }; + E51693D42AFBDB8A0089FB8F /* NavigationBar+Appearence..swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationBar+Appearence..swift"; sourceTree = ""; }; + E51693D72AFBE5680089FB8F /* CAGradientLayer+Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CAGradientLayer+Image.swift"; sourceTree = ""; }; + E5217F382AEA7B0E00123442 /* Macros+UI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Macros+UI.swift"; sourceTree = ""; }; + E5217F3B2AEA886000123442 /* BorderedTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BorderedTextField.swift; sourceTree = ""; }; + E5217F3E2AEAC7A900123442 /* Theme+LightPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Theme+LightPalette.swift"; sourceTree = ""; }; + E5217F3F2AEAC7A900123442 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; + E5217F442AEACE0200123442 /* NavigationLogoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationLogoView.swift; sourceTree = ""; }; + E5217F472AEAD28000123442 /* UIViewLoading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewLoading.swift; sourceTree = ""; }; + E5217F4A2AEAD29F00123442 /* PurchasePlanCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchasePlanCell.swift; sourceTree = ""; }; + E5217F532AEAD54600123442 /* WalkthroughPageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalkthroughPageView.swift; sourceTree = ""; }; + E5217F5C2AEAE01400123442 /* NotificationCategory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationCategory.swift; sourceTree = ""; }; + E5217F652AEAF94C00123442 /* SwiftGen+Assets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+Assets.swift"; sourceTree = ""; }; + E5217F6E2AEB042800123442 /* ClientError+Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ClientError+Localization.swift"; sourceTree = ""; }; + E5217F712AEB055D00123442 /* Client+Storyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Client+Storyboard.swift"; sourceTree = ""; }; + E59E8F932AEA7A29009278F5 /* ActivityButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityButton.swift; sourceTree = ""; }; + E59E8F962AEA7A5A009278F5 /* AutolayoutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutolayoutViewController.swift; sourceTree = ""; }; + E59E8F992AEA7A7F009278F5 /* SignupInternetUnreachableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignupInternetUnreachableViewController.swift; sourceTree = ""; }; + E59E8F9A2AEA7A7F009278F5 /* SignupSuccessViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignupSuccessViewController.swift; sourceTree = ""; }; + E59E8F9B2AEA7A7F009278F5 /* SignupInProgressViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignupInProgressViewController.swift; sourceTree = ""; }; + E59E8F9C2AEA7A80009278F5 /* MagicLinkLoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MagicLinkLoginViewController.swift; sourceTree = ""; }; + E59E8F9D2AEA7A80009278F5 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; + E59E8F9E2AEA7A80009278F5 /* PIAWelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PIAWelcomeViewController.swift; sourceTree = ""; }; + E59E8F9F2AEA7A80009278F5 /* GetStartedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetStartedViewController.swift; sourceTree = ""; }; + E59E8FA02AEA7A80009278F5 /* ConfirmVPNPlanViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfirmVPNPlanViewController.swift; sourceTree = ""; }; + E59E8FA12AEA7A80009278F5 /* RestoreSignupViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestoreSignupViewController.swift; sourceTree = ""; }; + E59E8FA22AEA7A80009278F5 /* GDPRViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GDPRViewController.swift; sourceTree = ""; }; + E59E8FA32AEA7A80009278F5 /* SignupFailureViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignupFailureViewController.swift; sourceTree = ""; }; + E59E8FA42AEA7A80009278F5 /* WelcomePageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomePageViewController.swift; sourceTree = ""; }; + E59E8FA52AEA7A81009278F5 /* ShareDataInformationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareDataInformationViewController.swift; sourceTree = ""; }; + E59E8FA62AEA7A81009278F5 /* PurchaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseViewController.swift; sourceTree = ""; }; + E59E8FA72AEA7A81009278F5 /* TermsAndConditionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TermsAndConditionsViewController.swift; sourceTree = ""; }; + E59E8FA82AEA7A81009278F5 /* OptionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionsViewController.swift; sourceTree = ""; }; E5F52A172A8A5D5300828883 /* WifiNetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiNetworkMonitor.swift; sourceTree = ""; }; E5F52A1A2A8A5E1E00828883 /* IPv4Address.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPv4Address.swift; sourceTree = ""; }; E5F52A1E2A8A614900828883 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = ""; }; @@ -881,7 +1050,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 697C59B02AFD0791008B6212 /* PIALibrary in Frameworks */, + E5C5076C2B0D60AD00200A6A /* PIALibrary in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -901,7 +1070,7 @@ 0EE2205C1F4EF307002805AE /* CoreGraphics.framework in Frameworks */, AABF826F28AD187800CDAC64 /* Popover in Frameworks */, 0EE2205F1F4EF307002805AE /* StoreKit.framework in Frameworks */, - 697C59AE2AFD0786008B6212 /* PIALibrary in Frameworks */, + E5C5076A2B0D60A500200A6A /* PIALibrary in Frameworks */, 0EE2205D1F4EF307002805AE /* UIKit.framework in Frameworks */, 0EE2205E1F4EF307002805AE /* Foundation.framework in Frameworks */, AABF826D28AD185E00CDAC64 /* TweetNacl in Frameworks */, @@ -939,7 +1108,7 @@ 2985E5671856BD1200D70E28 /* QuartzCore.framework in Frameworks */, 291C6382183EBC210039EC03 /* CoreGraphics.framework in Frameworks */, 291C6384183EBC210039EC03 /* UIKit.framework in Frameworks */, - 697C59AC2AFD0771008B6212 /* PIALibrary in Frameworks */, + E5C507682B0D609300200A6A /* PIALibrary in Frameworks */, DD606ABA21C7A17900E0781D /* NetworkExtension.framework in Frameworks */, AA36CDC928A6733500180A33 /* DZNEmptyDataSet in Frameworks */, 291C6380183EBC210039EC03 /* Foundation.framework in Frameworks */, @@ -969,7 +1138,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 697C59B22AFD079B008B6212 /* PIALibrary in Frameworks */, + E5C5076E2B0D60B300200A6A /* PIALibrary in Frameworks */, AA36CDDB28A6860F00180A33 /* TweetNacl in Frameworks */, DDC8F5F923EC10E4005D19C6 /* NetworkExtension.framework in Frameworks */, ); @@ -1104,6 +1273,7 @@ DDD65312247E66AD00F0A897 /* CoordinatesFinder.swift */, 829EB51B2535A8FF003E74DD /* CollectionViewAutoSizeUtils.swift */, 3524670A26B431B800E3F0AC /* TrustedNetworkHelper.swift */, + E51693C52AFAD3550089FB8F /* UIDevice+WIFI.swift */, ); name = Utils; sourceTree = ""; @@ -1155,6 +1325,7 @@ 0EEE1C191E4F719E00397DE2 /* Resources */ = { isa = PBXGroup; children = ( + E501CBB82AE9806800515006 /* Resources */, DD51F8B02372E494009FEED3 /* PublicCerts */, DD4368A3224A73E4005D373B /* UI */, DD6DC5C021B6C27F00F9D538 /* Lottie */, @@ -1274,13 +1445,15 @@ 296B7BB21A1840CB005606AD /* Shared */ = { isa = PBXGroup; children = ( + E5217F6E2AEB042800123442 /* ClientError+Localization.swift */, 0E9452981FDB4DF500891948 /* GradientView.swift */, 0EE14D171FF15812008D9AC2 /* ModalNavigationSegue.swift */, 0E9452AD1FDB5F7A00891948 /* PIAPageControl.swift */, 0E2215CB2008BF8300F5FB4D /* SwiftGen+Assets.swift */, + E5217F652AEAF94C00123442 /* SwiftGen+Assets.swift */, DDC8124B2176185000CB290C /* SwiftGen+SeguesStoryboards.swift */, DDC8124E21761B0B00CB290C /* SwiftGen+ScenesStoryboards.swift */, - 0E2215C820084CD700F5FB4D /* SwiftGen+Strings.swift */, + E51693D02AFADCC20089FB8F /* SwiftGen+Strings.swift */, DD58F4BE21B12CFE00D043F7 /* PIAConnectionButton.swift */, DD125DD021E7A694004ECCB6 /* ServerButton.swift */, 69B70ABD2ACC2CFE0072A09D /* AccessibilityId.swift */, @@ -1291,11 +1464,41 @@ 296BBFEE1840066A00944151 /* UI */ = { isa = PBXGroup; children = ( + E5217F5C2AEAE01400123442 /* NotificationCategory.swift */, + E5217F532AEAD54600123442 /* WalkthroughPageView.swift */, + E5217F4A2AEAD29F00123442 /* PurchasePlanCell.swift */, + E5217F472AEAD28000123442 /* UIViewLoading.swift */, + E5217F442AEACE0200123442 /* NavigationLogoView.swift */, + E5217F3F2AEAC7A900123442 /* Theme.swift */, + E5217F3E2AEAC7A900123442 /* Theme+LightPalette.swift */, + E5217F382AEA7B0E00123442 /* Macros+UI.swift */, + E59E8FA02AEA7A80009278F5 /* ConfirmVPNPlanViewController.swift */, + E59E8FA22AEA7A80009278F5 /* GDPRViewController.swift */, + E59E8F9F2AEA7A80009278F5 /* GetStartedViewController.swift */, + E59E8F9D2AEA7A80009278F5 /* LoginViewController.swift */, + E59E8F9C2AEA7A80009278F5 /* MagicLinkLoginViewController.swift */, + E59E8FA82AEA7A81009278F5 /* OptionsViewController.swift */, + E59E8F9E2AEA7A80009278F5 /* PIAWelcomeViewController.swift */, + E59E8FA62AEA7A81009278F5 /* PurchaseViewController.swift */, + E59E8FA12AEA7A80009278F5 /* RestoreSignupViewController.swift */, + E59E8FA52AEA7A81009278F5 /* ShareDataInformationViewController.swift */, + E59E8FA32AEA7A80009278F5 /* SignupFailureViewController.swift */, + E59E8F9B2AEA7A7F009278F5 /* SignupInProgressViewController.swift */, + E59E8F992AEA7A7F009278F5 /* SignupInternetUnreachableViewController.swift */, + E59E8F9A2AEA7A7F009278F5 /* SignupSuccessViewController.swift */, + E59E8FA72AEA7A81009278F5 /* TermsAndConditionsViewController.swift */, + E59E8FA42AEA7A80009278F5 /* WelcomePageViewController.swift */, + E59E8F962AEA7A5A009278F5 /* AutolayoutViewController.swift */, + E59E8F932AEA7A29009278F5 /* ActivityButton.swift */, + E5217F3B2AEA886000123442 /* BorderedTextField.swift */, 82F41374264E8AAD0098FF4B /* Settings */, 82A1AD2424B86ADB0003DD02 /* Cards */, 0E1CFCBD1EBB9F040073155D /* Dashboard */, 0E1CFCBE1EBB9F860073155D /* Menu */, 296B7BB21A1840CB005606AD /* Shared */, + E5217F712AEB055D00123442 /* Client+Storyboard.swift */, + E51693D42AFBDB8A0089FB8F /* NavigationBar+Appearence..swift */, + E51693D72AFBE5680089FB8F /* CAGradientLayer+Image.swift */, ); name = UI; sourceTree = ""; @@ -1568,6 +1771,8 @@ DD4368A4224A73F1005D373B /* Core */, 0ECB081F1D61D2A900043852 /* Launch Screen.storyboard */, 291C6391183EBC210039EC03 /* Main.storyboard */, + E501CBB22AE97EBA00515006 /* Signup.storyboard */, + E501CBB52AE97EC800515006 /* Welcome.storyboard */, ); path = UI; sourceTree = ""; @@ -1659,6 +1864,33 @@ path = "PIA VPN WG Tunnel"; sourceTree = ""; }; + E501CBB82AE9806800515006 /* Resources */ = { + isa = PBXGroup; + children = ( + E501CBB92AE9806800515006 /* Shared */, + E501CBCD2AE9806800515006 /* iOS */, + ); + path = Resources; + sourceTree = ""; + }; + E501CBB92AE9806800515006 /* Shared */ = { + isa = PBXGroup; + children = ( + E501CBBA2AE9806800515006 /* UI.strings */, + ); + path = Shared; + sourceTree = ""; + }; + E501CBCD2AE9806800515006 /* iOS */ = { + isa = PBXGroup; + children = ( + E501CBCE2AE9806800515006 /* Signup.strings */, + E501CBD02AE9806800515006 /* Welcome.strings */, + E501CBDC2AE9806800515006 /* UI.xcassets */, + ); + path = iOS; + sourceTree = ""; + }; E5F52A162A8A5D3800828883 /* Wifi */ = { isa = PBXGroup; children = ( @@ -1694,7 +1926,7 @@ ); name = "PIA VPN Tunnel"; packageProductDependencies = ( - 697C59AF2AFD0791008B6212 /* PIALibrary */, + E5C5076B2B0D60AD00200A6A /* PIALibrary */, ); productName = "PIA OpenVPN"; productReference = 0E67FC221E3F802D00EF9929 /* PIA VPN Tunnel.appex */; @@ -1728,7 +1960,7 @@ AABF827228AE2FF500CDAC64 /* GradientProgressBar */, AABF827428AE2FFE00CDAC64 /* SideMenu */, AABF827728AE333200CDAC64 /* DZNEmptyDataSet */, - 697C59AD2AFD0786008B6212 /* PIALibrary */, + E5C507692B0D60A500200A6A /* PIALibrary */, ); productName = "PIA VPN"; productReference = 0EE2207A1F4EF307002805AE /* PIA VPN dev.app */; @@ -1796,7 +2028,7 @@ AA36CDCB28A673C900180A33 /* GradientProgressBar */, AA36CDCE28A6746500180A33 /* SideMenu */, AA36CDDD28A6878000180A33 /* AlamofireImage */, - 697C59AB2AFD0770008B6212 /* PIALibrary */, + E5C507672B0D609300200A6A /* PIALibrary */, ); productName = "PIA VPN"; productReference = 291C637C183EBC210039EC03 /* PIA VPN.app */; @@ -1856,7 +2088,7 @@ name = "PIA VPN WG Tunnel"; packageProductDependencies = ( AA36CDDA28A6860F00180A33 /* TweetNacl */, - 697C59B12AFD079B008B6212 /* PIALibrary */, + E5C5076D2B0D60B300200A6A /* PIALibrary */, ); productName = "PIA VPN WG Tunnel"; productReference = DDC8F5EC23EC106F005D19C6 /* PIA VPN WG Tunnel.appex */; @@ -1990,7 +2222,7 @@ AA36CDDC28A6878000180A33 /* XCRemoteSwiftPackageReference "AlamofireImage" */, 35950B2F2ADD2877006F3CD9 /* XCRemoteSwiftPackageReference "Quick" */, 35950B322ADD2EE5006F3CD9 /* XCRemoteSwiftPackageReference "Nimble" */, - 697C59AA2AFD0770008B6212 /* XCRemoteSwiftPackageReference "mobile-ios-library" */, + E5C507662B0D609300200A6A /* XCRemoteSwiftPackageReference "mobile-ios-library" */, ); productRefGroup = 291C637D183EBC210039EC03 /* Products */; projectDirPath = ""; @@ -2023,16 +2255,20 @@ 82A1AD2A24B87CCD0003DD02 /* PIACard.xib in Resources */, 82183D9925014FDC0033023F /* CustomNetworkCollectionViewCell.xib in Resources */, DD172AA02254C39300071CFB /* FavoriteServersTile.xib in Resources */, + E501CBFA2AE9806800515006 /* Welcome.strings in Resources */, 0ED66BD020A9918000333B35 /* staging.endpoint in Resources */, DD76292021ECCD510092DF50 /* UsageTileCollectionViewCell.xib in Resources */, + E501CBB72AE97EC800515006 /* Welcome.storyboard in Resources */, DD6DC5C321B6C27F00F9D538 /* pia-spinner.json in Resources */, 0E0715E7201CBB7100D6F666 /* Flags-dev.plist in Resources */, DD1C138A21E60C63004004B3 /* IPTile.xib in Resources */, 82C9F3CF25C43863005039F9 /* ActiveDedicatedIpHeaderViewCell.xib in Resources */, + E501CBF62AE9806800515006 /* UI.strings in Resources */, 82CAB8F5255C0CD100BB08EF /* MessagesTileCollectionViewCell.xib in Resources */, DD76291421ECBD8B0092DF50 /* SubscriptionTileCollectionViewCell.xib in Resources */, DD51F8BA2372E494009FEED3 /* PIA-RSA-4096.pem in Resources */, DD746958217F070700B7BD73 /* DNS.plist in Resources */, + E501CBFC2AE9806800515006 /* UI.xcassets in Resources */, 0E7EC046209326E30029811E /* Localizable.strings in Resources */, DD76291A21ECBDA50092DF50 /* SubscriptionTile.xib in Resources */, 82183D9725014FDC0033023F /* NetworkCollectionViewCell.xib in Resources */, @@ -2040,6 +2276,7 @@ 82CAB8FE255C115100BB08EF /* MessagesTile.xib in Resources */, DD76292621ECCD650092DF50 /* UsageTile.xib in Resources */, DDFCFA9221E892070081F235 /* RegionTileCollectionViewCell.xib in Resources */, + E501CBF82AE9806800515006 /* Signup.strings in Resources */, DDFCFA9821E8921F0081F235 /* RegionTile.xib in Resources */, 824C531E24C04800003DB740 /* ConnectionTile.xib in Resources */, 0EE220631F4EF307002805AE /* Launch Screen.storyboard in Resources */, @@ -2058,6 +2295,7 @@ 829EB51425359DBB003E74DD /* DedicatedIpRowViewCell.xib in Resources */, 0ED984221FC48C6500542EE6 /* Roboto-Thin.ttf in Resources */, 824C531824C04796003DB740 /* ConnectionTileCollectionViewCell.xib in Resources */, + E501CBB42AE97EBA00515006 /* Signup.storyboard in Resources */, 829EB54D2535BA40003E74DD /* DedicatedIPTitleHeaderViewCell.xib in Resources */, DD9706AE224262E000630220 /* QuickSettingsTileCollectionViewCell.xib in Resources */, DD125DC921E7704F004ECCB6 /* QuickConnectTile.xib in Resources */, @@ -2103,8 +2341,10 @@ 82A1AD2924B87CCD0003DD02 /* PIACard.xib in Resources */, 82183D9A25014FDC0033023F /* NetworkFooterCollectionViewCell.xib in Resources */, 0E7EC045209326E30029811E /* Localizable.strings in Resources */, + E501CBF92AE9806800515006 /* Welcome.strings in Resources */, 0ED9841D1FC48C6000542EE6 /* Roboto-Regular.ttf in Resources */, DD76291F21ECCD510092DF50 /* UsageTileCollectionViewCell.xib in Resources */, + E501CBFB2AE9806800515006 /* UI.xcassets in Resources */, 82C9F3CE25C43863005039F9 /* ActiveDedicatedIpHeaderViewCell.xib in Resources */, DD76292521ECCD650092DF50 /* UsageTile.xib in Resources */, DD172A9C2254C36D00071CFB /* FavoriteServersTileCollectionViewCell.xib in Resources */, @@ -2113,18 +2353,22 @@ 291C6398183EBC210039EC03 /* Images.xcassets in Resources */, 82183D9625014FDC0033023F /* NetworkCollectionViewCell.xib in Resources */, 82183D9825014FDC0033023F /* CustomNetworkCollectionViewCell.xib in Resources */, + E501CBF72AE9806800515006 /* Signup.strings in Resources */, 0E0786DE1EFA7EAE00F77466 /* Components.plist in Resources */, + E501CBB32AE97EBA00515006 /* Signup.storyboard in Resources */, DDFCFA9121E892070081F235 /* RegionTileCollectionViewCell.xib in Resources */, 0ED9841C1FC48C6000542EE6 /* Roboto-Light.ttf in Resources */, 0ED9841E1FC48C6000542EE6 /* Roboto-Thin.ttf in Resources */, 0E7EC0302093265C0029811E /* InfoPlist.strings in Resources */, 291C6393183EBC210039EC03 /* Main.storyboard in Resources */, + E501CBF52AE9806800515006 /* UI.strings in Resources */, 829EB54C2535BA40003E74DD /* DedicatedIPTitleHeaderViewCell.xib in Resources */, 824C531D24C04800003DB740 /* ConnectionTile.xib in Resources */, DD6DC5C221B6C27F00F9D538 /* pia-spinner.json in Resources */, DD746957217F070700B7BD73 /* DNS.plist in Resources */, DD1C139B21E65F90004004B3 /* IPTileCollectionViewCell.xib in Resources */, 829EB5342535AD27003E74DD /* DedicatedIpEmptyHeaderViewCell.xib in Resources */, + E501CBB62AE97EC800515006 /* Welcome.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2217,10 +2461,12 @@ 8272C6342657EE4E00D846A8 /* AutomationSettingsViewController.swift in Sources */, 3524670F26B4332E00E3F0AC /* DashboardViewController+ServerSelection.swift in Sources */, 0E9452A01FDB547D00891948 /* ExpirationCell.swift in Sources */, + E59E8FB82AEA7A81009278F5 /* ConfirmVPNPlanViewController.swift in Sources */, E5F52A202A8A614900828883 /* NetworkMonitor.swift in Sources */, 0EFDC1F01FE4B9E6007C0B9B /* AppConfiguration.swift in Sources */, DD76292321ECCD5C0092DF50 /* UsageTile.swift in Sources */, DDD65314247E66AD00F0A897 /* CoordinatesFinder.swift in Sources */, + E5217F4C2AEAD29F00123442 /* PurchasePlanCell.swift in Sources */, DDD271DF21D616AA00B6D20F /* Server+Favorite.swift in Sources */, 698F4F312ABA1DA10010B2B0 /* PIAConnectionLiveActivityManager.swift in Sources */, DD125DC621E77046004ECCB6 /* QuickConnectTile.swift in Sources */, @@ -2237,24 +2483,35 @@ DD9706AC224262E000630220 /* QuickSettingsTileCollectionViewCell.swift in Sources */, 82CAB87B255AEA3500BB08EF /* MessagesManager.swift in Sources */, 0E1F318720176A6300FC1000 /* Theme+DarkPalette.swift in Sources */, + E59E8FC62AEA7A81009278F5 /* TermsAndConditionsViewController.swift in Sources */, 8272C6312657D42700D846A8 /* PrivacyFeaturesSettingsViewController.swift in Sources */, 0ECC1E3F1FDB3F2F0039891D /* RegionsViewController.swift in Sources */, E5F52A192A8A5D5300828883 /* WifiNetworkMonitor.swift in Sources */, + E59E8FB62AEA7A81009278F5 /* GetStartedViewController.swift in Sources */, 8272C62B26551F9B00D846A8 /* SettingPopoverSelectionView.swift in Sources */, 0EB9ED1B1FDA1C4D00D1214D /* SettingsViewController.swift in Sources */, + E59E8FC82AEA7A81009278F5 /* OptionsViewController.swift in Sources */, 0EB966751FDF0D6E0086ABC2 /* ServerProvider+UI.swift in Sources */, 0ECF5C092017EBAD0047596C /* ThemeCode.swift in Sources */, DD76291221ECBD8B0092DF50 /* SubscriptionTileCollectionViewCell.swift in Sources */, + E5217F3A2AEA7B0E00123442 /* Macros+UI.swift in Sources */, DDF7F756240D35BA00A671C7 /* CustomServerSettingsViewController.swift in Sources */, + E59E8FAC2AEA7A81009278F5 /* SignupSuccessViewController.swift in Sources */, 3545E98426AADC7E00B812CC /* ServerSelectionDelegate.swift in Sources */, + E59E8FB42AEA7A81009278F5 /* PIAWelcomeViewController.swift in Sources */, 0E392DA71FE3283C0002160D /* TransientState.swift in Sources */, + E59E8FBE2AEA7A81009278F5 /* SignupFailureViewController.swift in Sources */, 8272C6372657F23400D846A8 /* HelpSettingsViewController.swift in Sources */, DD3B504724B758330002F4B5 /* Collectable.swift in Sources */, 0E3A35361FD9EBDA000B0F99 /* AppDelegate.swift in Sources */, 3545E98326AADC7C00B812CC /* ServerSelectingTileCell.swift in Sources */, + E59E8FC22AEA7A81009278F5 /* ShareDataInformationViewController.swift in Sources */, 824C531624C04796003DB740 /* ConnectionTileCollectionViewCell.swift in Sources */, + E51693D62AFBDB8A0089FB8F /* NavigationBar+Appearence..swift in Sources */, DDC8124D2176185D00CB290C /* SwiftGen+SeguesStoryboards.swift in Sources */, + E5217F552AEAD54600123442 /* WalkthroughPageView.swift in Sources */, DDD271E221D6262100B6D20F /* PropertyStoring.swift in Sources */, + E59E8FB22AEA7A81009278F5 /* LoginViewController.swift in Sources */, DD829EE02449A1EF00E2EE55 /* SiriShortcutsBuilder.swift in Sources */, 0EFB839120209CF200980F69 /* VPNPermissionViewController.swift in Sources */, 0EFDC1D81FE46177007C0B9B /* SensitiveOperation.swift in Sources */, @@ -2270,13 +2527,16 @@ DD829EE32449A4A400E2EE55 /* SiriShortcutsManager.swift in Sources */, DD58F4C021B12CFE00D043F7 /* PIAConnectionButton.swift in Sources */, 827570DE24DABE88008F9800 /* AddCustomNetworksViewController.swift in Sources */, + E5217F732AEB055D00123442 /* Client+Storyboard.swift in Sources */, DD125DD221E7A694004ECCB6 /* ServerButton.swift in Sources */, 8272C62826540B2100D846A8 /* ProtocolSettingsViewController.swift in Sources */, + E59E8FAA2AEA7A81009278F5 /* SignupInternetUnreachableViewController.swift in Sources */, 824C530924C046CB003DB740 /* ConnectionTile.swift in Sources */, 827570D224DAA128008F9800 /* PIAHeaderCollectionViewCell.swift in Sources */, 82F41377264E8AC20098FF4B /* SettingOptions.swift in Sources */, + E5C507722B0DEC7400200A6A /* UIDevice+WIFI.swift in Sources */, + E59E8FBC2AEA7A81009278F5 /* GDPRViewController.swift in Sources */, 8272C63A2657F54400D846A8 /* DevelopmentSettingsViewController.swift in Sources */, - 0E2215CA2008BA9100F5FB4D /* SwiftGen+Strings.swift in Sources */, DD9329A3237AFD0A0025B6BC /* ShowQuickSettingsViewController.swift in Sources */, DD606ABD21C904BB00E0781D /* PIAHotspotHelper.swift in Sources */, 827570D924DAB6E4008F9800 /* NetworkFooterCollectionViewCell.swift in Sources */, @@ -2286,12 +2546,15 @@ DD125DCD21E772B6004ECCB6 /* QuickConnectTileCollectionViewCell.swift in Sources */, 0E8DCA06204D94E800B086DE /* ContentBlockerViewController.swift in Sources */, DD172A9B2254C36D00071CFB /* FavoriteServersTileCollectionViewCell.swift in Sources */, + E5217F5E2AEAE01400123442 /* NotificationCategory.swift in Sources */, DD76291721ECBD9C0092DF50 /* SubscriptionTile.swift in Sources */, DD51F8C52372E4C8009FEED3 /* PemUtil.swift in Sources */, + E59E8FC42AEA7A81009278F5 /* PurchaseViewController.swift in Sources */, 0EA660091FEC7A9500CB2B0D /* PIATunnelProvider+UI.swift in Sources */, DD3B504A24B75A1E0002F4B5 /* CardFactory.swift in Sources */, DD522D23237D74380072F555 /* BooleanUtil.swift in Sources */, 0E7361EC1FD99A1000706BFF /* MenuViewController.swift in Sources */, + E59E8FAE2AEA7A81009278F5 /* SignupInProgressViewController.swift in Sources */, 0EB966781FDF11B80086ABC2 /* Server+UI.swift in Sources */, DD1C139A21E65F90004004B3 /* IPTileCollectionViewCell.swift in Sources */, 829EB5332535AD27003E74DD /* DedicatedIpEmptyHeaderViewCell.swift in Sources */, @@ -2304,18 +2567,22 @@ DDFCFA9021E892070081F235 /* RegionTileCollectionViewCell.swift in Sources */, 6924831B2AB045A5002A0407 /* PIAWidgetAttributes.swift in Sources */, DD9706A5224262BF00630220 /* QuickSettingsTile.swift in Sources */, + E5217F672AEAF94C00123442 /* SwiftGen+Assets.swift in Sources */, 82CAB8E9255C0CB000BB08EF /* MessagesTile.swift in Sources */, DD3B504424B7576F0002F4B5 /* Card.swift in Sources */, 82183D7C2500FD460033023F /* String+Substrings.swift in Sources */, 0E7361A11FD86F8300706BFF /* AccountObserver.swift in Sources */, 829EB54B2535BA40003E74DD /* DedicatedIPTitleHeaderViewCell.swift in Sources */, + E5C507712B0DEC3F00200A6A /* BorderedTextField.swift in Sources */, 829EB51225359DBB003E74DD /* DedicatedIpRowViewCell.swift in Sources */, DDFCFA9521E892130081F235 /* RegionTile.swift in Sources */, 0EFDC1E71FE4ABAA007C0B9B /* Notification+App.swift in Sources */, E5F52A1C2A8A5E1E00828883 /* IPv4Address.swift in Sources */, 827570E224DAC2C6008F9800 /* CustomNetworkCollectionViewCell.swift in Sources */, DD918577246BFA7F006B3A2B /* RatingManager.swift in Sources */, + E5217F492AEAD28000123442 /* UIViewLoading.swift in Sources */, 82C0071425231F2800F21AF2 /* String+VPNType.swift in Sources */, + E5217F412AEAC7A900123442 /* Theme+LightPalette.swift in Sources */, DD1C138721E60BAE004004B3 /* IPTile.swift in Sources */, 0E9452971FDB4C5800891948 /* AboutComponentCell.swift in Sources */, 3524670C26B432BC00E3F0AC /* TrustedNetworkHelper.swift in Sources */, @@ -2328,11 +2595,21 @@ 0E492C681FE60907007F23DF /* Flags.swift in Sources */, DD172A972254C35000071CFB /* FavoriteServersTile.swift in Sources */, 0EE14D191FF15812008D9AC2 /* ModalNavigationSegue.swift in Sources */, + E59E8FBA2AEA7A81009278F5 /* RestoreSignupViewController.swift in Sources */, + E5217F432AEAC7A900123442 /* Theme.swift in Sources */, 826BE8EF253861BE002339F3 /* DedicatedRegionCell.swift in Sources */, + E51693D22AFADCC20089FB8F /* SwiftGen+Strings.swift in Sources */, + E51693D92AFBE5680089FB8F /* CAGradientLayer+Image.swift in Sources */, 82B7374C26527E330097FFF6 /* GeneralSettingsViewController.swift in Sources */, + E59E8F952AEA7A29009278F5 /* ActivityButton.swift in Sources */, + E59E8FC02AEA7A81009278F5 /* WelcomePageViewController.swift in Sources */, + E5C507702B0DEC2400200A6A /* AutolayoutViewController.swift in Sources */, 82CAB8F3255C0CD100BB08EF /* MessagesTileCollectionViewCell.swift in Sources */, + E5217F462AEACE0200123442 /* NavigationLogoView.swift in Sources */, + E59E8FB02AEA7A81009278F5 /* MagicLinkLoginViewController.swift in Sources */, 0E3A352D1FD9CDC5000B0F99 /* Theme+App.swift in Sources */, DD07AACD242CBFF3000EE1A3 /* AddEmailToAccountViewController.swift in Sources */, + E5217F702AEB042800123442 /* ClientError+Localization.swift in Sources */, DD1C139F21E6623D004004B3 /* XIBSetup.swift in Sources */, DDB6B95421C95CD400DE8C5F /* EnumsBuilder.swift in Sources */, 0E9AEA6320683FDF00B6E59A /* AboutComponent.swift in Sources */, @@ -2382,23 +2659,35 @@ 0EFDC1EF1FE4B9E6007C0B9B /* AppConfiguration.swift in Sources */, 0E9452A21FDB568700891948 /* MenuItemCell.swift in Sources */, 829EB51125359DBB003E74DD /* DedicatedIpRowViewCell.swift in Sources */, + E59E8FBF2AEA7A81009278F5 /* WelcomePageViewController.swift in Sources */, 0EFDC1DA1FE4640C007C0B9B /* DNSResolver.swift in Sources */, 0E9452991FDB4DF500891948 /* GradientView.swift in Sources */, + E5C5076F2B0D61B800200A6A /* PurchaseViewController.swift in Sources */, 0E7361E81FD98C3400706BFF /* AccountViewController.swift in Sources */, DD1C13A421E6743F004004B3 /* TileFlowLayout.swift in Sources */, DD76291D21ECCD510092DF50 /* UsageTileCollectionViewCell.swift in Sources */, + E59E8FC52AEA7A81009278F5 /* TermsAndConditionsViewController.swift in Sources */, + E59E8FB52AEA7A81009278F5 /* GetStartedViewController.swift in Sources */, + E59E8F972AEA7A5A009278F5 /* AutolayoutViewController.swift in Sources */, + E5217F722AEB055D00123442 /* Client+Storyboard.swift in Sources */, DD9706AB224262E000630220 /* QuickSettingsTileCollectionViewCell.swift in Sources */, 0EE14D151FF15626008D9AC2 /* UINavigationController+StatusBar.swift in Sources */, 0E1F318620176A5F00FC1000 /* Theme+DarkPalette.swift in Sources */, + E5217F3C2AEA886000123442 /* BorderedTextField.swift in Sources */, 0ECC1E3E1FDB3F2F0039891D /* RegionsViewController.swift in Sources */, 8272C6302657D42700D846A8 /* PrivacyFeaturesSettingsViewController.swift in Sources */, + E5217F482AEAD28000123442 /* UIViewLoading.swift in Sources */, 0EB9ED1A1FDA1C4D00D1214D /* SettingsViewController.swift in Sources */, 0EB966741FDF0D6E0086ABC2 /* ServerProvider+UI.swift in Sources */, DD76291121ECBD8B0092DF50 /* SubscriptionTileCollectionViewCell.swift in Sources */, 8272C6332657EE4E00D846A8 /* AutomationSettingsViewController.swift in Sources */, + E59E8FBB2AEA7A81009278F5 /* GDPRViewController.swift in Sources */, DDF7F755240D35BA00A671C7 /* CustomServerSettingsViewController.swift in Sources */, 0ECF5C082017EBAD0047596C /* ThemeCode.swift in Sources */, 3545E98026AAD60C00B812CC /* ServerSelectionDelegate.swift in Sources */, + E5217F402AEAC7A900123442 /* Theme+LightPalette.swift in Sources */, + E5217F392AEA7B0E00123442 /* Macros+UI.swift in Sources */, + E59E8FA92AEA7A81009278F5 /* SignupInternetUnreachableViewController.swift in Sources */, 0E392DA61FE3283C0002160D /* TransientState.swift in Sources */, 6924831A2AB045A5002A0407 /* PIAWidgetAttributes.swift in Sources */, 82F41376264E8AC20098FF4B /* SettingOptions.swift in Sources */, @@ -2407,6 +2696,7 @@ DD3B504924B75A1E0002F4B5 /* CardFactory.swift in Sources */, DD829EDF2449A1EF00E2EE55 /* SiriShortcutsBuilder.swift in Sources */, DDC8124C2176185000CB290C /* SwiftGen+SeguesStoryboards.swift in Sources */, + E51693D82AFBE5680089FB8F /* CAGradientLayer+Image.swift in Sources */, DDD271E121D6262100B6D20F /* PropertyStoring.swift in Sources */, 82A1AD2F24B87CF20003DD02 /* PIACard.swift in Sources */, 8272C6392657F54400D846A8 /* DevelopmentSettingsViewController.swift in Sources */, @@ -2414,28 +2704,35 @@ 7ECBB8DD27B6C4F500C0C774 /* UserSurveyManager.swift in Sources */, 821674F12678A1BE0028E4FD /* SettingsDelegate.swift in Sources */, 0EFDC1D71FE46177007C0B9B /* SensitiveOperation.swift in Sources */, + E59E8FAB2AEA7A81009278F5 /* SignupSuccessViewController.swift in Sources */, 82C0071325231F2800F21AF2 /* String+VPNType.swift in Sources */, 826BE8EE253861BE002339F3 /* DedicatedRegionCell.swift in Sources */, 0E3A35281FD9A960000B0F99 /* DashboardViewController.swift in Sources */, DDB6B95021C94E2E00DE8C5F /* TrustedNetworksViewController.swift in Sources */, 82C9F3CC25C43863005039F9 /* ActiveDedicatedIpHeaderViewCell.swift in Sources */, + E59E8FAF2AEA7A81009278F5 /* MagicLinkLoginViewController.swift in Sources */, 8276DFE62608B63800BB7B40 /* ShowConnectionStatsViewController.swift in Sources */, 827570D424DAA317008F9800 /* NetworkRuleOptionView.swift in Sources */, 0E9452A51FDB578400891948 /* RegionCell.swift in Sources */, DD829EE22449A4A400E2EE55 /* SiriShortcutsManager.swift in Sources */, + E51693D12AFADCC20089FB8F /* SwiftGen+Strings.swift in Sources */, 82BAACFA25B09C9200B3C733 /* PIAWidget.intentdefinition in Sources */, 0EFDC1EC1FE4B9DC007C0B9B /* AppConstants.swift in Sources */, 827570DD24DABE88008F9800 /* AddCustomNetworksViewController.swift in Sources */, 8272C62A26551F9B00D846A8 /* SettingPopoverSelectionView.swift in Sources */, + E59E8FB72AEA7A81009278F5 /* ConfirmVPNPlanViewController.swift in Sources */, + E59E8FC12AEA7A81009278F5 /* ShareDataInformationViewController.swift in Sources */, DD58F4BF21B12CFE00D043F7 /* PIAConnectionButton.swift in Sources */, 824C530824C046CB003DB740 /* ConnectionTile.swift in Sources */, 827570D124DAA128008F9800 /* PIAHeaderCollectionViewCell.swift in Sources */, + E51693C62AFAD3550089FB8F /* UIDevice+WIFI.swift in Sources */, DD125DD121E7A694004ECCB6 /* ServerButton.swift in Sources */, DD9329A2237AFD0A0025B6BC /* ShowQuickSettingsViewController.swift in Sources */, 69B70ABE2ACC2CFE0072A09D /* AccessibilityId.swift in Sources */, - 0E2215C920084CD700F5FB4D /* SwiftGen+Strings.swift in Sources */, 827570D824DAB6E4008F9800 /* NetworkFooterCollectionViewCell.swift in Sources */, + E5217F6F2AEB042800123442 /* ClientError+Localization.swift in Sources */, E5F52A1F2A8A614900828883 /* NetworkMonitor.swift in Sources */, + E59E8FB12AEA7A81009278F5 /* LoginViewController.swift in Sources */, DD3B504324B7576F0002F4B5 /* Card.swift in Sources */, 82CAB8F2255C0CD100BB08EF /* MessagesTileCollectionViewCell.swift in Sources */, DD606ABC21C904BB00E0781D /* PIAHotspotHelper.swift in Sources */, @@ -2464,26 +2761,33 @@ 0E7361A01FD86F8300706BFF /* AccountObserver.swift in Sources */, 0EFDC1E61FE4ABAA007C0B9B /* Notification+App.swift in Sources */, 827570E124DAC2C6008F9800 /* CustomNetworkCollectionViewCell.swift in Sources */, + E5217F452AEACE0200123442 /* NavigationLogoView.swift in Sources */, 3524670E26B432ED00E3F0AC /* DashboardViewController+ServerSelection.swift in Sources */, + E59E8FC72AEA7A81009278F5 /* OptionsViewController.swift in Sources */, DD918576246BFA7F006B3A2B /* RatingManager.swift in Sources */, DD1C138621E60BAE004004B3 /* IPTile.swift in Sources */, 0E9452961FDB4C5800891948 /* AboutComponentCell.swift in Sources */, 0E53A83B1FE5A156000C2A18 /* AccountProvider+Refresh.swift in Sources */, 82B7374B26527E330097FFF6 /* GeneralSettingsViewController.swift in Sources */, 0EFDC1E01FE4A450007C0B9B /* AppPreferences.swift in Sources */, + E51693D52AFBDB8A0089FB8F /* NavigationBar+Appearence..swift in Sources */, 3524670B26B431B800E3F0AC /* TrustedNetworkHelper.swift in Sources */, 829EB54A2535BA40003E74DD /* DedicatedIPTitleHeaderViewCell.swift in Sources */, 82B7374F265280180097FFF6 /* PIABaseSettingsViewController.swift in Sources */, DDC8124F21761B0B00CB290C /* SwiftGen+ScenesStoryboards.swift in Sources */, 0E492C671FE60907007F23DF /* Flags.swift in Sources */, 0EE14D181FF15812008D9AC2 /* ModalNavigationSegue.swift in Sources */, + E5217F5D2AEAE01400123442 /* NotificationCategory.swift in Sources */, + E5217F422AEAC7A900123442 /* Theme.swift in Sources */, DD172A962254C35000071CFB /* FavoriteServersTile.swift in Sources */, + E59E8FB32AEA7A81009278F5 /* PIAWelcomeViewController.swift in Sources */, 0E3A352C1FD9CDC5000B0F99 /* Theme+App.swift in Sources */, 82CAB8E8255C0CB000BB08EF /* MessagesTile.swift in Sources */, 8272C6362657F23400D846A8 /* HelpSettingsViewController.swift in Sources */, DD1C139E21E6623D004004B3 /* XIBSetup.swift in Sources */, DD07AACC242CBFF2000EE1A3 /* AddEmailToAccountViewController.swift in Sources */, 8272C62726540B2100D846A8 /* ProtocolSettingsViewController.swift in Sources */, + E59E8F942AEA7A29009278F5 /* ActivityButton.swift in Sources */, 82CAB8B0255B050000BB08EF /* MessagesCommands.swift in Sources */, DDE432DF2498EADB0095B197 /* Array+Group.swift in Sources */, 698F4F302ABA1DA10010B2B0 /* PIAConnectionLiveActivityManager.swift in Sources */, @@ -2492,12 +2796,18 @@ 829EB5322535AD27003E74DD /* DedicatedIpEmptyHeaderViewCell.swift in Sources */, 829EB51C2535A8FF003E74DD /* CollectionViewAutoSizeUtils.swift in Sources */, 0E9AEA6220683FDF00B6E59A /* AboutComponent.swift in Sources */, + E59E8FB92AEA7A81009278F5 /* RestoreSignupViewController.swift in Sources */, + E59E8FAD2AEA7A81009278F5 /* SignupInProgressViewController.swift in Sources */, + E5217F4B2AEAD29F00123442 /* PurchasePlanCell.swift in Sources */, DD4E84572243BD1200929B39 /* DashboardCollectionViewUtil.swift in Sources */, 829EB507253598BD003E74DD /* DedicatedIpViewController.swift in Sources */, DD74695A217F07AC00B7BD73 /* DNSList.swift in Sources */, + E59E8FBD2AEA7A81009278F5 /* SignupFailureViewController.swift in Sources */, E5F52A1B2A8A5E1E00828883 /* IPv4Address.swift in Sources */, + E5217F542AEAD54600123442 /* WalkthroughPageView.swift in Sources */, 0E9452AE1FDB5F7A00891948 /* PIAPageControl.swift in Sources */, DDD271F021D6718F00B6D20F /* RegionFilter.swift in Sources */, + E5217F662AEAF94C00123442 /* SwiftGen+Assets.swift in Sources */, 82CAB87A255AEA3500BB08EF /* MessagesManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2536,8 +2846,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E51693D32AFAE3670089FB8F /* SwiftGen+Strings.swift in Sources */, 8269A6FE251CBB36000B4DBF /* WidgetInformation.swift in Sources */, - 695BF81F2AC410E000D1139C /* SwiftGen+Strings.swift in Sources */, + 695BF81F2AC410E000D1139C /* (null) in Sources */, 698F4F2D2AB978BF0010B2B0 /* PIACircleImageView.swift in Sources */, 822F97B4251DD53100644EF2 /* WidgetUserDefaultsDatasource.swift in Sources */, 8269A6DA251CB5E0000B4DBF /* PIAWidget.swift in Sources */, @@ -2694,6 +3005,81 @@ name = PIAWidget.intentdefinition; sourceTree = ""; }; + E501CBBA2AE9806800515006 /* UI.strings */ = { + isa = PBXVariantGroup; + children = ( + E501CBBB2AE9806800515006 /* de */, + E501CBBC2AE9806800515006 /* ar */, + E501CBBD2AE9806800515006 /* zh-Hans */, + E501CBBE2AE9806800515006 /* ja */, + E501CBBF2AE9806800515006 /* en */, + E501CBC02AE9806800515006 /* nb */, + E501CBC12AE9806800515006 /* da */, + E501CBC22AE9806800515006 /* it */, + E501CBC32AE9806800515006 /* ko */, + E501CBC42AE9806800515006 /* zh-Hant */, + E501CBC52AE9806800515006 /* tr */, + E501CBC62AE9806800515006 /* pl */, + E501CBC72AE9806800515006 /* pt-BR */, + E501CBC82AE9806800515006 /* es-MX */, + E501CBC92AE9806800515006 /* ru */, + E501CBCA2AE9806800515006 /* fr */, + E501CBCB2AE9806800515006 /* nl */, + E501CBCC2AE9806800515006 /* th */, + ); + name = UI.strings; + sourceTree = ""; + }; + E501CBCE2AE9806800515006 /* Signup.strings */ = { + isa = PBXVariantGroup; + children = ( + E501CBCF2AE9806800515006 /* de */, + E501CBD22AE9806800515006 /* ar */, + E501CBD42AE9806800515006 /* zh-Hans */, + E501CBD62AE9806800515006 /* ja */, + E501CBD82AE9806800515006 /* en */, + E501CBDA2AE9806800515006 /* nb */, + E501CBDD2AE9806800515006 /* da */, + E501CBDF2AE9806800515006 /* it */, + E501CBE12AE9806800515006 /* ko */, + E501CBE32AE9806800515006 /* zh-Hant */, + E501CBE52AE9806800515006 /* tr */, + E501CBE72AE9806800515006 /* pl */, + E501CBE92AE9806800515006 /* pt-BR */, + E501CBEB2AE9806800515006 /* es-MX */, + E501CBED2AE9806800515006 /* ru */, + E501CBEF2AE9806800515006 /* fr */, + E501CBF12AE9806800515006 /* nl */, + E501CBF32AE9806800515006 /* th */, + ); + name = Signup.strings; + sourceTree = ""; + }; + E501CBD02AE9806800515006 /* Welcome.strings */ = { + isa = PBXVariantGroup; + children = ( + E501CBD12AE9806800515006 /* de */, + E501CBD32AE9806800515006 /* ar */, + E501CBD52AE9806800515006 /* zh-Hans */, + E501CBD72AE9806800515006 /* ja */, + E501CBD92AE9806800515006 /* en */, + E501CBDB2AE9806800515006 /* nb */, + E501CBDE2AE9806800515006 /* da */, + E501CBE02AE9806800515006 /* it */, + E501CBE22AE9806800515006 /* ko */, + E501CBE42AE9806800515006 /* zh-Hant */, + E501CBE62AE9806800515006 /* tr */, + E501CBE82AE9806800515006 /* pl */, + E501CBEA2AE9806800515006 /* pt-BR */, + E501CBEC2AE9806800515006 /* es-MX */, + E501CBEE2AE9806800515006 /* ru */, + E501CBF02AE9806800515006 /* fr */, + E501CBF22AE9806800515006 /* nl */, + E501CBF42AE9806800515006 /* th */, + ); + name = Welcome.strings; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ @@ -3508,14 +3894,6 @@ version = 13.0.0; }; }; - 697C59AA2AFD0770008B6212 /* XCRemoteSwiftPackageReference "mobile-ios-library" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "git@github.com:pia-foss/mobile-ios-library.git"; - requirement = { - branch = master; - kind = branch; - }; - }; AA36CDB928A6622A00180A33 /* XCRemoteSwiftPackageReference "tweetnacl-swiftwrap" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/bitmark-inc/tweetnacl-swiftwrap.git"; @@ -3564,6 +3942,14 @@ version = 4.0.0; }; }; + E5C507662B0D609300200A6A /* XCRemoteSwiftPackageReference "mobile-ios-library" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pia-foss/mobile-ios-library.git"; + requirement = { + branch = master; + kind = branch; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -3577,26 +3963,6 @@ package = 35950B322ADD2EE5006F3CD9 /* XCRemoteSwiftPackageReference "Nimble" */; productName = Nimble; }; - 697C59AB2AFD0770008B6212 /* PIALibrary */ = { - isa = XCSwiftPackageProductDependency; - package = 697C59AA2AFD0770008B6212 /* XCRemoteSwiftPackageReference "mobile-ios-library" */; - productName = PIALibrary; - }; - 697C59AD2AFD0786008B6212 /* PIALibrary */ = { - isa = XCSwiftPackageProductDependency; - package = 697C59AA2AFD0770008B6212 /* XCRemoteSwiftPackageReference "mobile-ios-library" */; - productName = PIALibrary; - }; - 697C59AF2AFD0791008B6212 /* PIALibrary */ = { - isa = XCSwiftPackageProductDependency; - package = 697C59AA2AFD0770008B6212 /* XCRemoteSwiftPackageReference "mobile-ios-library" */; - productName = PIALibrary; - }; - 697C59B12AFD079B008B6212 /* PIALibrary */ = { - isa = XCSwiftPackageProductDependency; - package = 697C59AA2AFD0770008B6212 /* XCRemoteSwiftPackageReference "mobile-ios-library" */; - productName = PIALibrary; - }; AA36CDBA28A6622A00180A33 /* TweetNacl */ = { isa = XCSwiftPackageProductDependency; package = AA36CDB928A6622A00180A33 /* XCRemoteSwiftPackageReference "tweetnacl-swiftwrap" */; @@ -3662,6 +4028,26 @@ package = AA36CDC728A6733500180A33 /* XCRemoteSwiftPackageReference "DZNEmptyDataSet" */; productName = DZNEmptyDataSet; }; + E5C507672B0D609300200A6A /* PIALibrary */ = { + isa = XCSwiftPackageProductDependency; + package = E5C507662B0D609300200A6A /* XCRemoteSwiftPackageReference "mobile-ios-library" */; + productName = PIALibrary; + }; + E5C507692B0D60A500200A6A /* PIALibrary */ = { + isa = XCSwiftPackageProductDependency; + package = E5C507662B0D609300200A6A /* XCRemoteSwiftPackageReference "mobile-ios-library" */; + productName = PIALibrary; + }; + E5C5076B2B0D60AD00200A6A /* PIALibrary */ = { + isa = XCSwiftPackageProductDependency; + package = E5C507662B0D609300200A6A /* XCRemoteSwiftPackageReference "mobile-ios-library" */; + productName = PIALibrary; + }; + E5C5076D2B0D60B300200A6A /* PIALibrary */ = { + isa = XCSwiftPackageProductDependency; + package = E5C507662B0D609300200A6A /* XCRemoteSwiftPackageReference "mobile-ios-library" */; + productName = PIALibrary; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 291C6374183EBC210039EC03 /* Project object */; diff --git a/PIA VPN/AboutComponentCell.swift b/PIA VPN/AboutComponentCell.swift index 21526099c..da839e510 100644 --- a/PIA VPN/AboutComponentCell.swift +++ b/PIA VPN/AboutComponentCell.swift @@ -117,17 +117,17 @@ class AboutLicenseCell: UITableViewCell, Restylable { var moreImage: UIImage? if isExpanded { - moreImage = Asset.buttonUp.image + moreImage = Asset.Images.buttonUp.image textLicense.isScrollEnabled = true } else { - moreImage = Asset.buttonDown.image + moreImage = Asset.Images.buttonDown.image textLicense.isScrollEnabled = false } buttonMore.setImage(moreImage, for: .normal) buttonName.accessibilityTraits = UIAccessibilityTraits.none buttonName.accessibilityLabel = component.name - buttonName.accessibilityHint = L10n.About.Accessibility.Component.expand + buttonName.accessibilityHint = L10n.Localizable.About.Accessibility.Component.expand } // MARK: Actions diff --git a/PIA VPN/AboutViewController.swift b/PIA VPN/AboutViewController.swift index af6dc9f82..14bf0a086 100644 --- a/PIA VPN/AboutViewController.swift +++ b/PIA VPN/AboutViewController.swift @@ -50,7 +50,7 @@ class AboutViewController: AutolayoutViewController { override func viewDidLoad() { super.viewDidLoad() - let textApp = L10n.About.app + let textApp = L10n.Localizable.About.app labelIntro.text = "Copyright © \(AppConfiguration.About.copyright) \(AppConfiguration.About.companyName)\n\(textApp) \(Macros.versionFullString()!)" tableView.scrollsToTop = true @@ -67,7 +67,7 @@ class AboutViewController: AutolayoutViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - styleNavigationBarWithTitle(L10n.Menu.Item.about) + styleNavigationBarWithTitle(L10n.Localizable.Menu.Item.about) } override func viewDidLayoutSubviews() { @@ -92,7 +92,7 @@ class AboutViewController: AutolayoutViewController { // MARK: Helpers @objc private func viewHasRotated() { - styleNavigationBarWithTitle(L10n.Menu.Item.settings) + styleNavigationBarWithTitle(L10n.Localizable.Menu.Item.settings) } private func loadLicensesInBackground() { @@ -122,7 +122,7 @@ class AboutViewController: AutolayoutViewController { tableView.separatorInset = UIEdgeInsets(top: 0, left: 30, bottom: 0, right: 0) Theme.current.applyDividerToSeparator(tableView) - styleNavigationBarWithTitle(L10n.Menu.Item.about) + styleNavigationBarWithTitle(L10n.Localizable.Menu.Item.about) // XXX: for some reason, UITableView is not affected by appearance updates if let viewContainer = viewContainer { Theme.current.applyPrincipalBackground(view) diff --git a/PIA VPN/AccountObserver.swift b/PIA VPN/AccountObserver.swift index 2588e36ef..0d98dd095 100644 --- a/PIA VPN/AccountObserver.swift +++ b/PIA VPN/AccountObserver.swift @@ -84,8 +84,8 @@ class AccountObserver { let note = UNMutableNotificationContent() - note.title = L10n.Expiration.title - note.body = L10n.Expiration.message + note.title = L10n.Localizable.Expiration.title + note.body = L10n.Localizable.Expiration.message note.userInfo = ["date": date] note.sound = .default note.badge = 1 diff --git a/PIA VPN/AccountViewController.swift b/PIA VPN/AccountViewController.swift index 8a181340a..8df114102 100644 --- a/PIA VPN/AccountViewController.swift +++ b/PIA VPN/AccountViewController.swift @@ -70,15 +70,15 @@ class AccountViewController: AutolayoutViewController { override func viewDidLoad() { super.viewDidLoad() - title = L10n.Menu.Item.account - labelUsername.text = L10n.Account.Username.caption - labelRestoreTitle.text = L10n.Account.Restore.title - labelRestoreInfo.text = L10n.Account.Restore.description + title = L10n.Localizable.Menu.Item.account + labelUsername.text = L10n.Localizable.Account.Username.caption + labelRestoreTitle.text = L10n.Localizable.Account.Restore.title + labelRestoreInfo.text = L10n.Localizable.Account.Restore.description imageViewTrash.image = Theme.current.trashIconImage() - buttonRestore.setTitle(L10n.Account.Restore.button.uppercased(), for: .normal) + buttonRestore.setTitle(L10n.Localizable.Account.Restore.button.uppercased(), for: .normal) labelSubscriptions.attributedText = Theme.current.textWithColoredLink( - withMessage: L10n.Account.Subscriptions.message, - link: L10n.Account.Subscriptions.linkMessage) + withMessage: L10n.Localizable.Account.Subscriptions.message, + link: L10n.Localizable.Account.Subscriptions.linkMessage) labelSubscriptions.isUserInteractionEnabled = true viewSafe.layoutMargins = .zero @@ -98,7 +98,7 @@ class AccountViewController: AutolayoutViewController { super.viewWillAppear(animated) // update local state immediately - styleNavigationBarWithTitle(L10n.Menu.Item.account) + styleNavigationBarWithTitle(L10n.Localizable.Menu.Item.account) redisplayAccount() } @@ -114,7 +114,7 @@ class AccountViewController: AutolayoutViewController { } @objc private func viewHasRotated() { - styleNavigationBarWithTitle(L10n.Menu.Item.settings) + styleNavigationBarWithTitle(L10n.Localizable.Menu.Item.settings) } // MARK: Actions @@ -131,11 +131,11 @@ class AccountViewController: AutolayoutViewController { @IBAction private func deleteUserAccount(_ sender: Any?) { let sheet = Macros.alert( - L10n.Account.Delete.Alert.title, - L10n.Account.Delete.Alert.message + L10n.Localizable.Account.Delete.Alert.title, + L10n.Localizable.Account.Delete.Alert.message ) - sheet.addCancelAction(L10n.Global.no) - sheet.addDestructiveActionWithTitle(L10n.Global.yes) { + sheet.addCancelAction(L10n.Localizable.Global.no) + sheet.addDestructiveActionWithTitle(L10n.Localizable.Global.yes) { self.showLoadingAnimation() log.debug("Account: Deleting...") @@ -154,8 +154,8 @@ class AccountViewController: AutolayoutViewController { } } else { self.hideLoadingAnimation() - let sheet = Macros.alert(nil, L10n.Account.Delete.Alert.failureMessage) - sheet.addCancelAction(L10n.Global.ok) + let sheet = Macros.alert(nil, L10n.Localizable.Account.Delete.Alert.failureMessage) + sheet.addCancelAction(L10n.Localizable.Global.ok) self.present(sheet, animated: true, completion: nil) log.debug("Account: Deleting failed...") } @@ -180,15 +180,15 @@ class AccountViewController: AutolayoutViewController { private func handleReceiptFailureWithError(_ error: Error?) { log.error("IAP: Failed to restore payment receipt (error: \(error?.localizedDescription ?? ""))") - let alert = Macros.alert(L10n.Global.error, error?.localizedDescription) - alert.addDefaultAction(L10n.Global.close) + let alert = Macros.alert(L10n.Localizable.Global.error, error?.localizedDescription) + alert.addDefaultAction(L10n.Localizable.Global.close) present(alert, animated: true, completion: nil) } private func handleReceiptSubmissionWithError(_ error: Error?) { if let error = error { - let alert = Macros.alert(L10n.Global.error, error.localizedDescription) - alert.addDefaultAction(L10n.Global.close) + let alert = Macros.alert(L10n.Localizable.Global.error, error.localizedDescription) + alert.addDefaultAction(L10n.Localizable.Global.close) present(alert, animated: true, completion: nil) return } @@ -196,10 +196,10 @@ class AccountViewController: AutolayoutViewController { log.debug("Account: Renewal successfully completed") let alert = Macros.alert( - L10n.Renewal.Success.title, - L10n.Renewal.Success.message + L10n.Localizable.Renewal.Success.title, + L10n.Localizable.Renewal.Success.message ) - alert.addDefaultAction(L10n.Global.close) + alert.addDefaultAction(L10n.Localizable.Global.close) present(alert, animated: true, completion: nil) redisplayAccount() @@ -207,10 +207,10 @@ class AccountViewController: AutolayoutViewController { private func handleBadReceipt() { let alert = Macros.alert( - L10n.Account.Restore.Failure.title, - L10n.Account.Restore.Failure.message + L10n.Localizable.Account.Restore.Failure.title, + L10n.Localizable.Account.Restore.Failure.message ) - alert.addDefaultAction(L10n.Global.close) + alert.addDefaultAction(L10n.Localizable.Global.close) present(alert, animated: true, completion: nil) } @@ -231,9 +231,9 @@ class AccountViewController: AutolayoutViewController { if let userInfo = currentUser?.info { if userInfo.isExpired { - labelExpiryInformation.text = L10n.Account.ExpiryDate.expired + labelExpiryInformation.text = L10n.Localizable.Account.ExpiryDate.expired } else { - labelExpiryInformation.text = L10n.Account.ExpiryDate.information(userInfo.humanReadableExpirationDate()) + labelExpiryInformation.text = L10n.Localizable.Account.ExpiryDate.information(userInfo.humanReadableExpirationDate()) } styleExpirationDate() @@ -264,7 +264,7 @@ class AccountViewController: AutolayoutViewController { self.viewAccountInfo.layer.cornerRadius = 5.0 - styleNavigationBarWithTitle(L10n.Menu.Item.account) + styleNavigationBarWithTitle(L10n.Localizable.Menu.Item.account) if let viewContainer = viewContainer { Theme.current.applyPrincipalBackground(view) @@ -279,7 +279,7 @@ class AccountViewController: AutolayoutViewController { for label in [labelExpiryInformation!] { Theme.current.applySubtitle(label) } - Theme.current.applyUnderline(labelDeleteAccount, with: L10n.Account.delete) + Theme.current.applyUnderline(labelDeleteAccount, with: L10n.Localizable.Account.delete) Theme.current.applyTitle(labelRestoreTitle, appearance: .dark) Theme.current.applySubtitle(labelRestoreInfo) buttonRestore.style(style: TextStyle.textStyle9) diff --git a/PIA VPN/ActiveDedicatedIpHeaderViewCell.swift b/PIA VPN/ActiveDedicatedIpHeaderViewCell.swift index e00d5c51e..98eba246e 100644 --- a/PIA VPN/ActiveDedicatedIpHeaderViewCell.swift +++ b/PIA VPN/ActiveDedicatedIpHeaderViewCell.swift @@ -30,8 +30,8 @@ class ActiveDedicatedIpHeaderViewCell: UITableViewCell { override func awakeFromNib() { super.awakeFromNib() self.backgroundColor = .clear - self.title.text = L10n.Dedicated.Ip.title - self.subtitle.text = L10n.Dedicated.Ip.Limit.title + self.title.text = L10n.Localizable.Dedicated.Ip.title + self.subtitle.text = L10n.Localizable.Dedicated.Ip.Limit.title } func setup() { diff --git a/PIA VPN/ActivityButton.swift b/PIA VPN/ActivityButton.swift new file mode 100644 index 000000000..781d41658 --- /dev/null +++ b/PIA VPN/ActivityButton.swift @@ -0,0 +1,161 @@ +// +// ActivityButton.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 10/20/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit + +/// A button with an embedded `UIActivityIndicatorView` for simulating a running activity. +public class ActivityButton: UIControl { + + /// :nodoc: + public override var backgroundColor: UIColor? { + get { + return button.backgroundColor + } + set { + button.backgroundColor = newValue + } + } + + /// :nodoc: + public override var isEnabled: Bool { + get { + return button.isEnabled + } + set { + button.isEnabled = newValue + } + } + + /// :nodoc: + public override var accessibilityIdentifier: String? { + get { + return button.accessibilityIdentifier + } + set { + button.accessibilityIdentifier = newValue + } + } + + /// The button text color. + public var textColor: UIColor? { + get { + return button.titleColor(for: .normal) + } + set { + button.setTitleColor(newValue, for: .normal) + } + } + + /// The button title. + public var title: String? { + get { + return button.title(for: .normal) + } + set { + button.setTitle(newValue, for: .normal) + } + } + + /// The button font. + public var font: UIFont? { + get { + return button.titleLabel?.font + } + set { + button.titleLabel?.font = newValue + } + } + + /// The button corner radius. + public var cornerRadius: CGFloat { + get { + return button.layer.cornerRadius + } + set { + button.layer.cornerRadius = newValue + } + } + + /// This is `true` after invoking `startActivity()`. + public var isRunningActivity: Bool { + return activity.isAnimating + } + + private lazy var button = UIButton(type: .custom) + + private lazy var activity = UIActivityIndicatorView(frame: .zero) + + private var previousTitle: String? + + /// :nodoc: + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + backgroundColor = .clear + + button.showsTouchWhenHighlighted = true // XXX: should replicate IB highlight behaviour + button.translatesAutoresizingMaskIntoConstraints = false + button.addTarget(self, action: #selector(handleButtonTouch), for: .touchUpInside) + addSubview(button) + + activity.hidesWhenStopped = true + activity.translatesAutoresizingMaskIntoConstraints = false + addSubview(activity) + + let top = button.topAnchor.constraint(equalTo: topAnchor) + let bottom = button.bottomAnchor.constraint(equalTo: bottomAnchor) + let left = button.leftAnchor.constraint(equalTo: leftAnchor) + let right = button.rightAnchor.constraint(equalTo: rightAnchor) + let activityX = activity.centerXAnchor.constraint(equalTo: button.centerXAnchor) + let activityY = activity.centerYAnchor.constraint(equalTo: button.centerYAnchor) + NSLayoutConstraint.activate([top, bottom, left, right, activityX, activityY]) + } + + /** + Simulates an activity by showing a spinning activity indicator. + */ + public func startActivity() { + guard !activity.isAnimating else { + return + } + previousTitle = title + button.isUserInteractionEnabled = false + title = nil + activity.startAnimating() + } + + /** + Hides the activity indicator previously shown with `startActivity()`. + */ + public func stopActivity() { + guard activity.isAnimating else { + return + } + activity.stopAnimating() + title = previousTitle + button.isUserInteractionEnabled = true + } + + @objc private func handleButtonTouch() { + sendActions(for: .touchUpInside) + } +} diff --git a/PIA VPN/AddCustomNetworksViewController.swift b/PIA VPN/AddCustomNetworksViewController.swift index a33a4d930..be69e0678 100644 --- a/PIA VPN/AddCustomNetworksViewController.swift +++ b/PIA VPN/AddCustomNetworksViewController.swift @@ -127,7 +127,7 @@ extension AddCustomNetworksViewController: UICollectionViewDelegateFlowLayout, U func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: Cells.header, for: indexPath) as! PIAHeaderCollectionViewCell - headerView.setup(withTitle: L10n.Network.Management.Tool.Add.rule, andSubtitle: L10n.Network.Management.Tool.Choose.wifi + L10n.Settings.Hotspothelper.Available.help) + headerView.setup(withTitle: L10n.Localizable.Network.Management.Tool.Add.rule, andSubtitle: L10n.Localizable.Network.Management.Tool.Choose.wifi + L10n.Localizable.Settings.Hotspothelper.Available.help) return headerView } diff --git a/PIA VPN/AddEmailToAccountViewController.swift b/PIA VPN/AddEmailToAccountViewController.swift index 5cf1a9af1..e36555454 100644 --- a/PIA VPN/AddEmailToAccountViewController.swift +++ b/PIA VPN/AddEmailToAccountViewController.swift @@ -62,10 +62,10 @@ class AddEmailToAccountViewController: AutolayoutViewController, BrandableNaviga navigationItem.hidesBackButton = true - labelTitle.text = L10n.Set.Email.Form.email - labelSubtitle.text = L10n.Set.Email.why + labelTitle.text = L10n.Localizable.Set.Email.Form.email + labelSubtitle.text = L10n.Localizable.Set.Email.why - textEmail.placeholder = L10n.Account.Email.placeholder + textEmail.placeholder = L10n.Localizable.Account.Email.placeholder if let currentUser = Client.providers.accountProvider.currentUser, let info = currentUser.info { @@ -77,9 +77,9 @@ class AddEmailToAccountViewController: AutolayoutViewController, BrandableNaviga scrollView.alpha = 0 - labelUsernameCaption.text = L10n.Account.Username.caption + labelUsernameCaption.text = L10n.Localizable.Account.Username.caption labelUsername.text = Client.providers.accountProvider.publicUsername ?? "" - labelPasswordCaption.text = L10n.Set.Email.Password.caption + labelPasswordCaption.text = L10n.Localizable.Set.Email.Password.caption self.styleDismissButton() self.styleContainers() @@ -93,17 +93,17 @@ class AddEmailToAccountViewController: AutolayoutViewController, BrandableNaviga @IBAction private func signUp(_ sender: Any?) { guard let email = textEmail.text?.trimmed(), Validator.validate(email: email) else { - Macros.displayImageNote(withImage: Asset.iconWarning.image, - message: L10n.Set.Email.Error.validation) + Macros.displayImageNote(withImage: Asset.Images.iconWarning.image, + message: L10n.Localizable.Set.Email.Error.validation) self.status = .error(element: textEmail) return } guard termsAndConditionsAgreed else { //present term and conditions - let alert = Macros.alert(L10n.Gdpr.Collect.Data.title, L10n.Gdpr.Collect.Data.description) - alert.addCancelAction(L10n.Global.cancel) - alert.addActionWithTitle(L10n.Gdpr.Accept.Button.title) { + let alert = Macros.alert(L10n.Localizable.Gdpr.Collect.Data.title, L10n.Localizable.Gdpr.Collect.Data.description) + alert.addCancelAction(L10n.Localizable.Global.cancel) + alert.addActionWithTitle(L10n.Localizable.Gdpr.Accept.Button.title) { self.termsAndConditionsAgreed = true self.signUp(nil) } @@ -137,8 +137,8 @@ class AddEmailToAccountViewController: AutolayoutViewController, BrandableNaviga self?.textEmail.text = "" - let alert = Macros.alert(L10n.Global.error, L10n.Account.Set.Email.error) - alert.addDefaultAction(L10n.Global.close) + let alert = Macros.alert(L10n.Localizable.Global.error, L10n.Localizable.Account.Set.Email.error) + alert.addDefaultAction(L10n.Localizable.Global.close) self?.present(alert, animated: true, completion: nil) return @@ -148,7 +148,7 @@ class AddEmailToAccountViewController: AutolayoutViewController, BrandableNaviga self?.textEmail.endEditing(true) //TODO SHOW CREDENTIALS - self?.labelMessage.text = L10n.Set.Email.Success.messageFormat(email) + self?.labelMessage.text = L10n.Localizable.Set.Email.Success.messageFormat(email) self?.labelPassword.text = Client.configuration.tempAccountPassword UIView.animate(withDuration: 0.3, animations: { @@ -176,7 +176,7 @@ class AddEmailToAccountViewController: AutolayoutViewController, BrandableNaviga private func setupAppleSignInUI() { if #available(iOS 13.0, *) { - labelOr.text = L10n.Global.or.uppercased() + labelOr.text = L10n.Localizable.Global.or.uppercased() labelOr.textAlignment = .center let signInWithAppleButton = ASAuthorizationAppleIDButton(type: .signIn, style: Theme.current.palette.appearance == .dark ? .whiteOutline : .black) @@ -252,20 +252,20 @@ class AddEmailToAccountViewController: AutolayoutViewController, BrandableNaviga private func styleConfirmButton() { buttonConfirm.setRounded() buttonConfirm.style(style: TextStyle.Buttons.piaGreenButton) - buttonConfirm.setTitle(L10n.Global.update.uppercased(), + buttonConfirm.setTitle(L10n.Localizable.Global.update.uppercased(), for: []) } private func styleLogoutButton() { Theme.current.applyButtonLabelMediumStyle(logoutButton) - logoutButton.setTitle(L10n.Menu.Logout.title.uppercased(), + logoutButton.setTitle(L10n.Localizable.Menu.Logout.title.uppercased(), for: []) } private func styleDismissButton() { buttonSubmit.setRounded() buttonSubmit.style(style: TextStyle.Buttons.piaGreenButton) - buttonSubmit.setTitle(L10n.Global.close.uppercased(), + buttonSubmit.setTitle(L10n.Localizable.Global.close.uppercased(), for: []) } diff --git a/PIA VPN/AppDelegate.swift b/PIA VPN/AppDelegate.swift index c921da89c..73a01162c 100644 --- a/PIA VPN/AppDelegate.swift +++ b/PIA VPN/AppDelegate.swift @@ -111,15 +111,15 @@ class AppDelegate: NSObject, UIApplicationDelegate { AppPreferences.shared.didAskToEnableNotifications = true let alert = Macros.alert( - L10n.Notifications.Disabled.title, - L10n.Notifications.Disabled.message + L10n.Localizable.Notifications.Disabled.title, + L10n.Localizable.Notifications.Disabled.message ) - alert.addActionWithTitle(L10n.Notifications.Disabled.settings) { + alert.addActionWithTitle(L10n.Localizable.Notifications.Disabled.settings) { application.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil) } - alert.addCancelAction(L10n.Global.ok) + alert.addCancelAction(L10n.Localizable.Global.ok) window?.rootViewController?.present(alert, animated: true, completion: nil) } @@ -233,11 +233,11 @@ class AppDelegate: NSObject, UIApplicationDelegate { var itemAsset: ImageAsset! let connectionStatusType = (isNotDisconnected ? ShortcutItem.disconnect : ShortcutItem.connect) - let connectionStatusString = (isNotDisconnected ? L10n.Shortcuts.disconnect : L10n.Shortcuts.connect) + let connectionStatusString = (isNotDisconnected ? L10n.Localizable.Shortcuts.disconnect : L10n.Localizable.Shortcuts.connect) var items: [UIApplicationShortcutItem] = [] - itemAsset = (isNotDisconnected ? Asset.icon3dtDisconnect : Asset.icon3dtConnect) + itemAsset = (isNotDisconnected ? Asset.Images.icon3dtDisconnect : Asset.Images.icon3dtConnect) let connectionStatusIcon = UIApplicationShortcutIcon(templateImageName: itemAsset.name) let connect = UIApplicationShortcutItem( type: connectionStatusType.rawValue, @@ -248,11 +248,11 @@ class AppDelegate: NSObject, UIApplicationDelegate { ) items.append(connect) - itemAsset = Asset.icon3dtSelectRegion + itemAsset = Asset.Images.icon3dtSelectRegion let selectRegionIcon = UIApplicationShortcutIcon(templateImageName: itemAsset.name) let selectRegion = UIApplicationShortcutItem( type: ShortcutItem.selectRegion.rawValue, - localizedTitle: L10n.Shortcuts.selectRegion, + localizedTitle: L10n.Localizable.Shortcuts.selectRegion, localizedSubtitle: nil, icon: selectRegionIcon, userInfo: nil @@ -346,7 +346,7 @@ extension AppDelegate { private func dismissLeakProtectionAlert() { if let presentedAlert = window?.rootViewController?.presentedViewController as? UIAlertController { - let leakProtectionAlertTitle = L10n.Dashboard.Vpn.Leakprotection.Alert.title + let leakProtectionAlertTitle = L10n.Localizable.Dashboard.Vpn.Leakprotection.Alert.title if presentedAlert.title == leakProtectionAlertTitle { presentedAlert.dismiss(animated: true) diff --git a/PIA VPN/AppPreferences.swift b/PIA VPN/AppPreferences.swift index 003c49a30..bae1dd396 100644 --- a/PIA VPN/AppPreferences.swift +++ b/PIA VPN/AppPreferences.swift @@ -240,7 +240,7 @@ class AppPreferences { var todayWidgetVpnStatus: String? { get { - return defaults.string(forKey: Entries.todayWidgetVpnStatus) ?? L10n.Today.Widget.login + return defaults.string(forKey: Entries.todayWidgetVpnStatus) ?? L10n.Localizable.Today.Widget.login } set { defaults.set(newValue, forKey: Entries.todayWidgetVpnStatus) @@ -608,7 +608,7 @@ class AppPreferences { Entries.themeCode: ThemeCode.light.rawValue, Entries.useConnectSiriShortcuts: false, Entries.useDisconnectSiriShortcuts: false, - Entries.todayWidgetButtonTitle: L10n.Today.Widget.login, + Entries.todayWidgetButtonTitle: L10n.Localizable.Today.Widget.login, Entries.todayWidgetVpnProtocol: IKEv2Profile.vpnType, Entries.todayWidgetVpnPort: "500", Entries.todayWidgetVpnSocket: "UDP", @@ -876,8 +876,8 @@ class AppPreferences { useDisconnectSiriShortcuts = false connectShortcut = nil disconnectShortcut = nil - todayWidgetVpnStatus = L10n.Today.Widget.login - todayWidgetButtonTitle = L10n.Today.Widget.login + todayWidgetVpnStatus = L10n.Localizable.Today.Widget.login + todayWidgetButtonTitle = L10n.Localizable.Today.Widget.login todayWidgetVpnProtocol = IKEv2Profile.vpnType todayWidgetVpnPort = "500" todayWidgetVpnSocket = "UDP" diff --git a/PIA VPN/AutolayoutViewController.swift b/PIA VPN/AutolayoutViewController.swift new file mode 100644 index 000000000..712bb410b --- /dev/null +++ b/PIA VPN/AutolayoutViewController.swift @@ -0,0 +1,347 @@ +// +// AutolayoutViewControllers.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 10/20/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit +import Lottie +import PIALibrary +/// Declares a generic, dismissable modal controller. +public protocol ModalController: class { + + /** + Dismisses the modal controller. + */ + func dismissModal() +} + +public protocol AnimatingLoadingDelegate: class { + func showLoadingAnimation() + func hideLoadingAnimation() +} + +/// Enum used to determinate the status of the view controller and apply effects over the UI elements +public enum ViewControllerStatus { + case initial + case restore(element: UIView) + case error(element: UIView) +} + +/// Base view controller with dynamic constraints and restyling support. +/// +/// - Seealso: `Theme` +open class AutolayoutViewController: UIViewController, ModalController, Restylable { + + /// The outlet to the main view container (optional). + /// + /// - Seealso: `ThemeStrategy.autolayoutContainerMargins(for:)` + @IBOutlet public weak var viewContainer: UIView? + + /// :nodoc: + open override var preferredStatusBarStyle: UIStatusBarStyle { + return Theme.current.statusBarAppearance(for: self) + } + + /// The initial status of the view controller. Every time the var changes the value, we reload the UI of the form element given as parameter. + /// Example of use: self.status = .error(element: textEmail) + open var status: ViewControllerStatus = .initial { + didSet { reloadFormElements() } + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + /// :nodoc: + open override func viewDidLoad() { + super.viewDidLoad() + + if let viewContainer = viewContainer { + Theme.current.applyPrincipalBackground(viewContainer) + } + + NotificationCenter.default.addObserver(self, selector: #selector(viewShouldRestyle), name: .PIAThemeDidChange, object: nil) + viewShouldRestyle() + } + + /// :nodoc: + open override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + refreshOrientationConstraints(size: view.bounds.size) + } + + open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + //MARK: - iOS13 Dark mode + if #available(iOS 13.0, *) { + Macros.postNotification(.PIAThemeShouldChange) + } + } + + private func refreshOrientationConstraints(size: CGSize) { + if let viewContainer = viewContainer { + let orientation: UIInterfaceOrientationMask = (isLandscape ? .landscape : .portrait) + viewContainer.layoutMargins = Theme.current.autolayoutContainerMargins(for: orientation) + } + didRefreshOrientationConstraints() + } + + // MARK: Public interface + + /// Shortcut for signalling landscape orientation. + public var isLandscape: Bool { + return (view.bounds.size.width > view.bounds.size.height) + } + + /** + Called right after refreshing the orientation contraints, e.g. when the device rotates. + */ + open func didRefreshOrientationConstraints() { + } + + // MARK: ModalController + + /// :nodoc: + @objc open func dismissModal() { + dismiss(animated: true, completion: nil) + } + + // MARK: Restylable + + /// :nodoc: + @objc open func viewShouldRestyle() { + Theme.current.applyNavigationBarStyle(to: self) + Theme.current.applyPrincipalBackground(view) + if let viewContainer = viewContainer { + Theme.current.applyPrincipalBackground(viewContainer) + } + setNeedsStatusBarAppearanceUpdate() + } + + private func reloadFormElements() { + switch status { + case .initial: + break + case .restore(let element): + restoreFormElementBorder(element) + case .error(let element): + updateFormElementBorder(element) + } + } + + private func restoreFormElementBorder(_ element: UIView) { + if let element = element as? UITextField { + Theme.current.applyInput(element) + element.rightView = nil + } + } + + private func updateFormElementBorder(_ element: UIView) { + if let element = element as? UITextField { + Theme.current.applyInputError(element) + let iconWarning = UIImageView(image:Asset.Images.iconWarning.image.withRenderingMode(.alwaysTemplate)) + iconWarning.tintColor = .piaRed + element.rightView = iconWarning + } + } + + public func styleNavigationBarWithTitle(_ title: String) { + + let currentStatus = Client.providers.vpnProvider.vpnStatus + + switch currentStatus { + case .connected: + let titleLabelView = UILabel(frame: CGRect.zero) + titleLabelView.style(style: TextStyle.textStyle6) + titleLabelView.text = title + if let navController = navigationController { + Theme.current.applyCustomNavigationBar(navController.navigationBar, + withTintColor: .white, + andBarTintColors: [UIColor.piaGreen, + UIColor.piaGreenDark20]) + } + let size = titleLabelView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + titleLabelView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) + navigationItem.titleView = titleLabelView + setNeedsStatusBarAppearanceUpdate() + + default: + let titleLabelView = UILabel(frame: CGRect.zero) + titleLabelView.style(style: Theme.current.palette.appearance == .dark ? + TextStyle.textStyle6 : + TextStyle.textStyle7) + titleLabelView.text = title + if let navigationController = navigationController { + Theme.current.applyCustomNavigationBar(navigationController.navigationBar, + withTintColor: nil, + andBarTintColors: nil) + } + + let size = titleLabelView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + titleLabelView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) + navigationItem.titleView = titleLabelView + setNeedsStatusBarAppearanceUpdate() + + } + } + + @objc public func back(_ sender: Any?) { + self.navigationController?.popViewController(animated: true) + } + +} + +extension AutolayoutViewController: AnimatingLoadingDelegate { + + private struct LottieRepos { + static var graphLoad: AnimationView? + static var containerView: UIView? + } + + var graphLoad: AnimationView? { + get { + return objc_getAssociatedObject(self, &LottieRepos.graphLoad) as? AnimationView + } + set { + if let unwrappedValue = newValue { + objc_setAssociatedObject(self, &LottieRepos.graphLoad, unwrappedValue as AnimationView?, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + } + + var containerView: UIView? { + get { + return LottieRepos.containerView + } + set { + if let unwrappedValue = newValue { + LottieRepos.containerView = unwrappedValue + } + } + } + + @objc public func showLoadingAnimation() { + if graphLoad == nil { + containerView = UIView(frame: UIScreen.main.bounds) + containerView?.backgroundColor = Theme.current.palette.appearance == .dark ? + UIColor.black.withAlphaComponent(0.72) : + UIColor.piaGrey1.withAlphaComponent(0.75) + graphLoad = AnimationView(name: "pia-spinner") + } + addLoadingAnimation() + } + + private func addLoadingAnimation() { + graphLoad?.loopMode = .loop + if let graphLoad = graphLoad, + let containerView = containerView { + if let key = self.navigationController?.view { + key.addSubview(containerView) + key.addSubview(graphLoad) + } + setLoadingConstraints() + graphLoad.play() + } + } + + @objc public func hideLoadingAnimation() { + graphLoad?.stop() + graphLoad?.removeFromSuperview() + containerView?.removeFromSuperview() + } + + private func setLoadingConstraints() { + if let graphLoad = graphLoad, + let keyView = self.navigationController?.view, + let containerView = containerView { + + containerView.translatesAutoresizingMaskIntoConstraints = false + graphLoad.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint(item: containerView, + attribute: .left, + relatedBy: .equal, + toItem: keyView, + attribute: .left, + multiplier: 1.0, + constant: 0.0).isActive = true + + NSLayoutConstraint(item: containerView, + attribute: .right, + relatedBy: .equal, + toItem: keyView, + attribute: .right, + multiplier: 1.0, + constant: 0.0).isActive = true + + NSLayoutConstraint(item: containerView, + attribute: .top, + relatedBy: .equal, + toItem: keyView, + attribute: .top, + multiplier: 1.0, + constant: 0.0).isActive = true + + NSLayoutConstraint(item: containerView, + attribute: .bottom, + relatedBy: .equal, + toItem: keyView, + attribute: .bottom, + multiplier: 1.0, + constant: 0.0).isActive = true + + NSLayoutConstraint(item: graphLoad, + attribute: .centerX, + relatedBy: .equal, + toItem: containerView, + attribute: .centerX, + multiplier: 1.0, + constant: 0.0).isActive = true + + NSLayoutConstraint(item: graphLoad, + attribute: .centerY, + relatedBy: .equal, + toItem: containerView, + attribute: .centerY, + multiplier: 1.0, + constant: 0.0).isActive = true + + let lottieWidth = UIScreen.main.bounds.width/4 + + NSLayoutConstraint(item: graphLoad, + attribute: .width, + relatedBy: .equal, + toItem: nil, + attribute: .width, + multiplier: 1.0, + constant: lottieWidth).isActive = true + + NSLayoutConstraint(item: graphLoad, + attribute: .height, + relatedBy: .equal, + toItem: nil, + attribute: .height, + multiplier: 1.0, + constant: lottieWidth).isActive = true + + } + } + +} diff --git a/PIA VPN/Bootstrapper.swift b/PIA VPN/Bootstrapper.swift index 0ef950e81..3d974406d 100644 --- a/PIA VPN/Bootstrapper.swift +++ b/PIA VPN/Bootstrapper.swift @@ -296,7 +296,7 @@ class Bootstrapper { } @objc private func internetUnreachable(notification: Notification) { - Macros.displayStickyNote(withMessage: L10n.Global.unreachable, - andImage: Asset.iconWarning.image) + Macros.displayStickyNote(withMessage: L10n.Localizable.Global.unreachable, + andImage: Asset.Images.iconWarning.image) } } diff --git a/PIA VPN/BorderedTextField.swift b/PIA VPN/BorderedTextField.swift new file mode 100644 index 000000000..7bb16f5e3 --- /dev/null +++ b/PIA VPN/BorderedTextField.swift @@ -0,0 +1,219 @@ +// +// BorderedTextField.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 10/20/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit + +/// An `UITextField` specialization with a border that can highlight while editing. +public class BorderedTextField: UITextField { + private static let allowedReadonlyActions: Set = Set([ + #selector(select(_:)), + #selector(selectAll(_:)), + #selector(copy(_:)) + ]) + + private static let allowedActions: Set = Set([ + #selector(select(_:)), + #selector(selectAll(_:)), + #selector(copy(_:)), + #selector(cut(_:)), + #selector(paste(_:)) + ]) + + private static let allowedSecureActions: Set = Set([ + #selector(paste(_:)) + ]) + + /// :nodoc: + public override var placeholder: String? { + didSet { + reloadPlaceholder() + } + } + + /// The default color of the border. + public var borderColor: UIColor? { + didSet { + if (!highlightsWhileEditing || !isEditing) { + self.layer.borderColor = borderColor?.cgColor + } + reloadPlaceholder() + } + } + + /// The color of the border while highlighted. + public var highlightedBorderColor: UIColor? = .blue { + didSet { + if (highlightsWhileEditing && isEditing) { + self.layer.borderColor = highlightedBorderColor?.cgColor + } + } + } + + /// When `true`, the text field border highlights while editing. + public var highlightsWhileEditing = true + + /// When `true`, the text field can be edited. + public var isEditable = true + + /// :nodoc: + public override var delegate: UITextFieldDelegate? { + get { + return realDelegate + } + set { + if (newValue as? BorderedTextField == self) { + super.delegate = newValue + } else { + realDelegate = newValue + } + } + } + + /// :nodoc: + public override func awakeFromNib() { + super.awakeFromNib() + rightViewMode = .unlessEditing + borderStyle = .none + textColor = .darkText + self.delegate = self + } + + private weak var viewBorder: UIView? + + private weak var realDelegate: UITextFieldDelegate? + + /// :nodoc: + public override func willMove(toSuperview newSuperview: UIView?) { + super.willMove(toSuperview: newSuperview) + + self.layer.cornerRadius = 6.0 + self.layer.borderColor = borderColor?.cgColor + self.layer.borderWidth = 1 + + reloadPlaceholder() + + } + + /// :nodoc: + public override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { + let allowed: Set + if isSecureTextEntry { + guard isEditable else { + return false + } + allowed = BorderedTextField.allowedSecureActions + } else { + if isEditable { + allowed = BorderedTextField.allowedActions + } else { + allowed = BorderedTextField.allowedReadonlyActions + } + } + return allowed.contains(action) + } + + private func reloadPlaceholder() { + if let placeholder = placeholder, let placeholderColor = borderColor { + let attributes: [NSAttributedString.Key: Any] = [ + .foregroundColor: placeholderColor + ] + attributedPlaceholder = NSAttributedString(string: placeholder, attributes: attributes) + } + } + + override public func textRect(forBounds bounds: CGRect) -> CGRect { + return bounds.inset(by: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)) + } + override public func editingRect(forBounds bounds: CGRect) -> CGRect { + return bounds.inset(by: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)) + } + override public func placeholderRect(forBounds bounds: CGRect) -> CGRect { + return bounds.inset(by: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)) + } + + public override func rightViewRect(forBounds bounds: CGRect) -> CGRect { + var rightViewRect = super.rightViewRect(forBounds: bounds) + rightViewRect.origin.x -= 16 + return rightViewRect + } + +} + +// XXX: hack to inject self delegate +/// :nodoc: +extension BorderedTextField: UITextFieldDelegate { + public func textFieldShouldClear(_ textField: UITextField) -> Bool { + if let method = realDelegate?.textFieldShouldClear { + return method(textField) + } + return true + } + + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + guard let borderedTextField = textField as? BorderedTextField, borderedTextField.isEditable else { + return false + } + if let method = realDelegate?.textField(_:shouldChangeCharactersIn:replacementString:) { + return method(textField, range, string) + } + return true + } + + public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + if let method = realDelegate?.textFieldShouldReturn(_:) { + return method(textField) + } + return true + } + + public func textFieldDidBeginEditing(_ textField: UITextField) { + if highlightsWhileEditing { + self.layer.borderColor = highlightedBorderColor?.cgColor + } + + if let method = realDelegate?.textFieldDidBeginEditing(_:) { + return method(textField) + } + } + + public func textFieldDidEndEditing(_ textField: UITextField) { + self.layer.borderColor = borderColor?.cgColor + + if let method = realDelegate?.textFieldDidEndEditing(_:) { + return method(textField) + } + } + + public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + if let method = realDelegate?.textFieldShouldEndEditing { + return method(textField) + } + return true + } + + public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + if let method = realDelegate?.textFieldShouldEndEditing(_:) { + return method(textField) + } + return true + } +} diff --git a/PIA VPN/CAGradientLayer+Image.swift b/PIA VPN/CAGradientLayer+Image.swift new file mode 100644 index 000000000..2c9197c54 --- /dev/null +++ b/PIA VPN/CAGradientLayer+Image.swift @@ -0,0 +1,37 @@ +// +// CAGradientLayer+Image.swift +// PIA VPN +// +// Created by Said Rehouni on 8/11/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import Foundation +import UIKit + +extension CAGradientLayer { + + convenience init(frame: CGRect, colors: [UIColor]) { + self.init() + self.frame = frame + self.colors = [] + for color in colors { + self.colors?.append(color.cgColor) + } + startPoint = CGPoint(x: 0, y: 0) + endPoint = CGPoint(x: 0, y: 1) + } + + func createGradientImage() -> UIImage? { + + var image: UIImage? = nil + UIGraphicsBeginImageContext(bounds.size) + if let context = UIGraphicsGetCurrentContext() { + render(in: context) + image = UIGraphicsGetImageFromCurrentImageContext() + } + UIGraphicsEndImageContext() + return image + } + +} diff --git a/PIA VPN/CardFactory.swift b/PIA VPN/CardFactory.swift index 5faf0b2ae..d359fc1ab 100644 --- a/PIA VPN/CardFactory.swift +++ b/PIA VPN/CardFactory.swift @@ -41,11 +41,11 @@ struct CardFactory { private static let availableCards = [ Card("3.7.1", - L10n.Card.Wireguard.title, - L10n.Card.Wireguard.description, + L10n.Localizable.Card.Wireguard.title, + L10n.Localizable.Card.Wireguard.description, "wg-background-", "wg-main", - L10n.Card.Wireguard.Cta.activate, + L10n.Localizable.Card.Wireguard.Cta.activate, URL(string: "https://www.privateinternetaccess.com/blog/wireguide-all-about-the-wireguard-vpn-protocol/"), { guard let rootView = AppDelegate.delegate().topViewControllerWithRootViewController(rootViewController: UIApplication.shared.keyWindow?.rootViewController) else { diff --git a/PIA VPN/Client+Storyboard.swift b/PIA VPN/Client+Storyboard.swift new file mode 100644 index 000000000..621d6b1f4 --- /dev/null +++ b/PIA VPN/Client+Storyboard.swift @@ -0,0 +1,20 @@ +// +// Client+Storyboard.swift +// PIA VPN +// +// Created by Said Rehouni on 26/10/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import Foundation +import PIALibrary +import UIKit + +extension Client { + /** + Returns the Signup Storyboard owned by the library to be used by the clients + */ + public static func signupStoryboard() -> UIStoryboard { + UIStoryboard(name: "Signup", bundle: Bundle.main) + } +} diff --git a/PIA VPN/ClientError+Localization.swift b/PIA VPN/ClientError+Localization.swift new file mode 100644 index 000000000..a96b4509f --- /dev/null +++ b/PIA VPN/ClientError+Localization.swift @@ -0,0 +1,21 @@ +// +// ClientError+Localization.swift +// +// +// Created by Juan Docal on 2022-08-11. +// + +import Foundation +import PIALibrary + +extension ClientError: LocalizedError { + public var errorDescription: String? { + switch self { + case .sandboxPurchase: + return NSLocalizedString(L10n.Signup.Failure.Purchase.Sandbox.message, + comment: L10n.Signup.Failure.Purchase.Sandbox.message) + default: + return nil + } + } +} diff --git a/PIA VPN/ConfirmVPNPlanViewController.swift b/PIA VPN/ConfirmVPNPlanViewController.swift new file mode 100644 index 000000000..20fdaaff5 --- /dev/null +++ b/PIA VPN/ConfirmVPNPlanViewController.swift @@ -0,0 +1,267 @@ +// +// ConfirmVPNPlanViewController.swift +// PIALibrary-iOS +// +// Created by Jose Antonio Blaya Garcia on 14/11/2018. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit +import SwiftyBeaver +import AuthenticationServices +import PIALibrary + +private let log = SwiftyBeaver.self + +public class ConfirmVPNPlanViewController: AutolayoutViewController, BrandableNavigationBar, WelcomeChild { + + @IBOutlet private weak var buttonConfirm: PIAButton! + @IBOutlet private weak var textEmail: BorderedTextField! + @IBOutlet private weak var labelTitle: UILabel! + @IBOutlet private weak var labelSubtitle: UILabel! + @IBOutlet private weak var addEmailContainer: UIView! + + private var labelOr = UILabel() + private var signupEmail: String? + private var signupTransaction: InAppTransaction? + var metadata: SignupMetadata! + weak var completionDelegate: WelcomeCompletionDelegate? + var omitsSiblingLink = false + var termsAndConditionsAgreed = false + + var preset: Preset? + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override public func viewDidLoad() { + super.viewDidLoad() + + guard let preset = self.preset else { + fatalError("Preset not propagated") + } + + navigationItem.hidesBackButton = true + + labelTitle.text = L10n.Welcome.Purchase.Confirm.Form.email + labelSubtitle.text = L10n.Welcome.Purchase.Email.why + + textEmail.placeholder = L10n.Welcome.Purchase.Email.placeholder + textEmail.text = preset.purchaseEmail + self.styleConfirmButton() + + if #available(iOSApplicationExtension 13.0, *) { + setupAppleSignInUI() + } + + } + + @IBAction private func signUp(_ sender: Any?) { + + guard let email = textEmail.text?.trimmed(), Validator.validate(email: email) else { + signupEmail = nil + Macros.displayImageNote(withImage: Asset.Images.iconWarning.image, + message: L10n.Welcome.Purchase.Error.validation) + self.status = .error(element: textEmail) + return + } + + guard termsAndConditionsAgreed else { + //present term and conditions + self.performSegue(withIdentifier: StoryboardSegue.Signup.presentGDPRTermsSegue.rawValue, + sender: nil) + return + } + + self.status = .restore(element: textEmail) + + self.showLoadingAnimation() + self.disableInteractions() + + log.debug("Account: Modifying account email...") + + metadata.title = L10n.Signup.InProgress.title + metadata.bodyImage = Asset.Images.imagePurchaseSuccess.image + metadata.bodyTitle = L10n.Signup.Success.title + metadata.bodySubtitle = L10n.Signup.Success.messageFormat(email) + + let request = UpdateAccountRequest(email: email) + + var password = "" + if let currentPassword = metadata.user?.credentials.password { + password = currentPassword + } + + Client.providers.accountProvider.update(with: request, + resetPassword: false, + andPassword: password) { [weak self] (info, error) in + self?.hideLoadingAnimation() + self?.enableInteractions() + + guard let _ = info else { + if let error = error { + log.error("Account: Failed to modify account email (error: \(error))") + } else { + log.error("Account: Failed to modify account email") + } + + self?.textEmail.text = "" + + let alert = Macros.alert(L10n.Signup.Unreachable.vcTitle, L10n.Welcome.Update.Account.Email.error) + alert.addDefaultAction(L10n.Ui.Global.close) + self?.present(alert, animated: true, completion: nil) + + return + } + + log.debug("Account: Email successfully modified") + self?.textEmail.endEditing(true) + self?.perform(segue: StoryboardSegue.Signup.successShowCredentialsSegueIdentifier) + } + + } + + private func disableInteractions() { + parent?.view.isUserInteractionEnabled = false + } + + private func enableInteractions() { + parent?.view.isUserInteractionEnabled = true + } + + override public func prepare(for segue: UIStoryboardSegue, sender: Any?) { + + guard let identifier = segue.identifier, let segueType = StoryboardSegue.Signup(rawValue: identifier) else { + return + } + switch segueType { + case .successShowCredentialsSegueIdentifier: + let vc = segue.destination as! SignupSuccessViewController + vc.metadata = metadata + vc.completionDelegate = completionDelegate + break + case .presentGDPRTermsSegue: + let gdprViewController = segue.destination as! GDPRViewController + gdprViewController.delegate = self + break + default: + break + } + + } + + private func setupAppleSignInUI() { + if #available(iOS 13.0, *) { + labelOr.text = L10n.Welcome.Purchase.or.uppercased() + labelOr.textAlignment = .center + + let signInWithAppleButton = ASAuthorizationAppleIDButton(type: .signIn, style: Theme.current.palette.appearance == .dark ? .whiteOutline : .black) + signInWithAppleButton.addTarget(self, action: #selector(handleAuthorizationAppleID), for: .touchUpInside) + + self.addEmailContainer.addSubview(signInWithAppleButton) + self.addEmailContainer.addSubview(labelOr) + + labelOr.translatesAutoresizingMaskIntoConstraints = false + signInWithAppleButton.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + labelOr.topAnchor.constraint(equalTo: self.buttonConfirm.bottomAnchor, constant: 15), + labelOr.leftAnchor.constraint(equalTo: self.addEmailContainer.leftAnchor), + labelOr.rightAnchor.constraint(equalTo: self.addEmailContainer.rightAnchor), + labelOr.heightAnchor.constraint(equalToConstant: 15), + + signInWithAppleButton.topAnchor.constraint(equalTo: self.labelOr.bottomAnchor, constant: 15), + signInWithAppleButton.leftAnchor.constraint(equalTo: self.addEmailContainer.leftAnchor), + signInWithAppleButton.rightAnchor.constraint(equalTo: self.addEmailContainer.rightAnchor), + signInWithAppleButton.heightAnchor.constraint(equalToConstant: 50) + ]) + + } + } + + // MARK: Actions + @IBAction private func handleAuthorizationAppleID() { + if #available(iOS 13.0, *) { + let request = ASAuthorizationAppleIDProvider().createRequest() + request.requestedScopes = [.email] + + let controller = ASAuthorizationController(authorizationRequests: [request]) + + controller.delegate = self + controller.presentationContextProvider = self + + controller.performRequests() + } + } + + // MARK: Restylable + override public func viewShouldRestyle() { + super.viewShouldRestyle() + navigationItem.titleView = NavigationLogoView() + Theme.current.applyNavigationBarStyle(to: self) + Theme.current.applyPrincipalBackground(view) + Theme.current.applyInput(textEmail) + Theme.current.applyTitle(labelTitle, appearance: .dark) + Theme.current.applySubtitle(labelSubtitle) + Theme.current.applySubtitle(labelOr) + } + + private func styleConfirmButton() { + buttonConfirm.setRounded() + buttonConfirm.style(style: TextStyle.Buttons.piaGreenButton) + buttonConfirm.setTitle(L10n.Welcome.Purchase.submit.uppercased(), + for: []) + } + +} + +extension ConfirmVPNPlanViewController: GDPRDelegate { + + public func gdprViewWasAccepted() { + self.termsAndConditionsAgreed = true + self.signUp(nil) + } + + public func gdprViewWasRejected() { + self.termsAndConditionsAgreed = false + } + +} + +@available(iOS 13.0, *) +extension ConfirmVPNPlanViewController: ASAuthorizationControllerPresentationContextProviding { + public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { + guard let appleIDCredentials = authorization.credential as? ASAuthorizationAppleIDCredential else { return } + if let email = appleIDCredentials.email { + textEmail.text = email + let preferences = Client.preferences.editable() + preferences.signInWithAppleFakeEmail = email + preferences.commit() + } else { + textEmail.text = Client.preferences.signInWithAppleFakeEmail + } + self.signUp(nil) + } +} + +@available(iOS 13.0, *) +extension ConfirmVPNPlanViewController: ASAuthorizationControllerDelegate { + public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { + return self.view.window! + } +} diff --git a/PIA VPN/ContentBlockerViewController.swift b/PIA VPN/ContentBlockerViewController.swift index 8159147c0..71d216076 100644 --- a/PIA VPN/ContentBlockerViewController.swift +++ b/PIA VPN/ContentBlockerViewController.swift @@ -37,13 +37,13 @@ class ContentBlockerViewController: AutolayoutViewController { override func viewDidLoad() { super.viewDidLoad() - title = L10n.ContentBlocker.title + title = L10n.Localizable.ContentBlocker.title - imvPicture.image = Asset.imageContentBlocker.image - labelTitle.text = L10n.ContentBlocker.title - labelMessage.text = L10n.ContentBlocker.Body.subtitle - labelFooter.text = L10n.ContentBlocker.Body.footer - buttonSubmit.title = L10n.Global.ok + imvPicture.image = Asset.Images.imageContentBlocker.image + labelTitle.text = L10n.Localizable.ContentBlocker.title + labelMessage.text = L10n.Localizable.ContentBlocker.Body.subtitle + labelFooter.text = L10n.Localizable.ContentBlocker.Body.footer + buttonSubmit.title = L10n.Localizable.Global.ok } @IBAction private func submit() { diff --git a/PIA VPN/CustomDNSSettingsViewController.swift b/PIA VPN/CustomDNSSettingsViewController.swift index d6527fd7c..017995a55 100644 --- a/PIA VPN/CustomDNSSettingsViewController.swift +++ b/PIA VPN/CustomDNSSettingsViewController.swift @@ -39,7 +39,7 @@ class CustomDNSSettingsViewController: AutolayoutViewController { override func viewDidLoad() { - self.title = L10n.Settings.Dns.Custom.dns + self.title = L10n.Localizable.Settings.Dns.Custom.dns configureTextfields() configureNavigationBar() @@ -49,7 +49,7 @@ class CustomDNSSettingsViewController: AutolayoutViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - styleNavigationBarWithTitle(L10n.Settings.Dns.Custom.dns) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Dns.Custom.dns) } // MARK: Actions @@ -73,10 +73,10 @@ class CustomDNSSettingsViewController: AutolayoutViewController { } @objc private func clear(_ sender: Any?) { - let alertController = Macros.alert(L10n.Settings.Dns.Alert.Clear.title, - L10n.Settings.Dns.Alert.Clear.message) + let alertController = Macros.alert(L10n.Localizable.Settings.Dns.Alert.Clear.title, + L10n.Localizable.Settings.Dns.Alert.Clear.message) - alertController.addActionWithTitle(L10n.Global.ok) { + alertController.addActionWithTitle(L10n.Localizable.Global.ok) { if let firstKey = DNSList.shared.firstKey() { DNSList.shared.removeServer(name: (self.vpnType == PIATunnelProfile.vpnType ? DNSList.CUSTOM_OPENVPN_DNS_KEY : DNSList.CUSTOM_WIREGUARD_DNS_KEY)) self.delegate?.updateSetting(NetworkSections.dns, @@ -84,7 +84,7 @@ class CustomDNSSettingsViewController: AutolayoutViewController { } self.navigationController?.popToRootViewController(animated: true) } - alertController.addCancelAction(L10n.Global.cancel) + alertController.addCancelAction(L10n.Localizable.Global.cancel) self.present(alertController, animated: true, @@ -96,7 +96,7 @@ class CustomDNSSettingsViewController: AutolayoutViewController { override func viewShouldRestyle() { super.viewShouldRestyle() - styleNavigationBarWithTitle(L10n.Settings.Dns.Custom.dns) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Dns.Custom.dns) if let viewContainer = viewContainer { Theme.current.applyPrincipalBackground(view) @@ -115,27 +115,27 @@ class CustomDNSSettingsViewController: AutolayoutViewController { // MARK: Private private func configureNavigationBar() { navigationItem.leftBarButtonItem = UIBarButtonItem( - title: L10n.Global.clear, + title: L10n.Localizable.Global.clear, style: .plain, target: self, action: #selector(clear(_:)) ) - navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Global.clear + navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Localizable.Global.clear navigationItem.rightBarButtonItem = UIBarButtonItem( - title: L10n.Global.update, + title: L10n.Localizable.Global.update, style: .plain, target: self, action: #selector(update(_:)) ) - navigationItem.rightBarButtonItem?.accessibilityLabel = L10n.Global.update + navigationItem.rightBarButtonItem?.accessibilityLabel = L10n.Localizable.Global.update } private func configureTextfields() { - labelPrimaryDNS.text = L10n.Settings.Dns.primaryDNS - labelSecondaryDNS.text = L10n.Settings.Dns.secondaryDNS - textPrimaryDNS.placeholder = L10n.Global.required - textSecondaryDNS.placeholder = L10n.Global.optional + labelPrimaryDNS.text = L10n.Localizable.Settings.Dns.primaryDNS + labelSecondaryDNS.text = L10n.Localizable.Settings.Dns.secondaryDNS + textPrimaryDNS.placeholder = L10n.Localizable.Global.required + textSecondaryDNS.placeholder = L10n.Localizable.Global.optional textPrimaryDNS.keyboardType = .numbersAndPunctuation textSecondaryDNS.keyboardType = .numbersAndPunctuation @@ -148,9 +148,9 @@ class CustomDNSSettingsViewController: AutolayoutViewController { if (textPrimaryDNS.text == nil || (textPrimaryDNS.text != nil && textPrimaryDNS.text!.isEmpty)) { - let alert = Macros.alert(L10n.Settings.Dns.Custom.dns, - L10n.Settings.Dns.Validation.Primary.mandatory) - alert.addDefaultAction(L10n.Global.ok) + let alert = Macros.alert(L10n.Localizable.Settings.Dns.Custom.dns, + L10n.Localizable.Settings.Dns.Validation.Primary.mandatory) + alert.addDefaultAction(L10n.Localizable.Global.ok) self.present(alert, animated: true, completion: nil) @@ -159,9 +159,9 @@ class CustomDNSSettingsViewController: AutolayoutViewController { if let primaryDNS = textPrimaryDNS.text, !isValidAddress(primaryDNS) { - let alert = Macros.alert(L10n.Settings.Dns.Custom.dns, - L10n.Settings.Dns.Validation.Primary.invalid) - alert.addDefaultAction(L10n.Global.ok) + let alert = Macros.alert(L10n.Localizable.Settings.Dns.Custom.dns, + L10n.Localizable.Settings.Dns.Validation.Primary.invalid) + alert.addDefaultAction(L10n.Localizable.Global.ok) self.present(alert, animated: true, completion: nil) @@ -171,9 +171,9 @@ class CustomDNSSettingsViewController: AutolayoutViewController { if let secondaryDNS = textSecondaryDNS.text, !secondaryDNS.isEmpty, !isValidAddress(secondaryDNS) { - let alert = Macros.alert(L10n.Settings.Dns.Custom.dns, - L10n.Settings.Dns.Validation.Secondary.invalid) - alert.addDefaultAction(L10n.Global.ok) + let alert = Macros.alert(L10n.Localizable.Settings.Dns.Custom.dns, + L10n.Localizable.Settings.Dns.Validation.Secondary.invalid) + alert.addDefaultAction(L10n.Localizable.Global.ok) self.present(alert, animated: true, completion: nil) diff --git a/PIA VPN/CustomNetworkCollectionViewCell.swift b/PIA VPN/CustomNetworkCollectionViewCell.swift index d92ced09b..f99566e49 100644 --- a/PIA VPN/CustomNetworkCollectionViewCell.swift +++ b/PIA VPN/CustomNetworkCollectionViewCell.swift @@ -60,7 +60,7 @@ class CustomNetworkCollectionViewCell: UICollectionViewCell { func viewShouldRestyle() { title.style(style: TextStyle.textStyle3) - let wifiImage = Asset.Piax.Nmt.iconNmtWifi.image.withRenderingMode(.alwaysTemplate) + let wifiImage = Asset.Images.Piax.Nmt.iconNmtWifi.image.withRenderingMode(.alwaysTemplate) wifiIcon.image = wifiImage wifiIcon.tintColor = .piaGrey4 popover.dismiss() diff --git a/PIA VPN/CustomServerSettingsViewController.swift b/PIA VPN/CustomServerSettingsViewController.swift index f4be21d4e..b9a697ad4 100644 --- a/PIA VPN/CustomServerSettingsViewController.swift +++ b/PIA VPN/CustomServerSettingsViewController.swift @@ -74,7 +74,7 @@ class CustomServerSettingsViewController: AutolayoutViewController { } else { let alertController = Macros.alert("", "You must provide a valid server information") - alertController.addCancelAction(L10n.Global.close) + alertController.addCancelAction(L10n.Localizable.Global.close) } } @@ -83,12 +83,12 @@ class CustomServerSettingsViewController: AutolayoutViewController { private func configureNavigationBar() { navigationItem.rightBarButtonItem = UIBarButtonItem( - title: L10n.Global.add, + title: L10n.Localizable.Global.add, style: .plain, target: self, action: #selector(update(_:)) ) - navigationItem.rightBarButtonItem?.accessibilityLabel = L10n.Global.add + navigationItem.rightBarButtonItem?.accessibilityLabel = L10n.Localizable.Global.add } private func configureTextfields() { diff --git a/PIA VPN/DNSList.swift b/PIA VPN/DNSList.swift index eeb75d689..9ef1d6d09 100644 --- a/PIA VPN/DNSList.swift +++ b/PIA VPN/DNSList.swift @@ -144,18 +144,18 @@ class DNSList: NSObject { if key == customKey { //L10n.Global.custom switch value.count { case 0: - return L10n.Settings.Dns.custom + return L10n.Localizable.Settings.Dns.custom case 1: - return L10n.Settings.Dns.custom + " (" + value.first! + ")" + return L10n.Localizable.Settings.Dns.custom + " (" + value.first! + ")" default: - return L10n.Settings.Dns.custom + " (" + value.first! + " / " + value.last! + ")" + return L10n.Localizable.Settings.Dns.custom + " (" + value.first! + " / " + value.last! + ")" } } return key } } } - return L10n.Settings.Dns.custom + return L10n.Localizable.Settings.Dns.custom } /// Return if a custom DNS is set for given protocol and its configured DNS servers diff --git a/PIA VPN/DashboardViewController.swift b/PIA VPN/DashboardViewController.swift index 8bad1a3e3..71e0f4e22 100644 --- a/PIA VPN/DashboardViewController.swift +++ b/PIA VPN/DashboardViewController.swift @@ -144,8 +144,8 @@ class DashboardViewController: AutolayoutViewController { guard Client.providers.accountProvider.isLoggedIn else { presentLogin() - AppPreferences.shared.todayWidgetVpnStatus = L10n.Today.Widget.login - AppPreferences.shared.todayWidgetButtonTitle = L10n.Today.Widget.login + AppPreferences.shared.todayWidgetVpnStatus = L10n.Localizable.Today.Widget.login + AppPreferences.shared.todayWidgetButtonTitle = L10n.Localizable.Today.Widget.login return } @@ -157,9 +157,9 @@ class DashboardViewController: AutolayoutViewController { AppPreferences.shared.todayWidgetVpnStatus = Client.providers.vpnProvider.vpnStatus.rawValue if Client.providers.vpnProvider.vpnStatus == .disconnected { - AppPreferences.shared.todayWidgetButtonTitle = L10n.Shortcuts.connect + AppPreferences.shared.todayWidgetButtonTitle = L10n.Localizable.Shortcuts.connect } else { - AppPreferences.shared.todayWidgetButtonTitle = L10n.Shortcuts.disconnect + AppPreferences.shared.todayWidgetButtonTitle = L10n.Localizable.Shortcuts.disconnect } viewContent.isHidden = false @@ -254,28 +254,28 @@ class DashboardViewController: AutolayoutViewController { switch self.tileModeStatus { //change the status case .normal: if let leftBarButton = navigationItem.leftBarButtonItem, - leftBarButton.accessibilityLabel != L10n.Global.cancel { - leftBarButton.image = Asset.itemMenu.image + leftBarButton.accessibilityLabel != L10n.Localizable.Global.cancel { + leftBarButton.image = Asset.Images.itemMenu.image leftBarButton.action = #selector(openMenu(_:)) } else { navigationItem.leftBarButtonItem = UIBarButtonItem( - image: Asset.itemMenu.image, + image: Asset.Images.itemMenu.image, style: .plain, target: self, action: #selector(openMenu(_:)) ) } - navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Menu.Accessibility.item + navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Localizable.Menu.Accessibility.item navigationItem.leftBarButtonItem?.accessibilityIdentifier = Accessibility.Id.Dashboard.menu if navigationItem.rightBarButtonItem == nil { navigationItem.rightBarButtonItem = UIBarButtonItem( - image: Asset.Piax.Global.iconEditTile.image, + image: Asset.Images.Piax.Global.iconEditTile.image, style: .plain, target: self, action: #selector(updateEditTileStatus(_:)) ) - navigationItem.rightBarButtonItem?.accessibilityLabel = L10n.Menu.Accessibility.Edit.tile + navigationItem.rightBarButtonItem?.accessibilityLabel = L10n.Localizable.Menu.Accessibility.Edit.tile } case .edit: @@ -285,7 +285,7 @@ class DashboardViewController: AutolayoutViewController { target: self, action: #selector(closeTileEditingMode(_:)) ) - navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Global.cancel + navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Localizable.Global.cancel navigationItem.leftBarButtonItem?.accessibilityIdentifier = nil navigationItem.rightBarButtonItem = nil @@ -327,7 +327,7 @@ class DashboardViewController: AutolayoutViewController { } if isUnauthorized { - Macros.displayImageNote(withImage: Asset.iconWarning.image, message: L10n.Account.unauthorized) + Macros.displayImageNote(withImage: Asset.Images.iconWarning.image, message: L10n.Localizable.Account.unauthorized) isUnauthorized = false } @@ -448,9 +448,9 @@ class DashboardViewController: AutolayoutViewController { } func showAutomationAlert(onNMTDisableAction: (() -> ())? = nil) { - let alert = Macros.alert(nil, L10n.Network.Management.Tool.alert) - alert.addCancelAction(L10n.Global.close) - alert.addActionWithTitle(L10n.Network.Management.Tool.disable) { + let alert = Macros.alert(nil, L10n.Localizable.Network.Management.Tool.alert) + alert.addCancelAction(L10n.Localizable.Global.close) + alert.addActionWithTitle(L10n.Localizable.Network.Management.Tool.disable) { let preferences = Client.preferences.editable() preferences.nmtRulesEnabled = !Client.preferences.nmtRulesEnabled preferences.commit() @@ -557,7 +557,7 @@ class DashboardViewController: AutolayoutViewController { @objc private func accountDidLogout(notification: Notification) { AppPreferences.shared.todayWidgetVpnStatus = nil - AppPreferences.shared.todayWidgetButtonTitle = L10n.Today.Widget.login + AppPreferences.shared.todayWidgetButtonTitle = L10n.Localizable.Today.Widget.login presentLogin() } @@ -578,9 +578,9 @@ class DashboardViewController: AutolayoutViewController { } @objc private func presentKillSwitchAlert() { - let alert = Macros.alert(nil, L10n.Settings.Nmt.Killswitch.disabled) - alert.addCancelAction(L10n.Global.close) - alert.addActionWithTitle(L10n.Global.enable) { + let alert = Macros.alert(nil, L10n.Localizable.Settings.Nmt.Killswitch.disabled) + alert.addCancelAction(L10n.Localizable.Global.close) + alert.addActionWithTitle(L10n.Localizable.Global.enable) { let preferences = Client.preferences.editable() preferences.isPersistentConnection = true preferences.commit() @@ -595,17 +595,17 @@ class DashboardViewController: AutolayoutViewController { if Client.providers.vpnProvider.vpnStatus != .disconnected { let alert = Macros.alert( title, - L10n.Settings.Commit.Messages.shouldReconnect + L10n.Localizable.Settings.Commit.Messages.shouldReconnect ) // reconnect -> reconnect VPN and close - alert.addActionWithTitle(L10n.Settings.Commit.Buttons.reconnect) { + alert.addActionWithTitle(L10n.Localizable.Settings.Commit.Buttons.reconnect) { Client.providers.vpnProvider.reconnect(after: nil, forceDisconnect: true, { error in }) } // later -> close - alert.addCancelActionWithTitle(L10n.Settings.Commit.Buttons.later) { + alert.addCancelActionWithTitle(L10n.Localizable.Settings.Commit.Buttons.later) { } present(alert, animated: true, completion: nil) @@ -718,24 +718,24 @@ class DashboardViewController: AutolayoutViewController { } private func presentNonCompliantWifiAlert() { - let title = L10n.Dashboard.Vpn.Leakprotection.Alert.title - let message = L10n.Dashboard.Vpn.Leakprotection.Alert.message + let title = L10n.Localizable.Dashboard.Vpn.Leakprotection.Alert.title + let message = L10n.Localizable.Dashboard.Vpn.Leakprotection.Alert.message var alertActions = [WifiAlertAction]() let reconnectAction = WifiAlertAction( - title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta1, + title: L10n.Localizable.Dashboard.Vpn.Leakprotection.Alert.cta1, style: .default, action: handleDisconnectAndReconnectAction) alertActions.append(reconnectAction) let learnMoreAction = WifiAlertAction( - title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta2, + title: L10n.Localizable.Dashboard.Vpn.Leakprotection.Alert.cta2, style: .default, action: handleLearnMoreAction) alertActions.append(learnMoreAction) let cancelAction = WifiAlertAction( - title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta3, + title: L10n.Localizable.Dashboard.Vpn.Leakprotection.Alert.cta3, style: .cancel, action: nil) alertActions.append(cancelAction) @@ -744,24 +744,25 @@ class DashboardViewController: AutolayoutViewController { } private func presentNonCompliantWireguardWifiAlert() { - let title = L10n.Dashboard.Vpn.Leakprotection.Alert.title - let message = L10n.Dashboard.Vpn.Leakprotection.Alert.IKEV2.message + let title = L10n.Localizable.Dashboard.Vpn.Leakprotection.Alert.title + let message = L10n.Localizable.Dashboard.Vpn.Leakprotection.Ikev2.Alert.message var alertActions = [WifiAlertAction]() let reconnectAction = WifiAlertAction( - title: L10n.Dashboard.Vpn.Leakprotection.Alert.IKEV2.cta1, + title: L10n.Localizable.Dashboard.Vpn.Leakprotection.Ikev2.Alert.cta1, + style: .default, action: handleSwitchProtocolAction) alertActions.append(reconnectAction) let learnMoreAction = WifiAlertAction( - title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta2, + title: L10n.Localizable.Dashboard.Vpn.Leakprotection.Alert.cta2, style: .default, action: handleLearnMoreAction) alertActions.append(learnMoreAction) let cancelAction = WifiAlertAction( - title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta3, + title: L10n.Localizable.Dashboard.Vpn.Leakprotection.Alert.cta3, style: .cancel, action: nil) alertActions.append(cancelAction) @@ -804,7 +805,7 @@ class DashboardViewController: AutolayoutViewController { removeNonCompliantWifiLocalNotification() // 2. Show the local notification for the current non-compliant wifi - Macros.showLocalNotificationIfNotAlreadyPresent(NotificationCategory.nonCompliantWifi, type: NotificationCategory.nonCompliantWifi, body: L10n.LocalNotification.NonCompliantWifi.text, title: L10n.LocalNotification.NonCompliantWifi.title(currentRFC1918VulnerableWifiName), delay: 0) + Macros.showLocalNotificationIfNotAlreadyPresent(NotificationCategory.nonCompliantWifi, type: NotificationCategory.nonCompliantWifi, body: L10n.Localizable.LocalNotification.NonCompliantWifi.text, title: L10n.Localizable.LocalNotification.NonCompliantWifi.title(currentRFC1918VulnerableWifiName), delay: 0) } private func removeNonCompliantWifiLocalNotification() { @@ -814,7 +815,7 @@ class DashboardViewController: AutolayoutViewController { private func removeLeakProtectionAlert() { guard let presentedLeakProtectionAlert = UIApplication.shared.delegate?.window??.rootViewController?.presentedViewController as? UIAlertController, - presentedLeakProtectionAlert.title == L10n.Dashboard.Vpn.Leakprotection.Alert.title else { return } + presentedLeakProtectionAlert.title == L10n.Localizable.Dashboard.Vpn.Leakprotection.Alert.title else { return } presentedLeakProtectionAlert.dismiss(animated: true) } @@ -892,10 +893,10 @@ class DashboardViewController: AutolayoutViewController { let effectiveServer = Client.preferences.displayedServer let vpn = Client.providers.vpnProvider - titleLabelView.text = L10n.Dashboard.Vpn.connected+": "+effectiveServer.name(forStatus: vpn.vpnStatus) + titleLabelView.text = L10n.Localizable.Dashboard.Vpn.connected+": "+effectiveServer.name(forStatus: vpn.vpnStatus) setNavBarTheme(.green, with: titleLabelView) AppPreferences.shared.todayWidgetVpnStatus = VPNStatus.connected.rawValue - AppPreferences.shared.todayWidgetButtonTitle = L10n.Shortcuts.disconnect + AppPreferences.shared.todayWidgetButtonTitle = L10n.Localizable.Shortcuts.disconnect Macros.removeStickyNote() connectingStatus = .none @@ -920,11 +921,11 @@ class DashboardViewController: AutolayoutViewController { TextStyle.textStyle7) switch connectingStatus { case .pleaseWait: - titleLabelView.text = L10n.Server.Reconnection.Please.wait.uppercased() + titleLabelView.text = L10n.Localizable.Server.Reconnection.Please.wait.uppercased() case .takingTime, .stillLoading: - titleLabelView.text = L10n.Server.Reconnection.Still.connection.uppercased() + titleLabelView.text = L10n.Localizable.Server.Reconnection.Still.connection.uppercased() default: - titleLabelView.text = L10n.Dashboard.Vpn.connecting.uppercased() + titleLabelView.text = L10n.Localizable.Dashboard.Vpn.connecting.uppercased() } setNavBarTheme(.normal, with: titleLabelView) @@ -938,7 +939,7 @@ class DashboardViewController: AutolayoutViewController { titleLabelView.style(style: Theme.current.palette.appearance == .dark ? TextStyle.textStyle6 : TextStyle.textStyle7) - titleLabelView.text = L10n.Dashboard.Vpn.disconnecting.uppercased() + titleLabelView.text = L10n.Localizable.Dashboard.Vpn.disconnecting.uppercased() setNavBarTheme(.normal, with: titleLabelView) case .unknown: @@ -992,7 +993,7 @@ class DashboardViewController: AutolayoutViewController { toggleConnection.isIndeterminate = false toggleConnection.isWarning = true let titleLabelView = UILabel(frame: CGRect.zero) - titleLabelView.text = L10n.Dashboard.Vpn.disconnected+": "+L10n.Tiles.Nmt.Accessibility.trusted + titleLabelView.text = L10n.Localizable.Dashboard.Vpn.disconnected+": "+L10n.Localizable.Tiles.Nmt.Accessibility.trusted titleLabelView.adjustsFontSizeToFitWidth = true titleLabelView.style(style: TextStyle.textStyle6) toggleConnection.tintColor = UIColor.piaOrange @@ -1003,7 +1004,7 @@ class DashboardViewController: AutolayoutViewController { toggleConnection.isWarning = false resetNavigationBar() AppPreferences.shared.todayWidgetVpnStatus = VPNStatus.disconnected.rawValue - AppPreferences.shared.todayWidgetButtonTitle = L10n.Shortcuts.connect + AppPreferences.shared.todayWidgetButtonTitle = L10n.Localizable.Shortcuts.connect } } // MARK: Restylable diff --git a/PIA VPN/DedicatedIPTitleHeaderViewCell.swift b/PIA VPN/DedicatedIPTitleHeaderViewCell.swift index 22d6551c1..d86d41333 100644 --- a/PIA VPN/DedicatedIPTitleHeaderViewCell.swift +++ b/PIA VPN/DedicatedIPTitleHeaderViewCell.swift @@ -30,7 +30,7 @@ class DedicatedIPTitleHeaderViewCell: UITableViewCell { super.awakeFromNib() titleLabel.style(style: TextStyle.textStyle9) titleLabel.font = UIFont.mediumFontWith(size: 14.0) - titleLabel.text = L10n.Dedicated.Ip.Plural.title.uppercased() + titleLabel.text = L10n.Localizable.Dedicated.Ip.Plural.title.uppercased() self.backgroundColor = .clear } diff --git a/PIA VPN/DedicatedIpEmptyHeaderViewCell.swift b/PIA VPN/DedicatedIpEmptyHeaderViewCell.swift index d044f4970..0628496d3 100644 --- a/PIA VPN/DedicatedIpEmptyHeaderViewCell.swift +++ b/PIA VPN/DedicatedIpEmptyHeaderViewCell.swift @@ -40,10 +40,10 @@ class DedicatedIpEmptyHeaderViewCell: UITableViewCell { override func awakeFromNib() { super.awakeFromNib() self.backgroundColor = .clear - self.title.text = L10n.Dedicated.Ip.title - self.subtitle.text = L10n.Dedicated.Ip.Activation.description - self.addTokenTextfield.accessibilityLabel = L10n.Dedicated.Ip.Token.Textfield.accessibility - self.addTokenTextfield.placeholder = L10n.Dedicated.Ip.Token.Textfield.placeholder + self.title.text = L10n.Localizable.Dedicated.Ip.title + self.subtitle.text = L10n.Localizable.Dedicated.Ip.Activation.description + self.addTokenTextfield.accessibilityLabel = L10n.Localizable.Dedicated.Ip.Token.Textfield.accessibility + self.addTokenTextfield.placeholder = L10n.Localizable.Dedicated.Ip.Token.Textfield.placeholder self.addTokenTextfield.delegate = self } @@ -65,7 +65,7 @@ class DedicatedIpEmptyHeaderViewCell: UITableViewCell { private func styleButton() { addTokenButton.setRounded() addTokenButton.style(style: TextStyle.Buttons.piaGreenButton) - addTokenButton.setTitle(L10n.Dedicated.Ip.Activate.Button.title, + addTokenButton.setTitle(L10n.Localizable.Dedicated.Ip.Activate.Button.title, for: []) } diff --git a/PIA VPN/DedicatedIpRowViewCell.swift b/PIA VPN/DedicatedIpRowViewCell.swift index 309c04531..d87472431 100644 --- a/PIA VPN/DedicatedIpRowViewCell.swift +++ b/PIA VPN/DedicatedIpRowViewCell.swift @@ -38,7 +38,7 @@ class DedicatedIpRowViewCell: UITableViewCell, Restylable { self.server = server imvFlag.setImage(fromServer: server) - imvFlag.accessibilityLabel = L10n.Dedicated.Ip.Country.Flag.accessibility(server.name) + imvFlag.accessibilityLabel = L10n.Localizable.Dedicated.Ip.Country.Flag.accessibility(server.name) labelRegion.text = server.name if let pingAddress = server.bestAddress() { diff --git a/PIA VPN/DedicatedIpViewController.swift b/PIA VPN/DedicatedIpViewController.swift index 97cf12a0d..60649ef1b 100644 --- a/PIA VPN/DedicatedIpViewController.swift +++ b/PIA VPN/DedicatedIpViewController.swift @@ -56,7 +56,7 @@ class DedicatedIpViewController: AutolayoutViewController { override func viewDidLoad() { super.viewDidLoad() - title = L10n.Dedicated.Ip.title + title = L10n.Localizable.Dedicated.Ip.title let nc = NotificationCenter.default nc.addObserver(self, selector: #selector(viewHasRotated), name: UIDevice.orientationDidChangeNotification, object: nil) @@ -70,7 +70,7 @@ class DedicatedIpViewController: AutolayoutViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - styleNavigationBarWithTitle(L10n.Dedicated.Ip.title) + styleNavigationBarWithTitle(L10n.Localizable.Dedicated.Ip.title) } override func viewWillDisappear(_ animated: Bool) { @@ -79,7 +79,7 @@ class DedicatedIpViewController: AutolayoutViewController { } @objc private func viewHasRotated() { - styleNavigationBarWithTitle(L10n.Dedicated.Ip.title) + styleNavigationBarWithTitle(L10n.Localizable.Dedicated.Ip.title) } @objc private func reloadTableView() { @@ -113,7 +113,7 @@ class DedicatedIpViewController: AutolayoutViewController { override func viewShouldRestyle() { super.viewShouldRestyle() - styleNavigationBarWithTitle(L10n.Dedicated.Ip.title) + styleNavigationBarWithTitle(L10n.Localizable.Dedicated.Ip.title) if let viewContainer = viewContainer { Theme.current.applyPrincipalBackground(view) @@ -129,16 +129,16 @@ class DedicatedIpViewController: AutolayoutViewController { private var invalidTokenLocalisedString: String { get { - return L10n.Dedicated.Ip.Message.Invalid.token + return L10n.Localizable.Dedicated.Ip.Message.Invalid.token } } private func showInvalidTokenMessage() { - Macros.displayStickyNote(withMessage: invalidTokenLocalisedString, andImage: Asset.iconWarning.image) + Macros.displayStickyNote(withMessage: invalidTokenLocalisedString, andImage: Asset.Images.iconWarning.image) } private func displayErrorMessage(errorMessage: String?, displayDuration: Double? = nil) { - Macros.displayImageNote(withImage: Asset.iconWarning.image, message: errorMessage ?? invalidTokenLocalisedString, andDuration: displayDuration) + Macros.displayImageNote(withImage: Asset.Images.iconWarning.image, message: errorMessage ?? invalidTokenLocalisedString, andDuration: displayDuration) } private func handleDIPActivationError(_ error: ClientError) { @@ -148,7 +148,7 @@ class DedicatedIpViewController: AutolayoutViewController { Macros.postNotification(.PIAUnauthorized) case .throttled(let retryAfter): let retryAfterSeconds = Double(retryAfter) - let localisedThrottlingString = L10n.Dedicated.Ip.Message.Error.retryafter("\(Int(retryAfter))") + let localisedThrottlingString = L10n.Localizable.Dedicated.Ip.Message.Error.retryafter("\(Int(retryAfter))") displayErrorMessage(errorMessage: NSLocalizedString(localisedThrottlingString, comment: localisedThrottlingString), displayDuration: retryAfterSeconds) @@ -185,12 +185,12 @@ extension DedicatedIpViewController: UITableViewDelegate, UITableViewDataSource func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { - let alert = Macros.alert(nil, L10n.Dedicated.Ip.remove) - alert.addCancelActionWithTitle(L10n.Global.cancel, handler: { + let alert = Macros.alert(nil, L10n.Localizable.Dedicated.Ip.remove) + alert.addCancelActionWithTitle(L10n.Localizable.Global.cancel, handler: { self.reloadTableView() }) - alert.addActionWithTitle(L10n.Global.ok) { + alert.addActionWithTitle(L10n.Localizable.Global.ok) { self.confirmDelete(row: indexPath.row) } @@ -245,13 +245,13 @@ extension DedicatedIpViewController: UITableViewDelegate, UITableViewDataSource extension DedicatedIpViewController: DedicatedIpEmptyHeaderViewCellDelegate { func handleDIPActivation(with token: String, cell: DedicatedIpEmptyHeaderViewCell) { if let timeUntilNextTry = timeToRetryDIP?.timeSinceNow() { - displayErrorMessage(errorMessage: L10n.Dedicated.Ip.Message.Error.retryafter("\(Int(timeUntilNextTry))"), displayDuration: timeUntilNextTry) + displayErrorMessage(errorMessage: L10n.Localizable.Dedicated.Ip.Message.Error.retryafter("\(Int(timeUntilNextTry))"), displayDuration: timeUntilNextTry) return } if token.isEmpty { - Macros.displayStickyNote(withMessage: L10n.Dedicated.Ip.Message.Incorrect.token, - andImage: Asset.iconWarning.image) + Macros.displayStickyNote(withMessage: L10n.Localizable.Dedicated.Ip.Message.Incorrect.token, + andImage: Asset.Images.iconWarning.image) return } @@ -271,11 +271,11 @@ extension DedicatedIpViewController: DedicatedIpEmptyHeaderViewCellDelegate { } switch dipServer?.dipStatus { case .active: - Macros.displaySuccessImageNote(withImage: Asset.iconWarning.image, message: L10n.Dedicated.Ip.Message.Valid.token) + Macros.displaySuccessImageNote(withImage: Asset.Images.iconWarning.image, message: L10n.Localizable.Dedicated.Ip.Message.Valid.token) case .expired: - print(L10n.Dedicated.Ip.Message.Expired.token) // we dont show the message to the user + print(L10n.Localizable.Dedicated.Ip.Message.Expired.token) // we dont show the message to the user default: - Macros.displayStickyNote(withMessage: self?.invalidTokenLocalisedString ?? "", andImage: Asset.iconWarning.image) + Macros.displayStickyNote(withMessage: self?.invalidTokenLocalisedString ?? "", andImage: Asset.Images.iconWarning.image) } NotificationCenter.default.post(name: .DedicatedIpReload, object: nil) NotificationCenter.default.post(name: .PIAThemeDidChange, object: nil) diff --git a/PIA VPN/DedicatedRegionCell.swift b/PIA VPN/DedicatedRegionCell.swift index 25f063efb..fe4890900 100644 --- a/PIA VPN/DedicatedRegionCell.swift +++ b/PIA VPN/DedicatedRegionCell.swift @@ -51,7 +51,7 @@ class DedicatedRegionCell: UITableViewCell, Restylable { imvFlag.setImage(fromServer: server) labelRegion.text = server.name labelIP.text = server.wireGuardAddressesForUDP?.first?.ip ?? "" - labelDedicatedIPTitle.text = L10n.Dedicated.Ip.title.uppercased() + labelDedicatedIPTitle.text = L10n.Localizable.Dedicated.Ip.title.uppercased() iconSelected = isSelected @@ -117,10 +117,10 @@ class DedicatedRegionCell: UITableViewCell, Restylable { private func updateFavoriteImage() { self.isFavorite ? - self.favoriteImageView.image = Asset.Piax.Global.favoriteSelected.image : + self.favoriteImageView.image = Asset.Images.Piax.Global.favoriteSelected.image : Theme.current.applyFavoriteUnselectedImage(self.favoriteImageView) favoriteButton.accessibilityLabel = self.isFavorite ? - L10n.Region.Accessibility.favorite : - L10n.Region.Accessibility.unfavorite + L10n.Localizable.Region.Accessibility.favorite : + L10n.Localizable.Region.Accessibility.unfavorite } } diff --git a/PIA VPN/ExpirationCell.swift b/PIA VPN/ExpirationCell.swift index 1cfe03a1f..7a9fac549 100644 --- a/PIA VPN/ExpirationCell.swift +++ b/PIA VPN/ExpirationCell.swift @@ -33,8 +33,8 @@ class ExpirationCell: UITableViewCell, Restylable { override func awakeFromNib() { super.awakeFromNib() - labelUpgrade.text = L10n.Menu.Expiration.upgrade - imvAccessory.image = Asset.accessoryExpire.image.withRenderingMode(.alwaysTemplate) + labelUpgrade.text = L10n.Localizable.Menu.Expiration.upgrade + imvAccessory.image = Asset.Images.accessoryExpire.image.withRenderingMode(.alwaysTemplate) imvAccessory.tintColor = .white } @@ -43,14 +43,14 @@ class ExpirationCell: UITableViewCell, Restylable { let timeLeftString: String if let day = timeLeft.day, (day > 0) { - timeLeftString = L10n.Menu.Expiration.days(day) + timeLeftString = L10n.Localizable.Menu.Expiration.days(day) } else if let hour = timeLeft.hour, (hour > 0) { - timeLeftString = L10n.Menu.Expiration.hours(hour) + timeLeftString = L10n.Localizable.Menu.Expiration.hours(hour) } else { - timeLeftString = L10n.Menu.Expiration.oneHour + timeLeftString = L10n.Localizable.Menu.Expiration.oneHour } - let prefix = L10n.Menu.Expiration.expiresIn + let prefix = L10n.Localizable.Menu.Expiration.expiresIn labelWarning.text = "\(prefix) \(timeLeftString)".uppercased() } diff --git a/PIA VPN/GDPRViewController.swift b/PIA VPN/GDPRViewController.swift new file mode 100644 index 000000000..ea13fbd4d --- /dev/null +++ b/PIA VPN/GDPRViewController.swift @@ -0,0 +1,82 @@ +// +// GDPRViewController.swift +// PIALibrary-iOS +// +// Created by Jose Antonio Blaya Garcia on 08/03/2019. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit +import PIALibrary + +public protocol GDPRDelegate: class { + + func gdprViewWasAccepted() + + func gdprViewWasRejected() + +} + +class GDPRViewController: AutolayoutViewController { + + @IBOutlet private weak var labelCollectTitle: UILabel! + @IBOutlet private weak var labelCollectDescription: UILabel! + @IBOutlet private weak var labelUseDataDescription: UILabel! + + @IBOutlet private weak var acceptButton: PIAButton! + @IBOutlet private weak var closeButton: UIButton! + + weak var delegate: GDPRDelegate? = nil + + override func viewDidLoad() { + super.viewDidLoad() + + self.labelCollectTitle.text = L10n.Welcome.Gdpr.Collect.Data.title + self.labelCollectDescription.text = L10n.Welcome.Gdpr.Collect.Data.description + self.labelUseDataDescription.text = L10n.Welcome.Gdpr.Usage.Data.description + self.acceptButton.setTitle(L10n.Welcome.Gdpr.Accept.Button.title, for: []) + } + + // MARK: Restylable + + override func viewShouldRestyle() { + super.viewShouldRestyle() + + Theme.current.applyTitle(labelCollectTitle, appearance: .dark) + Theme.current.applySubtitle(labelCollectDescription) + Theme.current.applySubtitle(labelUseDataDescription) + + acceptButton.setRounded() + acceptButton.style(style: TextStyle.Buttons.piaGreenButton) + + } + + @IBAction func accept(_ sender: Any) { + if let delegate = delegate { + delegate.gdprViewWasAccepted() + } + dismissModal() + } + + @IBAction func reject(_ sender: Any) { + if let delegate = delegate { + delegate.gdprViewWasRejected() + } + dismissModal() + } + +} diff --git a/PIA VPN/GeneralSettingsViewController.swift b/PIA VPN/GeneralSettingsViewController.swift index 41f1e4e3a..134110f00 100644 --- a/PIA VPN/GeneralSettingsViewController.swift +++ b/PIA VPN/GeneralSettingsViewController.swift @@ -52,7 +52,7 @@ class GeneralSettingsViewController: PIABaseSettingsViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - styleNavigationBarWithTitle(L10n.Settings.Section.general) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Section.general) } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -81,7 +81,7 @@ class GeneralSettingsViewController: PIABaseSettingsViewController { override func viewShouldRestyle() { super.viewShouldRestyle() - styleNavigationBarWithTitle(L10n.Settings.Section.general) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Section.general) // XXX: for some reason, UITableView is not affected by appearance updates if let viewContainer = viewContainer { Theme.current.applyPrincipalBackground(view) @@ -107,7 +107,7 @@ extension GeneralSettingsViewController: UITableViewDelegate, UITableViewDataSou cell.textLabel?.numberOfLines = 0 cell.textLabel?.style(style: TextStyle.textStyle21) cell.backgroundColor = .clear - cell.textLabel?.text = L10n.Settings.Reset.footer + cell.textLabel?.text = L10n.Localizable.Settings.Reset.footer return cell } return nil diff --git a/PIA VPN/GetStartedViewController.swift b/PIA VPN/GetStartedViewController.swift new file mode 100644 index 000000000..0f249b09a --- /dev/null +++ b/PIA VPN/GetStartedViewController.swift @@ -0,0 +1,692 @@ +// +// GetStartedViewController.swift +// PIALibrary-iOS +// +// Created by Jose Antonio Blaya Garcia on 26/10/2018. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit +import PIALibrary + +public class GetStartedViewController: PIAWelcomeViewController { + + private struct Cells { + static let plan = "PlanCell" + } + + private static let smallDeviceMaxViewHeight: CGFloat = 520 + private static let maxViewHeight: CGFloat = 500 + private static let extraViewButtonsHeight: CGFloat = 48 + private static let defaultViewHeight: CGFloat = 276 + + @IBOutlet private weak var spinner: UIActivityIndicatorView! + + @IBOutlet private weak var loginButton: PIAButton! + @IBOutlet private weak var buyButton: UIButton! + @IBOutlet private weak var subscribeNowButton: PIAButton! + @IBOutlet private weak var subscribeNowTitle: UILabel! + @IBOutlet private weak var subscribeNowDescription: UILabel! + + @IBOutlet private weak var scrollContent: UIScrollView! + @IBOutlet private weak var scrollBackground: UIImageView! + @IBOutlet private weak var viewContent: UIView! + @IBOutlet private weak var pageControl: PIAPageControl! + @IBOutlet weak var hiddenButtonsView: UIView! + + @IBOutlet private weak var textAgreement: UITextView! + @IBOutlet weak var visualEffectView: UIVisualEffectView! + + private var isFetchingProducts = true + private var isFetchingFF = true + + private var signupEmail: String? + private var signupTransaction: InAppTransaction? + private var isPurchasing = false + private var isNewFlow = false + + weak var completionDelegate: WelcomeCompletionDelegate? + + @IBOutlet private weak var buttonViewConstraintHeight: NSLayoutConstraint! + @IBOutlet private weak var hiddenButtonsConstraintHeight: NSLayoutConstraint! + + //New flow + var allNewPlans: [PurchasePlan] = [.dummy, .dummy] + + @IBOutlet private weak var containerNewFlow: UIView! + @IBOutlet private weak var walkthroughImage: UIImageView! + @IBOutlet private weak var walkthroughTitle: UILabel! + @IBOutlet private weak var walkthroughDescription: UILabel! + + @IBOutlet private weak var collectionPlans: UICollectionView! + @IBOutlet private weak var newSubscribeNowButton: PIAButton! + @IBOutlet private weak var newLoginButton: PIAButton! + @IBOutlet private weak var newTextAgreement: UITextView! + + private var buttonViewIsExpanded = false { + didSet { + self.updateButtonView() + } + } + + private lazy var allData: [WalkthroughPageView.PageData] = [ + WalkthroughPageView.PageData( + title: L10n.Signup.Walkthrough.Page._1.title, + detail: L10n.Signup.Walkthrough.Page._1.description, + image: Asset.Ui.imageWalkthrough1.image + ), + WalkthroughPageView.PageData( + title: L10n.Signup.Walkthrough.Page._2.title, + detail: L10n.Signup.Walkthrough.Page._2.description, + image: Asset.Ui.imageWalkthrough2.image + ), + WalkthroughPageView.PageData( + title: L10n.Signup.Walkthrough.Page._3.title, + detail: L10n.Signup.Walkthrough.Page._3.description, + image: Asset.Ui.imageWalkthrough3.image + ) + ] + + private var tutorialViews: [WalkthroughPageView] = [] + + private var currentPageIndex = 0 + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override public var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return .portrait + } + + private func setupNavigationBarButtons() { + + navigationItem.leftBarButtonItem = nil + navigationItem.rightBarButtonItem = nil + + } + + override public func viewDidLoad() { + + handleInitialStatus() + setupNavigationBarButtons() + self.containerNewFlow.isHidden = true + self.visualEffectView.isHidden = true + self.pageControl.isHidden = true + collectionPlans.isUserInteractionEnabled = false + collectionPlans.delegate = self + collectionPlans.dataSource = self + + self.walkthroughTitle.text = L10n.Signup.Walkthrough.Page._2.title + self.walkthroughDescription.text = L10n.Signup.Walkthrough.Page._2.description + "\n" + L10n.Signup.Purchase.Trials.intro + ". " + + allNewPlans = [.dummy, .dummy] + completionDelegate = self + + view.backgroundColor = UIColor.piaGrey1 + + let agreement = composeAgreementText(message: L10n.Welcome.Agreement.message("")) + + textAgreement.attributedText = Theme.current.agreementText( + withMessage: agreement, + tos: L10n.Welcome.Agreement.Message.tos, + tosUrl: Client.configuration.tosUrl, + privacy: L10n.Welcome.Agreement.Message.privacy, + privacyUrl: Client.configuration.privacyUrl + ) + newTextAgreement.attributedText = Theme.current.agreementText( + withMessage: agreement, + tos: L10n.Welcome.Agreement.Message.tos, + tosUrl: Client.configuration.tosUrl, + privacy: L10n.Welcome.Agreement.Message.privacy, + privacyUrl: Client.configuration.privacyUrl + ) + + + let nc = NotificationCenter.default + nc.addObserver(self, selector: #selector(recoverAccount), name: .PIARecoverAccount, object: nil) + nc.addObserver(self, selector: #selector(productsDidFetch(notification:)), name: .__InAppDidFetchProducts, object: nil) + nc.addObserver(self, selector: #selector(featureFlagsDidFetch(notification:)), name: .__AppDidFetchFeatureFlags, object: nil) + + self.styleButtons() + visualEffectView.clipsToBounds = true + visualEffectView.layer.cornerRadius = 15 + visualEffectView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner] + + fireTimeoutForFeatureFlags() + + super.viewDidLoad() + + } + + private func composeAgreementText(message: String) -> String { + + var agreement = message + + if isNewFlow, + let index = agreement.range(of: "\n\n", options: .backwards)?.upperBound { + agreement = String(agreement.suffix(from: index)) + } + + return agreement + } + + @objc func respondToSwipeGesture(gesture: UIGestureRecognizer) { + + if let swipeGesture = gesture as? UISwipeGestureRecognizer { + + switch swipeGesture.direction { + case UISwipeGestureRecognizer.Direction.down: + buttonViewIsExpanded = false + case UISwipeGestureRecognizer.Direction.up: + buttonViewIsExpanded = true + default: + break + } + } + + } + + override public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + coordinator.animate(alongsideTransition: { (context) in + self.scrollToPage(self.currentPageIndex, animated: false, force: true, width: size.width) + }, completion: nil) + } + + // MARK: Actions + @IBAction func confirmPlan() { + + if let index = selectedPlanIndex { + let plan = allNewPlans[index] + self.startPurchaseProcessWithEmail("", andPlan: plan) + } + + } + + private func startPurchaseProcessWithEmail(_ email: String, + andPlan plan: PurchasePlan) { + + guard !Client.store.hasUncreditedTransactions else { + let alert = Macros.alert( + nil, + L10n.Signup.Purchase.Uncredited.Alert.message + ) + alert.addCancelAction(L10n.Signup.Purchase.Uncredited.Alert.Button.cancel) + alert.addActionWithTitle(L10n.Signup.Purchase.Uncredited.Alert.Button.recover) { + self.navigationController?.popToRootViewController(animated: true) + Macros.postNotification(.PIARecoverAccount) + } + present(alert, animated: true, completion: nil) + return + + } + + isPurchasing = true + disableInteractions(fully: true) + self.showLoadingAnimation() + + preset.accountProvider.purchase(plan: plan.plan) { (transaction, error) in + self.isPurchasing = false + self.enableInteractions() + self.hideLoadingAnimation() + + guard let transaction = transaction else { + if let error = error { + let message = error.localizedDescription + Macros.displayImageNote(withImage: Asset.Images.iconWarning.image, + message: message) + } + return + } + self.signupEmail = email + self.signupTransaction = transaction + self.perform(segue: StoryboardSegue.Welcome.signupViaPurchaseSegue) + } + + } + + + @IBAction private func scrollPage(_ sender: UIPageControl) { + scrollToPage(sender.currentPage, animated: true) + } + + public static func withPurchase(preset: Preset? = nil, delegate: PIAWelcomeViewControllerDelegate? = nil) -> UIViewController { + if let vc = StoryboardScene.Welcome.storyboard.instantiateViewController(withIdentifier: "PIAWelcomeViewController") as? PIAWelcomeViewController { + if let customPreset = preset { + vc.preset = customPreset + } + vc.delegate = delegate + let navigationController = UINavigationController(rootViewController: vc) + return navigationController + } + return UIViewController() + } + + public override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + + if (segue.identifier == StoryboardSegue.Welcome.signupViaPurchaseSegue.rawValue) { + let nav = segue.destination as! UINavigationController + let vc = nav.topViewController as! SignupInProgressViewController + + guard let email = signupEmail else { + fatalError("Signing up and signupEmail is not set") + } + var metadata = SignupMetadata(email: email) + metadata.title = L10n.Signup.InProgress.title + metadata.bodySubtitle = L10n.Signup.InProgress.message + vc.metadata = metadata + vc.signupRequest = SignupRequest(email: email, transaction: signupTransaction) + vc.preset = preset + vc.completionDelegate = completionDelegate + } + + guard let vc = segue.destination as? PIAWelcomeViewController else { + return + } + + vc.delegate = self.delegate + vc.preset = self.preset + + switch segue.identifier { + case StoryboardSegue.Welcome.purchaseVPNPlanSegue.rawValue: + vc.preset.pages = .purchase + case StoryboardSegue.Welcome.loginAccountSegue.rawValue: + vc.preset.pages = .login + case StoryboardSegue.Welcome.restorePurchaseSegue.rawValue: + vc.preset.pages = .restore + default: + break + } + + } + + public func handleInitialStatus() { + + if Client.configuration.featureFlags.contains(Client.FeatureFlags.showNewInitialScreen) { + isFetchingFF = false + isNewFlow = true + } + + if let _ = preset.accountProvider.planProducts { + isFetchingProducts = false + } + + if !isFetchingProducts && !isFetchingProducts { + self.handleVisibilityOfVIews() + } + + } + + // MARK: Orientation + @objc func onlyPortrait() -> Void {} + + // MARK: Notifications + + @objc private func productsDidFetch(notification: Notification) { + isFetchingProducts = false + let products: [Plan: InAppProduct] = notification.userInfo(for: .products) + DispatchQueue.main.async { + self.handleVisibilityOfVIews() + self.refreshPlans(products) + self.enableInteractions() + } + } + + @objc private func featureFlagsDidFetch(notification: Notification) { + isFetchingFF = false + self.isNewFlow = Client.configuration.featureFlags.contains(Client.FeatureFlags.showNewInitialScreen) + self.handleVisibilityOfVIews() + } + + private func handleVisibilityOfVIews() { + if !isFetchingFF && !isFetchingProducts { + if !isPurchasing { + self.hideLoadingAnimation() + } + + DispatchQueue.main.async { + + self.containerNewFlow.isHidden = !self.isNewFlow + self.scrollContent.isHidden = self.isNewFlow + + if self.isNewFlow { + if let products = self.preset.accountProvider.planProducts { + self.refreshPlans(products) + } + } else { + self.visualEffectView.isHidden = false + self.pageControl.isHidden = false + + let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture)) + swipeDown.direction = UISwipeGestureRecognizer.Direction.down + self.view.addGestureRecognizer(swipeDown) + + let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture)) + swipeUp.direction = UISwipeGestureRecognizer.Direction.up + self.view.addGestureRecognizer(swipeUp) + + self.subscribeNowTitle.text = L10n.Signup.Purchase.Trials.intro + } + self.addPages() + self.pageControl.numberOfPages = self.allData.count + } + + } + + } + + /// :nodoc: + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + setupNavigationBarButtons() + UIDevice.current.setValue(Int(UIInterfaceOrientation.portrait.rawValue), forKey: "orientation") + if let products = preset.accountProvider.planProducts { + refreshPlans(products) + } else { + showLoadingAnimation() + disableInteractions(fully: false) + } + } + + public override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + setupNavigationBarButtons() + } + + private func styleButtons() { + loginButton.setRounded() + subscribeNowButton.setRounded() + newLoginButton.setRounded() + newSubscribeNowButton.setRounded() + + subscribeNowButton.style(style: TextStyle.Buttons.piaGreenButton) + loginButton.style(style: TextStyle.Buttons.piaPlainTextButton) + newSubscribeNowButton.style(style: TextStyle.Buttons.piaGreenButton) + newLoginButton.style(style: TextStyle.Buttons.piaPlainTextButton) + + loginButton.setTitle(L10n.Welcome.Login.submit.uppercased(), + for: []) + newLoginButton.setTitle(L10n.Welcome.Login.submit.uppercased(), + for: []) + + loginButton.accessibilityIdentifier = Accessibility.Id.Login.submit + newLoginButton.accessibilityIdentifier = Accessibility.Id.Login.submitNew + + buyButton.setTitle(L10n.Signup.Purchase.Trials.All.plans, + for: []) + subscribeNowButton.setTitle(L10n.Signup.Purchase.Subscribe.now.uppercased(), + for: []) + newSubscribeNowButton.setTitle(L10n.Signup.Purchase.Subscribe.now.uppercased(), + for: []) + } + + // MARK: Helpers + + private func updateButtonView() { + UIView.animate(withDuration: 0.3, animations: { + if self.buttonViewIsExpanded { + + var maxViewHeight: CGFloat = GetStartedViewController.maxViewHeight + switch UIDevice().type { + case .iPhoneSE, .iPhone5, .iPhone5S: + maxViewHeight = GetStartedViewController.smallDeviceMaxViewHeight + default: break + } + + self.buttonViewConstraintHeight.constant = maxViewHeight + self.hiddenButtonsConstraintHeight.constant = GetStartedViewController.extraViewButtonsHeight + self.hiddenButtonsView.alpha = 1 + } else { + self.buttonViewConstraintHeight.constant = GetStartedViewController.defaultViewHeight + self.hiddenButtonsConstraintHeight.constant = 0 + self.hiddenButtonsView.alpha = 0 + } + self.view.layoutIfNeeded() + self.visualEffectView.layoutIfNeeded() + }) + } + + private func disableInteractions(fully: Bool) { + self.subscribeNowButton.isEnabled = false + self.buyButton.isEnabled = false + self.spinner.startAnimating() + } + + private func enableInteractions() { + if !isPurchasing { //dont reenable the screen if we are still purchasing + self.subscribeNowButton.isEnabled = true + self.buyButton.isEnabled = true + self.spinner.stopAnimating() + } + } + + private func fireTimeoutForFeatureFlags() { + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + // Cancel the FF request + if self.isFetchingFF { + NotificationCenter.default.removeObserver(self, name: .__AppDidFetchFeatureFlags, object: nil) + self.isFetchingFF = false + self.isNewFlow = false + self.handleVisibilityOfVIews() + } + } + } + public func navigateToLoginView() { + self.performSegue(withIdentifier: StoryboardSegue.Welcome.loginAccountSegue.rawValue, + sender: nil) + } + + // MARK: Onboarding walkthrough + + private func addPages() { + let parent = viewContent! + var constraints: [NSLayoutConstraint] = [] + var previousPage: UIView? + + for (i, data) in allData.enumerated() { + let page = WalkthroughPageView(data: data) + tutorialViews.append(page) + page.translatesAutoresizingMaskIntoConstraints = false + parent.addSubview(page) + + // size + constraints.append(page.widthAnchor.constraint(equalTo: scrollContent.widthAnchor)) + constraints.append(page.centerYAnchor.constraint(equalTo: parent.centerYAnchor)) + constraints.append(page.topAnchor.constraint(greaterThanOrEqualTo: parent.topAnchor, constant: 20.0)) + constraints.append(page.bottomAnchor.constraint(lessThanOrEqualTo: parent.bottomAnchor, constant: -20.0)) + + // left + if let previousPage = previousPage { + constraints.append(page.leftAnchor.constraint(equalTo: previousPage.rightAnchor)) + } else { + constraints.append(page.leftAnchor.constraint(equalTo: parent.leftAnchor)) + } + + // right + if (i == allData.count - 1) { + constraints.append(page.rightAnchor.constraint(equalTo: parent.rightAnchor)) + } + + previousPage = page + } + + NSLayoutConstraint.activate(constraints) + } + + private func scrollToPage(_ pageIndex: Int, animated: Bool) { + scrollToPage(pageIndex, animated: animated, force: false, width: scrollContent.frame.size.width) + } + + private func scrollToPage(_ pageIndex: Int, animated: Bool, force: Bool, width: CGFloat) { + guard (force || (pageIndex != currentPageIndex)) else { + return + } + currentPageIndex = pageIndex + scrollContent.setContentOffset(CGPoint(x: CGFloat(pageIndex * Int(width)), y: 0), animated: animated) + pageControl.currentPage = pageIndex + updateButtonsToCurrentPage() + } + + private func updateButtonsToCurrentPage() { + guard (currentPageIndex < allData.count - 1) else { + return + } + } + + + // MARK: Restylable + + /// :nodoc: + public override func viewShouldRestyle() { + super.viewShouldRestyle() + navigationItem.titleView = NavigationLogoView() + Theme.current.applyNavigationBarStyle(to: self) + + Theme.current.applyTitle(subscribeNowDescription, appearance: .light) + Theme.current.applySubtitle(subscribeNowTitle) + Theme.current.applyTitle(walkthroughTitle, appearance: .light) + Theme.current.applySubtitle(walkthroughDescription) + + Theme.current.applyTransparentButton(loginButton, + withSize: 1.0) + Theme.current.applyTransparentButton(newLoginButton, + withSize: 1.0) + Theme.current.applyButtonLabelMediumStyle(buyButton) + Theme.current.applyScrollableMap(scrollBackground) + Theme.current.applyPageControl(pageControl) + Theme.current.applyLinkAttributes(textAgreement) + Theme.current.applyLinkAttributes(newTextAgreement) + Theme.current.applyActivityIndicator(spinner) + tutorialViews.forEach({ + $0.applyStyles() + }) + + } + + // MARK: Notification event + @objc private func recoverAccount() { + self.performSegue(withIdentifier: StoryboardSegue.Welcome.restorePurchaseSegue.rawValue, + sender: nil) + } + + // MARK: InApp refresh plan + private func refreshPlans(_ plans: [Plan: InAppProduct]) { + if let yearly = plans[.yearly] { + let purchase = PurchasePlan( + plan: .yearly, + product: yearly, + monthlyFactor: 12.0 + ) + + purchase.title = L10n.Welcome.Plan.Yearly.title + let currencySymbol = purchase.product.priceLocale.currencySymbol ?? "" + purchase.detail = L10n.Welcome.Plan.Yearly.detailFormat(currencySymbol, purchase.product.price.description) + purchase.bestValue = true + let price = L10n.Welcome.Plan.Yearly.detailFormat(currencySymbol, purchase.product.price.description) + allNewPlans[0] = purchase + + DispatchQueue.main.async { [weak self] in + if let label = self?.subscribeNowDescription { + label.text = L10n.Signup.Purchase.Trials.Price.after(price) + Theme.current.makeSmallLabelToStandOut(label, + withTextToStandOut: price) + } + if let label = self?.walkthroughDescription { + label.text = L10n.Signup.Walkthrough.Page._2.description + "\n" + L10n.Signup.Purchase.Trials.intro + ". " + L10n.Signup.Purchase.Trials.Price.after(price) + Theme.current.makeSmallLabelToStandOut(label, + withTextToStandOut: price) + } + let agreement = self?.composeAgreementText(message: L10n.Welcome.Agreement.message(price)) ?? L10n.Welcome.Agreement.message(price) + if let label = self?.textAgreement { + label.attributedText = Theme.current.agreementText( + withMessage: agreement, + tos: L10n.Welcome.Agreement.Message.tos, + tosUrl: Client.configuration.tosUrl, + privacy: L10n.Welcome.Agreement.Message.privacy, + privacyUrl: Client.configuration.privacyUrl + ) + } + if let label = self?.newTextAgreement { + label.attributedText = Theme.current.agreementText( + withMessage: agreement, + tos: L10n.Welcome.Agreement.Message.tos, + tosUrl: Client.configuration.tosUrl, + privacy: L10n.Welcome.Agreement.Message.privacy, + privacyUrl: Client.configuration.privacyUrl + ) + } + } + + } + + if let monthly = plans[.monthly] { + let purchase = PurchasePlan( + plan: .monthly, + product: monthly, + monthlyFactor: 1.0 + ) + purchase.title = L10n.Welcome.Plan.Monthly.title + purchase.bestValue = false + + allNewPlans[1] = purchase + } + + collectionPlans.isUserInteractionEnabled = true + collectionPlans.reloadData() + if (selectedPlanIndex == nil) { + selectedPlanIndex = 0 + } + collectionPlans.selectItem(at: IndexPath(row: selectedPlanIndex!, section: 0), animated: false, scrollPosition: []) + + } + +} + +extension GetStartedViewController: UIScrollViewDelegate { + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + currentPageIndex = Int(scrollView.contentOffset.x / scrollView.bounds.size.width) + pageControl.currentPage = currentPageIndex + updateButtonsToCurrentPage() + } +} + +extension GetStartedViewController: UICollectionViewDataSource, UICollectionViewDelegate { + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return allNewPlans.count + } + + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let plan = allNewPlans[indexPath.row] + let cell = collectionPlans.dequeueReusableCell(withReuseIdentifier: Cells.plan, for: indexPath) as! PurchasePlanCell + cell.fill(plan: plan) + cell.isSelected = (indexPath.row == selectedPlanIndex) + return cell + } + + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + selectedPlanIndex = indexPath.row + } +} + +extension GetStartedViewController: UICollectionViewDelegateFlowLayout { + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let itemWidth = collectionView.bounds.size.width + let itemHeight = (collectionView.bounds.size.height - 20) / 2.0 + return CGSize(width: itemWidth, + height: itemHeight) + } +} diff --git a/PIA VPN/LoginViewController.swift b/PIA VPN/LoginViewController.swift new file mode 100644 index 000000000..1455943e0 --- /dev/null +++ b/PIA VPN/LoginViewController.swift @@ -0,0 +1,410 @@ +// +// LoginViewController.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 10/19/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit +import SwiftyBeaver +import PIALibrary + +private let log = SwiftyBeaver.self + +class LoginViewController: AutolayoutViewController, WelcomeChild, PIAWelcomeViewControllerDelegate { + + private enum LoginOption { + case credentials + case receipt + case magicLink + } + + @IBOutlet private weak var scrollView: UIScrollView! + + @IBOutlet private weak var labelTitle: UILabel! + + @IBOutlet private weak var textUsername: BorderedTextField! + + @IBOutlet private weak var textPassword: BorderedTextField! + + @IBOutlet private weak var buttonLogin: PIAButton! + + @IBOutlet private weak var couldNotGetPlanButton: UIButton! + + @IBOutlet private weak var loginWithReceipt: UIButton! + + @IBOutlet private weak var loginWithLink: UIButton! + + var preset: Preset? + private weak var delegate: PIAWelcomeViewControllerDelegate? + + var omitsSiblingLink = false + + weak var completionDelegate: WelcomeCompletionDelegate? + + private var signupEmail: String? + + private var isLogging = false + + private var timeToRetryCredentials: TimeInterval? = nil + private var timeToRetryReceipt: TimeInterval? = nil + private var timeToRetryMagicLink: TimeInterval? = nil + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override func viewDidLoad() { + super.viewDidLoad() + + guard let preset = self.preset else { + fatalError("Preset not propagated") + } + + NotificationCenter.default.addObserver(self, selector: #selector(finishLoginWithMagicLink(notification:)), name: .PIAFinishLoginWithMagicLink, object: nil) + + labelTitle.text = L10n.Welcome.Login.title + textUsername.placeholder = L10n.Welcome.Login.Username.placeholder + textPassword.placeholder = L10n.Welcome.Login.Password.placeholder + + textUsername.accessibilityIdentifier = Accessibility.Id.Login.username + textPassword.accessibilityIdentifier = Accessibility.Id.Login.password + + textUsername.text = preset.loginUsername + textPassword.text = preset.loginPassword + + styleButtons() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + enableInteractions(true) + } + + override func didRefreshOrientationConstraints() { + scrollView.isScrollEnabled = (traitCollection.verticalSizeClass == .compact) + } + + public override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + + + guard let vc = segue.destination as? PIAWelcomeViewController else { + return + } + + vc.delegate = delegate ?? self + if let preset = preset { + vc.preset = preset + } + + switch segue.identifier { + case StoryboardSegue.Welcome.restoreLoginPurchaseSegue.rawValue: + vc.preset.pages = .restore + case StoryboardSegue.Welcome.expiredAccountPurchaseSegue.rawValue: + vc.preset.isExpired = true + vc.preset.pages = .purchase + default: + break + } + + } + // MARK: Actions + @IBAction private func logInWithLink(_ sender: Any?) { + if let timeUntilNextTry = timeToRetryMagicLink?.timeSinceNow() { + displayErrorMessage(errorMessage: L10n.Welcome.Login.Error.throttled("\(Int(timeUntilNextTry))"), displayDuration: timeUntilNextTry) + return + } + + let storyboard = UIStoryboard(name: "Welcome", bundle: Bundle.main) + if let magicLinkLoginViewController = storyboard.instantiateViewController(withIdentifier: "MagicLinkLoginViewController") as? MagicLinkLoginViewController { + let alert = Macros.alert(magicLinkLoginViewController) + alert.addCancelAction(L10n.Signup.Purchase.Uncredited.Alert.Button.cancel) + alert.addActionWithTitle(L10n.Welcome.Login.Magic.Link.send.uppercased(), handler: { + + let email = magicLinkLoginViewController.email() + guard Validator.validate(email: email) else { + Macros.displayImageNote(withImage: Asset.Images.iconWarning.image, + message: L10n.Welcome.Login.Magic.Link.Invalid.email) + return + } + + guard !self.isLogging else { + return + } + + self.showLoadingAnimation() + self.preset?.accountProvider.loginUsingMagicLink(withEmail: email, { (error) in + + self.hideLoadingAnimation() + guard error == nil else { + self.handleLoginFailed(error, loginOption: .magicLink) + return + } + + Macros.displaySuccessImageNote(withImage: Asset.Images.iconWarning.image, + message: L10n.Welcome.Login.Magic.Link.response) + }) + + }) + present(alert, animated: true, completion: nil) + } + + } + + @objc private func finishLoginWithMagicLink(notification: Notification) { + + if let userInfo = notification.userInfo, let error = userInfo[NotificationKey.error] as? Error { + displayErrorMessage(errorMessage: L10n.Welcome.Purchase.Error.Connectivity.title) + return + } + + self.completionDelegate?.welcomeDidLogin(withUser: + UserAccount(credentials: Credentials(username: "", + password: ""), + info: nil), + topViewController: self) + } + + @IBAction private func logInWithReceipt(_ sender: Any?) { + if let timeUntilNextTry = timeToRetryReceipt?.timeSinceNow() { + displayErrorMessage(errorMessage: L10n.Welcome.Login.Error.throttled("\(Int(timeUntilNextTry))"), displayDuration: timeUntilNextTry) + return + } + + guard !isLogging else { + return + } + + guard let receipt = Client.store.paymentReceipt else { + return + } + + let request = LoginReceiptRequest(receipt: receipt) + + prepareLogin() + preset?.accountProvider.login(with: request, { userAccount, error in + self.handleLoginResult(user: userAccount, error: error, loginOption: .receipt) + }) + } + + @IBAction private func logIn(_ sender: Any?) { + if let timeUntilNextTry = timeToRetryCredentials?.timeSinceNow() { + displayErrorMessage(errorMessage: L10n.Welcome.Login.Error.throttled("\(Int(timeUntilNextTry))"), displayDuration: timeUntilNextTry) + return + } + + guard !isLogging else { + return + } + + guard let credentials = getValidCredentials() else { + return + } + + let request = LoginRequest(credentials: credentials) + + prepareLogin() + preset?.accountProvider.login(with: request, { userAccount, error in + self.handleLoginResult(user: userAccount, error: error, loginOption: .credentials) + }) + } + + private func getValidCredentials() -> Credentials? { + guard let username = getValidTextFrom(textField: textUsername) else { + handleUsernameFieldInvalid() + return nil + } + + self.status = .restore(element: textUsername) + + guard let password = getValidTextFrom(textField: textPassword) else { + handleLoginFieldInvalid(textField: textPassword) + return nil + } + + self.status = .restore(element: textPassword) + self.status = .initial + + view.endEditing(false) + + return Credentials(username: username, password: password) + } + + private func getValidTextFrom(textField: UITextField) -> String? { + guard let text = textField.text?.trimmed(), !text.isEmpty else { + return nil + } + return text + } + + private func handleUsernameFieldInvalid() { + handleLoginFieldInvalid(textField: textUsername) + if textPassword.text == nil || textPassword.text!.isEmpty { + self.status = .error(element: textPassword) + } + } + + private func handleLoginFieldInvalid(textField: UITextField) { + let errorMessage = L10n.Welcome.Login.Error.validation + Macros.displayImageNote(withImage: Asset.Images.iconWarning.image, + message: errorMessage) + self.status = .error(element: textField) + } + + + private func prepareLogin() { + log.debug("Logging in...") + enableInteractions(false) + showLoadingAnimation() + } + + private func handleLoginResult(user: UserAccount?, error: Error?, loginOption: LoginOption) { + enableInteractions(true) + + hideLoadingAnimation() + + guard let user = user else { + handleLoginFailed(error, loginOption: loginOption) + return + } + + log.debug("Login succeeded!") + + self.completionDelegate?.welcomeDidLogin(withUser: user, topViewController: self) + } + + private func updateTimeToRetry(loginOption: LoginOption, retryAfterSeconds: Double) { + let retryAfterTimeStamp = Date().timeIntervalSince1970 + retryAfterSeconds + switch loginOption { + case .credentials: + timeToRetryCredentials = retryAfterTimeStamp + case .receipt: + timeToRetryReceipt = retryAfterTimeStamp + case .magicLink: + timeToRetryMagicLink = retryAfterTimeStamp + } + } + + private func handleLoginFailed(_ error: Error?, loginOption: LoginOption) { + var displayDuration: Double? + var errorMessage: String? + if let error = error { + if let clientError = error as? ClientError { + switch clientError { + case .unauthorized: + errorMessage = L10n.Welcome.Login.Error.unauthorized + + case .throttled(retryAfter: let retryAfter): + let localisedThrottlingString = L10n.Welcome.Login.Error.throttled("\(retryAfter)") + errorMessage = NSLocalizedString(localisedThrottlingString, comment: localisedThrottlingString) + + let retryAfterSeconds = Double(retryAfter) + displayDuration = retryAfterSeconds + + updateTimeToRetry(loginOption: loginOption, retryAfterSeconds: retryAfterSeconds) + + case .expired: + handleExpiredAccount() + return + default: + break + } + } + if (errorMessage == nil) { + errorMessage = error.localizedDescription + } + log.error("Failed to log in (error: \(error))") + } else { + log.error("Failed to log in") + } + displayErrorMessage(errorMessage: errorMessage, displayDuration: displayDuration) + } + + private func displayErrorMessage(errorMessage: String?, displayDuration: Double? = nil) { + + Macros.displayImageNote(withImage: Asset.Images.iconWarning.image, + message: errorMessage ?? L10n.Welcome.Login.Error.title, andDuration: displayDuration, + accessbilityIdentifier: Accessibility.Id.Login.Error.banner) + } + + private func handleExpiredAccount() { + perform(segue: StoryboardSegue.Welcome.expiredAccountPurchaseSegue, sender: self) + } + + private func enableInteractions(_ enable: Bool) { + parent?.view.isUserInteractionEnabled = enable + isLogging = !enable + } + + // MARK: Restylable + + override func viewShouldRestyle() { + super.viewShouldRestyle() + Theme.current.applyPrincipalBackground(view) + Theme.current.applyTitle(labelTitle, appearance: .dark) + Theme.current.applyInput(textUsername) + Theme.current.applyInput(textPassword) + Theme.current.applyButtonLabelMediumStyle(loginWithReceipt) + Theme.current.applyButtonLabelMediumStyle(loginWithLink) + Theme.current.applyButtonLabelMediumStyle(couldNotGetPlanButton) + } + + private func styleButtons() { + buttonLogin.setRounded() + buttonLogin.style(style: TextStyle.Buttons.piaGreenButton) + buttonLogin.setTitle(L10n.Welcome.Login.submit.uppercased(), + for: []) + buttonLogin.accessibilityIdentifier = Accessibility.Id.Login.submit + + couldNotGetPlanButton.setTitle(L10n.Welcome.Login.Restore.button, + for: []) + couldNotGetPlanButton.titleLabel?.numberOfLines = 0 + couldNotGetPlanButton.titleLabel?.textAlignment = .center + + loginWithReceipt.setTitle(L10n.Welcome.Login.Receipt.button, + for: []) + loginWithReceipt.titleLabel?.numberOfLines = 0 + loginWithReceipt.titleLabel?.textAlignment = .center + + loginWithLink.setTitle(L10n.Welcome.Login.Magic.Link.title, + for: []) + loginWithLink.titleLabel?.numberOfLines = 0 + loginWithLink.titleLabel?.textAlignment = .center + } + + func welcomeController(_ welcomeController: PIAWelcomeViewController, didSignupWith user: UserAccount, topViewController: UIViewController) { + completionDelegate?.welcomeDidSignup(withUser: user, topViewController: topViewController) + } + + func welcomeController(_ welcomeController: PIAWelcomeViewController, didLoginWith user: UserAccount, topViewController: UIViewController) { + completionDelegate?.welcomeDidLogin(withUser: user, topViewController: topViewController) + + } + +} + +extension LoginViewController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + if (textField == textUsername) { + textPassword.becomeFirstResponder() + } else if (textField == textPassword) { + logIn(nil) + } + return true + } +} diff --git a/PIA VPN/Macros+UI.swift b/PIA VPN/Macros+UI.swift new file mode 100644 index 000000000..8e9dc07f8 --- /dev/null +++ b/PIA VPN/Macros+UI.swift @@ -0,0 +1,523 @@ +// +// Macros+UI.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 10/19/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import Foundation +import UIKit +import SwiftEntryKit +import PopupDialog +import PIALibrary + +extension Macros { + + private static let bannerHeight: CGFloat = 78.5 + private static let stickyNoteName: String = "sticky_note" + + /** + Creates an `UIColor` from its RGBA components. + + - Parameter r: The red component + - Parameter g: The green component + - Parameter b: The blue component + - Parameter alpha: The alpha channel component + - Returns: An `UIColor` with the provided parameters + */ + public static func color(r: UInt8, g: UInt8, b: UInt8, alpha: UInt8) -> UIColor { + return UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: CGFloat(alpha) / 255.0) + } + + /** + Creates an `UIColor` from its RGBA components expressed as a 24-bit hex plus an alpha channel. + + - Parameter hex: The red, green and blue components expressed as a 24-bit number + - Parameter alpha: The alpha channel component + - Returns: An `UIColor` with the provided parameters + */ + public static func color(hex: UInt32, alpha: UInt8) -> UIColor { + let r = UInt8((hex >> 16) & 0xff) + let g = UInt8((hex >> 8) & 0xff) + let b = UInt8(hex & 0xff) + + return color(r: r, g: g, b: b, alpha: alpha) + } + + /** + Creates an `UIColor` from its RGBA components expressed as a String hex plus an alpha channel. + + - Parameter hex: The red, green and blue components expressed as a String + - Parameter alpha: The alpha channel component + - Returns: An `UIColor` with the provided parameters + */ + public static func color(hex:String, alpha: UInt8) -> UIColor { + var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + + if (cString.hasPrefix("#")) { + cString.remove(at: cString.startIndex) + } + + if ((cString.count) != 6) { + return UIColor.gray + } + + var rgbValue:UInt64 = 0 + Scanner(string: cString).scanHexInt64(&rgbValue) + + return UIColor( + red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, + green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, + blue: CGFloat(rgbValue & 0x0000FF) / 255.0, + alpha: CGFloat(alpha) + ) + } + + /** + Checks iPad device. + + - Returns: `true` if the device is an iPad + */ + public static var isDevicePad: Bool { + return (UI_USER_INTERFACE_IDIOM() == .pad) + } + + /** + Checks iPhone Plus device. + + - Returns: `true` if the device is an iPhone Plus + */ + public static var isDevicePlus: Bool { + let screen = UIScreen.main + let maxEdge = max(screen.bounds.size.width, screen.bounds.size.height) + return ((screen.scale >= 3.0) && (maxEdge < 812.0)) + } + + /** + Checks big devices, typically an iPad or iPhone Plus. + + - Returns: `true` if the device is an iPad or an iPhone Plus + */ + public static var isDeviceBig: Bool { + return (isDevicePad || (UIScreen.main.scale >= 3.0)) + } + + /** + Returns a localized full version string. + + - Returns: A localized full version string built upon the input `format` + */ + public static func localizedVersionFullString() -> String? { + guard let info = Bundle.main.infoDictionary else { + return nil + } + let versionNumber = info["CFBundleShortVersionString"] as! String + let buildNumber = info[kCFBundleVersionKey as String] as! String + return L10n.Ui.Global.Version.format(versionNumber, buildNumber) + } + + /** + Returns a localized version number string. Example "3.9.0" + */ + public static func localizedVersionNumber() -> String { + guard let info = Bundle.main.infoDictionary else { + return "" + } + let versionNumber = info["CFBundleShortVersionString"] as! String + return versionNumber + } + + /** + Shortcut to create a `PopupDialog`. + + - Parameter title: The alert title + - Parameter message: The alert message + - Returns: A `PopupDialog` object + */ + public static func alert(_ title: String?, _ message: String?) -> PopupDialog { + Macros.styleAlertPopupDialog() + let popup = PopupDialog(title: title, + message: message, + buttonAlignment: .horizontal) + return popup + } + + public static func alert(_ viewController: UIViewController, completionHandler completion: (() -> Void)? = nil) -> PopupDialog { + Macros.styleAlertPopupDialog() + let popup = PopupDialog(viewController: viewController, + buttonAlignment: .horizontal, + completion: completion) + return popup + } + + /** + Shortcut to create an `UIAlertController`. + + - Parameter title: The alert title + - Parameter message: The alert message + - Returns: An `UIAlertController` object + */ + public static func alertController(_ title: String?, _ message: String?) -> UIAlertController { + return UIAlertController(title: title, message: message, preferredStyle: .alert) + } + + /** + Style a `PopupDialog` object. + */ + public static func stylePopupDialog() { + let dialogAppearance = PopupDialogDefaultView.appearance() + dialogAppearance.backgroundColor = Theme.current.palette.appearance == .dark ? UIColor.piaGrey6 : .white + dialogAppearance.messageFont = TextStyle.textStyle12.font! + dialogAppearance.messageColor = Theme.current.palette.appearance == .dark ? .white : TextStyle.textStyle12.color + + let containerAppearance = PopupDialogContainerView.appearance() + containerAppearance.cornerRadius = 0 + containerAppearance.shadowEnabled = false + + let overlayAppearance = PopupDialogOverlayView.appearance() + overlayAppearance.color = .black + overlayAppearance.blurEnabled = false + overlayAppearance.liveBlurEnabled = false + overlayAppearance.opacity = 0.5 + + let buttonAppearance = DefaultButton.appearance() + buttonAppearance.titleFont = TextStyle.textStyle21.font! + buttonAppearance.titleColor = TextStyle.textStyle21.color + buttonAppearance.buttonColor = Theme.current.palette.appearance == .dark ? UIColor.piaGrey6 : .white + buttonAppearance.separatorColor = Theme.current.palette.appearance == .dark ? UIColor.piaGrey10 : UIColor.piaGrey1 + } + + /** + Style a PopupDialog alert view object. + */ + public static func styleAlertPopupDialog() { + let dialogAppearance = PopupDialogDefaultView.appearance() + dialogAppearance.backgroundColor = Theme.current.palette.appearance == .dark ? UIColor.piaGrey6 : .white + dialogAppearance.titleFont = TextStyle.textStyle7.font! + dialogAppearance.titleColor = Theme.current.palette.appearance == .dark ? .white : TextStyle.textStyle7.color + dialogAppearance.messageFont = TextStyle.textStyle12.font! + dialogAppearance.messageColor = Theme.current.palette.appearance == .dark ? .white : TextStyle.textStyle12.color + let containerAppearance = PopupDialogContainerView.appearance() + containerAppearance.cornerRadius = 0 + containerAppearance.shadowEnabled = false + + let overlayAppearance = PopupDialogOverlayView.appearance() + overlayAppearance.color = .black + overlayAppearance.blurEnabled = false + overlayAppearance.liveBlurEnabled = false + overlayAppearance.opacity = 0.5 + + let buttonAppearance = DefaultButton.appearance() + buttonAppearance.titleFont = TextStyle.textStyle14.font! + buttonAppearance.titleColor = TextStyle.textStyle14.color + buttonAppearance.buttonColor = Theme.current.palette.appearance == .dark ? UIColor.piaGrey6 : .white + buttonAppearance.separatorColor = Theme.current.palette.appearance == .dark ? UIColor.piaGrey10 : UIColor.piaGrey1 + + let cancelButtonAppearance = CancelButton.appearance() + cancelButtonAppearance.titleFont = TextStyle.textStyle21.font! + cancelButtonAppearance.titleColor = TextStyle.textStyle21.color + cancelButtonAppearance.buttonColor = Theme.current.palette.appearance == .dark ? UIColor.piaGrey6 : .white + cancelButtonAppearance.separatorColor = Theme.current.palette.appearance == .dark ? UIColor.piaGrey10 : UIColor.piaGrey1 + + let destructiveButtonAppearance = DestructiveButton.appearance() + destructiveButtonAppearance.titleFont = TextStyle.textStyle15.font! + destructiveButtonAppearance.titleColor = TextStyle.textStyle15.color + destructiveButtonAppearance.buttonColor = Theme.current.palette.appearance == .dark ? UIColor.piaGrey6 : .white + destructiveButtonAppearance.separatorColor = Theme.current.palette.appearance == .dark ? UIColor.piaGrey10 : UIColor.piaGrey1 + + } + + + /** + Shortcut to display an `EKImageNoteMessageView`. + + - Parameter image: The note image + - Parameter message: The note message + - Parameter duration: Optional duration of the note + */ + public static func displayImageNote(withImage image: UIImage, + message: String, + andDuration duration: Double? = nil, + accessbilityIdentifier: String = "") { + + + var attributes = EKAttributes() + attributes = .topToast + attributes.hapticFeedbackType = .success + attributes.entryBackground = .color(color: EKColor(UIColor.piaRed)) + attributes.positionConstraints.size = .init(width: EKAttributes.PositionConstraints.Edge.fill, + height: EKAttributes.PositionConstraints.Edge.constant(value: bannerHeight)) + + if let duration = duration { + attributes.displayDuration = duration + } + + let labelContent = EKProperty.LabelContent(text: message, + style: .init(font: TextStyle.textStyle7.font!, + color: .white)) + let imageContent = EKProperty.ImageContent(image: image) + let contentView = EKImageNoteMessageView(with: labelContent, + imageContent: imageContent) + contentView.accessibilityIdentifier = accessbilityIdentifier + + SwiftEntryKit.display(entry: contentView, + using: attributes) + + } + + /** + Shortcut to display a success `EKImageNoteMessageView`. + + - Parameter image: The note image + - Parameter message: The note message + - Parameter duration: Optional duration of the note + */ + public static func displaySuccessImageNote(withImage image: UIImage, + message: String, + andDuration duration: Double? = nil) { + + + var attributes = EKAttributes() + attributes = .topToast + attributes.hapticFeedbackType = .success + attributes.entryBackground = .color(color: EKColor(UIColor.piaGreenDark20)) + attributes.positionConstraints.size = .init(width: EKAttributes.PositionConstraints.Edge.fill, + height: EKAttributes.PositionConstraints.Edge.constant(value: bannerHeight)) + + if let duration = duration { + attributes.displayDuration = duration + } + + let labelContent = EKProperty.LabelContent(text: message, + style: .init(font: TextStyle.textStyle7.font!, + color: .white)) + let imageContent = EKProperty.ImageContent(image: image) + let contentView = EKImageNoteMessageView(with: labelContent, + imageContent: imageContent) + + SwiftEntryKit.display(entry: contentView, + using: attributes) + + } + + /** + Shortcut to display a warning `EKImageNoteMessageView`. + + - Parameter image: The note image + - Parameter message: The note message + - Parameter duration: Optional duration of the note + */ + public static func displayWarningImageNote(withImage image: UIImage, + message: String, + andDuration duration: Double? = nil) { + + + var attributes = EKAttributes() + attributes = .topToast + attributes.hapticFeedbackType = .success + attributes.entryBackground = .color(color: EKColor(UIColor.piaOrange)) + attributes.positionConstraints.size = .init(width: EKAttributes.PositionConstraints.Edge.fill, + height: EKAttributes.PositionConstraints.Edge.constant(value: bannerHeight)) + + if let duration = duration { + attributes.displayDuration = duration + } + + let labelContent = EKProperty.LabelContent(text: message, + style: .init(font: TextStyle.textStyle7.font!, + color: .white)) + let imageContent = EKProperty.ImageContent(image: image) + let contentView = EKImageNoteMessageView(with: labelContent, + imageContent: imageContent) + + SwiftEntryKit.display(entry: contentView, + using: attributes) + + } + /** + Shortcut to display an infinite `EKImageNoteMessageView`. + + - Parameter message: The note message + - Parameter image: The note image + */ + public static func displayStickyNote(withMessage message: String, + andImage image: UIImage) { + + var attributes = EKAttributes() + attributes = .topToast + attributes.name = stickyNoteName + attributes.hapticFeedbackType = .success + attributes.entryBackground = .color(color: EKColor(UIColor.piaRed)) + attributes.positionConstraints.size = .init(width: EKAttributes.PositionConstraints.Edge.fill, + height: EKAttributes.PositionConstraints.Edge.constant(value: bannerHeight)) + attributes.displayDuration = .infinity + + let labelContent = EKProperty.LabelContent(text: message, + style: .init(font: TextStyle.textStyle7.font!, + color: .white)) + let imageContent = EKProperty.ImageContent(image: image) + let contentView = EKImageNoteMessageView(with: labelContent, + imageContent: imageContent) + SwiftEntryKit.display(entry: contentView, + using: attributes) + + } + + /** + Removes the current presented sticky note `EKImageNoteMessageView`. + */ + public static func removeStickyNote() { + if SwiftEntryKit.isCurrentlyDisplaying(entryNamed: stickyNoteName) { + SwiftEntryKit.dismiss() + } + } + + + /** + Shortcut to create an `UIAlertController` with `.actionSheet` preferred style. + + - Parameter request: The sheet title + - Parameter message: The sheet message + - Returns: An `UIAlertController` with `.actionSheet` preferred style + */ + public static func actionSheet(_ title: String?, _ message: String?) -> UIAlertController { + return UIAlertController(title: title, message: message, preferredStyle: .actionSheet) + } + +} + +/// Convenience methods for `PopupDialog`. +public extension PopupDialog { + + /// Add a PopupDialog DefaultButton with the handler action + /// - Parameter title: The button title + /// - Parameter handler: The button action + func addActionWithTitle(_ title: String, handler: @escaping () -> Void) { + let button = DefaultButton(title: title.uppercased(), dismissOnTap: true) { + handler() + } + self.addButton(button) + } + + /// Add a PopupDialog DestructiveButton with the handler action + /// - Parameter title: The button title + /// - Parameter handler: The button action + func addDestructiveActionWithTitle(_ title: String, handler: @escaping () -> Void) { + let button = DestructiveButton(title: title.uppercased(), dismissOnTap: true) { + handler() + } + button.accessibilityIdentifier = Accessibility.Id.Dialog.destructive + self.addButton(button) + } + + /// Add a PopupDialog CancelButton with the handler action + /// - Parameter title: The button title + /// - Parameter handler: The button action + func addCancelActionWithTitle(_ title: String, handler: @escaping () -> Void) { + let button = CancelButton(title: title.uppercased(), dismissOnTap: true) { + handler() + } + self.addButton(button) + } + + /// Add a PopupDialog Button with the handler action depending of the UIAlertAction given + /// - Parameter action: The UIAlertAction to convert into PopupDialog button + /// - Parameter handler: The button action + func addAction(_ action: UIAlertAction, handler: @escaping () -> Void) { + if let title = action.title { + switch action.style { + case .cancel: + let button = CancelButton(title: title.uppercased(), dismissOnTap: true) { + handler() + } + self.addButton(button) + default: + let button = DefaultButton(title: title.uppercased(), dismissOnTap: true) { + handler() + } + self.addButton(button) + } + } + } + + /// Add a PopupDialog simple CancelButton without handler and dismissing on tap + /// - Parameter title: The button title + func addCancelAction(_ title: String) { + let button = CancelButton(title: title.uppercased(), dismissOnTap: true, action: nil) + self.addButton(button) + } + + /// Add a PopupDialog simple DefaultButton without handler and dismissing on tap + /// - Parameter title: The button title + func addDefaultAction(_ title: String) { + let button = DefaultButton(title: title.uppercased(), dismissOnTap: true, action: nil) + self.addButton(button) + } + +} + +/// Convenience methods for `UIAlertController`. +public extension UIAlertController { + + /** + Adds a default action to an `UIAlertController`. + + - Parameter title: The action title + - Parameter handler: The action handler + */ + public func addDefaultAction(_ title: String, handler: @escaping () -> Void) { + let action = UIAlertAction(title: title, style: .default) { (action) in + handler() + } + addAction(action) + preferredAction = action + } + + /** + Adds a cancel action to an `UIAlertController`. + + - Parameter title: The action title + */ + public func addCancelAction(_ title: String) { + let action = UIAlertAction(title: title, style: .cancel) + addAction(action) + if (actions.count == 1) { + preferredAction = action + } + } + + /** + Adds a destructive action to an `UIAlertController`. + + - Parameter title: The action title + - Parameter handler: The action handler + */ + public func addDestructiveAction(_ title: String, handler: @escaping () -> Void) { + let action = UIAlertAction(title: title, style: .destructive) { (action) in + handler() + } + addAction(action) + preferredAction = action + } +} + +public extension String { + func trimmed() -> String { + return trimmingCharacters(in: .whitespacesAndNewlines) + } +} diff --git a/PIA VPN/MagicLinkLoginViewController.swift b/PIA VPN/MagicLinkLoginViewController.swift new file mode 100644 index 000000000..d3023c10e --- /dev/null +++ b/PIA VPN/MagicLinkLoginViewController.swift @@ -0,0 +1,45 @@ +// +// MagicLinkLoginViewController.swift +// PIALibrary +// +// Created by Jose Blaya on 03/09/2020. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// + +import UIKit + +class MagicLinkLoginViewController: AutolayoutViewController { + + @IBOutlet private weak var emailTextField: BorderedTextField! + + override func viewDidLoad() { + super.viewDidLoad() + emailTextField.placeholder = L10n.Welcome.Purchase.Email.placeholder + emailTextField.text = "" + emailTextField.keyboardType = .emailAddress + } + + override func viewShouldRestyle() { + super.viewShouldRestyle() + Theme.current.applyPrincipalBackground(view) + Theme.current.applyInput(emailTextField) + } + + public func email() -> String { + return emailTextField.text ?? "" + } + +} diff --git a/PIA VPN/MenuViewController.swift b/PIA VPN/MenuViewController.swift index 170ee4077..f54d8b13e 100644 --- a/PIA VPN/MenuViewController.swift +++ b/PIA VPN/MenuViewController.swift @@ -91,29 +91,29 @@ class MenuViewController: AutolayoutViewController { ] private lazy var stringForItem: [Item: String] = [ - .selectRegion: L10n.Menu.Item.region, - .account: L10n.Menu.Item.account, - .dedicatedIp: L10n.Dedicated.Ip.title, - .settings: L10n.Menu.Item.settings, - .logout: L10n.Menu.Item.logout, - .about: L10n.Menu.Item.about, - .privacy: L10n.Menu.Item.Web.privacy, - .homepage: L10n.Menu.Item.Web.home, - .support: L10n.Menu.Item.Web.support, + .selectRegion: L10n.Localizable.Menu.Item.region, + .account: L10n.Localizable.Menu.Item.account, + .dedicatedIp: L10n.Localizable.Dedicated.Ip.title, + .settings: L10n.Localizable.Menu.Item.settings, + .logout: L10n.Localizable.Menu.Item.logout, + .about: L10n.Localizable.Menu.Item.about, + .privacy: L10n.Localizable.Menu.Item.Web.privacy, + .homepage: L10n.Localizable.Menu.Item.Web.home, + .support: L10n.Localizable.Menu.Item.Web.support, .version: Macros.localizedVersionFullString() ?? "" ] private lazy var iconForItem: [Item: ImageAsset] = [ - .selectRegion: Asset.iconRegion, - .account: Asset.iconAccount, - .dedicatedIp: Asset.iconDip, - .settings: Asset.iconSettings, - .logout: Asset.iconLogout, - .about: Asset.iconmenuAbout, - .privacy: Asset.iconmenuPrivacy, - .homepage: Asset.iconHomepage, - .support: Asset.iconContact, - .version: Asset.iconAccount + .selectRegion: Asset.Images.iconRegion, + .account: Asset.Images.iconAccount, + .dedicatedIp: Asset.Images.iconDip, + .settings: Asset.Images.iconSettings, + .logout: Asset.Images.iconLogout, + .about: Asset.Images.iconmenuAbout, + .privacy: Asset.Images.iconmenuPrivacy, + .homepage: Asset.Images.iconHomepage, + .support: Asset.Images.iconContact, + .version: Asset.Images.iconAccount ] deinit { @@ -132,7 +132,7 @@ class MenuViewController: AutolayoutViewController { modalPresentationCapturesStatusBarAppearance = true - imvAvatar.image = Asset.imageRobot.image + imvAvatar.image = Asset.Images.imageRobot.image var planDescription = "" if let currentUser = Client.providers.accountProvider.currentUser, @@ -142,17 +142,17 @@ class MenuViewController: AutolayoutViewController { switch info.plan { case .yearly: - planDescription = L10n.Account.Subscriptions.yearly + planDescription = L10n.Localizable.Account.Subscriptions.yearly case .monthly: - planDescription = L10n.Account.Subscriptions.monthly + planDescription = L10n.Localizable.Account.Subscriptions.monthly default: - planDescription = L10n.Account.Subscriptions.trial + planDescription = L10n.Localizable.Account.Subscriptions.trial } labelVersion.numberOfLines = 0 labelVersion.attributedText = Theme.current.smallTextWithColoredLink( - withMessage: planDescription + "\n" + L10n.Account.Subscriptions.Short.message, - link: L10n.Account.Subscriptions.Short.linkMessage) + withMessage: planDescription + "\n" + L10n.Localizable.Account.Subscriptions.Short.message, + link: L10n.Localizable.Account.Subscriptions.Short.linkMessage) labelVersion.isUserInteractionEnabled = true let tap = UITapGestureRecognizer(target: self, action: #selector(openManageSubscription)) labelVersion.addGestureRecognizer(tap) @@ -175,7 +175,7 @@ class MenuViewController: AutolayoutViewController { super.viewWillAppear(animated) currentUser = Client.providers.accountProvider.currentUser labelUsername.text = Client.providers.accountProvider.publicUsername ?? "" - labelUsername.accessibilityLabel = L10n.Menu.Accessibility.loggedAs(Client.providers.accountProvider.publicUsername ?? "") + labelUsername.accessibilityLabel = L10n.Localizable.Menu.Accessibility.loggedAs(Client.providers.accountProvider.publicUsername ?? "") } override func didRefreshOrientationConstraints() { @@ -258,12 +258,12 @@ class MenuViewController: AutolayoutViewController { } private func handlePlansListingError(_ error: Error?) { - let errorMessage = error?.localizedDescription ?? L10n.Menu.Renewal.Message.unavailable + let errorMessage = error?.localizedDescription ?? L10n.Localizable.Menu.Renewal.Message.unavailable let alert = Macros.alert( - L10n.Global.error, + L10n.Localizable.Global.error, errorMessage ) - alert.addDefaultAction(L10n.Global.close) + alert.addDefaultAction(L10n.Localizable.Global.close) present(alert, animated: true, completion: nil) } @@ -271,11 +271,11 @@ class MenuViewController: AutolayoutViewController { log.error("Account: Cannot renew trial account") let alert = Macros.alert( - L10n.Menu.Renewal.title, - L10n.Menu.Renewal.Message.trial + L10n.Localizable.Menu.Renewal.title, + L10n.Localizable.Menu.Renewal.Message.trial ) - alert.addCancelAction(L10n.Global.cancel) - alert.addActionWithTitle(L10n.Menu.Renewal.purchase) { + alert.addCancelAction(L10n.Localizable.Global.cancel) + alert.addActionWithTitle(L10n.Localizable.Menu.Renewal.purchase) { self.dismiss(animated: true) { self.delegate?.menu(didDetectTrialUpgrade: self) } @@ -288,11 +288,11 @@ class MenuViewController: AutolayoutViewController { // should never happen as the "Renew" button *should* only appear when the account is trial or renewable let alert = Macros.alert( - L10n.Menu.Renewal.title, - L10n.Menu.Renewal.Message.website + L10n.Localizable.Menu.Renewal.title, + L10n.Localizable.Menu.Renewal.Message.website ) - alert.addCancelAction(L10n.Global.cancel) - alert.addActionWithTitle(L10n.Menu.Renewal.renew) { + alert.addCancelAction(L10n.Localizable.Global.cancel) + alert.addActionWithTitle(L10n.Localizable.Menu.Renewal.renew) { guard UIApplication.shared.canOpenURL(AppConstants.Web.homeURL) else { return } UIApplication.shared.open(AppConstants.Web.homeURL, options: [:], completionHandler: nil) } @@ -335,8 +335,8 @@ class MenuViewController: AutolayoutViewController { log.error("IAP: Purchase failed (error: \(error)") - let alert = Macros.alert(L10n.Global.error, error.localizedDescription) - alert.addDefaultAction(L10n.Global.close) + let alert = Macros.alert(L10n.Localizable.Global.error, error.localizedDescription) + alert.addDefaultAction(L10n.Localizable.Global.close) present(alert, animated: true, completion: nil) } @@ -344,10 +344,10 @@ class MenuViewController: AutolayoutViewController { log.debug("Account: Renewal successfully completed") let alert = Macros.alert( - L10n.Renewal.Success.title, - L10n.Renewal.Success.message + L10n.Localizable.Renewal.Success.title, + L10n.Localizable.Renewal.Success.message ) - alert.addDefaultAction(L10n.Global.close) + alert.addDefaultAction(L10n.Localizable.Global.close) present(alert, animated: true, completion: nil) } @@ -359,21 +359,21 @@ class MenuViewController: AutolayoutViewController { } let alert = Macros.alert( - L10n.Global.error, - L10n.Renewal.Failure.message + L10n.Localizable.Global.error, + L10n.Localizable.Renewal.Failure.message ) - alert.addDefaultAction(L10n.Global.close) + alert.addDefaultAction(L10n.Localizable.Global.close) present(alert, animated: true, completion: nil) } private func logOut() { let sheet = Macros.alert( - L10n.Menu.Logout.title, - L10n.Menu.Logout.message + L10n.Localizable.Menu.Logout.title, + L10n.Localizable.Menu.Logout.message ) - sheet.addCancelAction(L10n.Global.cancel) - sheet.addDestructiveActionWithTitle(L10n.Menu.Logout.confirm) { + sheet.addCancelAction(L10n.Localizable.Global.cancel) + sheet.addDestructiveActionWithTitle(L10n.Localizable.Menu.Logout.confirm) { self.dismiss(animated: true) { log.debug("Account: Logging out...") DashboardViewController.instanceInNavigationStack()?.showLoadingAnimation() diff --git a/PIA VPN/MessagesManager.swift b/PIA VPN/MessagesManager.swift index 6905be26b..e92a3c6ac 100644 --- a/PIA VPN/MessagesManager.swift +++ b/PIA VPN/MessagesManager.swift @@ -113,7 +113,7 @@ extension InAppMessage { case .action: if let actions = self.settingAction { command = ActionCommand(actions) - Macros.displaySuccessImageNote(withImage: Asset.iconWarning.image, message: L10n.Inapp.Messages.Settings.updated) + Macros.displaySuccessImageNote(withImage: Asset.Images.iconWarning.image, message: L10n.Localizable.Inapp.Messages.Settings.updated) } default: break @@ -169,7 +169,7 @@ extension MessagesManager { @objc private func presentExpiringDIPRegionSystemMessage(notification: Notification) { if let userInfo = notification.userInfo, let token = userInfo[NotificationKey.token] as? String { - let message = InAppMessage(withMessage: ["en-US": L10n.Dedicated.Ip.Message.Token.willexpire], id: token, link: ["en-US": L10n.Dedicated.Ip.Message.Token.Willexpire.link], type: .link, level: .system, actions: nil, view: nil, uri: AppConstants.Web.homeURL.absoluteString) + let message = InAppMessage(withMessage: ["en-US": L10n.Localizable.Dedicated.Ip.Message.Token.willexpire], id: token, link: ["en-US": L10n.Localizable.Dedicated.Ip.Message.Token.Willexpire.link], type: .link, level: .system, actions: nil, view: nil, uri: AppConstants.Web.homeURL.absoluteString) MessagesManager.shared.postSystemMessage(message: message) } @@ -186,7 +186,7 @@ extension MessagesManager { if relation[token] != nil && relation[token] != ip { //changes relation[token] = ip - let message = InAppMessage(withMessage: ["en-US": L10n.Dedicated.Ip.Message.Ip.updated], id: token, link: ["en-US":""], type: .none, level: .system, actions: nil, view: nil, uri: nil) + let message = InAppMessage(withMessage: ["en-US": L10n.Localizable.Dedicated.Ip.Message.Ip.updated], id: token, link: ["en-US":""], type: .none, level: .system, actions: nil, view: nil, uri: nil) MessagesManager.shared.postSystemMessage(message: message) } } @@ -196,7 +196,7 @@ extension MessagesManager { func showInAppSurveyMessage() { - let message = InAppMessage(withMessage: ["en-US": L10n.Account.Survey.message.appendDetailSymbol()], id: MessagesManager.surveyMessageID, link: ["en-US": L10n.Account.Survey.messageLink.appendDetailSymbol()], type: .link, level: .api, actions: nil, view: nil, uri: AppConstants.Survey.formURL.absoluteString) { [weak self] in + let message = InAppMessage(withMessage: ["en-US": L10n.Localizable.Account.Survey.message.appendDetailSymbol()], id: MessagesManager.surveyMessageID, link: ["en-US": L10n.Localizable.Account.Survey.messageLink.appendDetailSymbol()], type: .link, level: .api, actions: nil, view: nil, uri: AppConstants.Survey.formURL.absoluteString) { [weak self] in self?.dismiss(message: MessagesManager.surveyMessageID) } MessagesManager.shared.postSystemMessage(message: message) diff --git a/PIA VPN/ModalNavigationSegue.swift b/PIA VPN/ModalNavigationSegue.swift index bc8f2e1bd..ab72dcab6 100644 --- a/PIA VPN/ModalNavigationSegue.swift +++ b/PIA VPN/ModalNavigationSegue.swift @@ -36,7 +36,7 @@ class ModalNavigationSegue: UIStoryboardSegue { target: modal, action: #selector(modal.dismissModal) ) - modal.navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Global.close + modal.navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Localizable.Global.close let nav = UINavigationController(rootViewController: modal) Theme.current.applyCustomNavigationBar(nav.navigationBar, diff --git a/PIA VPN/NavigationBar+Appearence..swift b/PIA VPN/NavigationBar+Appearence..swift new file mode 100644 index 000000000..e5b776e0d --- /dev/null +++ b/PIA VPN/NavigationBar+Appearence..swift @@ -0,0 +1,38 @@ +// +// NavigationBar+Appearence..swift +// PIA VPN +// +// Created by Said Rehouni on 8/11/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import Foundation +import UIKit + +extension UINavigationBar { + func setBackgroundAppearenceColor(_ color: UIColor?) { + if #available(iOS 13.0, *), color != nil { + let appearance = UINavigationBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.backgroundColor = color + standardAppearance = appearance + scrollEdgeAppearance = standardAppearance + } + else { + barTintColor = color + } + } + + func setBackgroundAppearenceImage(_ image: UIImage?) { + if #available(iOS 13.0, *), image != nil { + let appearance = UINavigationBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.backgroundImage = image + standardAppearance = appearance + scrollEdgeAppearance = standardAppearance + } + else { + setBackgroundImage(image, for: UIBarMetrics.default) + } + } +} diff --git a/PIA VPN/NavigationLogoView.swift b/PIA VPN/NavigationLogoView.swift new file mode 100644 index 000000000..a9d436ed9 --- /dev/null +++ b/PIA VPN/NavigationLogoView.swift @@ -0,0 +1,77 @@ +// +// NavigationLogoView.swift +// PIALibrary-iOS +// +// Created by Jose Antonio Blaya Garcia on 31/10/2018. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import Foundation +import UIKit + +public class NavigationLogoView: UIView { + private let imvLogo: UIImageView + + private struct Defaults { + static let maxWidth: CGFloat = 100 + } + + required init?(coder aDecoder: NSCoder) { + fatalError("Not implemented") + } + + override init(frame: CGRect) { + imvLogo = UIImageView(image: Theme.current.palette.logo) + super.init(frame: .zero) + + addSubview(imvLogo) + + // backgroundColor = .orange + // imvLogo.backgroundColor = .green + imvLogo.contentMode = .scaleAspectFit + } + + override public func layoutSubviews() { + super.layoutSubviews() + + // let navBar = navigationBar() + let imageLogo = imvLogo.image! + var imageSize = imageLogo.size + // if !Macros.isDevicePad { + let logoRatio: CGFloat = imageLogo.size.width / imageLogo.size.height + imageSize.width = min(imageLogo.size.width, Defaults.maxWidth) + imageSize.height = imageSize.width / logoRatio + // } + + var logoFrame: CGRect = .zero + logoFrame.origin.x = -imageSize.width / 2.0 + logoFrame.origin.y = -imageSize.height / 2.0 + logoFrame.size = imageSize + imvLogo.frame = logoFrame.integral + } + + private func navigationBar() -> UINavigationBar { + var parent = superview + while (parent != nil) { + if let navBar = parent as? UINavigationBar { + return navBar + } + parent = parent?.superview + } + fatalError("Not subview of a UINavigationBar") + } +} diff --git a/PIA VPN/NetworkCollectionViewCell.swift b/PIA VPN/NetworkCollectionViewCell.swift index 741c679a2..4dab7c107 100644 --- a/PIA VPN/NetworkCollectionViewCell.swift +++ b/PIA VPN/NetworkCollectionViewCell.swift @@ -45,68 +45,68 @@ class NetworkCollectionViewCell: UICollectionViewCell { guard let data = data else { return } switch data.type { case .openWiFi: - title.text = L10n.Network.Management.Tool.Open.wifi + title.text = L10n.Localizable.Network.Management.Tool.Open.wifi switch data.rule { case .alwaysConnect: - networkIcon.image = Asset.Piax.Nmt.iconOpenWifiConnect.image - networkIcon.accessibilityLabel = L10n.Network.Management.Tool.Open.wifi + " " + L10n.Network.Management.Tool.Always.connect + networkIcon.image = Asset.Images.Piax.Nmt.iconOpenWifiConnect.image + networkIcon.accessibilityLabel = L10n.Localizable.Network.Management.Tool.Open.wifi + " " + L10n.Localizable.Network.Management.Tool.Always.connect case .alwaysDisconnect: - networkIcon.image = Asset.Piax.Nmt.iconOpenWifiDisconnect.image - networkIcon.accessibilityLabel = L10n.Network.Management.Tool.Open.wifi + " " + L10n.Network.Management.Tool.Always.disconnect + networkIcon.image = Asset.Images.Piax.Nmt.iconOpenWifiDisconnect.image + networkIcon.accessibilityLabel = L10n.Localizable.Network.Management.Tool.Open.wifi + " " + L10n.Localizable.Network.Management.Tool.Always.disconnect case .retainState: - networkIcon.image = Asset.Piax.Nmt.iconOpenWifiRetain.image - networkIcon.accessibilityLabel = L10n.Network.Management.Tool.Open.wifi + " " + L10n.Network.Management.Tool.Retain.state + networkIcon.image = Asset.Images.Piax.Nmt.iconOpenWifiRetain.image + networkIcon.accessibilityLabel = L10n.Localizable.Network.Management.Tool.Open.wifi + " " + L10n.Localizable.Network.Management.Tool.Retain.state } case .protectedWiFi: - title.text = L10n.Network.Management.Tool.Secure.wifi + title.text = L10n.Localizable.Network.Management.Tool.Secure.wifi switch data.rule { case .alwaysConnect: - networkIcon.image = Asset.Piax.Nmt.iconSecureWifiConnect.image - networkIcon.accessibilityLabel = L10n.Network.Management.Tool.Secure.wifi + " " + L10n.Network.Management.Tool.Always.connect + networkIcon.image = Asset.Images.Piax.Nmt.iconSecureWifiConnect.image + networkIcon.accessibilityLabel = L10n.Localizable.Network.Management.Tool.Secure.wifi + " " + L10n.Localizable.Network.Management.Tool.Always.connect case .alwaysDisconnect: - networkIcon.image = Asset.Piax.Nmt.iconSecureWifiDisconnect.image - networkIcon.accessibilityLabel = L10n.Network.Management.Tool.Secure.wifi + " " + L10n.Network.Management.Tool.Always.disconnect + networkIcon.image = Asset.Images.Piax.Nmt.iconSecureWifiDisconnect.image + networkIcon.accessibilityLabel = L10n.Localizable.Network.Management.Tool.Secure.wifi + " " + L10n.Localizable.Network.Management.Tool.Always.disconnect case .retainState: - networkIcon.image = Asset.Piax.Nmt.iconSecureWifiRetain.image - networkIcon.accessibilityLabel = L10n.Network.Management.Tool.Secure.wifi + " " + L10n.Network.Management.Tool.Retain.state + networkIcon.image = Asset.Images.Piax.Nmt.iconSecureWifiRetain.image + networkIcon.accessibilityLabel = L10n.Localizable.Network.Management.Tool.Secure.wifi + " " + L10n.Localizable.Network.Management.Tool.Retain.state } case .cellular: - title.text = L10n.Network.Management.Tool.Mobile.data + title.text = L10n.Localizable.Network.Management.Tool.Mobile.data switch data.rule { case .alwaysConnect: - networkIcon.image = Asset.Piax.Nmt.iconMobileDataConnect.image - networkIcon.accessibilityLabel = L10n.Network.Management.Tool.Mobile.data + " " + L10n.Network.Management.Tool.Always.connect + networkIcon.image = Asset.Images.Piax.Nmt.iconMobileDataConnect.image + networkIcon.accessibilityLabel = L10n.Localizable.Network.Management.Tool.Mobile.data + " " + L10n.Localizable.Network.Management.Tool.Always.connect case .alwaysDisconnect: - networkIcon.image = Asset.Piax.Nmt.iconMobileDataDisconnect.image - networkIcon.accessibilityLabel = L10n.Network.Management.Tool.Mobile.data + " " + L10n.Network.Management.Tool.Always.disconnect + networkIcon.image = Asset.Images.Piax.Nmt.iconMobileDataDisconnect.image + networkIcon.accessibilityLabel = L10n.Localizable.Network.Management.Tool.Mobile.data + " " + L10n.Localizable.Network.Management.Tool.Always.disconnect case .retainState: - networkIcon.image = Asset.Piax.Nmt.iconMobileDataRetain.image - networkIcon.accessibilityLabel = L10n.Network.Management.Tool.Mobile.data + " " + L10n.Network.Management.Tool.Retain.state + networkIcon.image = Asset.Images.Piax.Nmt.iconMobileDataRetain.image + networkIcon.accessibilityLabel = L10n.Localizable.Network.Management.Tool.Mobile.data + " " + L10n.Localizable.Network.Management.Tool.Retain.state } case .trustedNetwork: title.text = data.ssid switch data.rule { case .alwaysConnect: - networkIcon.image = Asset.Piax.Nmt.iconCustomWifiConnect.image - networkIcon.accessibilityLabel = data.ssid + " " + L10n.Network.Management.Tool.Always.connect + networkIcon.image = Asset.Images.Piax.Nmt.iconCustomWifiConnect.image + networkIcon.accessibilityLabel = data.ssid + " " + L10n.Localizable.Network.Management.Tool.Always.connect case .alwaysDisconnect: - networkIcon.image = Asset.Piax.Nmt.iconCustomWifiDisconnect.image - networkIcon.accessibilityLabel = data.ssid + " " + L10n.Network.Management.Tool.Always.disconnect + networkIcon.image = Asset.Images.Piax.Nmt.iconCustomWifiDisconnect.image + networkIcon.accessibilityLabel = data.ssid + " " + L10n.Localizable.Network.Management.Tool.Always.disconnect case .retainState: - networkIcon.image = Asset.Piax.Nmt.iconCustomWifiRetain.image - networkIcon.accessibilityLabel = data.ssid + " " + L10n.Network.Management.Tool.Retain.state + networkIcon.image = Asset.Images.Piax.Nmt.iconCustomWifiRetain.image + networkIcon.accessibilityLabel = data.ssid + " " + L10n.Localizable.Network.Management.Tool.Retain.state } } switch data.rule { case .alwaysConnect: - subtitle.text = L10n.Network.Management.Tool.Always.connect + subtitle.text = L10n.Localizable.Network.Management.Tool.Always.connect statusColor.backgroundColor = UIColor.piaNMTGreen case .alwaysDisconnect: - subtitle.text = L10n.Network.Management.Tool.Always.disconnect + subtitle.text = L10n.Localizable.Network.Management.Tool.Always.disconnect statusColor.backgroundColor = UIColor.piaNMTRed case .retainState: - subtitle.text = L10n.Network.Management.Tool.Retain.state + subtitle.text = L10n.Localizable.Network.Management.Tool.Retain.state statusColor.backgroundColor = UIColor.piaNMTBlue } @@ -154,10 +154,10 @@ class NetworkCollectionViewCell: UICollectionViewCell { title.style(style: Theme.current.palette.appearance == .dark ? TextStyle.textStyleCardTitleDark : TextStyle.textStyleCardTitleLight) subtitle.style(style: TextStyle.textStyle8) - let optionsImage = Asset.Piax.Nmt.iconOptions.image.withRenderingMode(.alwaysTemplate) + let optionsImage = Asset.Images.Piax.Nmt.iconOptions.image.withRenderingMode(.alwaysTemplate) manageButton.setImage(optionsImage, for: .normal) manageButton.tintColor = Theme.current.palette.appearance == .dark ? UIColor.white : UIColor.piaGrey6 - manageButton.accessibilityLabel = L10n.Global.edit + manageButton.accessibilityLabel = L10n.Localizable.Global.edit popover.dismiss() } diff --git a/PIA VPN/NetworkFooterCollectionViewCell.swift b/PIA VPN/NetworkFooterCollectionViewCell.swift index b61a63c9e..635b3b3b4 100644 --- a/PIA VPN/NetworkFooterCollectionViewCell.swift +++ b/PIA VPN/NetworkFooterCollectionViewCell.swift @@ -32,7 +32,7 @@ class NetworkFooterCollectionViewCell: UICollectionViewCell { } func setup() { - self.addRuleButton.setTitle(L10n.Network.Management.Tool.Add.rule, for: .normal) + self.addRuleButton.setTitle(L10n.Localizable.Network.Management.Tool.Add.rule, for: .normal) viewShouldRestyle() } // MARK: Restylable @@ -40,10 +40,10 @@ class NetworkFooterCollectionViewCell: UICollectionViewCell { func viewShouldRestyle() { addRuleButton.titleLabel?.font = TextStyle.textStyle4.font addRuleButton.setTitleColor(TextStyle.textStyle4.color, for: .normal) - let optionsImage = Asset.Piax.Nmt.iconAddRule.image.withRenderingMode(.alwaysTemplate) + let optionsImage = Asset.Images.Piax.Nmt.iconAddRule.image.withRenderingMode(.alwaysTemplate) addRuleButton.setImage(optionsImage, for: .normal) addRuleButton.tintColor = TextStyle.textStyle4.color - addRuleButton.accessibilityLabel = L10n.Network.Management.Tool.Add.rule + addRuleButton.accessibilityLabel = L10n.Localizable.Network.Management.Tool.Add.rule } @IBAction private func addNewRule() { diff --git a/PIA VPN/NetworkRuleOptionView.swift b/PIA VPN/NetworkRuleOptionView.swift index 13f5dbe7a..55fd48214 100644 --- a/PIA VPN/NetworkRuleOptionView.swift +++ b/PIA VPN/NetworkRuleOptionView.swift @@ -73,20 +73,20 @@ extension NetworkRuleOptionView: UITableViewDelegate, UITableViewDataSource { switch indexPath.row { case NMTRules.alwaysConnect.rawValue: - cell.textLabel?.text = L10n.Network.Management.Tool.Always.connect - cell.textLabel?.accessibilityLabel = L10n.Global.Row.selection + " " + L10n.Network.Management.Tool.Always.connect - cell.accessoryView = UIImageView(image: Asset.Piax.Nmt.iconNmtConnect.image.withRenderingMode(.alwaysTemplate)) + cell.textLabel?.text = L10n.Localizable.Network.Management.Tool.Always.connect + cell.textLabel?.accessibilityLabel = L10n.Localizable.Global.Row.selection + " " + L10n.Localizable.Network.Management.Tool.Always.connect + cell.accessoryView = UIImageView(image: Asset.Images.Piax.Nmt.iconNmtConnect.image.withRenderingMode(.alwaysTemplate)) case NMTRules.alwaysDisconnect.rawValue: - cell.textLabel?.text = L10n.Network.Management.Tool.Always.disconnect - cell.textLabel?.accessibilityLabel = L10n.Global.Row.selection + " " + L10n.Network.Management.Tool.Always.disconnect - cell.accessoryView = UIImageView(image: Asset.Piax.Nmt.iconDisconnect.image.withRenderingMode(.alwaysTemplate)) + cell.textLabel?.text = L10n.Localizable.Network.Management.Tool.Always.disconnect + cell.textLabel?.accessibilityLabel = L10n.Localizable.Global.Row.selection + " " + L10n.Localizable.Network.Management.Tool.Always.disconnect + cell.accessoryView = UIImageView(image: Asset.Images.Piax.Nmt.iconDisconnect.image.withRenderingMode(.alwaysTemplate)) case NMTRules.retainState.rawValue: - cell.textLabel?.text = L10n.Network.Management.Tool.Retain.state - cell.textLabel?.accessibilityLabel = L10n.Global.Row.selection + " " + L10n.Network.Management.Tool.Retain.state - cell.accessoryView = UIImageView(image: Asset.Piax.Nmt.iconRetain.image.withRenderingMode(.alwaysTemplate)) + cell.textLabel?.text = L10n.Localizable.Network.Management.Tool.Retain.state + cell.textLabel?.accessibilityLabel = L10n.Localizable.Global.Row.selection + " " + L10n.Localizable.Network.Management.Tool.Retain.state + cell.accessoryView = UIImageView(image: Asset.Images.Piax.Nmt.iconRetain.image.withRenderingMode(.alwaysTemplate)) default: - cell.textLabel?.text = L10n.Global.remove - cell.textLabel?.accessibilityLabel = L10n.Global.Row.selection + " " + L10n.Global.remove + cell.textLabel?.text = L10n.Localizable.Global.remove + cell.textLabel?.accessibilityLabel = L10n.Localizable.Global.Row.selection + " " + L10n.Localizable.Global.remove cell.textLabel?.style(style: TextStyle.textStyle10) cell.accessoryView = nil } diff --git a/PIA VPN/NotificationCategory.swift b/PIA VPN/NotificationCategory.swift new file mode 100644 index 000000000..584634b5a --- /dev/null +++ b/PIA VPN/NotificationCategory.swift @@ -0,0 +1,55 @@ + +import Foundation +import UserNotifications +import PIALibrary + +public struct NotificationCategory { + public static let nonCompliantWifi = "NONCOMPLIANTWIFI" +} + + +// MARK: Local Notifications + +extension Macros { + + public static func showLocalNotification(_ id: String, type: String, body: String, info: [String: String] = [:], title: String? = nil, delay: Double = 0) { + + let content = UNMutableNotificationContent() + content.categoryIdentifier = type + content.body = body + if let title = title { + content.title = title + } + content.userInfo = info + + // Fire in minutes (60 seconds times ) + var trigger : UNTimeIntervalNotificationTrigger? = nil + if delay > 0 { + trigger = UNTimeIntervalNotificationTrigger(timeInterval: (delay * 60), repeats: false) + } + + // Create the request + let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger) + + // Schedule the request with the system. + let notificationCenter = UNUserNotificationCenter.current() + notificationCenter.add(request) + + } + + public static func showLocalNotificationIfNotAlreadyPresent(_ id: String, type: String, body: String, info: [String: String] = [:], title: String? = nil, delay: Double = 1) { + let notificationCenter = UNUserNotificationCenter.current() + notificationCenter.getDeliveredNotifications { notifications in + // If the notification is not present in Notification Center, then show the notification + if notifications.first(where:{ $0.request.identifier == id }) == nil { + self.showLocalNotification(id, type: type, body: body, info: info, title: title, delay: delay) + } + } + } + + public static func removeLocalNotification(_ id: String) { + let notificationCenter = UNUserNotificationCenter.current() + notificationCenter.removeDeliveredNotifications(withIdentifiers: [id]) + } + +} diff --git a/PIA VPN/OptionsViewController.swift b/PIA VPN/OptionsViewController.swift new file mode 100644 index 000000000..31119a626 --- /dev/null +++ b/PIA VPN/OptionsViewController.swift @@ -0,0 +1,206 @@ +// +// OptionsViewController.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 12/8/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit + +/// Displays an `UITableView` with a list of dynamically rendered options. +public class OptionsViewController: AutolayoutViewController, UITableViewDataSource, UITableViewDelegate { + private var tableView: UITableView! + + /// The list of options. + public var options: [AnyHashable] = [] + + /// The initially selected option (optional). + public var selectedOption: AnyHashable? + + /// The tag of this controller for future reference. + public var tag = 0 + + /// The `OptionsViewControllerDelegate` for rendering and events. + public weak var delegate: OptionsViewControllerDelegate? + + // FIXME: table view "expands" on appearance + + /// :nodoc: + public override func viewDidLoad() { + let viewContainer = UIView(frame: view.bounds) + viewContainer.autoresizingMask = [.flexibleWidth, .flexibleHeight] + viewContainer.backgroundColor = .clear + view.addSubview(viewContainer) + self.viewContainer = viewContainer + + viewContainer.insetsLayoutMarginsFromSafeArea = false + + super.viewDidLoad() + + guard let bgColor = delegate?.backgroundColorForOptionsController(self) else { + return + } + viewContainer.backgroundColor = bgColor + guard let style = delegate?.tableStyleForOptionsController(self) else { + return + } + tableView = UITableView(frame: viewContainer.bounds, style: style) + tableView.backgroundColor = bgColor + tableView.dataSource = self + tableView.delegate = self + + viewContainer.addSubview(tableView) + + tableView.addConstaintsToSuperview(leadingOffset: 0, + trailingOffset: 0, + topOffset: 0, + bottomOffset: 0) + + delegate?.optionsController(self, didLoad: tableView) + + NotificationCenter.default.addObserver(self, selector: #selector(viewHasRotated), name: UIDevice.orientationDidChangeNotification, object: nil) + + viewShouldRestyle() + + } + + public func reload() { + self.tableView.reloadData() + } + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + styleNavigationBarWithTitle(self.navigationController?.title ?? "") + } + + @objc private func viewHasRotated() { + styleNavigationBarWithTitle(self.navigationController?.title ?? "") + } + + // MARK: Restylable + + override public func viewShouldRestyle() { + super.viewShouldRestyle() + + styleNavigationBarWithTitle(self.navigationController?.title ?? "") + // XXX: for some reason, UITableView is not affected by appearance updates + if let viewContainer = viewContainer { + Theme.current.applySecondaryBackground(view) + Theme.current.applySecondaryBackground(viewContainer) + } + if tableView != nil { + Theme.current.applyPrincipalBackground(tableView) + Theme.current.applyDividerToSeparator(tableView) + tableView.reloadData() + } + + } + + // MARK: UITableViewDataSource + + /// :nodoc: + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return options.count + } + + /// :nodoc: + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = delegate?.optionsController(self, tableView: tableView, reusableCellAt: indexPath) else { + fatalError("Implement optionsController:tableView:reusableCellAt: in delegate") + } + + let option = options[indexPath.row] + var isSelected = false + if let selectedOption = selectedOption { + isSelected = (option == selectedOption) + } + delegate?.optionsController(self, renderOption: option, in: cell, at: indexPath.row, isSelected: isSelected) + + Theme.current.applySecondaryBackground(cell) + if let textLabel = cell.textLabel { + Theme.current.applySettingsCellTitle(textLabel, + appearance: .dark) + } + if let detailLabel = cell.detailTextLabel { + Theme.current.applySubtitle(detailLabel) + } + + let backgroundView = UIView() + Theme.current.applyPrincipalBackground(backgroundView) + cell.selectedBackgroundView = backgroundView + + return cell + } + + // MARK: UITableViewDelegate + + /// :nodoc: + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let option = options[indexPath.row] + + delegate?.optionsController(self, didSelectOption: option, at: indexPath.row) + tableView.deselectRow(at: indexPath, animated: true) + } +} + +/// Handles rendering and receives events of an `OptionsViewController`. +public protocol OptionsViewControllerDelegate: class { + + /** + Sets the background color of the view controller. + */ + func backgroundColorForOptionsController(_ controller: OptionsViewController) -> UIColor + + /** + Sets the `UITableViewStyle` for the table view. + */ + func tableStyleForOptionsController(_ controller: OptionsViewController) -> UITableView.Style + + /** + Called after loading the embedded `UITableView`. + + - Parameter tableView: The loaded `UITableView`. + */ + func optionsController(_ controller: OptionsViewController, didLoad tableView: UITableView) + + /** + Returns a reusable `UITableViewCell` for displaying the options. + + - Parameter tableView: The parent `UITableView`. + - Parameter indexPath: The `IndexPath` for caching purposes. + */ + func optionsController(_ controller: OptionsViewController, tableView: UITableView, reusableCellAt indexPath: IndexPath) -> UITableViewCell + + /** + Renders an option in a reusable `UITableViewCell` as returned by `optionsController(_:tableView:reusableCellAt:)`. + + - Parameter option: The generic option to render. + - Parameter cell: The `UITableViewCell` in which to render the option. + - Parameter row: The row the option is in. + - Parameter isSelected: If `true`, the option is to be rendered selected. + */ + func optionsController(_ controller: OptionsViewController, renderOption option: AnyHashable, in cell: UITableViewCell, at row: Int, isSelected: Bool) + + /** + Called when an option is selected. + + - Parameter option: The selected option. + - Parameter row: The row the option is in. + */ + func optionsController(_ controller: OptionsViewController, didSelectOption option: AnyHashable, at row: Int) +} diff --git a/PIA VPN/PIACardsViewController.swift b/PIA VPN/PIACardsViewController.swift index 0583fd23a..f265e1cd1 100644 --- a/PIA VPN/PIACardsViewController.swift +++ b/PIA VPN/PIACardsViewController.swift @@ -49,7 +49,7 @@ class PIACardsViewController: UIViewController { func createSlides() -> [PIACard] { - let closeImage = Asset.iconClose.image.withRenderingMode(.alwaysTemplate) + let closeImage = Asset.Images.iconClose.image.withRenderingMode(.alwaysTemplate) var collectingCards = [PIACard]() for card in cards { @@ -72,8 +72,8 @@ class PIACardsViewController: UIViewController { } if card.hasSecondCTA() { slide.cardSecondaryCTAButton.isHidden = false - slide.cardSecondaryCTAButton.setTitle(L10n.Card.Wireguard.Cta.learn, for: []) - slide.cardSecondaryCTAButton.accessibilityIdentifier = L10n.Card.Wireguard.Cta.learn + slide.cardSecondaryCTAButton.setTitle(L10n.Localizable.Card.Wireguard.Cta.learn, for: []) + slide.cardSecondaryCTAButton.accessibilityIdentifier = L10n.Localizable.Card.Wireguard.Cta.learn slide.cardSecondaryCTAButton.addAction(for: .touchUpInside) { (button) in if let url = card.learnMoreLink { if UIApplication.shared.canOpenURL(url) { diff --git a/PIA VPN/PIAConnectionButton.swift b/PIA VPN/PIAConnectionButton.swift index c525268ad..94bd902fb 100644 --- a/PIA VPN/PIAConnectionButton.swift +++ b/PIA VPN/PIAConnectionButton.swift @@ -45,9 +45,9 @@ class PIAConnectionButton: UIButton, Restylable { var isOn: Bool = false { didSet { if isOn == true { - self.accessibilityLabel = L10n.Dashboard.Accessibility.Vpn.Button.isOn + self.accessibilityLabel = L10n.Localizable.Dashboard.Accessibility.Vpn.Button.isOn } else { - self.accessibilityLabel = L10n.Dashboard.Accessibility.Vpn.Button.isOff + self.accessibilityLabel = L10n.Localizable.Dashboard.Accessibility.Vpn.Button.isOff } } } @@ -78,13 +78,13 @@ class PIAConnectionButton: UIButton, Restylable { private func setupView() { - self.accessibilityLabel = L10n.Dashboard.Accessibility.Vpn.button + self.accessibilityLabel = L10n.Localizable.Dashboard.Accessibility.Vpn.button //Notification when the theme has changed NotificationCenter.default.addObserver(self, selector: #selector(viewShouldRestyle), name: .PIAThemeDidChange, object: nil) //Image - let vpnImage = Asset.Piax.Dashboard.vpnButton.image.withRenderingMode(.alwaysTemplate) + let vpnImage = Asset.Images.Piax.Dashboard.vpnButton.image.withRenderingMode(.alwaysTemplate) self.setImage(vpnImage, for: []) displayLink = CADisplayLink(target: self, selector: #selector(redrawUpdate)) diff --git a/PIA VPN/PIAHotspotHelper.swift b/PIA VPN/PIAHotspotHelper.swift index f8e5728a0..9d6eee4b5 100644 --- a/PIA VPN/PIAHotspotHelper.swift +++ b/PIA VPN/PIAHotspotHelper.swift @@ -147,9 +147,9 @@ class PIAHotspotHelper { private func hotspotHelperMessage() -> String { if Client.preferences.nmtRulesEnabled, Client.preferences.useWiFiProtection { - return L10n.Hotspothelper.Display.Protected.name + return L10n.Localizable.Hotspothelper.Display.Protected.name } else { - return L10n.Hotspothelper.Display.name + return L10n.Localizable.Hotspothelper.Display.name } } diff --git a/PIA VPN/PIAWelcomeViewController.swift b/PIA VPN/PIAWelcomeViewController.swift new file mode 100644 index 000000000..4d93cfbe5 --- /dev/null +++ b/PIA VPN/PIAWelcomeViewController.swift @@ -0,0 +1,280 @@ +// +// PIAWelcomeViewController.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 10/19/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit +import PIALibrary + +/** + The welcome view controller is a graphic gateway to the PIA services. + */ +public class PIAWelcomeViewController: AutolayoutViewController, WelcomeCompletionDelegate, ConfigurationAccess, InAppAccess, BrandableNavigationBar { + + @IBOutlet private weak var buttonCancel: UIButton! + @IBOutlet private weak var buttonEnvironment: UIButton! + + var preset = Preset() + + var selectedPlanIndex: Int? + + var allPlans: [PurchasePlan]? + + private var pendingSignupRequest: SignupRequest? + weak var delegate: PIAWelcomeViewControllerDelegate? + + /// It's `true` if the controller was created with `Preset.isEphemeral`. + /// + /// - Seealso: `Preset.isEphemeral` + public var isEphemeral: Bool { + return preset.isEphemeral + } + + /** + Creates a wrapped `PIAWelcomeViewController` ready for presentation. + + - Parameter preset: The optional `Preset` to configure this controller with + - Parameter delegate: The `PIAWelcomeViewControllerDelegate` to handle raised events + */ + public static func with(preset: Preset? = nil, delegate: PIAWelcomeViewControllerDelegate? = nil) -> UIViewController { + let nav = StoryboardScene.Welcome.initialScene.instantiate() + + let vc = nav.topViewController as! PIAWelcomeViewController + if let customPreset = preset { + vc.preset = customPreset + } + vc.delegate = delegate + return nav + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + /// :nodoc: + public override func viewDidLoad() { + super.viewDidLoad() + + guard !preset.accountProvider.isLoggedIn else { + fatalError("You are already logged in, you might want to Client.database.truncate() to start clean") + } + + buttonCancel.isHidden = true + buttonEnvironment.isHidden = !accessedConfiguration.isDevelopment + buttonEnvironment.accessibilityIdentifier = Accessibility.Id.Welcome.environment + + #if os(iOS) + let nc = NotificationCenter.default + nc.addObserver(self, selector: #selector(inAppDidAddUncredited(notification:)), name: .__InAppDidAddUncredited, object: nil) + #endif + + } + + /// :nodoc: + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if !preset.openFromDashboard { + self.navigationController?.setNavigationBarHidden(false, animated: true) + self.navigationItem.leftBarButtonItem = UIBarButtonItem( + image: Theme.current.palette.navigationBarBackIcon?.withRenderingMode(.alwaysOriginal), + style: .plain, + target: self, + action: #selector(back(_:)) + ) + self.navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Welcome.Redeem.Accessibility.back + } else { + if preset.allowsCancel { + self.navigationItem.leftBarButtonItem = UIBarButtonItem( + image: Asset.Images.iconClose.image.withRenderingMode(.alwaysOriginal), + style: .plain, + target: self, + action: #selector(cancelClicked(_:)) + ) + self.navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Ui.Global.cancel + } + } + + refreshEnvironmentButton() + } + + /// :nodoc: + public override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + tryRecoverSignupProcess() + } + + // MARK: Actions + @objc private func cancelClicked(_ sender: Any?) { + delegate?.welcomeControllerDidCancel(self) + } + + @IBAction private func toggleEnvironment(_ sender: Any?) { + if (Client.environment == .production) { + Client.environment = .staging + } else { + Client.environment = .production + } + Client.resetWebServices() + Client.providers.serverProvider.download(nil) + refreshEnvironmentButton() + } + + private func refreshEnvironmentButton() { + if (Client.environment == .production) { + buttonEnvironment.setTitle("Production", for: .normal) + } else { + buttonEnvironment.setTitle("Staging", for: .normal) + } + } + + private func tryRecoverSignupProcess() { + guard preset.shouldRecoverPendingSignup else { + return + } + guard let request = preset.accountProvider.lastSignupRequest else { + return + } + guard (pendingSignupRequest == nil) else { + return + } + guard accessedStore.hasUncreditedTransactions else { + return + } + pendingSignupRequest = request + perform(segue: StoryboardSegue.Welcome.signupViaRecoverSegue) + } + + /// :nodoc: + public override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if let vc = segue.destination as? WelcomePageViewController { + vc.preset = preset + vc.completionDelegate = self + vc.allPlans = allPlans + vc.selectedPlanIndex = selectedPlanIndex + } + // recover pending signup + else if (segue.identifier == StoryboardSegue.Welcome.signupViaRecoverSegue.rawValue) { + let nav = segue.destination as! UINavigationController + let vc = nav.topViewController as! SignupInProgressViewController + + guard let request = pendingSignupRequest else { + fatalError("Recovering signup and pendingSignupRequest is not set") + } + var metadata = SignupMetadata(email: request.email) + metadata.title = L10n.Signup.InProgress.title + metadata.bodySubtitle = L10n.Signup.InProgress.message + vc.metadata = metadata + vc.signupRequest = request + } + } + + // MARK: WelcomeCompletionDelegate + + func welcomeDidLogin(withUser user: UserAccount, topViewController: UIViewController) { + delegate?.welcomeController(self, didLoginWith: user, topViewController: topViewController) + } + + func welcomeDidSignup(withUser user: UserAccount, topViewController: UIViewController) { + delegate?.welcomeController(self, didSignupWith: user, topViewController: topViewController) + } + + // MARK: Notifications + + #if os(iOS) + @objc private func inAppDidAddUncredited(notification: Notification) { + tryRecoverSignupProcess() + } + #endif + + // MARK: Size classes + + // consider compact height in landscape + /// :nodoc: + public override var traitCollection: UITraitCollection { + if isLandscape { + return UITraitCollection(verticalSizeClass: .compact) + } + return super.traitCollection + } + + // MARK: Restylable + + /// :nodoc: + public override func viewShouldRestyle() { + super.viewShouldRestyle() + if !preset.isExpired { + navigationItem.titleView = NavigationLogoView() + } + else { + navigationItem.title = L10n.Welcome.Upgrade.header + } + Theme.current.applyPrincipalBackground(view) + Theme.current.applyNavigationBarStyle(to: self) + Theme.current.applyCancelButton(buttonCancel, appearance: .dark) + buttonEnvironment.setTitleColor(buttonCancel.titleColor(for: .normal), for: .normal) + } +} + +/// Receives events from a `PIAWelcomeViewController`. +public protocol PIAWelcomeViewControllerDelegate: class { + + /** + Invoked after a successful login. + + - Parameter welcomeController: The delegating controller + - Parameter user: The logged in `UserAccount` + */ + func welcomeController(_ welcomeController: PIAWelcomeViewController, didLoginWith user: UserAccount, topViewController: UIViewController) + + /** + Invoked after a successful signup. + + - Parameter welcomeController: The delegating controller + - Parameter user: The signed up `UserAccount` + */ + func welcomeController(_ welcomeController: PIAWelcomeViewController, didSignupWith user: UserAccount, topViewController: UIViewController) + + /** + Invoked after a cancel. + + - Parameter welcomeController: The delegating controller + */ + func welcomeControllerDidCancel(_ welcomeController: PIAWelcomeViewController) +} + +public extension PIAWelcomeViewControllerDelegate { + func welcomeControllerDidCancel(_ welcomeController: PIAWelcomeViewController) { + } +} + +protocol WelcomeChild: class { + var preset: Preset? { get set } + + var omitsSiblingLink: Bool { get set } + + var completionDelegate: WelcomeCompletionDelegate? { get set } +} + +protocol WelcomeCompletionDelegate: class { + func welcomeDidLogin(withUser user: UserAccount, topViewController: UIViewController) + + func welcomeDidSignup(withUser user: UserAccount, topViewController: UIViewController) +} diff --git a/PIA VPN/ProtocolSettingsViewController.swift b/PIA VPN/ProtocolSettingsViewController.swift index fe38f7147..4be151bf0 100644 --- a/PIA VPN/ProtocolSettingsViewController.swift +++ b/PIA VPN/ProtocolSettingsViewController.swift @@ -75,7 +75,7 @@ class ProtocolSettingsViewController: PIABaseSettingsViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - styleNavigationBarWithTitle(L10n.Settings.Section.protocols) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Section.protocols) } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -213,7 +213,7 @@ class ProtocolSettingsViewController: PIABaseSettingsViewController { override func viewShouldRestyle() { super.viewShouldRestyle() - styleNavigationBarWithTitle(L10n.Settings.Section.general) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Section.general) // XXX: for some reason, UITableView is not affected by appearance updates if let viewContainer = viewContainer { Theme.current.applyPrincipalBackground(view) @@ -248,7 +248,7 @@ extension ProtocolSettingsViewController: UITableViewDelegate, UITableViewDataSo cell.textLabel?.numberOfLines = 0 cell.textLabel?.style(style: TextStyle.textStyle21) cell.backgroundColor = .clear - cell.textLabel?.text = L10n.Settings.Small.Packets.description + cell.textLabel?.text = L10n.Localizable.Settings.Small.Packets.description return cell } return nil @@ -261,12 +261,12 @@ extension ProtocolSettingsViewController: UITableViewDelegate, UITableViewDataSo case .protocolSelection: cell.detailTextLabel?.text = pendingPreferences.vpnType.vpnProtocol case .transport: - cell.detailTextLabel?.text = settingsDelegate.pendingOpenVPNSocketType?.rawValue ?? L10n.Global.automatic + cell.detailTextLabel?.text = settingsDelegate.pendingOpenVPNSocketType?.rawValue ?? L10n.Localizable.Global.automatic case .remotePort: if let port = settingsDelegate.pendingOpenVPNConfiguration.currentPort, port != ProtocolSettingsViewController.AUTOMATIC_PORT { cell.detailTextLabel?.text = port.description } else { - cell.detailTextLabel?.text = L10n.Global.automatic + cell.detailTextLabel?.text = L10n.Localizable.Global.automatic } case .dataEncryption: @@ -299,7 +299,7 @@ extension ProtocolSettingsViewController: UITableViewDelegate, UITableViewDataSo cell.accessoryType = .none } case .useSmallPackets: - cell.textLabel?.text = L10n.Settings.Small.Packets.title + cell.textLabel?.text = L10n.Localizable.Settings.Small.Packets.title cell.detailTextLabel?.text = nil cell.accessoryView = switchSmallPackets cell.selectionStyle = .none diff --git a/PIA VPN/PurchasePlanCell.swift b/PIA VPN/PurchasePlanCell.swift new file mode 100644 index 000000000..05e14b0f8 --- /dev/null +++ b/PIA VPN/PurchasePlanCell.swift @@ -0,0 +1,133 @@ +// +// PurchasePlanCell.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 10/19/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit +import PIALibrary + +class PurchasePlanCell: UICollectionViewCell, Restylable { + + // XXX + private static let textPlaceholder = " " + private static let pricePlaceholder = " " + private static let bestValueContainerHeight: CGFloat = 20.0 + private static let priceBottomConstant: CGFloat = 26.0 + + @IBOutlet private weak var viewContainer: UIView! + @IBOutlet private weak var viewBestValue: UIView! + @IBOutlet private weak var labelBestValue: UILabel! + @IBOutlet private weak var labelPlan: UILabel! + @IBOutlet private weak var labelPrice: UILabel! + @IBOutlet private weak var labelDetail: UILabel! + + @IBOutlet private weak var unselectedPlanImageView: UIImageView! + @IBOutlet private weak var selectedPlanImageView: UIImageView! + + @IBOutlet private weak var bestValueHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var priceBottomConstraint: NSLayoutConstraint! + + override func awakeFromNib() { + super.awakeFromNib() + isSelected = false + + labelBestValue.text = Client.configuration.eligibleForTrial ? + "\(L10n.Welcome.Plan.bestValue.uppercased()) - FREE TRIAL" : + L10n.Welcome.Plan.bestValue.uppercased() + + selectedPlanImageView.alpha = 0 + self.accessibilityTraits = UIAccessibilityTraits.button + self.isAccessibilityElement = true + } + + func fill(plan: PurchasePlan) { + viewShouldRestyle() + + if plan.isDummy { + let pendingBackgroundColor = UIColor(white: 0.95, alpha: 1.0) + labelPlan.backgroundColor = pendingBackgroundColor + labelDetail.backgroundColor = pendingBackgroundColor + labelPrice.backgroundColor = pendingBackgroundColor + + labelPlan.text = PurchasePlanCell.textPlaceholder + labelDetail.text = PurchasePlanCell.textPlaceholder + labelPrice.text = PurchasePlanCell.pricePlaceholder + viewBestValue.isHidden = true + } else { + labelPlan.backgroundColor = .clear + labelDetail.backgroundColor = .clear + labelPrice.backgroundColor = .clear + + labelPlan.text = plan.title + labelDetail.text = plan.detail + labelPrice.text = L10n.Welcome.Plan.priceFormat(plan.monthlyPriceString) + self.accessibilityLabel = "\(plan.title) \(plan.detail) \(labelPrice.text)" + viewBestValue.isHidden = !plan.bestValue + if viewBestValue.isHidden { + bestValueHeightConstraint.constant = 0 + priceBottomConstraint.constant = 0 + } else { + bestValueHeightConstraint.constant = PurchasePlanCell.bestValueContainerHeight + priceBottomConstraint.constant = PurchasePlanCell.priceBottomConstant + } + + if plan.plan == Plan.yearly { + Theme.current.applyTitle(labelDetail, appearance: .dark) + Theme.current.applySmallInfo(labelPrice, appearance: .dark) + } else { + Theme.current.applyTitle(labelPrice, appearance: .dark) + Theme.current.applySmallInfo(labelDetail, appearance: .dark) + } + + self.layoutSubviews() + + accessibilityLabel = "\(plan.title), \(plan.accessibleMonthlyPriceString) \(L10n.Welcome.Plan.Accessibility.perMonth)" + } + viewBestValue.isHidden = !plan.bestValue + } + + override var isSelected: Bool { + didSet { + Theme.current.applyBorder(viewContainer, selected: isSelected) +// Theme.current.applyTitle(labelPrice, appearance:(isSelected ? .emphasis : .dark)) + + if isSelected { + UIView.animate(withDuration: 0.2, animations: { + self.selectedPlanImageView.alpha = 1 + }) + } else { + UIView.animate(withDuration: 0.2, animations: { + self.selectedPlanImageView.alpha = 0 + }) + } + } + } + + // MARK: Restylable + + func viewShouldRestyle() { + Theme.current.applyCorner(viewBestValue, factor: 1.0) + Theme.current.applyWarningBackground(viewBestValue) + Theme.current.applyBlackLabelInBox(labelBestValue) + Theme.current.applySubtitle(labelPlan) + Theme.current.applyTitle(labelPrice, appearance: .dark) + Theme.current.applySubtitle(labelDetail) + } +} diff --git a/PIA VPN/PurchaseViewController.swift b/PIA VPN/PurchaseViewController.swift new file mode 100644 index 000000000..e6950d59a --- /dev/null +++ b/PIA VPN/PurchaseViewController.swift @@ -0,0 +1,355 @@ +// +// PurchaseViewController.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 10/19/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit +import SwiftyBeaver +import PIALibrary + +private let log = SwiftyBeaver.self + +class PurchaseViewController: AutolayoutViewController, BrandableNavigationBar, WelcomeChild { + + private struct Cells { + static let plan = "PlanCell" + } + + @IBOutlet private weak var scrollView: UIScrollView! + + @IBOutlet private weak var labelTitle: UILabel! + @IBOutlet private weak var labelSubtitle: UILabel! + + @IBOutlet private weak var collectionPlans: UICollectionView! + + @IBOutlet private weak var textAgreement: UITextView! + + @IBOutlet private weak var buttonPurchase: PIAButton! + + var preset: Preset? + weak var completionDelegate: WelcomeCompletionDelegate? + var omitsSiblingLink = false + + var allPlans: [PurchasePlan] = [.dummy, .dummy] + + var selectedPlanIndex: Int? + + private var isExpired = false + private var signupEmail: String? + private var signupTransaction: InAppTransaction? + private var isPurchasing = false + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override func viewDidLoad() { + super.viewDidLoad() + + guard let preset = self.preset else { + fatalError("Preset not propagated") + } + + isExpired = preset.isExpired + + styleButtons() + + collectionPlans.isUserInteractionEnabled = false + + self.navigationItem.leftBarButtonItem = UIBarButtonItem( + image: Theme.current.palette.navigationBarBackIcon?.withRenderingMode(.alwaysOriginal), + style: .plain, + target: self, + action: #selector(back(_:)) + ) + self.navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Welcome.Redeem.Accessibility.back + + if isExpired { + labelTitle.text = L10n.Welcome.Upgrade.title + labelTitle.textAlignment = .center + labelSubtitle.text = "" + } + else { + labelTitle.text = L10n.Welcome.Purchase.title + labelSubtitle.text = L10n.Welcome.Purchase.subtitle + } + textAgreement.attributedText = Theme.current.agreementText( + withMessage: L10n.Welcome.Agreement.message(""), + tos: L10n.Welcome.Agreement.Message.tos, + tosUrl: Client.configuration.tosUrl, + privacy: L10n.Welcome.Agreement.Message.privacy, + privacyUrl: Client.configuration.privacyUrl + ) + + let nc = NotificationCenter.default + nc.addObserver(self, selector: #selector(productsDidFetch(notification:)), name: .__InAppDidFetchProducts, object: nil) + + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if let products = preset?.accountProvider.planProducts { + refreshPlans(products) + } else { + disableInteractions(fully: false) + } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + NotificationCenter.default.removeObserver(self) + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if (segue.identifier == StoryboardSegue.Welcome.signupViaPurchaseSegue.rawValue) { + let nav = segue.destination as! UINavigationController + let vc = nav.topViewController as! SignupInProgressViewController + + guard let email = signupEmail else { + fatalError("Signing up and signupEmail is not set") + } + var metadata = SignupMetadata(email: email) + metadata.title = L10n.Signup.InProgress.title + metadata.bodySubtitle = L10n.Signup.InProgress.message + vc.metadata = metadata + vc.signupRequest = SignupRequest(email: email, transaction: signupTransaction) + vc.preset = preset + vc.completionDelegate = completionDelegate + } + } + + /// Populate the view with the values from GetStartedView + /// - Parameters: + /// - plans: The available plans. + /// - selectedIndex: The selected plan from the previous screen. + func populateViewWith(plans: [PurchasePlan], andSelectedPlanIndex selectedIndex: Int) { + self.allPlans = plans + self.selectedPlanIndex = selectedIndex + } + + // MARK: Actions + + @IBAction func confirmPlan() { + + /** + self.performSegue(withIdentifier: StoryboardSegue.Welcome.confirmPurchaseVPNPlanSegue.rawValue, + sender: nil) + **/ + + if let index = selectedPlanIndex { + let plan = allPlans[index] + self.startPurchaseProcessWithEmail("", andPlan: plan) + } + + + } + + private func startPurchaseProcessWithEmail(_ email: String, + andPlan plan: PurchasePlan) { + + guard !Client.store.hasUncreditedTransactions else { + let alert = Macros.alert( + nil, + L10n.Signup.Purchase.Uncredited.Alert.message + ) + alert.addCancelAction(L10n.Signup.Purchase.Uncredited.Alert.Button.cancel) + alert.addActionWithTitle(L10n.Signup.Purchase.Uncredited.Alert.Button.recover) { + self.navigationController?.popToRootViewController(animated: true) + Macros.postNotification(.PIARecoverAccount) + } + present(alert, animated: true, completion: nil) + return + + } + + //textEmail.text = email + log.debug("Will purchase plan: \(plan.product)") + + isPurchasing = true + disableInteractions(fully: true) + self.showLoadingAnimation() + + preset?.accountProvider.purchase(plan: plan.plan) { (transaction, error) in + self.isPurchasing = false + self.enableInteractions() + self.hideLoadingAnimation() + + guard let transaction = transaction else { + if let error = error { + let message = error.localizedDescription + log.error("Purchase failed (error: \(error))") + Macros.displayImageNote(withImage: Asset.Images.iconWarning.image, + message: message) + } else { + log.warning("Cancelled purchase") + } + return + } + + log.debug("Purchased with transaction: \(transaction)") + + self.signupEmail = email + self.signupTransaction = transaction + self.perform(segue: StoryboardSegue.Welcome.signupViaPurchaseSegue) + } + + } + + private func refreshPlans(_ plans: [Plan: InAppProduct]) { + if let yearly = plans[.yearly] { + let purchase = PurchasePlan( + plan: .yearly, + product: yearly, + monthlyFactor: 12.0 + ) + + purchase.title = L10n.Welcome.Plan.Yearly.title + let currencySymbol = purchase.product.priceLocale.currencySymbol ?? "" + purchase.detail = L10n.Welcome.Plan.Yearly.detailFormat(currencySymbol, purchase.product.price.description) + purchase.bestValue = true + + allPlans[0] = purchase + + textAgreement.attributedText = Theme.current.agreementText( + withMessage: L10n.Welcome.Agreement.message(purchase.detail), + tos: L10n.Welcome.Agreement.Message.tos, + tosUrl: Client.configuration.tosUrl, + privacy: L10n.Welcome.Agreement.Message.privacy, + privacyUrl: Client.configuration.privacyUrl + ) + + } + if let monthly = plans[.monthly] { + let purchase = PurchasePlan( + plan: .monthly, + product: monthly, + monthlyFactor: 1.0 + ) + purchase.title = L10n.Welcome.Plan.Monthly.title + purchase.bestValue = false + + allPlans[1] = purchase + } + + collectionPlans.isUserInteractionEnabled = true + collectionPlans.reloadData() + if (selectedPlanIndex == nil) { + selectedPlanIndex = 0 + } + collectionPlans.selectItem(at: IndexPath(row: selectedPlanIndex!, section: 0), animated: false, scrollPosition: []) + } + + private func disableInteractions(fully: Bool) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.showLoadingAnimation() + } + collectionPlans.isUserInteractionEnabled = false + if fully { + parent?.view.isUserInteractionEnabled = false + } + } + + private func enableInteractions() { + if !isPurchasing { //dont reenable the screen if we are still purchasing + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.hideLoadingAnimation() + } + collectionPlans.isUserInteractionEnabled = true + parent?.view.isUserInteractionEnabled = true + } + } + + // MARK: Notifications + + @objc private func productsDidFetch(notification: Notification) { + let products: [Plan: InAppProduct] = notification.userInfo(for: .products) + refreshPlans(products) + enableInteractions() + } + + // MARK: Restylable + + override func viewShouldRestyle() { + super.viewShouldRestyle() + Theme.current.applyNavigationBarStyle(to: self) + Theme.current.applyPrincipalBackground(view) + Theme.current.applyPrincipalBackground(scrollView) + Theme.current.applyPrincipalBackground(collectionPlans) + Theme.current.applyTitle(labelTitle, appearance: .dark) + Theme.current.applySubtitle(labelSubtitle) + Theme.current.applyLinkAttributes(textAgreement) + } + + private func styleButtons() { + buttonPurchase.setRounded() + buttonPurchase.style(style: TextStyle.Buttons.piaGreenButton) + if !isExpired { + buttonPurchase.setTitle(L10n.Signup.Purchase.Subscribe.now.uppercased(), for: []) + } + else { + buttonPurchase.setTitle(L10n.Welcome.Upgrade.Renew.now.uppercased(), for: []) + } + } + +} + +extension PurchaseViewController: UICollectionViewDataSource, UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return allPlans.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let plan = allPlans[indexPath.row] + let cell = collectionPlans.dequeueReusableCell(withReuseIdentifier: Cells.plan, for: indexPath) as! PurchasePlanCell + cell.fill(plan: plan) + cell.isSelected = (indexPath.row == selectedPlanIndex) + return cell + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + selectedPlanIndex = indexPath.row + } +} + +extension PurchaseViewController: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let itemWidth = collectionView.bounds.size.width + let itemHeight = (collectionView.bounds.size.height - 13) / 2.0 + return CGSize(width: itemWidth, + height: itemHeight) + } +} + +extension PurchaseViewController: UITextViewDelegate { + func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool { + return true + } +} + +extension PurchaseViewController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + //if (textField == textEmail) { + // signUp(nil) + //} + return true + } +} diff --git a/PIA VPN/RatingManager.swift b/PIA VPN/RatingManager.swift index f40ed816c..164218318 100644 --- a/PIA VPN/RatingManager.swift +++ b/PIA VPN/RatingManager.swift @@ -135,13 +135,13 @@ class RatingManager { return } - let sheet = Macros.alertController(L10n.Rating.Enjoy.question, nil) - sheet.addAction(UIAlertAction(title: L10n.Rating.Alert.Button.notreally, style: .default, handler: { action in + let sheet = Macros.alertController(L10n.Localizable.Rating.Enjoy.question, nil) + sheet.addAction(UIAlertAction(title: L10n.Localizable.Rating.Alert.Button.notreally, style: .default, handler: { action in // Ask for feedback let alert = self.createDefaultFeedbackDialog() rootView.present(alert, animated: true, completion: nil) })) - sheet.addAction(UIAlertAction(title: L10n.Global.yes, style: .default, handler: { action in + sheet.addAction(UIAlertAction(title: L10n.Localizable.Global.yes, style: .default, handler: { action in let alert = self.createDefaultReviewAlert() rootView.present(alert, animated: true, completion: nil) })) @@ -149,22 +149,22 @@ class RatingManager { } private func createDefaultFeedbackDialog() -> UIAlertController { - let sheet = Macros.alertController(L10n.Rating.Problems.question, L10n.Rating.Problems.subtitle) - sheet.addAction(UIAlertAction(title: L10n.Global.no, style: .default, handler: { action in + let sheet = Macros.alertController(L10n.Localizable.Rating.Problems.question, L10n.Localizable.Rating.Problems.subtitle) + sheet.addAction(UIAlertAction(title: L10n.Localizable.Global.no, style: .default, handler: { action in log.debug("No feedback") })) - sheet.addAction(UIAlertAction(title: L10n.Global.yes, style: .default, handler: { action in + sheet.addAction(UIAlertAction(title: L10n.Localizable.Global.yes, style: .default, handler: { action in self.openFeedbackWebsite() })) return sheet } private func createDefaultReviewAlert() -> UIAlertController { - let sheet = Macros.alertController(L10n.Rating.Rate.question, nil) - sheet.addAction(UIAlertAction(title: L10n.Rating.Alert.Button.nothanks, style: .default, handler: { action in + let sheet = Macros.alertController(L10n.Localizable.Rating.Rate.question, nil) + sheet.addAction(UIAlertAction(title: L10n.Localizable.Rating.Alert.Button.nothanks, style: .default, handler: { action in self.handleRatingAlertCancel() })) - sheet.addAction(UIAlertAction(title: L10n.Rating.Alert.Button.oksure, style: .default, handler: { action in + sheet.addAction(UIAlertAction(title: L10n.Localizable.Rating.Alert.Button.oksure, style: .default, handler: { action in self.openRatingViewInAppstore() })) return sheet @@ -179,16 +179,16 @@ class RatingManager { } let sheet = Macros.alert( - L10n.Rating.Enjoy.question, - L10n.Rating.Enjoy.subtitle + L10n.Localizable.Rating.Enjoy.question, + L10n.Localizable.Rating.Enjoy.subtitle ) - sheet.addCancelActionWithTitle(L10n.Global.no, handler: { + sheet.addCancelActionWithTitle(L10n.Localizable.Global.no, handler: { // Ask for feedback let alert = self.createCustomFeedbackDialog() rootView.present(alert, animated: true, completion: nil) }) - sheet.addActionWithTitle(L10n.Global.yes) { + sheet.addActionWithTitle(L10n.Localizable.Global.yes) { let alert = self.createCustomReviewDialog() rootView.present(alert, animated: true, completion: nil) } @@ -200,14 +200,14 @@ class RatingManager { private func createCustomFeedbackDialog() -> PopupDialog { let sheet = Macros.alert( - L10n.Rating.Problems.question, - L10n.Rating.Problems.subtitle + L10n.Localizable.Rating.Problems.question, + L10n.Localizable.Rating.Problems.subtitle ) - sheet.addCancelActionWithTitle(L10n.Global.no, handler: { + sheet.addCancelActionWithTitle(L10n.Localizable.Global.no, handler: { log.debug("No feedback") }) - sheet.addActionWithTitle(L10n.Global.yes) { + sheet.addActionWithTitle(L10n.Localizable.Global.yes) { self.openFeedbackWebsite() } @@ -218,14 +218,14 @@ class RatingManager { private func createCustomReviewDialog() -> PopupDialog { let sheet = Macros.alert( - L10n.Rating.Review.question, - L10n.Rating.Rate.subtitle + L10n.Localizable.Rating.Review.question, + L10n.Localizable.Rating.Rate.subtitle ) - sheet.addCancelActionWithTitle(L10n.Global.no, handler: { + sheet.addCancelActionWithTitle(L10n.Localizable.Global.no, handler: { self.handleRatingAlertCancel() }) - sheet.addActionWithTitle(L10n.Global.yes) { + sheet.addActionWithTitle(L10n.Localizable.Global.yes) { self.openRatingViewInAppstore() } @@ -240,12 +240,12 @@ class RatingManager { } let sheet = Macros.alert( - L10n.Rating.Error.question, - L10n.Rating.Error.subtitle + L10n.Localizable.Rating.Error.question, + L10n.Localizable.Rating.Error.subtitle ) - sheet.addCancelAction(L10n.Global.close) + sheet.addCancelAction(L10n.Localizable.Global.close) - sheet.addActionWithTitle(L10n.Rating.Error.Button.send) { + sheet.addActionWithTitle(L10n.Localizable.Rating.Error.Button.send) { self.openFeedbackWebsite() } diff --git a/PIA VPN/RegionCell.swift b/PIA VPN/RegionCell.swift index e3d4a2d54..bff2eb1d8 100644 --- a/PIA VPN/RegionCell.swift +++ b/PIA VPN/RegionCell.swift @@ -174,16 +174,16 @@ class RegionCell: UITableViewCell, Restylable { private func updateFavoriteImage() { self.isFavorite ? - self.favoriteImageView.image = Asset.Piax.Global.favoriteSelected.image : + self.favoriteImageView.image = Asset.Images.Piax.Global.favoriteSelected.image : Theme.current.applyFavoriteUnselectedImage(self.favoriteImageView) favoriteButton.accessibilityLabel = self.isFavorite ? - L10n.Region.Accessibility.favorite : - L10n.Region.Accessibility.unfavorite + L10n.Localizable.Region.Accessibility.favorite : + L10n.Localizable.Region.Accessibility.unfavorite } private func updateOfflineImage() { - self.favoriteImageView.image = Asset.offlineServerIcon.image - self.favoriteButton.accessibilityLabel = L10n.Global.disabled + self.favoriteImageView.image = Asset.Images.offlineServerIcon.image + self.favoriteButton.accessibilityLabel = L10n.Localizable.Global.disabled } } diff --git a/PIA VPN/RegionsViewController.swift b/PIA VPN/RegionsViewController.swift index 6fee90758..cce7a9a74 100644 --- a/PIA VPN/RegionsViewController.swift +++ b/PIA VPN/RegionsViewController.swift @@ -67,7 +67,7 @@ class RegionsViewController: AutolayoutViewController { override func viewDidLoad() { super.viewDidLoad() - title = L10n.Menu.Item.region + title = L10n.Localizable.Menu.Item.region var servers = Client.providers.serverProvider.currentServers if Client.configuration.isDevelopment, let customServers = AppConstants.Servers.customServers { @@ -128,12 +128,12 @@ class RegionsViewController: AutolayoutViewController { private func setupRightBarButton() { navigationItem.rightBarButtonItem = UIBarButtonItem( - image: Asset.Piax.Global.iconFilter.image, + image: Asset.Images.Piax.Global.iconFilter.image, style: .plain, target: self, action: #selector(showFilter(_:)) ) - navigationItem.rightBarButtonItem?.accessibilityLabel = L10n.Region.Accessibility.filter + navigationItem.rightBarButtonItem?.accessibilityLabel = L10n.Localizable.Region.Accessibility.filter } override func dismissModal() { @@ -149,7 +149,7 @@ class RegionsViewController: AutolayoutViewController { private func setupSearchBarController() { searchController.searchResultsUpdater = self searchController.obscuresBackgroundDuringPresentation = false - searchController.searchBar.placeholder = L10n.Region.Search.placeholder + searchController.searchBar.placeholder = L10n.Localizable.Region.Search.placeholder self.tableView.tableHeaderView = self.searchController.searchBar searchController.hidesNavigationBarDuringPresentation = false @@ -158,7 +158,7 @@ class RegionsViewController: AutolayoutViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - styleNavigationBarWithTitle(L10n.Menu.Item.region) + styleNavigationBarWithTitle(L10n.Localizable.Menu.Item.region) setupRightBarButton() tableView.reloadData() @@ -194,17 +194,17 @@ class RegionsViewController: AutolayoutViewController { } let popup = PopupDialog(title: nil, - message: L10n.Region.Filter.sortby.uppercased()) + message: L10n.Localizable.Region.Filter.sortby.uppercased()) - let buttonName = DefaultButton(title: L10n.Region.Filter.name.uppercased(), dismissOnTap: true) { + let buttonName = DefaultButton(title: L10n.Localizable.Region.Filter.name.uppercased(), dismissOnTap: true) { AppPreferences.shared.regionFilter = .name self.filterServers() } - let buttonLatency = DefaultButton(title: L10n.Region.Filter.latency.uppercased(), dismissOnTap: true) { + let buttonLatency = DefaultButton(title: L10n.Localizable.Region.Filter.latency.uppercased(), dismissOnTap: true) { AppPreferences.shared.regionFilter = .latency self.filterServers() } - let buttonFavorites = DefaultButton(title: L10n.Region.Filter.favorites.uppercased(), dismissOnTap: true) { + let buttonFavorites = DefaultButton(title: L10n.Localizable.Region.Filter.favorites.uppercased(), dismissOnTap: true) { AppPreferences.shared.regionFilter = .favorite self.filterServers() } @@ -246,7 +246,7 @@ class RegionsViewController: AutolayoutViewController { } @objc private func viewHasRotated() { - styleNavigationBarWithTitle(L10n.Menu.Item.region) + styleNavigationBarWithTitle(L10n.Localizable.Menu.Item.region) } // MARK: Notifications @@ -267,7 +267,7 @@ class RegionsViewController: AutolayoutViewController { override func viewShouldRestyle() { super.viewShouldRestyle() - styleNavigationBarWithTitle(L10n.Menu.Item.region) + styleNavigationBarWithTitle(L10n.Localizable.Menu.Item.region) if let viewContainer = viewContainer { Theme.current.applyRegionSolidLightBackground(view) diff --git a/PIA VPN/RestoreSignupViewController.swift b/PIA VPN/RestoreSignupViewController.swift new file mode 100644 index 000000000..661a5385f --- /dev/null +++ b/PIA VPN/RestoreSignupViewController.swift @@ -0,0 +1,202 @@ +// +// RestoreSignupViewController.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 10/21/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit +import SwiftyBeaver +import PIALibrary + +private let log = SwiftyBeaver.self + +public class RestoreSignupViewController: AutolayoutViewController, BrandableNavigationBar, WelcomeChild { + + var omitsSiblingLink = false + + var completionDelegate: WelcomeCompletionDelegate? + + @IBOutlet private weak var scrollView: UIScrollView! + + @IBOutlet private weak var viewModal: UIView! + + @IBOutlet private weak var labelTitle: UILabel! + + @IBOutlet private weak var labelDescription: UILabel! + + @IBOutlet private weak var textEmail: BorderedTextField! + + @IBOutlet private weak var buttonRestorePurchase: PIAButton! + + var preset: Preset? + + private var signupEmail: String? + private var isRunningActivity = false + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override public func viewDidLoad() { + super.viewDidLoad() + + self.navigationController?.setNavigationBarHidden(false, animated: true) + self.navigationItem.leftBarButtonItem = UIBarButtonItem( + image: Theme.current.palette.navigationBarBackIcon?.withRenderingMode(.alwaysOriginal), + style: .plain, + target: self, + action: #selector(back(_:)) + ) + self.navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Welcome.Redeem.Accessibility.back + + labelTitle.text = L10n.Welcome.Restore.title + labelDescription.text = L10n.Welcome.Restore.subtitle + textEmail.placeholder = L10n.Welcome.Restore.Email.placeholder + + textEmail.text = preset?.purchaseEmail + + // XXX: signup scrolling hack, disable on iPad and iPhone Plus + if Macros.isDeviceBig { + scrollView.isScrollEnabled = false + } + + styleRestoreButton() + } + + override public func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + enableInteractions(true) + } + + override public func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // signup after receipt restore + if (segue.identifier == StoryboardSegue.Welcome.signupViaRestoreSegue.rawValue) { + let nav = segue.destination as! UINavigationController + let vc = nav.topViewController as! SignupInProgressViewController + + guard let email = signupEmail else { + fatalError("Signing up and signupEmail is not set") + } + var metadata = SignupMetadata(email: email) + metadata.title = L10n.Signup.InProgress.title + metadata.bodySubtitle = L10n.Signup.InProgress.message + vc.metadata = metadata + vc.preset = preset + vc.signupRequest = SignupRequest(email: email) + vc.completionDelegate = completionDelegate + } + } + + // MARK: Actions + + @IBAction private func restorePurchase(_ sender: Any?) { + guard !isRunningActivity else { + return + } + + guard let email = textEmail.text, Validator.validate(email: email.trimmed()) else { + signupEmail = nil + textEmail.becomeFirstResponder() + Macros.displayImageNote(withImage: Asset.Images.iconWarning.image, + message: L10n.Welcome.Purchase.Error.validation) + self.status = .error(element: textEmail) + return + } + + self.status = .restore(element: textEmail) + signupEmail = email.trimmed() + + enableInteractions(false) + isRunningActivity = true + self.showLoadingAnimation() + preset?.accountProvider.restorePurchases { (error) in + self.hideLoadingAnimation() + self.isRunningActivity = false + if let _ = error { + self.reportRestoreFailure(error) + self.enableInteractions(true) + return + } + self.reportRestoreSuccess() + } + } + + private func reportRestoreSuccess() { + log.debug("Restored payment receipt, redeeming..."); + + guard let email = signupEmail else { + fatalError("Restore receipt and signupEmail is not set") + } + self.restoreController(self, + didRefreshReceiptWith: email) + } + + private func reportRestoreFailure(_ optionalError: Error?) { + var message = optionalError?.localizedDescription ?? L10n.Welcome.Iap.Error.title + if let error = optionalError { + log.error("Failed to restore payment receipt (error: \(error))") + } else { + log.error("Failed to restore payment receipt") + } + Macros.displayImageNote(withImage: Asset.Images.iconWarning.image, + message: message) + + } + + private func enableInteractions(_ enable: Bool) { + textEmail.isEnabled = enable + } + + // MARK: Restylable + + override public func viewShouldRestyle() { + super.viewShouldRestyle() + navigationItem.titleView = NavigationLogoView() + Theme.current.applyNavigationBarStyle(to: self) + Theme.current.applyPrincipalBackground(view) + Theme.current.applyPrincipalBackground(viewModal) + Theme.current.applyTitle(labelTitle, appearance: .dark) + Theme.current.applySubtitle(labelDescription) + Theme.current.applyInput(textEmail) + } + + private func styleRestoreButton() { + buttonRestorePurchase.setRounded() + buttonRestorePurchase.style(style: TextStyle.Buttons.piaGreenButton) + buttonRestorePurchase.setTitle(L10n.Welcome.Restore.submit.uppercased(), + for: []) + } + + private func restoreController(_ restoreController: RestoreSignupViewController, didRefreshReceiptWith email: String) { + self.signupEmail = email + self.perform(segue: StoryboardSegue.Welcome.signupViaRestoreSegue) + } + +} + +extension RestoreSignupViewController: UITextFieldDelegate { + public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + if (textField == textEmail) { + restorePurchase(nil) + } + return true + } +} + diff --git a/PIA VPN/Server+Automatic.swift b/PIA VPN/Server+Automatic.swift index e33a961af..7b3180990 100644 --- a/PIA VPN/Server+Automatic.swift +++ b/PIA VPN/Server+Automatic.swift @@ -26,7 +26,7 @@ import PIALibrary extension Server { static let automatic = Server( serial: "", - name: L10n.Global.automatic, + name: L10n.Localizable.Global.automatic, country: "universal", hostname: "auto.bogus.domain", pingAddress: nil, diff --git a/PIA VPN/Server+UI.swift b/PIA VPN/Server+UI.swift index 21763a819..970d37b76 100644 --- a/PIA VPN/Server+UI.swift +++ b/PIA VPN/Server+UI.swift @@ -41,9 +41,9 @@ extension Server: CustomStringConvertible { return localizedName case .connecting: - return L10n.Dashboard.Vpn.connecting + return L10n.Localizable.Dashboard.Vpn.connecting case .disconnecting: - return L10n.Dashboard.Vpn.disconnecting + return L10n.Localizable.Dashboard.Vpn.disconnecting case .disconnected, .unknown: return localizedName } diff --git a/PIA VPN/SettingOptions.swift b/PIA VPN/SettingOptions.swift index a287a6800..667fcfcea 100644 --- a/PIA VPN/SettingOptions.swift +++ b/PIA VPN/SettingOptions.swift @@ -35,12 +35,12 @@ public enum SettingOptions: Int, EnumsBuilder { public func localizedTitleMessage() -> String { switch self { - case .general: return L10n.Settings.Section.general - case .protocols: return L10n.Settings.Section.protocols - case .network: return L10n.Settings.Section.network - case .privacyFeatures: return L10n.Settings.Section.privacyFeatures - case .automation: return L10n.Settings.Section.automation - case .help: return L10n.Settings.Section.help + case .general: return L10n.Localizable.Settings.Section.general + case .protocols: return L10n.Localizable.Settings.Section.protocols + case .network: return L10n.Localizable.Settings.Section.network + case .privacyFeatures: return L10n.Localizable.Settings.Section.privacyFeatures + case .automation: return L10n.Localizable.Settings.Section.automation + case .help: return L10n.Localizable.Settings.Section.help case .development: return "Development" } } @@ -59,13 +59,13 @@ public enum SettingOptions: Int, EnumsBuilder { public func imageForSection() -> UIImage { switch self { - case .general: return Asset.Piax.Settings.iconGeneral.image - case .protocols: return Asset.Piax.Settings.iconProtocols.image - case .network: return Asset.Piax.Settings.iconNetwork.image - case .privacyFeatures: return Asset.Piax.Settings.iconPrivacy.image - case .automation: return Asset.Piax.Settings.iconAutomation.image - case .help: return Asset.Piax.Settings.iconAbout.image - case .development: return Asset.Piax.Settings.iconGeneral.image + case .general: return Asset.Images.Piax.Settings.iconGeneral.image + case .protocols: return Asset.Images.Piax.Settings.iconProtocols.image + case .network: return Asset.Images.Piax.Settings.iconNetwork.image + case .privacyFeatures: return Asset.Images.Piax.Settings.iconPrivacy.image + case .automation: return Asset.Images.Piax.Settings.iconAutomation.image + case .help: return Asset.Images.Piax.Settings.iconAbout.image + case .development: return Asset.Images.Piax.Settings.iconGeneral.image } } @@ -104,11 +104,11 @@ public enum GeneralSections: Int, SettingSection, EnumsBuilder { public func localizedTitleMessage() -> String { switch self { - case .connectSiri: return L10n.Siri.Shortcuts.Connect.Row.title - case .disconnectSiri: return L10n.Siri.Shortcuts.Disconnect.Row.title - case .showGeoRegions: return L10n.Settings.Geo.Servers.description - case .showServiceCommunicationMessages: return L10n.Inapp.Messages.Toggle.title - case .resetSettings: return L10n.Settings.Reset.Defaults.title + case .connectSiri: return L10n.Localizable.Siri.Shortcuts.Connect.Row.title + case .disconnectSiri: return L10n.Localizable.Siri.Shortcuts.Disconnect.Row.title + case .showGeoRegions: return L10n.Localizable.Settings.Geo.Servers.description + case .showServiceCommunicationMessages: return L10n.Localizable.Inapp.Messages.Toggle.title + case .resetSettings: return L10n.Localizable.Settings.Reset.Defaults.title } } @@ -139,12 +139,12 @@ public enum ProtocolsSections: Int, SettingSection, EnumsBuilder { public func localizedTitleMessage() -> String { switch self { - case .protocolSelection: return L10n.Settings.Connection.VpnProtocol.title - case .transport: return L10n.Settings.Connection.Transport.title - case .remotePort: return L10n.Settings.Connection.RemotePort.title - case .dataEncryption: return L10n.Settings.Encryption.Cipher.title - case .handshake: return L10n.Settings.Encryption.Handshake.title - case .useSmallPackets: return L10n.Settings.Small.Packets.title + case .protocolSelection: return L10n.Localizable.Settings.Connection.VpnProtocol.title + case .transport: return L10n.Localizable.Settings.Connection.Transport.title + case .remotePort: return L10n.Localizable.Settings.Connection.RemotePort.title + case .dataEncryption: return L10n.Localizable.Settings.Encryption.Cipher.title + case .handshake: return L10n.Localizable.Settings.Encryption.Handshake.title + case .useSmallPackets: return L10n.Localizable.Settings.Small.Packets.title } } @@ -197,11 +197,11 @@ public enum PrivacyFeaturesSections: Int, SettingSection, EnumsBuilder { public func localizedTitleMessage() -> String { switch self { - case .killswitch: return L10n.Settings.ApplicationSettings.KillSwitch.title - case .leakProtection: return L10n.Settings.ApplicationSettings.LeakProtection.title - case .allowAccessOnLocalNetwork: return L10n.Settings.ApplicationSettings.AllowLocalNetwork.title - case .safariContentBlocker: return L10n.Settings.ContentBlocker.title - case .refresh: return L10n.Settings.ContentBlocker.Refresh.title + case .killswitch: return L10n.Localizable.Settings.ApplicationSettings.KillSwitch.title + case .leakProtection: return L10n.Localizable.Settings.ApplicationSettings.LeakProtection.title + case .allowAccessOnLocalNetwork: return L10n.Localizable.Settings.ApplicationSettings.AllowLocalNetwork.title + case .safariContentBlocker: return L10n.Localizable.Settings.ContentBlocker.title + case .refresh: return L10n.Localizable.Settings.ContentBlocker.Refresh.title } } @@ -228,8 +228,8 @@ public enum AutomationSections: Int, SettingSection, EnumsBuilder { public func localizedTitleMessage() -> String { switch self { - case .automation: return L10n.Network.Management.Tool.Enable.automation - case .manageAutomation: return L10n.Network.Management.Tool.title + case .automation: return L10n.Localizable.Network.Management.Tool.Enable.automation + case .manageAutomation: return L10n.Localizable.Network.Management.Tool.title } } @@ -256,11 +256,11 @@ public enum HelpSections: Int, SettingSection, EnumsBuilder { public func localizedTitleMessage() -> String { switch self { - case .sendDebugLogs: return L10n.Settings.ApplicationInformation.Debug.title - case .kpiShareStatistics: return L10n.Settings.Service.Quality.Share.title - case .kpiViewEvents: return L10n.Settings.Service.Quality.Show.title - case .latestNews: return L10n.Settings.Cards.History.title - case .version: return L10n.Global.version + case .sendDebugLogs: return L10n.Localizable.Settings.ApplicationInformation.Debug.title + case .kpiShareStatistics: return L10n.Localizable.Settings.Service.Quality.Share.title + case .kpiViewEvents: return L10n.Localizable.Settings.Service.Quality.Show.title + case .latestNews: return L10n.Localizable.Settings.Cards.History.title + case .version: return L10n.Localizable.Global.version } } diff --git a/PIA VPN/Settings/AutomationSettingsViewController.swift b/PIA VPN/Settings/AutomationSettingsViewController.swift index ffdc29185..93ebf643a 100644 --- a/PIA VPN/Settings/AutomationSettingsViewController.swift +++ b/PIA VPN/Settings/AutomationSettingsViewController.swift @@ -47,7 +47,7 @@ class AutomationSettingsViewController: PIABaseSettingsViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - styleNavigationBarWithTitle(L10n.Settings.Section.automation) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Section.automation) } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -88,7 +88,7 @@ class AutomationSettingsViewController: PIABaseSettingsViewController { override func viewShouldRestyle() { super.viewShouldRestyle() - styleNavigationBarWithTitle(L10n.Settings.Section.automation) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Section.automation) // XXX: for some reason, UITableView is not affected by appearance updates if let viewContainer = viewContainer { Theme.current.applyPrincipalBackground(view) @@ -113,7 +113,7 @@ extension AutomationSettingsViewController: UITableViewDelegate, UITableViewData cell.textLabel?.numberOfLines = 0 cell.textLabel?.style(style: TextStyle.textStyle21) cell.backgroundColor = .clear - cell.textLabel?.text = L10n.Settings.Hotspothelper.description + cell.textLabel?.text = L10n.Localizable.Settings.Hotspothelper.description return cell } return nil @@ -161,7 +161,7 @@ extension AutomationSettingsViewController: UITableViewDelegate, UITableViewData cell.selectionStyle = .none switchEnableNMT.isOn = Client.preferences.nmtRulesEnabled case .manageAutomation: - cell.textLabel?.text = L10n.Network.Management.Tool.title + cell.textLabel?.text = L10n.Localizable.Network.Management.Tool.title } Theme.current.applySecondaryBackground(cell) diff --git a/PIA VPN/Settings/DevelopmentSettingsViewController.swift b/PIA VPN/Settings/DevelopmentSettingsViewController.swift index 91f746153..44faf6635 100644 --- a/PIA VPN/Settings/DevelopmentSettingsViewController.swift +++ b/PIA VPN/Settings/DevelopmentSettingsViewController.swift @@ -288,7 +288,7 @@ extension DevelopmentSettingsViewController: UITableViewDelegate, UITableViewDat } let alert = Macros.alert(nil, addresses.joined(separator: ",")) - alert.addDefaultAction(L10n.Global.close) + alert.addDefaultAction(L10n.Localizable.Global.close) self.present(alert, animated: true, completion: nil) } } diff --git a/PIA VPN/Settings/HelpSettingsViewController.swift b/PIA VPN/Settings/HelpSettingsViewController.swift index fec85269c..538645e01 100644 --- a/PIA VPN/Settings/HelpSettingsViewController.swift +++ b/PIA VPN/Settings/HelpSettingsViewController.swift @@ -57,7 +57,7 @@ class HelpSettingsViewController: PIABaseSettingsViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - styleNavigationBarWithTitle(L10n.Settings.Section.help) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Section.help) } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -97,7 +97,7 @@ class HelpSettingsViewController: PIABaseSettingsViewController { override func viewShouldRestyle() { super.viewShouldRestyle() - styleNavigationBarWithTitle(L10n.Settings.Section.help) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Section.help) // XXX: for some reason, UITableView is not affected by appearance updates if let viewContainer = viewContainer { Theme.current.applyPrincipalBackground(view) @@ -130,7 +130,7 @@ extension HelpSettingsViewController: UITableViewDelegate, UITableViewDataSource switch section { case HelpSections.sendDebugLogs.rawValue: - cell.textLabel?.text = L10n.Settings.Log.information + cell.textLabel?.text = L10n.Localizable.Settings.Log.information case HelpSections.kpiShareStatistics.rawValue: configureShareDataFooterCell(cell) default: @@ -221,9 +221,9 @@ extension HelpSettingsViewController: UITableViewDelegate, UITableViewDataSource private func setupShareDataInformationLabel(_ label: UILabel?) { let attributedString = NSMutableAttributedString() - let description = L10n.Settings.Service.Quality.Share.description + let description = L10n.Localizable.Settings.Service.Quality.Share.description let carriageReturn = "\n" - let findOutMore = L10n.Settings.Service.Quality.Share.findoutmore + let findOutMore = L10n.Localizable.Settings.Service.Quality.Share.findoutmore attributedString.append(NSAttributedString(string: description+carriageReturn, attributes: [.underlineStyle: 0])) attributedString.append(NSAttributedString(string: findOutMore, @@ -267,23 +267,23 @@ extension HelpSettingsViewController: UITableViewDelegate, UITableViewDataSource defer { let alert = Macros.alert(title, message) - alert.addDefaultAction(L10n.Global.ok) + alert.addDefaultAction(L10n.Localizable.Global.ok) self.present(alert, animated: true, completion: nil) } guard let reportId = reportIdentifier else { - title = L10n.Settings.ApplicationInformation.Debug.Failure.title - message = L10n.Settings.ApplicationInformation.Debug.Failure.message + title = L10n.Localizable.Settings.ApplicationInformation.Debug.Failure.title + message = L10n.Localizable.Settings.ApplicationInformation.Debug.Failure.message return } guard !reportId.isEmpty else { - title = L10n.Settings.ApplicationInformation.Debug.Empty.title - message = L10n.Settings.ApplicationInformation.Debug.Empty.message + title = L10n.Localizable.Settings.ApplicationInformation.Debug.Empty.title + message = L10n.Localizable.Settings.ApplicationInformation.Debug.Empty.message return } - title = L10n.Settings.ApplicationInformation.Debug.Success.title - message = L10n.Settings.ApplicationInformation.Debug.Success.message(reportId) + title = L10n.Localizable.Settings.ApplicationInformation.Debug.Success.title + message = L10n.Localizable.Settings.ApplicationInformation.Debug.Success.message(reportId) } } diff --git a/PIA VPN/Settings/NetworkSettingsViewController.swift b/PIA VPN/Settings/NetworkSettingsViewController.swift index 37719ff3c..d68112541 100644 --- a/PIA VPN/Settings/NetworkSettingsViewController.swift +++ b/PIA VPN/Settings/NetworkSettingsViewController.swift @@ -39,7 +39,7 @@ class NetworkSettingsViewController: PIABaseSettingsViewController { private var controller: OptionsViewController? private static let DNS: String = "DNS" - private lazy var imvSelectedOption = UIImageView(image: Asset.accessorySelected.image) + private lazy var imvSelectedOption = UIImageView(image: Asset.Images.accessorySelected.image) override func viewDidLoad() { @@ -61,7 +61,7 @@ class NetworkSettingsViewController: PIABaseSettingsViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - styleNavigationBarWithTitle(L10n.Settings.Section.network) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Section.network) } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -93,7 +93,7 @@ class NetworkSettingsViewController: PIABaseSettingsViewController { override func viewShouldRestyle() { super.viewShouldRestyle() - styleNavigationBarWithTitle(L10n.Settings.Section.network) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Section.network) // XXX: for some reason, UITableView is not affected by appearance updates if let viewContainer = viewContainer { Theme.current.applyPrincipalBackground(view) @@ -203,7 +203,7 @@ extension NetworkSettingsViewController: UITableViewDelegate, UITableViewDataSou if key == (pendingPreferences.vpnType == PIATunnelProfile.vpnType ? DNSList.CUSTOM_OPENVPN_DNS_KEY : DNSList.CUSTOM_WIREGUARD_DNS_KEY) { if !value.isEmpty { controller?.navigationItem.rightBarButtonItem = UIBarButtonItem( - title: L10n.Global.edit, + title: L10n.Localizable.Global.edit, style: .plain, target: self, action: #selector(edit(_:)) @@ -357,12 +357,12 @@ extension NetworkSettingsViewController: OptionsViewControllerDelegate { } if !isFound && option == (pendingPreferences.vpnType == PIATunnelProfile.vpnType ? DNSList.CUSTOM_OPENVPN_DNS_KEY : DNSList.CUSTOM_WIREGUARD_DNS_KEY) { - let alertController = Macros.alert(L10n.Settings.Dns.Custom.dns, - L10n.Settings.Dns.Alert.Create.message) - alertController.addActionWithTitle(L10n.Global.ok) { + let alertController = Macros.alert(L10n.Localizable.Settings.Dns.Custom.dns, + L10n.Localizable.Settings.Dns.Alert.Create.message) + alertController.addActionWithTitle(L10n.Localizable.Global.ok) { self.perform(segue: StoryboardSegue.Main.customDNSSegueIdentifier) } - alertController.addCancelAction(L10n.Global.cancel) + alertController.addCancelAction(L10n.Localizable.Global.cancel) self.present(alertController, animated: true, completion: nil) diff --git a/PIA VPN/Settings/PrivacyFeaturesSettingsViewController.swift b/PIA VPN/Settings/PrivacyFeaturesSettingsViewController.swift index 2639af4f9..dc6e4d7cc 100644 --- a/PIA VPN/Settings/PrivacyFeaturesSettingsViewController.swift +++ b/PIA VPN/Settings/PrivacyFeaturesSettingsViewController.swift @@ -83,7 +83,7 @@ class PrivacyFeaturesSettingsViewController: PIABaseSettingsViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) refreshContentBlockerState() - styleNavigationBarWithTitle(L10n.Settings.Section.privacyFeatures) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Section.privacyFeatures) } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -103,9 +103,9 @@ class PrivacyFeaturesSettingsViewController: PIABaseSettingsViewController { @objc private func togglePersistentConnection(_ sender: UISwitch) { if !sender.isOn, Client.preferences.nmtRulesEnabled { - let alert = Macros.alert(nil, L10n.Settings.Nmt.Killswitch.disabled) - alert.addCancelAction(L10n.Global.close) - alert.addActionWithTitle(L10n.Global.enable) { [weak self] in + let alert = Macros.alert(nil, L10n.Localizable.Settings.Nmt.Killswitch.disabled) + alert.addCancelAction(L10n.Localizable.Global.close) + alert.addActionWithTitle(L10n.Localizable.Global.enable) { [weak self] in self?.pendingPreferences.isPersistentConnection = true self?.settingsDelegate.refreshSettings() self?.settingsDelegate.reportUpdatedPreferences() @@ -153,8 +153,8 @@ class PrivacyFeaturesSettingsViewController: PIABaseSettingsViewController { return } - let sheet = Macros.alertController(L10n.Settings.ApplicationSettings.LeakProtection.Alert.title, nil) - sheet.addAction(UIAlertAction(title: L10n.Global.ok, style: .default, handler: nil)) + let sheet = Macros.alertController(L10n.Localizable.Settings.ApplicationSettings.LeakProtection.Alert.title, nil) + sheet.addAction(UIAlertAction(title: L10n.Localizable.Global.ok, style: .default, handler: nil)) present(sheet, animated: true) } @@ -163,7 +163,7 @@ class PrivacyFeaturesSettingsViewController: PIABaseSettingsViewController { override func viewShouldRestyle() { super.viewShouldRestyle() - styleNavigationBarWithTitle(L10n.Settings.Section.privacyFeatures) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Section.privacyFeatures) // XXX: for some reason, UITableView is not affected by appearance updates if let viewContainer = viewContainer { Theme.current.applyPrincipalBackground(view) @@ -200,21 +200,21 @@ extension PrivacyFeaturesSettingsViewController: UITableViewDelegate, UITableVie switch privacySettingsSection { case .killswitch: - cell.textLabel?.text = L10n.Settings.ApplicationSettings.KillSwitch.footer + cell.textLabel?.text = L10n.Localizable.Settings.ApplicationSettings.KillSwitch.footer return cell case .leakProtection: - let leakProtectionDescription = L10n.Settings.ApplicationSettings.LeakProtection.footer + let leakProtectionDescription = L10n.Localizable.Settings.ApplicationSettings.LeakProtection.footer let attributtedDescription = NSMutableAttributedString(string: leakProtectionDescription) - let moreInfoText = L10n.Settings.ApplicationSettings.LeakProtection.moreInfo + let moreInfoText = L10n.Localizable.Settings.ApplicationSettings.LeakProtection.moreInfo let moreInfoTextRange = (leakProtectionDescription as NSString).range(of: moreInfoText) attributtedDescription.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: moreInfoTextRange) cell.textLabel?.attributedText = attributtedDescription return cell case .allowAccessOnLocalNetwork: - cell.textLabel?.text = L10n.Settings.ApplicationSettings.AllowLocalNetwork.footer + cell.textLabel?.text = L10n.Localizable.Settings.ApplicationSettings.AllowLocalNetwork.footer return cell case .safariContentBlocker: - cell.textLabel?.text = L10n.Settings.ContentBlocker.footer + cell.textLabel?.text = L10n.Localizable.Settings.ContentBlocker.footer return cell case .refresh: return nil @@ -233,33 +233,33 @@ extension PrivacyFeaturesSettingsViewController: UITableViewDelegate, UITableVie switch section { case .killswitch: - cell.textLabel?.text = L10n.Settings.ApplicationSettings.KillSwitch.title + cell.textLabel?.text = L10n.Localizable.Settings.ApplicationSettings.KillSwitch.title cell.detailTextLabel?.text = nil cell.accessoryView = switchPersistent cell.selectionStyle = .none switchPersistent.isOn = pendingPreferences.isPersistentConnection case .leakProtection: - cell.textLabel?.text = L10n.Settings.ApplicationSettings.LeakProtection.title + cell.textLabel?.text = L10n.Localizable.Settings.ApplicationSettings.LeakProtection.title cell.detailTextLabel?.text = nil cell.accessoryView = switchLeakProtection cell.selectionStyle = .none switchLeakProtection.isOn = Client.preferences.leakProtection case .allowAccessOnLocalNetwork: - cell.textLabel?.text = L10n.Settings.ApplicationSettings.AllowLocalNetwork.title + cell.textLabel?.text = L10n.Localizable.Settings.ApplicationSettings.AllowLocalNetwork.title cell.detailTextLabel?.text = nil cell.accessoryView = switchAllowDevicesOnLocalNetwork cell.selectionStyle = .none switchAllowDevicesOnLocalNetwork.isEnabled = Client.preferences.leakProtection switchAllowDevicesOnLocalNetwork.isOn = !Client.preferences.leakProtection ? false : Client.preferences.allowLocalDeviceAccess case .safariContentBlocker: - cell.textLabel?.text = L10n.Settings.ContentBlocker.title + cell.textLabel?.text = L10n.Localizable.Settings.ContentBlocker.title cell.detailTextLabel?.text = nil cell.accessoryView = switchContentBlocker cell.selectionStyle = .none switchContentBlocker.isOn = isContentBlockerEnabled case .refresh: - cell.textLabel?.text = L10n.Settings.ContentBlocker.Refresh.title + cell.textLabel?.text = L10n.Localizable.Settings.ContentBlocker.Refresh.title cell.detailTextLabel?.text = nil } diff --git a/PIA VPN/Settings/SettingPopoverSelectionView.swift b/PIA VPN/Settings/SettingPopoverSelectionView.swift index df42d0b07..14ce71dc4 100644 --- a/PIA VPN/Settings/SettingPopoverSelectionView.swift +++ b/PIA VPN/Settings/SettingPopoverSelectionView.swift @@ -151,7 +151,7 @@ extension TransportPopoverSelectionView: UITableViewDelegate, UITableViewDataSou cell.textLabel?.style(style: cellTextStyle) if options[indexPath.row] == ProtocolSettingsViewController.AUTOMATIC_SOCKET { - cell.textLabel?.text = L10n.Global.automatic + cell.textLabel?.text = L10n.Localizable.Global.automatic } else { cell.textLabel?.text = options[indexPath.row] } @@ -209,7 +209,7 @@ extension PortPopoverSelectionView: UITableViewDelegate, UITableViewDataSource { cell.textLabel?.style(style: cellTextStyle) if options[indexPath.row] == ProtocolSettingsViewController.AUTOMATIC_PORT { - cell.textLabel?.text = L10n.Global.automatic + cell.textLabel?.text = L10n.Localizable.Global.automatic } else { cell.textLabel?.text = options[indexPath.row].description } diff --git a/PIA VPN/SettingsViewController.swift b/PIA VPN/SettingsViewController.swift index af0dd1093..5397c261b 100644 --- a/PIA VPN/SettingsViewController.swift +++ b/PIA VPN/SettingsViewController.swift @@ -93,7 +93,7 @@ class SettingsViewController: AutolayoutViewController, SettingsDelegate { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - styleNavigationBarWithTitle(L10n.Menu.Item.settings) + styleNavigationBarWithTitle(L10n.Localizable.Menu.Item.settings) } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -206,7 +206,7 @@ class SettingsViewController: AutolayoutViewController, SettingsDelegate { // MARK: Actions @objc private func viewHasRotated() { - styleNavigationBarWithTitle(L10n.Menu.Item.settings) + styleNavigationBarWithTitle(L10n.Localizable.Menu.Item.settings) } @objc func refreshSettings() { @@ -230,13 +230,13 @@ class SettingsViewController: AutolayoutViewController, SettingsDelegate { func resetToDefaultSettings() { let alert = Macros.alert( - L10n.Settings.Reset.Defaults.Confirm.title, - L10n.Settings.Reset.Defaults.Confirm.message + L10n.Localizable.Settings.Reset.Defaults.Confirm.title, + L10n.Localizable.Settings.Reset.Defaults.Confirm.message ) - alert.addDestructiveActionWithTitle(L10n.Settings.Reset.Defaults.Confirm.button) { + alert.addDestructiveActionWithTitle(L10n.Localizable.Settings.Reset.Defaults.Confirm.button) { self.doReset() } - alert.addCancelAction(L10n.Global.cancel) + alert.addCancelAction(L10n.Localizable.Global.cancel) self.present(alert, animated: true, completion: nil) } @@ -332,17 +332,17 @@ class SettingsViewController: AutolayoutViewController, SettingsDelegate { guard action.canRetainConnection else { let alert = Macros.alert( title, - L10n.Settings.Commit.Messages.mustDisconnect + L10n.Localizable.Settings.Commit.Messages.mustDisconnect ) // reconnect -> reconnect VPN and close - alert.addActionWithTitle(L10n.Settings.Commit.Buttons.reconnect) { + alert.addActionWithTitle(L10n.Localizable.Settings.Commit.Buttons.reconnect) { self.commitPreferences() completionHandlerAfterVPNAction(true) } // cancel -> revert changes and close - alert.addCancelActionWithTitle(L10n.Global.cancel) { + alert.addCancelActionWithTitle(L10n.Localizable.Global.cancel) { completionHandler() } present(alert, animated: true, completion: nil) @@ -355,16 +355,16 @@ class SettingsViewController: AutolayoutViewController, SettingsDelegate { let alert = Macros.alert( title, - L10n.Settings.Commit.Messages.shouldReconnect + L10n.Localizable.Settings.Commit.Messages.shouldReconnect ) // reconnect -> reconnect VPN and close - alert.addActionWithTitle(L10n.Settings.Commit.Buttons.reconnect) { + alert.addActionWithTitle(L10n.Localizable.Settings.Commit.Buttons.reconnect) { completionHandlerAfterVPNAction(true) } // later -> close - alert.addCancelActionWithTitle(L10n.Settings.Commit.Buttons.later) { + alert.addCancelActionWithTitle(L10n.Localizable.Settings.Commit.Buttons.later) { completionHandler() } @@ -494,7 +494,7 @@ class SettingsViewController: AutolayoutViewController, SettingsDelegate { override func viewShouldRestyle() { super.viewShouldRestyle() - styleNavigationBarWithTitle(L10n.Menu.Item.settings) + styleNavigationBarWithTitle(L10n.Localizable.Menu.Item.settings) // XXX: for some reason, UITableView is not affected by appearance updates if let viewContainer = viewContainer { Theme.current.applyPrincipalBackground(view) @@ -582,7 +582,7 @@ extension SettingsViewController: UITableViewDataSource, UITableViewDelegate { switch section { case .automation: - cell.detailTextLabel?.text = Client.preferences.nmtRulesEnabled ? L10n.Global.enabled : L10n.Global.disabled + cell.detailTextLabel?.text = Client.preferences.nmtRulesEnabled ? L10n.Localizable.Global.enabled : L10n.Localizable.Global.disabled case .protocols: cell.detailTextLabel?.text = pendingPreferences?.vpnType.vpnProtocol default: break diff --git a/PIA VPN/ShareDataInformationViewController.swift b/PIA VPN/ShareDataInformationViewController.swift new file mode 100644 index 000000000..8ee63ebbe --- /dev/null +++ b/PIA VPN/ShareDataInformationViewController.swift @@ -0,0 +1,37 @@ +// +// ShareDataInformationViewController.swift +// PIALibrary +// +// Created by Miguel Berrocal on 17/6/21. +// + + +import Foundation +import UIKit + +public class ShareDataInformationViewController: AutolayoutViewController { + + @IBOutlet private weak var labelInformation: UILabel! + + @IBOutlet private weak var contentView: UIView! + @IBOutlet private weak var closeButton: UIButton! + + public override func viewDidLoad() { + super.viewDidLoad() + + self.labelInformation.text = L10n.Signup.Share.Data.ReadMore.Text.description + } + + // MARK: Restylable + + public override func viewShouldRestyle() { + super.viewShouldRestyle() + Theme.current.applyPrincipalBackground(contentView) + Theme.current.applySubtitle(labelInformation) + } + + @IBAction private func close() { + dismissModal() + } + +} diff --git a/PIA VPN/ShowConnectionStatsViewController.swift b/PIA VPN/ShowConnectionStatsViewController.swift index aa6ff8e56..b3de820c4 100644 --- a/PIA VPN/ShowConnectionStatsViewController.swift +++ b/PIA VPN/ShowConnectionStatsViewController.swift @@ -61,7 +61,7 @@ class ShowConnectionStatsViewController: AutolayoutViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - styleNavigationBarWithTitle(L10n.Settings.Service.Quality.Show.title) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Service.Quality.Show.title) } override func viewDidLayoutSubviews() { @@ -77,7 +77,7 @@ class ShowConnectionStatsViewController: AutolayoutViewController { // MARK: Helpers @objc private func viewHasRotated() { - styleNavigationBarWithTitle(L10n.Settings.Service.Quality.Show.title) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Service.Quality.Show.title) } // MARK: Restylable @@ -85,7 +85,7 @@ class ShowConnectionStatsViewController: AutolayoutViewController { override func viewShouldRestyle() { super.viewShouldRestyle() - styleNavigationBarWithTitle(L10n.Settings.Service.Quality.Show.title) + styleNavigationBarWithTitle(L10n.Localizable.Settings.Service.Quality.Show.title) // XXX: for some reason, UITableView is not affected by appearance updates if let viewContainer = viewContainer { Theme.current.applyPrincipalBackground(view) diff --git a/PIA VPN/ShowQuickSettingsViewController.swift b/PIA VPN/ShowQuickSettingsViewController.swift index d5825226a..830353f03 100644 --- a/PIA VPN/ShowQuickSettingsViewController.swift +++ b/PIA VPN/ShowQuickSettingsViewController.swift @@ -72,11 +72,11 @@ class ShowQuickSettingsViewController: AutolayoutViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - styleNavigationBarWithTitle(L10n.Tiles.Quicksettings.title) + styleNavigationBarWithTitle(L10n.Localizable.Tiles.Quicksettings.title) } @objc private func viewHasRotated() { - styleNavigationBarWithTitle(L10n.Tiles.Quicksettings.title) + styleNavigationBarWithTitle(L10n.Localizable.Tiles.Quicksettings.title) } // MARK: Switch actions @@ -123,10 +123,10 @@ class ShowQuickSettingsViewController: AutolayoutViewController { private func cancelDisablingAction() { tableView.reloadData() let alert = Macros.alert( - L10n.Tiles.Quicksettings.title, - L10n.Tiles.Quicksettings.Min.Elements.message + L10n.Localizable.Tiles.Quicksettings.title, + L10n.Localizable.Tiles.Quicksettings.Min.Elements.message ) - alert.addActionWithTitle(L10n.Global.ok) { + alert.addActionWithTitle(L10n.Localizable.Global.ok) { } present(alert, animated: true, completion: nil) } @@ -143,7 +143,7 @@ class ShowQuickSettingsViewController: AutolayoutViewController { override func viewShouldRestyle() { super.viewShouldRestyle() - styleNavigationBarWithTitle(L10n.Tiles.Quicksettings.title) + styleNavigationBarWithTitle(L10n.Localizable.Tiles.Quicksettings.title) // XXX: for some reason, UITableView is not affected by appearance updates if let viewContainer = viewContainer { @@ -183,32 +183,32 @@ extension ShowQuickSettingsViewController: UITableViewDataSource, UITableViewDel let option = options[indexPath.row] switch option { case .theme: - cell.titleLabel.text = L10n.Settings.ApplicationSettings.ActiveTheme.title + cell.titleLabel.text = L10n.Localizable.Settings.ApplicationSettings.ActiveTheme.title cell.accessoryView = switchThemeSettings - cell.settingImage.image = Theme.current.palette.appearance == .light ? Asset.Piax.Global.themeLightInactive.image : - Asset.Piax.Global.themeDarkInactive.image - cell.settingImage.accessibilityLabel = L10n.Settings.ApplicationSettings.ActiveTheme.title + cell.settingImage.image = Theme.current.palette.appearance == .light ? Asset.Images.Piax.Global.themeLightInactive.image : + Asset.Images.Piax.Global.themeDarkInactive.image + cell.settingImage.accessibilityLabel = L10n.Localizable.Settings.ApplicationSettings.ActiveTheme.title switchThemeSettings.isOn = AppPreferences.shared.quickSettingThemeVisible case .killswitch: - cell.titleLabel.text = L10n.Settings.ApplicationSettings.KillSwitch.title + cell.titleLabel.text = L10n.Localizable.Settings.ApplicationSettings.KillSwitch.title cell.accessoryView = switchKillSwitchSetting - cell.settingImage.image = Theme.current.palette.appearance == .light ? Asset.Piax.Global.killswitchLightInactive.image : - Asset.Piax.Global.killswitchDarkInactive.image - cell.settingImage.accessibilityLabel = L10n.Settings.ApplicationSettings.KillSwitch.title + cell.settingImage.image = Theme.current.palette.appearance == .light ? Asset.Images.Piax.Global.killswitchLightInactive.image : + Asset.Images.Piax.Global.killswitchDarkInactive.image + cell.settingImage.accessibilityLabel = L10n.Localizable.Settings.ApplicationSettings.KillSwitch.title switchKillSwitchSetting.isOn = AppPreferences.shared.quickSettingKillswitchVisible case .networkTools: - cell.titleLabel.text = L10n.Tiles.Quicksetting.Nmt.title + cell.titleLabel.text = L10n.Localizable.Tiles.Quicksetting.Nmt.title cell.accessoryView = switchNetworkToolsSetting - cell.settingImage.image = Theme.current.palette.appearance == .light ? Asset.Piax.Global.nmtLightInactive.image : - Asset.Piax.Global.nmtDarkInactive.image - cell.settingImage.accessibilityLabel = L10n.Tiles.Quicksetting.Nmt.title + cell.settingImage.image = Theme.current.palette.appearance == .light ? Asset.Images.Piax.Global.nmtLightInactive.image : + Asset.Images.Piax.Global.nmtDarkInactive.image + cell.settingImage.accessibilityLabel = L10n.Localizable.Tiles.Quicksetting.Nmt.title switchNetworkToolsSetting.isOn = AppPreferences.shared.quickSettingNetworkToolVisible case .privateBrowsing: - cell.titleLabel.text = L10n.Tiles.Quicksetting.Private.Browser.title + cell.titleLabel.text = L10n.Localizable.Tiles.Quicksetting.Private.Browser.title cell.accessoryView = switchPrivateBrowserSetting - cell.settingImage.image = Theme.current.palette.appearance == .light ? Asset.Piax.Global.browserLightInactive.image : - Asset.Piax.Global.browserDarkInactive.image - cell.settingImage.accessibilityLabel = L10n.Tiles.Quicksetting.Private.Browser.title + cell.settingImage.image = Theme.current.palette.appearance == .light ? Asset.Images.Piax.Global.browserLightInactive.image : + Asset.Images.Piax.Global.browserDarkInactive.image + cell.settingImage.accessibilityLabel = L10n.Localizable.Tiles.Quicksetting.Private.Browser.title switchPrivateBrowserSetting.isOn = AppPreferences.shared.quickSettingPrivateBrowserVisible } Theme.current.applySettingsCellTitle(cell.titleLabel, diff --git a/PIA VPN/SignupFailureViewController.swift b/PIA VPN/SignupFailureViewController.swift new file mode 100644 index 000000000..1a8575d6b --- /dev/null +++ b/PIA VPN/SignupFailureViewController.swift @@ -0,0 +1,91 @@ +// +// SignupFailureViewController.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 10/8/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit +import PIALibrary + +public class SignupFailureViewController: AutolayoutViewController, BrandableNavigationBar { + + @IBOutlet private weak var imvPicture: UIImageView! + @IBOutlet private weak var labelTitle: UILabel! + @IBOutlet private weak var labelMessage: UILabel! + @IBOutlet private weak var buttonSubmit: PIAButton! + + var error: Error? + + override public func viewDidLoad() { + super.viewDidLoad() + + navigationItem.hidesBackButton = true + + title = L10n.Signup.Failure.vcTitle + imvPicture.image = Asset.Ui.imageAccountFailed.image + labelTitle.text = L10n.Signup.Failure.title + labelMessage.text = L10n.Signup.Failure.message + + if let clientError = error as? ClientError { + switch clientError { + case .redeemInvalid: + title = L10n.Welcome.Redeem.title + imvPicture.image = Asset.Ui.imageRedeemInvalid.image + labelTitle.text = L10n.Signup.Failure.Redeem.Invalid.title + labelMessage.text = L10n.Signup.Failure.Redeem.Invalid.message + break + case .redeemClaimed: + title = L10n.Welcome.Redeem.title + imvPicture.image = Asset.Ui.imageRedeemClaimed.image + labelTitle.text = L10n.Signup.Failure.Redeem.Claimed.title + labelMessage.text = L10n.Signup.Failure.Redeem.Claimed.message + break + + default: + break + } + } + + self.styleSubmitButton() + } + + @IBAction private func submit() { + dismissModal() + } + + // MARK: Restylable + + override public func viewShouldRestyle() { + super.viewShouldRestyle() + navigationItem.titleView = NavigationLogoView() + Theme.current.applyNavigationBarStyle(to: self) + Theme.current.applyPrincipalBackground(view) + Theme.current.applyPrincipalBackground(viewContainer!) + Theme.current.applySubtitle(labelMessage) + Theme.current.applyTitle(labelTitle, appearance: .dark) + } + + private func styleSubmitButton() { + buttonSubmit.setRounded() + buttonSubmit.style(style: TextStyle.Buttons.piaGreenButton) + buttonSubmit.setTitle(L10n.Signup.Failure.submit.uppercased(), + for: []) + } + +} diff --git a/PIA VPN/SignupInProgressViewController.swift b/PIA VPN/SignupInProgressViewController.swift new file mode 100644 index 000000000..368d7e503 --- /dev/null +++ b/PIA VPN/SignupInProgressViewController.swift @@ -0,0 +1,151 @@ +// +// SignupInProgressViewController.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 10/8/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit +import SwiftyBeaver +import PIALibrary + +private let log = SwiftyBeaver.self + +public class SignupInProgressViewController: AutolayoutViewController, BrandableNavigationBar { + @IBOutlet private weak var progressView: CircleProgressView! + + @IBOutlet private weak var titleMessage: UILabel! + @IBOutlet private weak var labelMessage: UILabel! + + var signupRequest: SignupRequest? + + var redeemRequest: RedeemRequest? + + var preset: Preset? + + var metadata: SignupMetadata? + + weak var completionDelegate: WelcomeCompletionDelegate? + + private var user: UserAccount? + + private var error: Error? + + override public func viewDidLoad() { + super.viewDidLoad() + + labelMessage.text = metadata?.bodySubtitle + titleMessage.text = metadata?.title + } + + override public func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + progressView.startAnimating() + + if let request = signupRequest { + performSignup(with: request) + } + } + + private func performSignup(with request: SignupRequest) { + log.debug("Signing up...") + + preset?.accountProvider.signup(with: request) { (user, error) in + guard let user = user else { + self.user = nil + self.error = error + if let clientError = error as? ClientError, (clientError == .internetUnreachable) { + log.error("Failed to sign up: Internet is unreachable") + self.perform(segue: StoryboardSegue.Signup.internetUnreachableSegueIdentifier, sender: nil) + return + } + if let error = error { + log.error("Failed to sign up (error: \(error))") + } else { + log.error("Failed to sign up") + } + self.perform(segue: StoryboardSegue.Signup.failureSegueIdentifier) + return + } + + log.debug("Sign-up succeeded!") + + self.user = user + self.error = nil + self.perform(segue: StoryboardSegue.Signup.successSegueIdentifier) + } + } + + override public func prepare(for segue: UIStoryboardSegue, sender: Any?) { + guard let identifier = segue.identifier, let segueType = StoryboardSegue.Signup(rawValue: identifier) else { + return + } + switch segueType { + case .successSegueIdentifier: + + guard let email = signupRequest?.email ?? redeemRequest?.email else { + fatalError("Email not provided with signup or redeem request") + } + + let vc = segue.destination as! ConfirmVPNPlanViewController + var metadata = SignupMetadata(email: email, user: user) + if let _ = signupRequest { + metadata.title = L10n.Signup.InProgress.title + metadata.bodyImage = Asset.Images.imagePurchaseSuccess.image + metadata.bodyTitle = L10n.Signup.Success.title + metadata.bodySubtitle = L10n.Signup.Success.messageFormat(metadata.email) + } else if let _ = redeemRequest { + metadata.title = L10n.Welcome.Redeem.title + metadata.bodyImage = Asset.Ui.imageRedeemSuccess.image + metadata.bodyImageOffset = CGPoint(x: -10.0, y: 0.0) + metadata.bodyTitle = L10n.Signup.Success.Redeem.title + metadata.bodySubtitle = L10n.Signup.Success.Redeem.message + } + vc.preset = preset + vc.metadata = metadata + vc.completionDelegate = completionDelegate + + case .failureSegueIdentifier: + let vc = segue.destination as! SignupFailureViewController + vc.error = error + break + + default: + break + } + } + + // MARK: Unwind + + @IBAction private func unwoundSignupInternetUnreachable(segue: UIStoryboardSegue) { + } + + // MARK: Restylable + + override public func viewShouldRestyle() { + super.viewShouldRestyle() + navigationItem.titleView = NavigationLogoView() + Theme.current.applyNavigationBarStyle(to: self) + Theme.current.applyPrincipalBackground(view) + Theme.current.applyPrincipalBackground(viewContainer!) + Theme.current.applyTitle(titleMessage, appearance: .dark) + Theme.current.applySubtitle(labelMessage) + Theme.current.applyCircleProgressView(progressView) + } +} diff --git a/PIA VPN/SignupInternetUnreachableViewController.swift b/PIA VPN/SignupInternetUnreachableViewController.swift new file mode 100644 index 000000000..9ce1cf3c8 --- /dev/null +++ b/PIA VPN/SignupInternetUnreachableViewController.swift @@ -0,0 +1,69 @@ +// +// SignupUnreachableViewController.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 10/8/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit +import PIALibrary + +public class SignupUnreachableViewController: AutolayoutViewController, BrandableNavigationBar { + + @IBOutlet private weak var imvPicture: UIImageView! + @IBOutlet private weak var labelTitle: UILabel! + @IBOutlet private weak var labelMessage: UILabel! + @IBOutlet private weak var buttonSubmit: PIAButton! + + override public func viewDidLoad() { + super.viewDidLoad() + + navigationItem.hidesBackButton = true + + title = L10n.Signup.Unreachable.vcTitle + imvPicture.image = Asset.Ui.imageNoInternet.image + labelTitle.text = L10n.Signup.Unreachable.title + labelMessage.text = L10n.Signup.Unreachable.message + self.styleSubmitButton() + + } + + @IBAction private func submit() { + perform(segue: StoryboardSegue.Signup.unwindInternetUnreachableSegueIdentifier) + } + + // MARK: Restylable + + override public func viewShouldRestyle() { + super.viewShouldRestyle() + navigationItem.titleView = NavigationLogoView() + Theme.current.applyNavigationBarStyle(to: self) + Theme.current.applyPrincipalBackground(view) + Theme.current.applyPrincipalBackground(viewContainer!) + Theme.current.applySubtitle(labelMessage) + Theme.current.applyTitle(labelTitle, appearance: .dark) + } + + private func styleSubmitButton() { + buttonSubmit.setRounded() + buttonSubmit.style(style: TextStyle.Buttons.piaGreenButton) + buttonSubmit.setTitle(L10n.Signup.Unreachable.submit.uppercased(), + for: []) + } + +} diff --git a/PIA VPN/SignupSuccessViewController.swift b/PIA VPN/SignupSuccessViewController.swift new file mode 100644 index 000000000..6c8ea624b --- /dev/null +++ b/PIA VPN/SignupSuccessViewController.swift @@ -0,0 +1,182 @@ +// +// SignupSuccessViewController.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 10/8/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import Foundation +import SwiftyBeaver +import UIKit +import PIALibrary + +private let log = SwiftyBeaver.self + +public class SignupSuccessViewController: AutolayoutViewController, BrandableNavigationBar { + + @IBOutlet private weak var imvPicture: UIImageView! + @IBOutlet private weak var labelTitle: UILabel! + @IBOutlet private weak var labelMessage: UILabel! + @IBOutlet private weak var labelUsernameCaption: UILabel! + @IBOutlet private weak var labelUsername: UILabel! + @IBOutlet private weak var labelPasswordCaption: UILabel! + @IBOutlet private weak var labelPassword: UILabel! + @IBOutlet private weak var buttonSubmit: PIAButton! + @IBOutlet private weak var usernameContainer: UIView! + @IBOutlet private weak var passwordContainer: UIView! + + //Share data + @IBOutlet private weak var shareDataContainer: UIView! + @IBOutlet private weak var shareDataImageView: UIImageView! + @IBOutlet private weak var shareDataTitleLabel: UILabel! + @IBOutlet private weak var shareDataDescriptionLabel: UILabel! + @IBOutlet private weak var readMoreButton: PIAButton! + @IBOutlet private weak var acceptButton: PIAButton! + @IBOutlet private weak var cancelButton: PIAButton! + @IBOutlet private weak var shareDataFooterLabel: UILabel! + + @IBOutlet private weak var constraintPictureXOffset: NSLayoutConstraint! + + var metadata: SignupMetadata? + + weak var completionDelegate: WelcomeCompletionDelegate? + + override public func viewDidLoad() { + super.viewDidLoad() + + guard let metadata = metadata else { + fatalError("Metadata not set") + } + + title = metadata.title + imvPicture.image = metadata.bodyImage + if let offset = metadata.bodyImageOffset { + constraintPictureXOffset.constant = offset.x + } + labelTitle.text = metadata.bodyTitle + labelMessage.text = metadata.bodySubtitle + + navigationItem.hidesBackButton = true + + labelUsernameCaption.text = L10n.Signup.Success.Username.caption + labelUsername.text = metadata.user?.credentials.username + labelPasswordCaption.text = L10n.Signup.Success.Password.caption + labelPassword.text = metadata.user?.credentials.password + + self.styleSubmitButton() + self.styleContainers() + + shareDataImageView.image = Asset.Ui.imageDocumentConsent.image + shareDataTitleLabel.text = L10n.Signup.Share.Data.Text.title + shareDataDescriptionLabel.text = L10n.Signup.Share.Data.Text.description + shareDataFooterLabel.text = L10n.Signup.Share.Data.Text.footer + self.styleShareDataButtons() + } + + @IBAction private func submit() { + guard let user = metadata?.user else { + fatalError("User account not set in metadata") + } + completionDelegate?.welcomeDidSignup(withUser: user, topViewController: self) + } + + @IBAction private func acceptShareData() { + let preferences = Client.preferences.editable() + preferences.shareServiceQualityData = true + preferences.versionWhenServiceQualityOpted = Macros.versionString() + preferences.commit() + DispatchQueue.main.async { + UIView.animate(withDuration: 0.3) { + self.shareDataContainer.alpha = 0 + } + } + } + + @IBAction private func rejectShareData() { + let preferences = Client.preferences.editable() + preferences.shareServiceQualityData = false + preferences.versionWhenServiceQualityOpted = nil + preferences.commit() + DispatchQueue.main.async { + UIView.animate(withDuration: 0.3) { + self.shareDataContainer.alpha = 0 + } + } + } + + // MARK: Restylable + + override public func viewShouldRestyle() { + super.viewShouldRestyle() + navigationItem.titleView = NavigationLogoView() + Theme.current.applyNavigationBarStyle(to: self) + Theme.current.applyPrincipalBackground(view) + Theme.current.applyPrincipalBackground(viewContainer!) + Theme.current.applyPrincipalBackground(shareDataContainer!) + + Theme.current.applyTitle(labelTitle, appearance: .dark) + Theme.current.applySubtitle(labelMessage) + + Theme.current.applyTitle(shareDataTitleLabel, appearance: .dark) + Theme.current.applySubtitle(shareDataDescriptionLabel) + Theme.current.applySmallSubtitle(shareDataFooterLabel) + Theme.current.applyTransparentButton(cancelButton, + withSize: 1.0) + Theme.current.applyButtonLabelMediumStyle(acceptButton) + + Theme.current.applySubtitle(labelUsernameCaption) + Theme.current.applyTitle(labelUsername, appearance: .dark) + Theme.current.applySubtitle(labelPasswordCaption) + Theme.current.applyTitle(labelPassword, appearance: .dark) + } + + private func styleSubmitButton() { + buttonSubmit.setRounded() + buttonSubmit.style(style: TextStyle.Buttons.piaGreenButton) + buttonSubmit.setTitle(L10n.Signup.Success.submit.uppercased(), + for: []) + } + + private func styleShareDataButtons() { + acceptButton.setRounded() + acceptButton.style(style: TextStyle.Buttons.piaGreenButton) + acceptButton.setTitle(L10n.Signup.Share.Data.Buttons.accept.uppercased(), + for: []) + cancelButton.setRounded() + cancelButton.style(style: TextStyle.Buttons.piaPlainTextButton) + cancelButton.setTitle(L10n.Signup.Share.Data.Buttons.noThanks.uppercased(), + for: []) + Theme.current.applyTransparentButton(cancelButton, + withSize: 1.0) + readMoreButton.setTitle(L10n.Signup.Share.Data.Buttons.readMore, for: .normal) + Theme.current.applyUnderlinedSubtitleButton(readMoreButton) + } + + private func styleContainers() { + self.styleContainerView(usernameContainer) + self.styleContainerView(passwordContainer) + } + + func styleContainerView(_ view: UIView) { + view.layer.cornerRadius = 6.0 + view.clipsToBounds = true + view.layer.borderWidth = 0.5 + view.layer.borderColor = UIColor.piaGrey4.cgColor + } + +} diff --git a/PIA VPN/Siri/SiriShortcutsBuilder.swift b/PIA VPN/Siri/SiriShortcutsBuilder.swift index 628beb4b9..ec97e8e33 100644 --- a/PIA VPN/Siri/SiriShortcutsBuilder.swift +++ b/PIA VPN/Siri/SiriShortcutsBuilder.swift @@ -52,7 +52,7 @@ extension SiriShortcutBuilder { class SiriShortcutConnect: SiriShortcutBuilder { var activityType = AppConstants.SiriShortcuts.shortcutConnect - var title = L10n.Siri.Shortcuts.Connect.title + var title = L10n.Localizable.Siri.Shortcuts.Connect.title var persistentIdentifier = AppConstants.SiriShortcuts.shortcutConnect } @@ -61,7 +61,7 @@ class SiriShortcutConnect: SiriShortcutBuilder { class SiriShortcutDisconnect: SiriShortcutBuilder { var activityType = AppConstants.SiriShortcuts.shortcutDisconnect - var title = L10n.Siri.Shortcuts.Disconnect.title + var title = L10n.Localizable.Siri.Shortcuts.Disconnect.title var persistentIdentifier = AppConstants.SiriShortcuts.shortcutDisconnect } diff --git a/PIA VPN/Siri/SiriShortcutsManager.swift b/PIA VPN/Siri/SiriShortcutsManager.swift index a5de0c4a0..7af4ecc3e 100644 --- a/PIA VPN/Siri/SiriShortcutsManager.swift +++ b/PIA VPN/Siri/SiriShortcutsManager.swift @@ -70,17 +70,17 @@ public class SiriShortcutsManager: NSObject { func descriptionActionForConnectShortcut() -> String { if AppPreferences.shared.useConnectSiriShortcuts { - return L10n.Global.edit + return L10n.Localizable.Global.edit } else { - return L10n.Global.add + return L10n.Localizable.Global.add } } func descriptionActionForDisconnectShortcut() -> String { if AppPreferences.shared.useDisconnectSiriShortcuts { - return L10n.Global.edit + return L10n.Localizable.Global.edit } else { - return L10n.Global.add + return L10n.Localizable.Global.add } } @@ -95,9 +95,9 @@ extension SiriShortcutsManager: INUIAddVoiceShortcutViewControllerDelegate { error: Error? ) { if let _ = error { - let message = L10n.Siri.Shortcuts.Add.error + let message = L10n.Localizable.Siri.Shortcuts.Add.error let alert = Macros.alert(nil, message) - alert.addCancelActionWithTitle(L10n.Global.cancel) { + alert.addCancelActionWithTitle(L10n.Localizable.Global.cancel) { controller.dismiss(animated: true, completion: nil) } controller.present(alert, animated: true, completion: nil) diff --git a/PIA VPN/String+VPNType.swift b/PIA VPN/String+VPNType.swift index e2de89442..4686e9d32 100644 --- a/PIA VPN/String+VPNType.swift +++ b/PIA VPN/String+VPNType.swift @@ -52,7 +52,7 @@ public extension String { return "\(port)" } } else { - return L10n.Global.automatic + return L10n.Localizable.Global.automatic } return "---" case IKEv2Profile.vpnType: @@ -67,7 +67,7 @@ public extension String { case PIAWGTunnelProfile.vpnType, IKEv2Profile.vpnType: return "UDP" case PIATunnelProfile.vpnType: - return AppPreferences.shared.piaSocketType?.rawValue ?? L10n.Global.automatic + return AppPreferences.shared.piaSocketType?.rawValue ?? L10n.Localizable.Global.automatic default: return self diff --git a/PIA VPN/SwiftGen+Assets.swift b/PIA VPN/SwiftGen+Assets.swift index 30dea8617..ad824e853 100644 --- a/PIA VPN/SwiftGen+Assets.swift +++ b/PIA VPN/SwiftGen+Assets.swift @@ -44,795 +44,870 @@ struct ColorAsset { // swiftlint:disable identifier_name line_length nesting type_body_length type_name enum Asset { - enum Cards { - enum WireGuard { - static let wgBackgroundDark = ImageAsset(name: "wg-background-dark") - static let wgBackgroundLight = ImageAsset(name: "wg-background-light") - static let wgMain = ImageAsset(name: "wg-main") - } - } - enum Piax { - enum DarkMap { - static let darkMap = ImageAsset(name: "Dark-Map") - } - enum Dashboard { - static let vpnButton = ImageAsset(name: "vpn-button") - } - enum Global { - static let browserDarkInactive = ImageAsset(name: "browser-dark-inactive") - static let browserLightInactive = ImageAsset(name: "browser-light-inactive") - static let dragDropIndicatorDark = ImageAsset(name: "drag-drop-indicator-dark") - static let dragDropIndicatorLight = ImageAsset(name: "drag-drop-indicator-light") - static let eyeActiveDark = ImageAsset(name: "eye-active-dark") - static let eyeActiveLight = ImageAsset(name: "eye-active-light") - static let eyeInactiveDark = ImageAsset(name: "eye-inactive-dark") - static let eyeInactiveLight = ImageAsset(name: "eye-inactive-light") - static let favoriteGreen = ImageAsset(name: "favorite-green") - static let favoriteSelected = ImageAsset(name: "favorite-selected") - static let favoriteUnselectedDark = ImageAsset(name: "favorite-unselected-dark") - static let favoriteUnselected = ImageAsset(name: "favorite-unselected") - static let iconBack = ImageAsset(name: "icon-back") - static let iconEditTile = ImageAsset(name: "icon-edit-tile") - static let iconFilter = ImageAsset(name: "icon-filter") - static let killswitchDarkActive = ImageAsset(name: "killswitch-dark-active") - static let killswitchDarkInactive = ImageAsset(name: "killswitch-dark-inactive") - static let killswitchLightInactive = ImageAsset(name: "killswitch-light-inactive") - static let nmtDarkActive = ImageAsset(name: "nmt-dark-active") - static let nmtDarkInactive = ImageAsset(name: "nmt-dark-inactive") - static let nmtLightActive = ImageAsset(name: "nmt-light-active") - static let nmtLightInactive = ImageAsset(name: "nmt-light-inactive") - static let regionSelected = ImageAsset(name: "region-selected") - static let themeDarkActive = ImageAsset(name: "theme-dark-active") - static let themeDarkInactive = ImageAsset(name: "theme-dark-inactive") - static let themeLightActive = ImageAsset(name: "theme-light-active") - static let themeLightInactive = ImageAsset(name: "theme-light-inactive") - static let trustedDarkIcon = ImageAsset(name: "trusted-dark-icon") - static let trustedLightIcon = ImageAsset(name: "trusted-light-icon") - static let untrustedDarkIcon = ImageAsset(name: "untrusted-dark-icon") - static let untrustedLightIcon = ImageAsset(name: "untrusted-light-icon") - } - enum Nmt { - static let iconAddRule = ImageAsset(name: "icon-add-rule") - static let iconCustomWifiConnect = ImageAsset(name: "icon-custom-wifi-connect") - static let iconCustomWifiDisconnect = ImageAsset(name: "icon-custom-wifi-disconnect") - static let iconCustomWifiRetain = ImageAsset(name: "icon-custom-wifi-retain") - static let iconDisconnect = ImageAsset(name: "icon-disconnect") - static let iconMobileDataConnect = ImageAsset(name: "icon-mobile-data-connect") - static let iconMobileDataDisconnect = ImageAsset(name: "icon-mobile-data-disconnect") - static let iconMobileDataRetain = ImageAsset(name: "icon-mobile-data-retain") - static let iconNmtConnect = ImageAsset(name: "icon-nmt-connect") - static let iconNmtWifi = ImageAsset(name: "icon-nmt-wifi") - static let iconOpenWifiConnect = ImageAsset(name: "icon-open-wifi-connect") - static let iconOpenWifiDisconnect = ImageAsset(name: "icon-open-wifi-disconnect") - static let iconOpenWifiRetain = ImageAsset(name: "icon-open-wifi-retain") - static let iconOptions = ImageAsset(name: "icon-options") - static let iconRetain = ImageAsset(name: "icon-retain") - static let iconSecureWifiConnect = ImageAsset(name: "icon-secure-wifi-connect") - static let iconSecureWifiDisconnect = ImageAsset(name: "icon-secure-wifi-disconnect") - static let iconSecureWifiRetain = ImageAsset(name: "icon-secure-wifi-retain") - static let iconSelect = ImageAsset(name: "icon-select") - } - enum Regions { - static let noResultsDark = ImageAsset(name: "no-results-dark") - static let noResultsLight = ImageAsset(name: "no-results-light") + enum Images { + enum Cards { + enum WireGuard { + static let wgBackgroundDark = ImageAsset(name: "wg-background-dark") + static let wgBackgroundLight = ImageAsset(name: "wg-background-light") + static let wgMain = ImageAsset(name: "wg-main") + } } - enum Settings { - static let iconAbout = ImageAsset(name: "icon-about") - static let iconAutomation = ImageAsset(name: "icon-automation") - static let iconGeneral = ImageAsset(name: "icon-general") - static let iconNetwork = ImageAsset(name: "icon-network") - static let iconPrivacy = ImageAsset(name: "icon-privacy") - static let iconProtocols = ImageAsset(name: "icon-protocols") + enum Piax { + enum DarkMap { + static let darkMap = ImageAsset(name: "Dark-Map") + } + enum Dashboard { + static let vpnButton = ImageAsset(name: "vpn-button") + } + enum Global { + static let browserDarkInactive = ImageAsset(name: "browser-dark-inactive") + static let browserLightInactive = ImageAsset(name: "browser-light-inactive") + static let dragDropIndicatorDark = ImageAsset(name: "drag-drop-indicator-dark") + static let dragDropIndicatorLight = ImageAsset(name: "drag-drop-indicator-light") + static let eyeActiveDark = ImageAsset(name: "eye-active-dark") + static let eyeActiveLight = ImageAsset(name: "eye-active-light") + static let eyeInactiveDark = ImageAsset(name: "eye-inactive-dark") + static let eyeInactiveLight = ImageAsset(name: "eye-inactive-light") + static let favoriteGreen = ImageAsset(name: "favorite-green") + static let favoriteSelected = ImageAsset(name: "favorite-selected") + static let favoriteUnselectedDark = ImageAsset(name: "favorite-unselected-dark") + static let favoriteUnselected = ImageAsset(name: "favorite-unselected") + static let iconBack = ImageAsset(name: "icon-back") + static let iconEditTile = ImageAsset(name: "icon-edit-tile") + static let iconFilter = ImageAsset(name: "icon-filter") + static let killswitchDarkActive = ImageAsset(name: "killswitch-dark-active") + static let killswitchDarkInactive = ImageAsset(name: "killswitch-dark-inactive") + static let killswitchLightInactive = ImageAsset(name: "killswitch-light-inactive") + static let nmtDarkActive = ImageAsset(name: "nmt-dark-active") + static let nmtDarkInactive = ImageAsset(name: "nmt-dark-inactive") + static let nmtLightActive = ImageAsset(name: "nmt-light-active") + static let nmtLightInactive = ImageAsset(name: "nmt-light-inactive") + static let regionSelected = ImageAsset(name: "region-selected") + static let themeDarkActive = ImageAsset(name: "theme-dark-active") + static let themeDarkInactive = ImageAsset(name: "theme-dark-inactive") + static let themeLightActive = ImageAsset(name: "theme-light-active") + static let themeLightInactive = ImageAsset(name: "theme-light-inactive") + static let trustedDarkIcon = ImageAsset(name: "trusted-dark-icon") + static let trustedLightIcon = ImageAsset(name: "trusted-light-icon") + static let untrustedDarkIcon = ImageAsset(name: "untrusted-dark-icon") + static let untrustedLightIcon = ImageAsset(name: "untrusted-light-icon") + } + enum Nmt { + static let iconAddRule = ImageAsset(name: "icon-add-rule") + static let iconCustomWifiConnect = ImageAsset(name: "icon-custom-wifi-connect") + static let iconCustomWifiDisconnect = ImageAsset(name: "icon-custom-wifi-disconnect") + static let iconCustomWifiRetain = ImageAsset(name: "icon-custom-wifi-retain") + static let iconDisconnect = ImageAsset(name: "icon-disconnect") + static let iconMobileDataConnect = ImageAsset(name: "icon-mobile-data-connect") + static let iconMobileDataDisconnect = ImageAsset(name: "icon-mobile-data-disconnect") + static let iconMobileDataRetain = ImageAsset(name: "icon-mobile-data-retain") + static let iconNmtConnect = ImageAsset(name: "icon-nmt-connect") + static let iconNmtWifi = ImageAsset(name: "icon-nmt-wifi") + static let iconOpenWifiConnect = ImageAsset(name: "icon-open-wifi-connect") + static let iconOpenWifiDisconnect = ImageAsset(name: "icon-open-wifi-disconnect") + static let iconOpenWifiRetain = ImageAsset(name: "icon-open-wifi-retain") + static let iconOptions = ImageAsset(name: "icon-options") + static let iconRetain = ImageAsset(name: "icon-retain") + static let iconSecureWifiConnect = ImageAsset(name: "icon-secure-wifi-connect") + static let iconSecureWifiDisconnect = ImageAsset(name: "icon-secure-wifi-disconnect") + static let iconSecureWifiRetain = ImageAsset(name: "icon-secure-wifi-retain") + static let iconSelect = ImageAsset(name: "icon-select") + } + enum Regions { + static let noResultsDark = ImageAsset(name: "no-results-dark") + static let noResultsLight = ImageAsset(name: "no-results-light") + } + enum Settings { + static let iconAbout = ImageAsset(name: "icon-about") + static let iconAutomation = ImageAsset(name: "icon-automation") + static let iconGeneral = ImageAsset(name: "icon-general") + static let iconNetwork = ImageAsset(name: "icon-network") + static let iconPrivacy = ImageAsset(name: "icon-privacy") + static let iconProtocols = ImageAsset(name: "icon-protocols") + } + enum Splash { + static let splash = ImageAsset(name: "splash") + } + enum Tiles { + enum ConnectionTile { + static let iconAuthentication = ImageAsset(name: "icon-authentication") + static let iconEncryption = ImageAsset(name: "icon-encryption") + static let iconHandshake = ImageAsset(name: "icon-handshake") + static let iconPort = ImageAsset(name: "icon-port") + static let iconProtocol = ImageAsset(name: "icon-protocol") + static let iconSocket = ImageAsset(name: "icon-socket") + } + static let ipTriangle = ImageAsset(name: "ip-triangle") + static let openTileDetails = ImageAsset(name: "open-tile-details") + static let quickConnectPlaceholderDark = ImageAsset(name: "quick-connect-placeholder-dark") + static let quickConnectPlaceholderLight = ImageAsset(name: "quick-connect-placeholder-light") + } } - enum Splash { - static let splash = ImageAsset(name: "splash") + static let accessoryExpire = ImageAsset(name: "accessory-expire") + static let accessorySelected = ImageAsset(name: "accessory-selected") + static let buttonDown = ImageAsset(name: "button-down") + static let buttonUp = ImageAsset(name: "button-up") + static let copyIcon = ImageAsset(name: "copy-icon") + static let dipBadgeDark = ImageAsset(name: "dip-badge-dark") + static let dipBadgeLight = ImageAsset(name: "dip-badge-light") + enum Flags { + static let flagAd = ImageAsset(name: "flag-ad") + static let flagAe = ImageAsset(name: "flag-ae") + static let flagAf = ImageAsset(name: "flag-af") + static let flagAg = ImageAsset(name: "flag-ag") + static let flagAi = ImageAsset(name: "flag-ai") + static let flagAl = ImageAsset(name: "flag-al") + static let flagAm = ImageAsset(name: "flag-am") + static let flagAo = ImageAsset(name: "flag-ao") + static let flagAq = ImageAsset(name: "flag-aq") + static let flagAr = ImageAsset(name: "flag-ar") + static let flagAs = ImageAsset(name: "flag-as") + static let flagAt = ImageAsset(name: "flag-at") + static let flagAu = ImageAsset(name: "flag-au") + static let flagAw = ImageAsset(name: "flag-aw") + static let flagAx = ImageAsset(name: "flag-ax") + static let flagAz = ImageAsset(name: "flag-az") + static let flagBa = ImageAsset(name: "flag-ba") + static let flagBb = ImageAsset(name: "flag-bb") + static let flagBd = ImageAsset(name: "flag-bd") + static let flagBe = ImageAsset(name: "flag-be") + static let flagBf = ImageAsset(name: "flag-bf") + static let flagBg = ImageAsset(name: "flag-bg") + static let flagBh = ImageAsset(name: "flag-bh") + static let flagBi = ImageAsset(name: "flag-bi") + static let flagBj = ImageAsset(name: "flag-bj") + static let flagBl = ImageAsset(name: "flag-bl") + static let flagBm = ImageAsset(name: "flag-bm") + static let flagBn = ImageAsset(name: "flag-bn") + static let flagBo = ImageAsset(name: "flag-bo") + static let flagBq = ImageAsset(name: "flag-bq") + static let flagBr = ImageAsset(name: "flag-br") + static let flagBs = ImageAsset(name: "flag-bs") + static let flagBt = ImageAsset(name: "flag-bt") + static let flagBv = ImageAsset(name: "flag-bv") + static let flagBw = ImageAsset(name: "flag-bw") + static let flagBy = ImageAsset(name: "flag-by") + static let flagBz = ImageAsset(name: "flag-bz") + static let flagCa = ImageAsset(name: "flag-ca") + static let flagCc = ImageAsset(name: "flag-cc") + static let flagCd = ImageAsset(name: "flag-cd") + static let flagCf = ImageAsset(name: "flag-cf") + static let flagCg = ImageAsset(name: "flag-cg") + static let flagCh = ImageAsset(name: "flag-ch") + static let flagCi = ImageAsset(name: "flag-ci") + static let flagCk = ImageAsset(name: "flag-ck") + static let flagCl = ImageAsset(name: "flag-cl") + static let flagCm = ImageAsset(name: "flag-cm") + static let flagCn = ImageAsset(name: "flag-cn") + static let flagCo = ImageAsset(name: "flag-co") + static let flagCr = ImageAsset(name: "flag-cr") + static let flagCu = ImageAsset(name: "flag-cu") + static let flagCv = ImageAsset(name: "flag-cv") + static let flagCw = ImageAsset(name: "flag-cw") + static let flagCx = ImageAsset(name: "flag-cx") + static let flagCy = ImageAsset(name: "flag-cy") + static let flagCz = ImageAsset(name: "flag-cz") + static let flagDe = ImageAsset(name: "flag-de") + static let flagDj = ImageAsset(name: "flag-dj") + static let flagDk = ImageAsset(name: "flag-dk") + static let flagDm = ImageAsset(name: "flag-dm") + static let flagDo = ImageAsset(name: "flag-do") + static let flagDz = ImageAsset(name: "flag-dz") + static let flagEc = ImageAsset(name: "flag-ec") + static let flagEe = ImageAsset(name: "flag-ee") + static let flagEg = ImageAsset(name: "flag-eg") + static let flagEh = ImageAsset(name: "flag-eh") + static let flagEr = ImageAsset(name: "flag-er") + static let flagEsCt = ImageAsset(name: "flag-es-ct") + static let flagEs = ImageAsset(name: "flag-es") + static let flagEt = ImageAsset(name: "flag-et") + static let flagEu = ImageAsset(name: "flag-eu") + static let flagFi = ImageAsset(name: "flag-fi") + static let flagFj = ImageAsset(name: "flag-fj") + static let flagFk = ImageAsset(name: "flag-fk") + static let flagFm = ImageAsset(name: "flag-fm") + static let flagFo = ImageAsset(name: "flag-fo") + static let flagFr = ImageAsset(name: "flag-fr") + static let flagGa = ImageAsset(name: "flag-ga") + static let flagGbEng = ImageAsset(name: "flag-gb-eng") + static let flagGbNir = ImageAsset(name: "flag-gb-nir") + static let flagGbSct = ImageAsset(name: "flag-gb-sct") + static let flagGbWls = ImageAsset(name: "flag-gb-wls") + static let flagGb = ImageAsset(name: "flag-gb") + static let flagGd = ImageAsset(name: "flag-gd") + static let flagGe = ImageAsset(name: "flag-ge") + static let flagGf = ImageAsset(name: "flag-gf") + static let flagGg = ImageAsset(name: "flag-gg") + static let flagGh = ImageAsset(name: "flag-gh") + static let flagGi = ImageAsset(name: "flag-gi") + static let flagGl = ImageAsset(name: "flag-gl") + static let flagGm = ImageAsset(name: "flag-gm") + static let flagGn = ImageAsset(name: "flag-gn") + static let flagGp = ImageAsset(name: "flag-gp") + static let flagGq = ImageAsset(name: "flag-gq") + static let flagGr = ImageAsset(name: "flag-gr") + static let flagGs = ImageAsset(name: "flag-gs") + static let flagGt = ImageAsset(name: "flag-gt") + static let flagGu = ImageAsset(name: "flag-gu") + static let flagGw = ImageAsset(name: "flag-gw") + static let flagGy = ImageAsset(name: "flag-gy") + static let flagHk = ImageAsset(name: "flag-hk") + static let flagHm = ImageAsset(name: "flag-hm") + static let flagHn = ImageAsset(name: "flag-hn") + static let flagHr = ImageAsset(name: "flag-hr") + static let flagHt = ImageAsset(name: "flag-ht") + static let flagHu = ImageAsset(name: "flag-hu") + static let flagId = ImageAsset(name: "flag-id") + static let flagIe = ImageAsset(name: "flag-ie") + static let flagIl = ImageAsset(name: "flag-il") + static let flagIm = ImageAsset(name: "flag-im") + static let flagIn = ImageAsset(name: "flag-in") + static let flagIo = ImageAsset(name: "flag-io") + static let flagIq = ImageAsset(name: "flag-iq") + static let flagIr = ImageAsset(name: "flag-ir") + static let flagIs = ImageAsset(name: "flag-is") + static let flagIt = ImageAsset(name: "flag-it") + static let flagJe = ImageAsset(name: "flag-je") + static let flagJm = ImageAsset(name: "flag-jm") + static let flagJo = ImageAsset(name: "flag-jo") + static let flagJp = ImageAsset(name: "flag-jp") + static let flagKe = ImageAsset(name: "flag-ke") + static let flagKg = ImageAsset(name: "flag-kg") + static let flagKh = ImageAsset(name: "flag-kh") + static let flagKi = ImageAsset(name: "flag-ki") + static let flagKm = ImageAsset(name: "flag-km") + static let flagKn = ImageAsset(name: "flag-kn") + static let flagKp = ImageAsset(name: "flag-kp") + static let flagKr = ImageAsset(name: "flag-kr") + static let flagKw = ImageAsset(name: "flag-kw") + static let flagKy = ImageAsset(name: "flag-ky") + static let flagKz = ImageAsset(name: "flag-kz") + static let flagLa = ImageAsset(name: "flag-la") + static let flagLb = ImageAsset(name: "flag-lb") + static let flagLc = ImageAsset(name: "flag-lc") + static let flagLi = ImageAsset(name: "flag-li") + static let flagLk = ImageAsset(name: "flag-lk") + static let flagLr = ImageAsset(name: "flag-lr") + static let flagLs = ImageAsset(name: "flag-ls") + static let flagLt = ImageAsset(name: "flag-lt") + static let flagLu = ImageAsset(name: "flag-lu") + static let flagLv = ImageAsset(name: "flag-lv") + static let flagLy = ImageAsset(name: "flag-ly") + static let flagMa = ImageAsset(name: "flag-ma") + static let flagMc = ImageAsset(name: "flag-mc") + static let flagMd = ImageAsset(name: "flag-md") + static let flagMe = ImageAsset(name: "flag-me") + static let flagMf = ImageAsset(name: "flag-mf") + static let flagMg = ImageAsset(name: "flag-mg") + static let flagMh = ImageAsset(name: "flag-mh") + static let flagMk = ImageAsset(name: "flag-mk") + static let flagMl = ImageAsset(name: "flag-ml") + static let flagMm = ImageAsset(name: "flag-mm") + static let flagMn = ImageAsset(name: "flag-mn") + static let flagMo = ImageAsset(name: "flag-mo") + static let flagMp = ImageAsset(name: "flag-mp") + static let flagMq = ImageAsset(name: "flag-mq") + static let flagMr = ImageAsset(name: "flag-mr") + static let flagMs = ImageAsset(name: "flag-ms") + static let flagMt = ImageAsset(name: "flag-mt") + static let flagMu = ImageAsset(name: "flag-mu") + static let flagMv = ImageAsset(name: "flag-mv") + static let flagMw = ImageAsset(name: "flag-mw") + static let flagMx = ImageAsset(name: "flag-mx") + static let flagMy = ImageAsset(name: "flag-my") + static let flagMz = ImageAsset(name: "flag-mz") + static let flagNa = ImageAsset(name: "flag-na") + static let flagNc = ImageAsset(name: "flag-nc") + static let flagNe = ImageAsset(name: "flag-ne") + static let flagNf = ImageAsset(name: "flag-nf") + static let flagNg = ImageAsset(name: "flag-ng") + static let flagNi = ImageAsset(name: "flag-ni") + static let flagNl = ImageAsset(name: "flag-nl") + static let flagNo = ImageAsset(name: "flag-no") + static let flagNp = ImageAsset(name: "flag-np") + static let flagNr = ImageAsset(name: "flag-nr") + static let flagNu = ImageAsset(name: "flag-nu") + static let flagNz = ImageAsset(name: "flag-nz") + static let flagOm = ImageAsset(name: "flag-om") + static let flagPa = ImageAsset(name: "flag-pa") + static let flagPe = ImageAsset(name: "flag-pe") + static let flagPf = ImageAsset(name: "flag-pf") + static let flagPg = ImageAsset(name: "flag-pg") + static let flagPh = ImageAsset(name: "flag-ph") + static let flagPk = ImageAsset(name: "flag-pk") + static let flagPl = ImageAsset(name: "flag-pl") + static let flagPm = ImageAsset(name: "flag-pm") + static let flagPn = ImageAsset(name: "flag-pn") + static let flagPr = ImageAsset(name: "flag-pr") + static let flagPs = ImageAsset(name: "flag-ps") + static let flagPt = ImageAsset(name: "flag-pt") + static let flagPw = ImageAsset(name: "flag-pw") + static let flagPy = ImageAsset(name: "flag-py") + static let flagQa = ImageAsset(name: "flag-qa") + static let flagRe = ImageAsset(name: "flag-re") + static let flagRo = ImageAsset(name: "flag-ro") + static let flagRs = ImageAsset(name: "flag-rs") + static let flagRu = ImageAsset(name: "flag-ru") + static let flagRw = ImageAsset(name: "flag-rw") + static let flagSa = ImageAsset(name: "flag-sa") + static let flagSb = ImageAsset(name: "flag-sb") + static let flagSc = ImageAsset(name: "flag-sc") + static let flagSd = ImageAsset(name: "flag-sd") + static let flagSe = ImageAsset(name: "flag-se") + static let flagSg = ImageAsset(name: "flag-sg") + static let flagSh = ImageAsset(name: "flag-sh") + static let flagSi = ImageAsset(name: "flag-si") + static let flagSj = ImageAsset(name: "flag-sj") + static let flagSk = ImageAsset(name: "flag-sk") + static let flagSl = ImageAsset(name: "flag-sl") + static let flagSm = ImageAsset(name: "flag-sm") + static let flagSn = ImageAsset(name: "flag-sn") + static let flagSo = ImageAsset(name: "flag-so") + static let flagSr = ImageAsset(name: "flag-sr") + static let flagSs = ImageAsset(name: "flag-ss") + static let flagSt = ImageAsset(name: "flag-st") + static let flagSv = ImageAsset(name: "flag-sv") + static let flagSx = ImageAsset(name: "flag-sx") + static let flagSy = ImageAsset(name: "flag-sy") + static let flagSz = ImageAsset(name: "flag-sz") + static let flagTc = ImageAsset(name: "flag-tc") + static let flagTd = ImageAsset(name: "flag-td") + static let flagTf = ImageAsset(name: "flag-tf") + static let flagTg = ImageAsset(name: "flag-tg") + static let flagTh = ImageAsset(name: "flag-th") + static let flagTj = ImageAsset(name: "flag-tj") + static let flagTk = ImageAsset(name: "flag-tk") + static let flagTl = ImageAsset(name: "flag-tl") + static let flagTm = ImageAsset(name: "flag-tm") + static let flagTn = ImageAsset(name: "flag-tn") + static let flagTo = ImageAsset(name: "flag-to") + static let flagTr = ImageAsset(name: "flag-tr") + static let flagTt = ImageAsset(name: "flag-tt") + static let flagTv = ImageAsset(name: "flag-tv") + static let flagTw = ImageAsset(name: "flag-tw") + static let flagTz = ImageAsset(name: "flag-tz") + static let flagUa = ImageAsset(name: "flag-ua") + static let flagUg = ImageAsset(name: "flag-ug") + static let flagUm = ImageAsset(name: "flag-um") + static let flagUn = ImageAsset(name: "flag-un") + static let flagUniversal = ImageAsset(name: "flag-universal") + static let flagUs = ImageAsset(name: "flag-us") + static let flagUy = ImageAsset(name: "flag-uy") + static let flagUz = ImageAsset(name: "flag-uz") + static let flagVa = ImageAsset(name: "flag-va") + static let flagVc = ImageAsset(name: "flag-vc") + static let flagVe = ImageAsset(name: "flag-ve") + static let flagVg = ImageAsset(name: "flag-vg") + static let flagVi = ImageAsset(name: "flag-vi") + static let flagVn = ImageAsset(name: "flag-vn") + static let flagVu = ImageAsset(name: "flag-vu") + static let flagWf = ImageAsset(name: "flag-wf") + static let flagWs = ImageAsset(name: "flag-ws") + static let flagYe = ImageAsset(name: "flag-ye") + static let flagYt = ImageAsset(name: "flag-yt") + static let flagZa = ImageAsset(name: "flag-za") + static let flagZm = ImageAsset(name: "flag-zm") + static let flagZw = ImageAsset(name: "flag-zw") } - enum Tiles { - enum ConnectionTile { - static let iconAuthentication = ImageAsset(name: "icon-authentication") - static let iconEncryption = ImageAsset(name: "icon-encryption") - static let iconHandshake = ImageAsset(name: "icon-handshake") - static let iconPort = ImageAsset(name: "icon-port") - static let iconProtocol = ImageAsset(name: "icon-protocol") - static let iconSocket = ImageAsset(name: "icon-socket") + static let icon3dtConnect = ImageAsset(name: "icon-3dt-connect") + static let icon3dtDisconnect = ImageAsset(name: "icon-3dt-disconnect") + static let icon3dtSelectRegion = ImageAsset(name: "icon-3dt-select-region") + static let iconAbout1 = ImageAsset(name: "icon-about-1") + static let iconAccount = ImageAsset(name: "icon-account") + static let iconAdd = ImageAsset(name: "icon-add") + static let iconAlert = ImageAsset(name: "icon-alert") + static let iconAutomation = ImageAsset(name: "icon-automation") + static let iconClose = ImageAsset(name: "icon-close") + static let iconContact = ImageAsset(name: "icon-contact") + static let iconDip = ImageAsset(name: "icon-dip") + static let iconGeneral = ImageAsset(name: "icon-general") + static let iconGeoDarkSelected = ImageAsset(name: "icon-geo-dark-selected") + static let iconGeoDark = ImageAsset(name: "icon-geo-dark") + static let iconGeoSelected = ImageAsset(name: "icon-geo-selected") + static let iconGeo = ImageAsset(name: "icon-geo") + static let iconHomepage = ImageAsset(name: "icon-homepage") + static let iconLogout = ImageAsset(name: "icon-logout") + static let iconNetwork = ImageAsset(name: "icon-network") + static let iconPrivacy1 = ImageAsset(name: "icon-privacy-1") + static let iconProtocols = ImageAsset(name: "icon-protocols") + static let iconRegion = ImageAsset(name: "icon-region") + static let iconRemove = ImageAsset(name: "icon-remove") + static let iconSettings = ImageAsset(name: "icon-settings") + static let iconTrashDark = ImageAsset(name: "icon-trash-dark") + static let iconTrash = ImageAsset(name: "icon-trash") + static let iconWarning = ImageAsset(name: "icon-warning") + static let iconWifi = ImageAsset(name: "icon-wifi") + static let iconmenuAbout = ImageAsset(name: "iconmenu-about") + static let iconmenuPrivacy = ImageAsset(name: "iconmenu-privacy") + static let imageContentBlocker = ImageAsset(name: "image-content-blocker") + static let imagePurchaseSuccess = ImageAsset(name: "image-purchase-success") + static let imageRobot = ImageAsset(name: "image-robot") + static let imageVpnAllow = ImageAsset(name: "image-vpn-allow") + static let itemMenu = ImageAsset(name: "item-menu") + static let navLogoWhite = ImageAsset(name: "nav-logo-white") + static let navLogo = ImageAsset(name: "nav-logo") + static let offlineServerIcon = ImageAsset(name: "offline-server-icon") + static let shareIcon = ImageAsset(name: "share-icon") + + // swiftlint:disable trailing_comma + static let allColors: [ColorAsset] = [ + ] + static let allImages: [ImageAsset] = [ + Cards.WireGuard.wgBackgroundDark, + Cards.WireGuard.wgBackgroundLight, + Cards.WireGuard.wgMain, + Piax.DarkMap.darkMap, + Piax.Dashboard.vpnButton, + Piax.Global.browserDarkInactive, + Piax.Global.browserLightInactive, + Piax.Global.dragDropIndicatorDark, + Piax.Global.dragDropIndicatorLight, + Piax.Global.eyeActiveDark, + Piax.Global.eyeActiveLight, + Piax.Global.eyeInactiveDark, + Piax.Global.eyeInactiveLight, + Piax.Global.favoriteGreen, + Piax.Global.favoriteSelected, + Piax.Global.favoriteUnselectedDark, + Piax.Global.favoriteUnselected, + Piax.Global.iconBack, + Piax.Global.iconEditTile, + Piax.Global.iconFilter, + Piax.Global.killswitchDarkActive, + Piax.Global.killswitchDarkInactive, + Piax.Global.killswitchLightInactive, + Piax.Global.nmtDarkActive, + Piax.Global.nmtDarkInactive, + Piax.Global.nmtLightActive, + Piax.Global.nmtLightInactive, + Piax.Global.regionSelected, + Piax.Global.themeDarkActive, + Piax.Global.themeDarkInactive, + Piax.Global.themeLightActive, + Piax.Global.themeLightInactive, + Piax.Global.trustedDarkIcon, + Piax.Global.trustedLightIcon, + Piax.Global.untrustedDarkIcon, + Piax.Global.untrustedLightIcon, + Piax.Nmt.iconAddRule, + Piax.Nmt.iconCustomWifiConnect, + Piax.Nmt.iconCustomWifiDisconnect, + Piax.Nmt.iconCustomWifiRetain, + Piax.Nmt.iconDisconnect, + Piax.Nmt.iconMobileDataConnect, + Piax.Nmt.iconMobileDataDisconnect, + Piax.Nmt.iconMobileDataRetain, + Piax.Nmt.iconNmtConnect, + Piax.Nmt.iconNmtWifi, + Piax.Nmt.iconOpenWifiConnect, + Piax.Nmt.iconOpenWifiDisconnect, + Piax.Nmt.iconOpenWifiRetain, + Piax.Nmt.iconOptions, + Piax.Nmt.iconRetain, + Piax.Nmt.iconSecureWifiConnect, + Piax.Nmt.iconSecureWifiDisconnect, + Piax.Nmt.iconSecureWifiRetain, + Piax.Nmt.iconSelect, + Piax.Regions.noResultsDark, + Piax.Regions.noResultsLight, + Piax.Settings.iconAbout, + Piax.Settings.iconAutomation, + Piax.Settings.iconGeneral, + Piax.Settings.iconNetwork, + Piax.Settings.iconPrivacy, + Piax.Settings.iconProtocols, + Piax.Splash.splash, + Piax.Tiles.ConnectionTile.iconAuthentication, + Piax.Tiles.ConnectionTile.iconEncryption, + Piax.Tiles.ConnectionTile.iconHandshake, + Piax.Tiles.ConnectionTile.iconPort, + Piax.Tiles.ConnectionTile.iconProtocol, + Piax.Tiles.ConnectionTile.iconSocket, + Piax.Tiles.ipTriangle, + Piax.Tiles.openTileDetails, + Piax.Tiles.quickConnectPlaceholderDark, + Piax.Tiles.quickConnectPlaceholderLight, + accessoryExpire, + accessorySelected, + buttonDown, + buttonUp, + copyIcon, + dipBadgeDark, + dipBadgeLight, + Flags.flagAd, + Flags.flagAe, + Flags.flagAf, + Flags.flagAg, + Flags.flagAi, + Flags.flagAl, + Flags.flagAm, + Flags.flagAo, + Flags.flagAq, + Flags.flagAr, + Flags.flagAs, + Flags.flagAt, + Flags.flagAu, + Flags.flagAw, + Flags.flagAx, + Flags.flagAz, + Flags.flagBa, + Flags.flagBb, + Flags.flagBd, + Flags.flagBe, + Flags.flagBf, + Flags.flagBg, + Flags.flagBh, + Flags.flagBi, + Flags.flagBj, + Flags.flagBl, + Flags.flagBm, + Flags.flagBn, + Flags.flagBo, + Flags.flagBq, + Flags.flagBr, + Flags.flagBs, + Flags.flagBt, + Flags.flagBv, + Flags.flagBw, + Flags.flagBy, + Flags.flagBz, + Flags.flagCa, + Flags.flagCc, + Flags.flagCd, + Flags.flagCf, + Flags.flagCg, + Flags.flagCh, + Flags.flagCi, + Flags.flagCk, + Flags.flagCl, + Flags.flagCm, + Flags.flagCn, + Flags.flagCo, + Flags.flagCr, + Flags.flagCu, + Flags.flagCv, + Flags.flagCw, + Flags.flagCx, + Flags.flagCy, + Flags.flagCz, + Flags.flagDe, + Flags.flagDj, + Flags.flagDk, + Flags.flagDm, + Flags.flagDo, + Flags.flagDz, + Flags.flagEc, + Flags.flagEe, + Flags.flagEg, + Flags.flagEh, + Flags.flagEr, + Flags.flagEsCt, + Flags.flagEs, + Flags.flagEt, + Flags.flagEu, + Flags.flagFi, + Flags.flagFj, + Flags.flagFk, + Flags.flagFm, + Flags.flagFo, + Flags.flagFr, + Flags.flagGa, + Flags.flagGbEng, + Flags.flagGbNir, + Flags.flagGbSct, + Flags.flagGbWls, + Flags.flagGb, + Flags.flagGd, + Flags.flagGe, + Flags.flagGf, + Flags.flagGg, + Flags.flagGh, + Flags.flagGi, + Flags.flagGl, + Flags.flagGm, + Flags.flagGn, + Flags.flagGp, + Flags.flagGq, + Flags.flagGr, + Flags.flagGs, + Flags.flagGt, + Flags.flagGu, + Flags.flagGw, + Flags.flagGy, + Flags.flagHk, + Flags.flagHm, + Flags.flagHn, + Flags.flagHr, + Flags.flagHt, + Flags.flagHu, + Flags.flagId, + Flags.flagIe, + Flags.flagIl, + Flags.flagIm, + Flags.flagIn, + Flags.flagIo, + Flags.flagIq, + Flags.flagIr, + Flags.flagIs, + Flags.flagIt, + Flags.flagJe, + Flags.flagJm, + Flags.flagJo, + Flags.flagJp, + Flags.flagKe, + Flags.flagKg, + Flags.flagKh, + Flags.flagKi, + Flags.flagKm, + Flags.flagKn, + Flags.flagKp, + Flags.flagKr, + Flags.flagKw, + Flags.flagKy, + Flags.flagKz, + Flags.flagLa, + Flags.flagLb, + Flags.flagLc, + Flags.flagLi, + Flags.flagLk, + Flags.flagLr, + Flags.flagLs, + Flags.flagLt, + Flags.flagLu, + Flags.flagLv, + Flags.flagLy, + Flags.flagMa, + Flags.flagMc, + Flags.flagMd, + Flags.flagMe, + Flags.flagMf, + Flags.flagMg, + Flags.flagMh, + Flags.flagMk, + Flags.flagMl, + Flags.flagMm, + Flags.flagMn, + Flags.flagMo, + Flags.flagMp, + Flags.flagMq, + Flags.flagMr, + Flags.flagMs, + Flags.flagMt, + Flags.flagMu, + Flags.flagMv, + Flags.flagMw, + Flags.flagMx, + Flags.flagMy, + Flags.flagMz, + Flags.flagNa, + Flags.flagNc, + Flags.flagNe, + Flags.flagNf, + Flags.flagNg, + Flags.flagNi, + Flags.flagNl, + Flags.flagNo, + Flags.flagNp, + Flags.flagNr, + Flags.flagNu, + Flags.flagNz, + Flags.flagOm, + Flags.flagPa, + Flags.flagPe, + Flags.flagPf, + Flags.flagPg, + Flags.flagPh, + Flags.flagPk, + Flags.flagPl, + Flags.flagPm, + Flags.flagPn, + Flags.flagPr, + Flags.flagPs, + Flags.flagPt, + Flags.flagPw, + Flags.flagPy, + Flags.flagQa, + Flags.flagRe, + Flags.flagRo, + Flags.flagRs, + Flags.flagRu, + Flags.flagRw, + Flags.flagSa, + Flags.flagSb, + Flags.flagSc, + Flags.flagSd, + Flags.flagSe, + Flags.flagSg, + Flags.flagSh, + Flags.flagSi, + Flags.flagSj, + Flags.flagSk, + Flags.flagSl, + Flags.flagSm, + Flags.flagSn, + Flags.flagSo, + Flags.flagSr, + Flags.flagSs, + Flags.flagSt, + Flags.flagSv, + Flags.flagSx, + Flags.flagSy, + Flags.flagSz, + Flags.flagTc, + Flags.flagTd, + Flags.flagTf, + Flags.flagTg, + Flags.flagTh, + Flags.flagTj, + Flags.flagTk, + Flags.flagTl, + Flags.flagTm, + Flags.flagTn, + Flags.flagTo, + Flags.flagTr, + Flags.flagTt, + Flags.flagTv, + Flags.flagTw, + Flags.flagTz, + Flags.flagUa, + Flags.flagUg, + Flags.flagUm, + Flags.flagUn, + Flags.flagUniversal, + Flags.flagUs, + Flags.flagUy, + Flags.flagUz, + Flags.flagVa, + Flags.flagVc, + Flags.flagVe, + Flags.flagVg, + Flags.flagVi, + Flags.flagVn, + Flags.flagVu, + Flags.flagWf, + Flags.flagWs, + Flags.flagYe, + Flags.flagYt, + Flags.flagZa, + Flags.flagZm, + Flags.flagZw, + icon3dtConnect, + icon3dtDisconnect, + icon3dtSelectRegion, + iconAbout1, + iconAccount, + iconAdd, + iconAlert, + iconAutomation, + iconClose, + iconContact, + iconDip, + iconGeneral, + iconGeoDarkSelected, + iconGeoDark, + iconGeoSelected, + iconGeo, + iconHomepage, + iconLogout, + iconNetwork, + iconPrivacy1, + iconProtocols, + iconRegion, + iconRemove, + iconSettings, + iconTrashDark, + iconTrash, + iconWarning, + iconWifi, + iconmenuAbout, + iconmenuPrivacy, + imageContentBlocker, + imagePurchaseSuccess, + imageRobot, + imageVpnAllow, + itemMenu, + navLogoWhite, + navLogo, + offlineServerIcon, + shareIcon, + ] + // swiftlint:enable trailing_comma + @available(*, deprecated, renamed: "allImages") + static let allValues: [AssetType] = allImages + } + enum Ui { + enum Piax { + enum Global { + static let centeredDarkMap = ImageAsset(name: "centered-dark-map") + static let centeredLightMap = ImageAsset(name: "centered-light-map") + static let computerIcon = ImageAsset(name: "computer-icon") + static let globeIcon = ImageAsset(name: "globe-icon") + static let iconBack = ImageAsset(name: "icon-back") + static let iconCamera = ImageAsset(name: "icon-camera") + static let iconClose = ImageAsset(name: "icon-close") + static let iconWarning = ImageAsset(name: "icon-warning") + static let pagecontrolSelectedDot = ImageAsset(name: "pagecontrol-selected-dot") + static let pagecontrolUnselectedDot = ImageAsset(name: "pagecontrol-unselected-dot") + static let planSelected = ImageAsset(name: "plan-selected") + static let planUnselected = ImageAsset(name: "plan-unselected") + static let scrollableMapDark = ImageAsset(name: "scrollableMap-dark") + static let scrollableMapLight = ImageAsset(name: "scrollableMap-light") + static let shieldIcon = ImageAsset(name: "shield-icon") } - static let ipTriangle = ImageAsset(name: "ip-triangle") - static let openTileDetails = ImageAsset(name: "open-tile-details") - static let quickConnectPlaceholderDark = ImageAsset(name: "quick-connect-placeholder-dark") - static let quickConnectPlaceholderLight = ImageAsset(name: "quick-connect-placeholder-light") } - } - static let accessoryExpire = ImageAsset(name: "accessory-expire") - static let accessorySelected = ImageAsset(name: "accessory-selected") - static let buttonDown = ImageAsset(name: "button-down") - static let buttonUp = ImageAsset(name: "button-up") - static let copyIcon = ImageAsset(name: "copy-icon") - static let dipBadgeDark = ImageAsset(name: "dip-badge-dark") - static let dipBadgeLight = ImageAsset(name: "dip-badge-light") - enum Flags { - static let flagAd = ImageAsset(name: "flag-ad") - static let flagAe = ImageAsset(name: "flag-ae") - static let flagAf = ImageAsset(name: "flag-af") - static let flagAg = ImageAsset(name: "flag-ag") - static let flagAi = ImageAsset(name: "flag-ai") - static let flagAl = ImageAsset(name: "flag-al") - static let flagAm = ImageAsset(name: "flag-am") - static let flagAo = ImageAsset(name: "flag-ao") - static let flagAq = ImageAsset(name: "flag-aq") - static let flagAr = ImageAsset(name: "flag-ar") - static let flagAs = ImageAsset(name: "flag-as") - static let flagAt = ImageAsset(name: "flag-at") - static let flagAu = ImageAsset(name: "flag-au") - static let flagAw = ImageAsset(name: "flag-aw") - static let flagAx = ImageAsset(name: "flag-ax") - static let flagAz = ImageAsset(name: "flag-az") - static let flagBa = ImageAsset(name: "flag-ba") - static let flagBb = ImageAsset(name: "flag-bb") - static let flagBd = ImageAsset(name: "flag-bd") - static let flagBe = ImageAsset(name: "flag-be") - static let flagBf = ImageAsset(name: "flag-bf") - static let flagBg = ImageAsset(name: "flag-bg") - static let flagBh = ImageAsset(name: "flag-bh") - static let flagBi = ImageAsset(name: "flag-bi") - static let flagBj = ImageAsset(name: "flag-bj") - static let flagBl = ImageAsset(name: "flag-bl") - static let flagBm = ImageAsset(name: "flag-bm") - static let flagBn = ImageAsset(name: "flag-bn") - static let flagBo = ImageAsset(name: "flag-bo") - static let flagBq = ImageAsset(name: "flag-bq") - static let flagBr = ImageAsset(name: "flag-br") - static let flagBs = ImageAsset(name: "flag-bs") - static let flagBt = ImageAsset(name: "flag-bt") - static let flagBv = ImageAsset(name: "flag-bv") - static let flagBw = ImageAsset(name: "flag-bw") - static let flagBy = ImageAsset(name: "flag-by") - static let flagBz = ImageAsset(name: "flag-bz") - static let flagCa = ImageAsset(name: "flag-ca") - static let flagCc = ImageAsset(name: "flag-cc") - static let flagCd = ImageAsset(name: "flag-cd") - static let flagCf = ImageAsset(name: "flag-cf") - static let flagCg = ImageAsset(name: "flag-cg") - static let flagCh = ImageAsset(name: "flag-ch") - static let flagCi = ImageAsset(name: "flag-ci") - static let flagCk = ImageAsset(name: "flag-ck") - static let flagCl = ImageAsset(name: "flag-cl") - static let flagCm = ImageAsset(name: "flag-cm") - static let flagCn = ImageAsset(name: "flag-cn") - static let flagCo = ImageAsset(name: "flag-co") - static let flagCr = ImageAsset(name: "flag-cr") - static let flagCu = ImageAsset(name: "flag-cu") - static let flagCv = ImageAsset(name: "flag-cv") - static let flagCw = ImageAsset(name: "flag-cw") - static let flagCx = ImageAsset(name: "flag-cx") - static let flagCy = ImageAsset(name: "flag-cy") - static let flagCz = ImageAsset(name: "flag-cz") - static let flagDe = ImageAsset(name: "flag-de") - static let flagDj = ImageAsset(name: "flag-dj") - static let flagDk = ImageAsset(name: "flag-dk") - static let flagDm = ImageAsset(name: "flag-dm") - static let flagDo = ImageAsset(name: "flag-do") - static let flagDz = ImageAsset(name: "flag-dz") - static let flagEc = ImageAsset(name: "flag-ec") - static let flagEe = ImageAsset(name: "flag-ee") - static let flagEg = ImageAsset(name: "flag-eg") - static let flagEh = ImageAsset(name: "flag-eh") - static let flagEr = ImageAsset(name: "flag-er") - static let flagEsCt = ImageAsset(name: "flag-es-ct") - static let flagEs = ImageAsset(name: "flag-es") - static let flagEt = ImageAsset(name: "flag-et") - static let flagEu = ImageAsset(name: "flag-eu") - static let flagFi = ImageAsset(name: "flag-fi") - static let flagFj = ImageAsset(name: "flag-fj") - static let flagFk = ImageAsset(name: "flag-fk") - static let flagFm = ImageAsset(name: "flag-fm") - static let flagFo = ImageAsset(name: "flag-fo") - static let flagFr = ImageAsset(name: "flag-fr") - static let flagGa = ImageAsset(name: "flag-ga") - static let flagGbEng = ImageAsset(name: "flag-gb-eng") - static let flagGbNir = ImageAsset(name: "flag-gb-nir") - static let flagGbSct = ImageAsset(name: "flag-gb-sct") - static let flagGbWls = ImageAsset(name: "flag-gb-wls") - static let flagGb = ImageAsset(name: "flag-gb") - static let flagGd = ImageAsset(name: "flag-gd") - static let flagGe = ImageAsset(name: "flag-ge") - static let flagGf = ImageAsset(name: "flag-gf") - static let flagGg = ImageAsset(name: "flag-gg") - static let flagGh = ImageAsset(name: "flag-gh") - static let flagGi = ImageAsset(name: "flag-gi") - static let flagGl = ImageAsset(name: "flag-gl") - static let flagGm = ImageAsset(name: "flag-gm") - static let flagGn = ImageAsset(name: "flag-gn") - static let flagGp = ImageAsset(name: "flag-gp") - static let flagGq = ImageAsset(name: "flag-gq") - static let flagGr = ImageAsset(name: "flag-gr") - static let flagGs = ImageAsset(name: "flag-gs") - static let flagGt = ImageAsset(name: "flag-gt") - static let flagGu = ImageAsset(name: "flag-gu") - static let flagGw = ImageAsset(name: "flag-gw") - static let flagGy = ImageAsset(name: "flag-gy") - static let flagHk = ImageAsset(name: "flag-hk") - static let flagHm = ImageAsset(name: "flag-hm") - static let flagHn = ImageAsset(name: "flag-hn") - static let flagHr = ImageAsset(name: "flag-hr") - static let flagHt = ImageAsset(name: "flag-ht") - static let flagHu = ImageAsset(name: "flag-hu") - static let flagId = ImageAsset(name: "flag-id") - static let flagIe = ImageAsset(name: "flag-ie") - static let flagIl = ImageAsset(name: "flag-il") - static let flagIm = ImageAsset(name: "flag-im") - static let flagIn = ImageAsset(name: "flag-in") - static let flagIo = ImageAsset(name: "flag-io") - static let flagIq = ImageAsset(name: "flag-iq") - static let flagIr = ImageAsset(name: "flag-ir") - static let flagIs = ImageAsset(name: "flag-is") - static let flagIt = ImageAsset(name: "flag-it") - static let flagJe = ImageAsset(name: "flag-je") - static let flagJm = ImageAsset(name: "flag-jm") - static let flagJo = ImageAsset(name: "flag-jo") - static let flagJp = ImageAsset(name: "flag-jp") - static let flagKe = ImageAsset(name: "flag-ke") - static let flagKg = ImageAsset(name: "flag-kg") - static let flagKh = ImageAsset(name: "flag-kh") - static let flagKi = ImageAsset(name: "flag-ki") - static let flagKm = ImageAsset(name: "flag-km") - static let flagKn = ImageAsset(name: "flag-kn") - static let flagKp = ImageAsset(name: "flag-kp") - static let flagKr = ImageAsset(name: "flag-kr") - static let flagKw = ImageAsset(name: "flag-kw") - static let flagKy = ImageAsset(name: "flag-ky") - static let flagKz = ImageAsset(name: "flag-kz") - static let flagLa = ImageAsset(name: "flag-la") - static let flagLb = ImageAsset(name: "flag-lb") - static let flagLc = ImageAsset(name: "flag-lc") - static let flagLi = ImageAsset(name: "flag-li") - static let flagLk = ImageAsset(name: "flag-lk") - static let flagLr = ImageAsset(name: "flag-lr") - static let flagLs = ImageAsset(name: "flag-ls") - static let flagLt = ImageAsset(name: "flag-lt") - static let flagLu = ImageAsset(name: "flag-lu") - static let flagLv = ImageAsset(name: "flag-lv") - static let flagLy = ImageAsset(name: "flag-ly") - static let flagMa = ImageAsset(name: "flag-ma") - static let flagMc = ImageAsset(name: "flag-mc") - static let flagMd = ImageAsset(name: "flag-md") - static let flagMe = ImageAsset(name: "flag-me") - static let flagMf = ImageAsset(name: "flag-mf") - static let flagMg = ImageAsset(name: "flag-mg") - static let flagMh = ImageAsset(name: "flag-mh") - static let flagMk = ImageAsset(name: "flag-mk") - static let flagMl = ImageAsset(name: "flag-ml") - static let flagMm = ImageAsset(name: "flag-mm") - static let flagMn = ImageAsset(name: "flag-mn") - static let flagMo = ImageAsset(name: "flag-mo") - static let flagMp = ImageAsset(name: "flag-mp") - static let flagMq = ImageAsset(name: "flag-mq") - static let flagMr = ImageAsset(name: "flag-mr") - static let flagMs = ImageAsset(name: "flag-ms") - static let flagMt = ImageAsset(name: "flag-mt") - static let flagMu = ImageAsset(name: "flag-mu") - static let flagMv = ImageAsset(name: "flag-mv") - static let flagMw = ImageAsset(name: "flag-mw") - static let flagMx = ImageAsset(name: "flag-mx") - static let flagMy = ImageAsset(name: "flag-my") - static let flagMz = ImageAsset(name: "flag-mz") - static let flagNa = ImageAsset(name: "flag-na") - static let flagNc = ImageAsset(name: "flag-nc") - static let flagNe = ImageAsset(name: "flag-ne") - static let flagNf = ImageAsset(name: "flag-nf") - static let flagNg = ImageAsset(name: "flag-ng") - static let flagNi = ImageAsset(name: "flag-ni") - static let flagNl = ImageAsset(name: "flag-nl") - static let flagNo = ImageAsset(name: "flag-no") - static let flagNp = ImageAsset(name: "flag-np") - static let flagNr = ImageAsset(name: "flag-nr") - static let flagNu = ImageAsset(name: "flag-nu") - static let flagNz = ImageAsset(name: "flag-nz") - static let flagOm = ImageAsset(name: "flag-om") - static let flagPa = ImageAsset(name: "flag-pa") - static let flagPe = ImageAsset(name: "flag-pe") - static let flagPf = ImageAsset(name: "flag-pf") - static let flagPg = ImageAsset(name: "flag-pg") - static let flagPh = ImageAsset(name: "flag-ph") - static let flagPk = ImageAsset(name: "flag-pk") - static let flagPl = ImageAsset(name: "flag-pl") - static let flagPm = ImageAsset(name: "flag-pm") - static let flagPn = ImageAsset(name: "flag-pn") - static let flagPr = ImageAsset(name: "flag-pr") - static let flagPs = ImageAsset(name: "flag-ps") - static let flagPt = ImageAsset(name: "flag-pt") - static let flagPw = ImageAsset(name: "flag-pw") - static let flagPy = ImageAsset(name: "flag-py") - static let flagQa = ImageAsset(name: "flag-qa") - static let flagRe = ImageAsset(name: "flag-re") - static let flagRo = ImageAsset(name: "flag-ro") - static let flagRs = ImageAsset(name: "flag-rs") - static let flagRu = ImageAsset(name: "flag-ru") - static let flagRw = ImageAsset(name: "flag-rw") - static let flagSa = ImageAsset(name: "flag-sa") - static let flagSb = ImageAsset(name: "flag-sb") - static let flagSc = ImageAsset(name: "flag-sc") - static let flagSd = ImageAsset(name: "flag-sd") - static let flagSe = ImageAsset(name: "flag-se") - static let flagSg = ImageAsset(name: "flag-sg") - static let flagSh = ImageAsset(name: "flag-sh") - static let flagSi = ImageAsset(name: "flag-si") - static let flagSj = ImageAsset(name: "flag-sj") - static let flagSk = ImageAsset(name: "flag-sk") - static let flagSl = ImageAsset(name: "flag-sl") - static let flagSm = ImageAsset(name: "flag-sm") - static let flagSn = ImageAsset(name: "flag-sn") - static let flagSo = ImageAsset(name: "flag-so") - static let flagSr = ImageAsset(name: "flag-sr") - static let flagSs = ImageAsset(name: "flag-ss") - static let flagSt = ImageAsset(name: "flag-st") - static let flagSv = ImageAsset(name: "flag-sv") - static let flagSx = ImageAsset(name: "flag-sx") - static let flagSy = ImageAsset(name: "flag-sy") - static let flagSz = ImageAsset(name: "flag-sz") - static let flagTc = ImageAsset(name: "flag-tc") - static let flagTd = ImageAsset(name: "flag-td") - static let flagTf = ImageAsset(name: "flag-tf") - static let flagTg = ImageAsset(name: "flag-tg") - static let flagTh = ImageAsset(name: "flag-th") - static let flagTj = ImageAsset(name: "flag-tj") - static let flagTk = ImageAsset(name: "flag-tk") - static let flagTl = ImageAsset(name: "flag-tl") - static let flagTm = ImageAsset(name: "flag-tm") - static let flagTn = ImageAsset(name: "flag-tn") - static let flagTo = ImageAsset(name: "flag-to") - static let flagTr = ImageAsset(name: "flag-tr") - static let flagTt = ImageAsset(name: "flag-tt") - static let flagTv = ImageAsset(name: "flag-tv") - static let flagTw = ImageAsset(name: "flag-tw") - static let flagTz = ImageAsset(name: "flag-tz") - static let flagUa = ImageAsset(name: "flag-ua") - static let flagUg = ImageAsset(name: "flag-ug") - static let flagUm = ImageAsset(name: "flag-um") - static let flagUn = ImageAsset(name: "flag-un") - static let flagUniversal = ImageAsset(name: "flag-universal") - static let flagUs = ImageAsset(name: "flag-us") - static let flagUy = ImageAsset(name: "flag-uy") - static let flagUz = ImageAsset(name: "flag-uz") - static let flagVa = ImageAsset(name: "flag-va") - static let flagVc = ImageAsset(name: "flag-vc") - static let flagVe = ImageAsset(name: "flag-ve") - static let flagVg = ImageAsset(name: "flag-vg") - static let flagVi = ImageAsset(name: "flag-vi") - static let flagVn = ImageAsset(name: "flag-vn") - static let flagVu = ImageAsset(name: "flag-vu") - static let flagWf = ImageAsset(name: "flag-wf") - static let flagWs = ImageAsset(name: "flag-ws") - static let flagYe = ImageAsset(name: "flag-ye") - static let flagYt = ImageAsset(name: "flag-yt") - static let flagZa = ImageAsset(name: "flag-za") - static let flagZm = ImageAsset(name: "flag-zm") - static let flagZw = ImageAsset(name: "flag-zw") - } - static let icon3dtConnect = ImageAsset(name: "icon-3dt-connect") - static let icon3dtDisconnect = ImageAsset(name: "icon-3dt-disconnect") - static let icon3dtSelectRegion = ImageAsset(name: "icon-3dt-select-region") - static let iconAbout1 = ImageAsset(name: "icon-about-1") - static let iconAccount = ImageAsset(name: "icon-account") - static let iconAdd = ImageAsset(name: "icon-add") - static let iconAlert = ImageAsset(name: "icon-alert") - static let iconAutomation = ImageAsset(name: "icon-automation") - static let iconClose = ImageAsset(name: "icon-close") - static let iconContact = ImageAsset(name: "icon-contact") - static let iconDip = ImageAsset(name: "icon-dip") - static let iconGeneral = ImageAsset(name: "icon-general") - static let iconGeoDarkSelected = ImageAsset(name: "icon-geo-dark-selected") - static let iconGeoDark = ImageAsset(name: "icon-geo-dark") - static let iconGeoSelected = ImageAsset(name: "icon-geo-selected") - static let iconGeo = ImageAsset(name: "icon-geo") - static let iconHomepage = ImageAsset(name: "icon-homepage") - static let iconLogout = ImageAsset(name: "icon-logout") - static let iconNetwork = ImageAsset(name: "icon-network") - static let iconPrivacy1 = ImageAsset(name: "icon-privacy-1") - static let iconProtocols = ImageAsset(name: "icon-protocols") - static let iconRegion = ImageAsset(name: "icon-region") - static let iconRemove = ImageAsset(name: "icon-remove") - static let iconSettings = ImageAsset(name: "icon-settings") - static let iconTrashDark = ImageAsset(name: "icon-trash-dark") - static let iconTrash = ImageAsset(name: "icon-trash") - static let iconWarning = ImageAsset(name: "icon-warning") - static let iconWifi = ImageAsset(name: "icon-wifi") - static let iconmenuAbout = ImageAsset(name: "iconmenu-about") - static let iconmenuPrivacy = ImageAsset(name: "iconmenu-privacy") - static let imageContentBlocker = ImageAsset(name: "image-content-blocker") - static let imagePurchaseSuccess = ImageAsset(name: "image-purchase-success") - static let imageRobot = ImageAsset(name: "image-robot") - static let imageVpnAllow = ImageAsset(name: "image-vpn-allow") - static let itemMenu = ImageAsset(name: "item-menu") - static let navLogoWhite = ImageAsset(name: "nav-logo-white") - static let navLogo = ImageAsset(name: "nav-logo") - static let offlineServerIcon = ImageAsset(name: "offline-server-icon") - static let shareIcon = ImageAsset(name: "share-icon") + static let closeIcon = ImageAsset(name: "close-icon") + static let imageAccountFailed = ImageAsset(name: "image-account-failed") + static let imageDocumentConsent = ImageAsset(name: "image-document-consent") + static let imageNoInternet = ImageAsset(name: "image-no-internet") + static let imagePurchaseSuccess = ImageAsset(name: "image-purchase-success") + static let imageReceiptBackground = ImageAsset(name: "image-receipt-background") + static let imageRedeemClaimed = ImageAsset(name: "image-redeem-claimed") + static let imageRedeemInvalid = ImageAsset(name: "image-redeem-invalid") + static let imageRedeemSuccess = ImageAsset(name: "image-redeem-success") + static let imageWalkthrough1 = ImageAsset(name: "image-walkthrough-1") + static let imageWalkthrough2 = ImageAsset(name: "image-walkthrough-2") + static let imageWalkthrough3 = ImageAsset(name: "image-walkthrough-3") + static let navLogo = ImageAsset(name: "nav-logo") + static let qrCode = ImageAsset(name: "qr-code") - // swiftlint:disable trailing_comma - static let allColors: [ColorAsset] = [ - ] - static let allImages: [ImageAsset] = [ - Cards.WireGuard.wgBackgroundDark, - Cards.WireGuard.wgBackgroundLight, - Cards.WireGuard.wgMain, - Piax.DarkMap.darkMap, - Piax.Dashboard.vpnButton, - Piax.Global.browserDarkInactive, - Piax.Global.browserLightInactive, - Piax.Global.dragDropIndicatorDark, - Piax.Global.dragDropIndicatorLight, - Piax.Global.eyeActiveDark, - Piax.Global.eyeActiveLight, - Piax.Global.eyeInactiveDark, - Piax.Global.eyeInactiveLight, - Piax.Global.favoriteGreen, - Piax.Global.favoriteSelected, - Piax.Global.favoriteUnselectedDark, - Piax.Global.favoriteUnselected, - Piax.Global.iconBack, - Piax.Global.iconEditTile, - Piax.Global.iconFilter, - Piax.Global.killswitchDarkActive, - Piax.Global.killswitchDarkInactive, - Piax.Global.killswitchLightInactive, - Piax.Global.nmtDarkActive, - Piax.Global.nmtDarkInactive, - Piax.Global.nmtLightActive, - Piax.Global.nmtLightInactive, - Piax.Global.regionSelected, - Piax.Global.themeDarkActive, - Piax.Global.themeDarkInactive, - Piax.Global.themeLightActive, - Piax.Global.themeLightInactive, - Piax.Global.trustedDarkIcon, - Piax.Global.trustedLightIcon, - Piax.Global.untrustedDarkIcon, - Piax.Global.untrustedLightIcon, - Piax.Nmt.iconAddRule, - Piax.Nmt.iconCustomWifiConnect, - Piax.Nmt.iconCustomWifiDisconnect, - Piax.Nmt.iconCustomWifiRetain, - Piax.Nmt.iconDisconnect, - Piax.Nmt.iconMobileDataConnect, - Piax.Nmt.iconMobileDataDisconnect, - Piax.Nmt.iconMobileDataRetain, - Piax.Nmt.iconNmtConnect, - Piax.Nmt.iconNmtWifi, - Piax.Nmt.iconOpenWifiConnect, - Piax.Nmt.iconOpenWifiDisconnect, - Piax.Nmt.iconOpenWifiRetain, - Piax.Nmt.iconOptions, - Piax.Nmt.iconRetain, - Piax.Nmt.iconSecureWifiConnect, - Piax.Nmt.iconSecureWifiDisconnect, - Piax.Nmt.iconSecureWifiRetain, - Piax.Nmt.iconSelect, - Piax.Regions.noResultsDark, - Piax.Regions.noResultsLight, - Piax.Settings.iconAbout, - Piax.Settings.iconAutomation, - Piax.Settings.iconGeneral, - Piax.Settings.iconNetwork, - Piax.Settings.iconPrivacy, - Piax.Settings.iconProtocols, - Piax.Splash.splash, - Piax.Tiles.ConnectionTile.iconAuthentication, - Piax.Tiles.ConnectionTile.iconEncryption, - Piax.Tiles.ConnectionTile.iconHandshake, - Piax.Tiles.ConnectionTile.iconPort, - Piax.Tiles.ConnectionTile.iconProtocol, - Piax.Tiles.ConnectionTile.iconSocket, - Piax.Tiles.ipTriangle, - Piax.Tiles.openTileDetails, - Piax.Tiles.quickConnectPlaceholderDark, - Piax.Tiles.quickConnectPlaceholderLight, - accessoryExpire, - accessorySelected, - buttonDown, - buttonUp, - copyIcon, - dipBadgeDark, - dipBadgeLight, - Flags.flagAd, - Flags.flagAe, - Flags.flagAf, - Flags.flagAg, - Flags.flagAi, - Flags.flagAl, - Flags.flagAm, - Flags.flagAo, - Flags.flagAq, - Flags.flagAr, - Flags.flagAs, - Flags.flagAt, - Flags.flagAu, - Flags.flagAw, - Flags.flagAx, - Flags.flagAz, - Flags.flagBa, - Flags.flagBb, - Flags.flagBd, - Flags.flagBe, - Flags.flagBf, - Flags.flagBg, - Flags.flagBh, - Flags.flagBi, - Flags.flagBj, - Flags.flagBl, - Flags.flagBm, - Flags.flagBn, - Flags.flagBo, - Flags.flagBq, - Flags.flagBr, - Flags.flagBs, - Flags.flagBt, - Flags.flagBv, - Flags.flagBw, - Flags.flagBy, - Flags.flagBz, - Flags.flagCa, - Flags.flagCc, - Flags.flagCd, - Flags.flagCf, - Flags.flagCg, - Flags.flagCh, - Flags.flagCi, - Flags.flagCk, - Flags.flagCl, - Flags.flagCm, - Flags.flagCn, - Flags.flagCo, - Flags.flagCr, - Flags.flagCu, - Flags.flagCv, - Flags.flagCw, - Flags.flagCx, - Flags.flagCy, - Flags.flagCz, - Flags.flagDe, - Flags.flagDj, - Flags.flagDk, - Flags.flagDm, - Flags.flagDo, - Flags.flagDz, - Flags.flagEc, - Flags.flagEe, - Flags.flagEg, - Flags.flagEh, - Flags.flagEr, - Flags.flagEsCt, - Flags.flagEs, - Flags.flagEt, - Flags.flagEu, - Flags.flagFi, - Flags.flagFj, - Flags.flagFk, - Flags.flagFm, - Flags.flagFo, - Flags.flagFr, - Flags.flagGa, - Flags.flagGbEng, - Flags.flagGbNir, - Flags.flagGbSct, - Flags.flagGbWls, - Flags.flagGb, - Flags.flagGd, - Flags.flagGe, - Flags.flagGf, - Flags.flagGg, - Flags.flagGh, - Flags.flagGi, - Flags.flagGl, - Flags.flagGm, - Flags.flagGn, - Flags.flagGp, - Flags.flagGq, - Flags.flagGr, - Flags.flagGs, - Flags.flagGt, - Flags.flagGu, - Flags.flagGw, - Flags.flagGy, - Flags.flagHk, - Flags.flagHm, - Flags.flagHn, - Flags.flagHr, - Flags.flagHt, - Flags.flagHu, - Flags.flagId, - Flags.flagIe, - Flags.flagIl, - Flags.flagIm, - Flags.flagIn, - Flags.flagIo, - Flags.flagIq, - Flags.flagIr, - Flags.flagIs, - Flags.flagIt, - Flags.flagJe, - Flags.flagJm, - Flags.flagJo, - Flags.flagJp, - Flags.flagKe, - Flags.flagKg, - Flags.flagKh, - Flags.flagKi, - Flags.flagKm, - Flags.flagKn, - Flags.flagKp, - Flags.flagKr, - Flags.flagKw, - Flags.flagKy, - Flags.flagKz, - Flags.flagLa, - Flags.flagLb, - Flags.flagLc, - Flags.flagLi, - Flags.flagLk, - Flags.flagLr, - Flags.flagLs, - Flags.flagLt, - Flags.flagLu, - Flags.flagLv, - Flags.flagLy, - Flags.flagMa, - Flags.flagMc, - Flags.flagMd, - Flags.flagMe, - Flags.flagMf, - Flags.flagMg, - Flags.flagMh, - Flags.flagMk, - Flags.flagMl, - Flags.flagMm, - Flags.flagMn, - Flags.flagMo, - Flags.flagMp, - Flags.flagMq, - Flags.flagMr, - Flags.flagMs, - Flags.flagMt, - Flags.flagMu, - Flags.flagMv, - Flags.flagMw, - Flags.flagMx, - Flags.flagMy, - Flags.flagMz, - Flags.flagNa, - Flags.flagNc, - Flags.flagNe, - Flags.flagNf, - Flags.flagNg, - Flags.flagNi, - Flags.flagNl, - Flags.flagNo, - Flags.flagNp, - Flags.flagNr, - Flags.flagNu, - Flags.flagNz, - Flags.flagOm, - Flags.flagPa, - Flags.flagPe, - Flags.flagPf, - Flags.flagPg, - Flags.flagPh, - Flags.flagPk, - Flags.flagPl, - Flags.flagPm, - Flags.flagPn, - Flags.flagPr, - Flags.flagPs, - Flags.flagPt, - Flags.flagPw, - Flags.flagPy, - Flags.flagQa, - Flags.flagRe, - Flags.flagRo, - Flags.flagRs, - Flags.flagRu, - Flags.flagRw, - Flags.flagSa, - Flags.flagSb, - Flags.flagSc, - Flags.flagSd, - Flags.flagSe, - Flags.flagSg, - Flags.flagSh, - Flags.flagSi, - Flags.flagSj, - Flags.flagSk, - Flags.flagSl, - Flags.flagSm, - Flags.flagSn, - Flags.flagSo, - Flags.flagSr, - Flags.flagSs, - Flags.flagSt, - Flags.flagSv, - Flags.flagSx, - Flags.flagSy, - Flags.flagSz, - Flags.flagTc, - Flags.flagTd, - Flags.flagTf, - Flags.flagTg, - Flags.flagTh, - Flags.flagTj, - Flags.flagTk, - Flags.flagTl, - Flags.flagTm, - Flags.flagTn, - Flags.flagTo, - Flags.flagTr, - Flags.flagTt, - Flags.flagTv, - Flags.flagTw, - Flags.flagTz, - Flags.flagUa, - Flags.flagUg, - Flags.flagUm, - Flags.flagUn, - Flags.flagUniversal, - Flags.flagUs, - Flags.flagUy, - Flags.flagUz, - Flags.flagVa, - Flags.flagVc, - Flags.flagVe, - Flags.flagVg, - Flags.flagVi, - Flags.flagVn, - Flags.flagVu, - Flags.flagWf, - Flags.flagWs, - Flags.flagYe, - Flags.flagYt, - Flags.flagZa, - Flags.flagZm, - Flags.flagZw, - icon3dtConnect, - icon3dtDisconnect, - icon3dtSelectRegion, - iconAbout1, - iconAccount, - iconAdd, - iconAlert, - iconAutomation, - iconClose, - iconContact, - iconDip, - iconGeneral, - iconGeoDarkSelected, - iconGeoDark, - iconGeoSelected, - iconGeo, - iconHomepage, - iconLogout, - iconNetwork, - iconPrivacy1, - iconProtocols, - iconRegion, - iconRemove, - iconSettings, - iconTrashDark, - iconTrash, - iconWarning, - iconWifi, - iconmenuAbout, - iconmenuPrivacy, - imageContentBlocker, - imagePurchaseSuccess, - imageRobot, - imageVpnAllow, - itemMenu, - navLogoWhite, - navLogo, - offlineServerIcon, - shareIcon, - ] - // swiftlint:enable trailing_comma - @available(*, deprecated, renamed: "allImages") - static let allValues: [AssetType] = allImages + // swiftlint:disable trailing_comma + static let allColors: [ColorAsset] = [ + ] + static let allImages: [ImageAsset] = [ + Piax.Global.centeredDarkMap, + Piax.Global.centeredLightMap, + Piax.Global.computerIcon, + Piax.Global.globeIcon, + Piax.Global.iconBack, + Piax.Global.iconCamera, + Piax.Global.iconClose, + Piax.Global.iconWarning, + Piax.Global.pagecontrolSelectedDot, + Piax.Global.pagecontrolUnselectedDot, + Piax.Global.planSelected, + Piax.Global.planUnselected, + Piax.Global.scrollableMapDark, + Piax.Global.scrollableMapLight, + Piax.Global.shieldIcon, + closeIcon, + imageAccountFailed, + imageDocumentConsent, + imageNoInternet, + imagePurchaseSuccess, + imageReceiptBackground, + imageRedeemClaimed, + imageRedeemInvalid, + imageRedeemSuccess, + imageWalkthrough1, + imageWalkthrough2, + imageWalkthrough3, + navLogo, + qrCode, + ] + // swiftlint:enable trailing_comma + @available(*, deprecated, renamed: "allImages") + static let allValues: [AssetType] = allImages + } } // swiftlint:enable identifier_name line_length nesting type_body_length type_name diff --git a/PIA VPN/SwiftGen+ScenesStoryboards.swift b/PIA VPN/SwiftGen+ScenesStoryboards.swift index 5ce12dc8b..cf57be0bf 100644 --- a/PIA VPN/SwiftGen+ScenesStoryboards.swift +++ b/PIA VPN/SwiftGen+ScenesStoryboards.swift @@ -1,5 +1,5 @@ // swiftlint:disable all -// Generated using SwiftGen, by O.Halligon — https://github.com/SwiftGen/SwiftGen +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen // swiftlint:disable sorted_imports import Foundation @@ -7,23 +7,59 @@ import SideMenu import UIKit // swiftlint:disable superfluous_disable_command -// swiftlint:disable file_length +// swiftlint:disable file_length implicit_return // MARK: - Storyboard Scenes -// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name +// swiftlint:disable explicit_type_interface identifier_name line_length prefer_self_in_static_references +// swiftlint:disable type_body_length type_name internal enum StoryboardScene { internal enum Main: StoryboardType { internal static let storyboardName = "Main" internal static let initialScene = InitialSceneType(storyboard: Main.self) + internal static let confirmVPNPlanViewController = SceneType(storyboard: Main.self, identifier: "ConfirmVPNPlanViewController") + + internal static let piaCardsViewController = SceneType(storyboard: Main.self, identifier: "PIACardsViewController") + internal static let sideMenuNavigationController = SceneType(storyboard: Main.self, identifier: "SideMenuNavigationController") internal static let vpnPermissionViewController = SceneType(storyboard: Main.self, identifier: "VPNPermissionViewController") } + internal enum Signup: StoryboardType { + internal static let storyboardName = "Signup" + + internal static let initialScene = InitialSceneType(storyboard: Signup.self) + + internal static let confirmVPNPlanViewController = SceneType(storyboard: Signup.self, identifier: "ConfirmVPNPlanViewController") + + internal static let gdprViewController = SceneType(storyboard: Signup.self, identifier: "GDPRViewController") + + internal static let shareDataInformationViewController = SceneType(storyboard: Signup.self, identifier: "ShareDataInformationViewController") + + internal static let signupSuccessViewController = SceneType(storyboard: Signup.self, identifier: "SignupSuccessViewController") + } + internal enum Welcome: StoryboardType { + internal static let storyboardName = "Welcome" + + internal static let initialScene = InitialSceneType(storyboard: Welcome.self) + + internal static let getStartedViewController = SceneType(storyboard: Welcome.self, identifier: "GetStartedViewController") + + internal static let loginViewController = SceneType(storyboard: Welcome.self, identifier: "LoginViewController") + + internal static let magicLinkLoginViewController = SceneType(storyboard: Welcome.self, identifier: "MagicLinkLoginViewController") + + internal static let piaWelcomeViewController = SceneType(storyboard: Welcome.self, identifier: "PIAWelcomeViewController") + + internal static let purchaseViewController = SceneType(storyboard: Welcome.self, identifier: "PurchaseViewController") + + internal static let restoreSignupViewController = SceneType(storyboard: Welcome.self, identifier: "RestoreSignupViewController") + } } -// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name +// swiftlint:enable explicit_type_interface identifier_name line_length prefer_self_in_static_references +// swiftlint:enable type_body_length type_name // MARK: - Implementation Details @@ -34,7 +70,7 @@ internal protocol StoryboardType { internal extension StoryboardType { static var storyboard: UIStoryboard { let name = self.storyboardName - return UIStoryboard(name: name, bundle: Bundle(for: BundleToken.self)) + return UIStoryboard(name: name, bundle: BundleToken.bundle) } } @@ -49,6 +85,11 @@ internal struct SceneType { } return controller } + + @available(iOS 13.0, tvOS 13.0, *) + internal func instantiate(creator block: @escaping (NSCoder) -> T?) -> T { + return storyboard.storyboard.instantiateViewController(identifier: identifier, creator: block) + } } internal struct InitialSceneType { @@ -60,6 +101,24 @@ internal struct InitialSceneType { } return controller } + + @available(iOS 13.0, tvOS 13.0, *) + internal func instantiate(creator block: @escaping (NSCoder) -> T?) -> T { + guard let controller = storyboard.storyboard.instantiateInitialViewController(creator: block) else { + fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") + } + return controller + } } -private final class BundleToken {} +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type diff --git a/PIA VPN/SwiftGen+SeguesStoryboards.swift b/PIA VPN/SwiftGen+SeguesStoryboards.swift index 15fbae565..9e8f6f66c 100644 --- a/PIA VPN/SwiftGen+SeguesStoryboards.swift +++ b/PIA VPN/SwiftGen+SeguesStoryboards.swift @@ -3,6 +3,7 @@ // swiftlint:disable sorted_imports import Foundation +import PIALibrary import SideMenu import UIKit @@ -39,6 +40,25 @@ internal enum StoryboardSegue { case trustedNetworksSegueIdentifier = "TrustedNetworksSegueIdentifier" case unwindContentBlockerSegueIdentifier = "UnwindContentBlockerSegueIdentifier" } + internal enum Signup: String, SegueType { + case failureSegueIdentifier = "FailureSegueIdentifier" + case internetUnreachableSegueIdentifier = "InternetUnreachableSegueIdentifier" + case presentGDPRTermsSegue = "PresentGDPRTermsSegue" + case successSegueIdentifier = "SuccessSegueIdentifier" + case successShowCredentialsSegueIdentifier = "SuccessShowCredentialsSegueIdentifier" + case unwindFailureSegueIdentifier = "UnwindFailureSegueIdentifier" + case unwindInternetUnreachableSegueIdentifier = "UnwindInternetUnreachableSegueIdentifier" + } + internal enum Welcome: String, SegueType { + case expiredAccountPurchaseSegue = "ExpiredAccountPurchaseSegue" + case loginAccountSegue = "LoginAccountSegue" + case purchaseVPNPlanSegue = "PurchaseVPNPlanSegue" + case restoreLoginPurchaseSegue = "RestoreLoginPurchaseSegue" + case restorePurchaseSegue = "RestorePurchaseSegue" + case signupViaPurchaseSegue = "SignupViaPurchaseSegue" + case signupViaRecoverSegue = "SignupViaRecoverSegue" + case signupViaRestoreSegue = "SignupViaRestoreSegue" + } } // swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name diff --git a/PIA VPN/SwiftGen+Strings.swift b/PIA VPN/SwiftGen+Strings.swift index c22d791db..da14e68e3 100644 --- a/PIA VPN/SwiftGen+Strings.swift +++ b/PIA VPN/SwiftGen+Strings.swift @@ -3,1333 +3,1803 @@ import Foundation -// swiftlint:disable superfluous_disable_command file_length implicit_return +// swiftlint:disable superfluous_disable_command file_length implicit_return prefer_self_in_static_references // MARK: - Strings // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces internal enum L10n { - - internal enum About { - /// VPN by Private Internet Access - internal static let app = L10n.tr("Localizable", "about.app") - /// This program uses the following components: - internal static let intro = L10n.tr("Localizable", "about.intro") - internal enum Accessibility { - internal enum Component { - /// Tap to read full license - internal static let expand = L10n.tr("Localizable", "about.accessibility.component.expand") + internal enum Localizable { + internal enum About { + /// VPN by Private Internet Access + internal static let app = L10n.tr("Localizable", "about.app", fallback: "VPN by Private Internet Access") + /// This program uses the following components: + internal static let intro = L10n.tr("Localizable", "about.intro", fallback: "This program uses the following components:") + internal enum Accessibility { + internal enum Component { + /// Tap to read full license + internal static let expand = L10n.tr("Localizable", "about.accessibility.component.expand", fallback: "Tap to read full license") + } } } - } - - internal enum Account { - /// Delete Account - internal static let delete = L10n.tr("Localizable", "account.delete") - /// Something went wrong. Please try logging in again - internal static let unauthorized = L10n.tr("Localizable", "account.unauthorized") - internal enum Delete { - internal enum Alert { - /// Something went wrong while deleting your account, please try again later. - internal static let failureMessage = L10n.tr("Localizable", "account.delete.alert.failureMessage") - /// Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active. - internal static let message = L10n.tr("Localizable", "account.delete.alert.message") - /// Are you sure? - internal static let title = L10n.tr("Localizable", "account.delete.alert.title") + internal enum Account { + /// Delete Account + internal static let delete = L10n.tr("Localizable", "account.delete", fallback: "Delete Account") + /// Something went wrong. Please try logging in again + internal static let unauthorized = L10n.tr("Localizable", "account.unauthorized", fallback: "Something went wrong. Please try logging in again") + internal enum Delete { + internal enum Alert { + /// Something went wrong while deleting your account, please try again later. + internal static let failureMessage = L10n.tr("Localizable", "account.delete.alert.failureMessage", fallback: "Something went wrong while deleting your account, please try again later.") + /// Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active. + internal static let message = L10n.tr("Localizable", "account.delete.alert.message", fallback: "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active.") + /// Are you sure? + internal static let title = L10n.tr("Localizable", "account.delete.alert.title", fallback: "Are you sure?") + } } - } - internal enum Email { - /// Email - internal static let caption = L10n.tr("Localizable", "account.email.caption") - /// Email address - internal static let placeholder = L10n.tr("Localizable", "account.email.placeholder") - } - internal enum Error { - /// Your username or password is incorrect. - internal static let unauthorized = L10n.tr("Localizable", "account.error.unauthorized") - } - internal enum ExpiryDate { - /// Your plan has expired. - internal static let expired = L10n.tr("Localizable", "account.expiry_date.expired") - /// Your plan will expire on %@. - internal static func information(_ p1: Any) -> String { - return L10n.tr("Localizable", "account.expiry_date.information", String(describing: p1)) + internal enum Email { + /// Email + internal static let caption = L10n.tr("Localizable", "account.email.caption", fallback: "Email") + /// Email address + internal static let placeholder = L10n.tr("Localizable", "account.email.placeholder", fallback: "Email address") } - } - internal enum Other { - /// Get the Private Internet Access app for your other devices and use the above username and password to login and secure your connection. - internal static let footer = L10n.tr("Localizable", "account.other.footer") - } - internal enum Restore { - /// RESTORE PURCHASE - internal static let button = L10n.tr("Localizable", "account.restore.button") - /// If you renewed your plan but your account still says it's about to expire, you can restart the renewal from here. You will not be charged during this process. - internal static let description = L10n.tr("Localizable", "account.restore.description") - /// Restore uncredited purchase - internal static let title = L10n.tr("Localizable", "account.restore.title") - internal enum Failure { - /// No redeemable purchase was found for renewal. - internal static let message = L10n.tr("Localizable", "account.restore.failure.message") - /// Restore purchase - internal static let title = L10n.tr("Localizable", "account.restore.failure.title") + internal enum Error { + /// Your username or password is incorrect. + internal static let unauthorized = L10n.tr("Localizable", "account.error.unauthorized", fallback: "Your username or password is incorrect.") + } + internal enum ExpiryDate { + /// Your plan has expired. + internal static let expired = L10n.tr("Localizable", "account.expiry_date.expired", fallback: "Your plan has expired.") + /// Your plan will expire on %@. + internal static func information(_ p1: Any) -> String { + return L10n.tr("Localizable", "account.expiry_date.information", String(describing: p1), fallback: "Your plan will expire on %@.") + } } - } - internal enum Reveal { - /// Authenticate to reveal - internal static let prompt = L10n.tr("Localizable", "account.reveal.prompt") - } - internal enum Save { - /// Update email - internal static let item = L10n.tr("Localizable", "account.save.item") - /// Authenticate to save changes - internal static let prompt = L10n.tr("Localizable", "account.save.prompt") - /// Your email address has been saved. - internal static let success = L10n.tr("Localizable", "account.save.success") - } - internal enum Set { - internal enum Email { - /// There was an error adding email. Please try again later. - internal static let error = L10n.tr("Localizable", "account.set.email.error") + internal enum Other { + /// Get the Private Internet Access app for your other devices and use the above username and password to login and secure your connection. + internal static let footer = L10n.tr("Localizable", "account.other.footer", fallback: "Get the Private Internet Access app for your other devices and use the above username and password to login and secure your connection.") + } + internal enum Restore { + /// RESTORE PURCHASE + internal static let button = L10n.tr("Localizable", "account.restore.button", fallback: "RESTORE PURCHASE") + /// If you renewed your plan but your account still says it's about to expire, you can restart the renewal from here. You will not be charged during this process. + internal static let description = L10n.tr("Localizable", "account.restore.description", fallback: "If you renewed your plan but your account still says it's about to expire, you can restart the renewal from here. You will not be charged during this process.") + /// Restore uncredited purchase + internal static let title = L10n.tr("Localizable", "account.restore.title", fallback: "Restore uncredited purchase") + internal enum Failure { + /// No redeemable purchase was found for renewal. + internal static let message = L10n.tr("Localizable", "account.restore.failure.message", fallback: "No redeemable purchase was found for renewal.") + /// Restore purchase + internal static let title = L10n.tr("Localizable", "account.restore.failure.title", fallback: "Restore purchase") + } } - } - internal enum Subscriptions { - /// here. - internal static let linkMessage = L10n.tr("Localizable", "account.subscriptions.linkMessage") - /// You can manage your subscription from here. - internal static let message = L10n.tr("Localizable", "account.subscriptions.message") - /// Monthly plan - internal static let monthly = L10n.tr("Localizable", "account.subscriptions.monthly") - /// Trial plan - internal static let trial = L10n.tr("Localizable", "account.subscriptions.trial") - /// Yearly plan - internal static let yearly = L10n.tr("Localizable", "account.subscriptions.yearly") - internal enum Short { - /// Manage subscription - internal static let linkMessage = L10n.tr("Localizable", "account.subscriptions.short.linkMessage") - /// Manage subscription - internal static let message = L10n.tr("Localizable", "account.subscriptions.short.message") + internal enum Reveal { + /// Authenticate to reveal + internal static let prompt = L10n.tr("Localizable", "account.reveal.prompt", fallback: "Authenticate to reveal") + } + internal enum Save { + /// Update email + internal static let item = L10n.tr("Localizable", "account.save.item", fallback: "Update email") + /// Authenticate to save changes + internal static let prompt = L10n.tr("Localizable", "account.save.prompt", fallback: "Authenticate to save changes") + /// Your email address has been saved. + internal static let success = L10n.tr("Localizable", "account.save.success", fallback: "Your email address has been saved.") + } + internal enum Set { + internal enum Email { + /// There was an error adding email. Please try again later. + internal static let error = L10n.tr("Localizable", "account.set.email.error", fallback: "There was an error adding email. Please try again later.") + } } - } - internal enum Survey { - /// Want to help make PIA better? Let us know how we can improve! - /// Take The Survey - internal static let message = L10n.tr("Localizable", "account.survey.message") - /// Take The Survey - internal static let messageLink = L10n.tr("Localizable", "account.survey.messageLink") - } - internal enum Update { - internal enum Email { - internal enum Require { - internal enum Password { - /// Submit - internal static let button = L10n.tr("Localizable", "account.update.email.require.password.button") - /// For security reasons we require your PIA password to perform a change in your account. Please input your PIA password to proceed. - internal static let message = L10n.tr("Localizable", "account.update.email.require.password.message") - /// PIA Password Required - internal static let title = L10n.tr("Localizable", "account.update.email.require.password.title") + internal enum Subscriptions { + /// here. + internal static let linkMessage = L10n.tr("Localizable", "account.subscriptions.linkMessage", fallback: "here.") + /// You can manage your subscription from here. + internal static let message = L10n.tr("Localizable", "account.subscriptions.message", fallback: "You can manage your subscription from here.") + /// Monthly plan + internal static let monthly = L10n.tr("Localizable", "account.subscriptions.monthly", fallback: "Monthly plan") + /// Trial plan + internal static let trial = L10n.tr("Localizable", "account.subscriptions.trial", fallback: "Trial plan") + /// Yearly plan + internal static let yearly = L10n.tr("Localizable", "account.subscriptions.yearly", fallback: "Yearly plan") + internal enum Short { + /// Manage subscription + internal static let linkMessage = L10n.tr("Localizable", "account.subscriptions.short.linkMessage", fallback: "Manage subscription") + /// Manage subscription + internal static let message = L10n.tr("Localizable", "account.subscriptions.short.message", fallback: "Manage subscription") + } + } + internal enum Survey { + /// Want to help make PIA better? Let us know how we can improve! + /// Take The Survey + internal static let message = L10n.tr("Localizable", "account.survey.message", fallback: "Want to help make PIA better? Let us know how we can improve!\nTake The Survey") + /// Take The Survey + internal static let messageLink = L10n.tr("Localizable", "account.survey.messageLink", fallback: "Take The Survey") + } + internal enum Update { + internal enum Email { + internal enum Require { + internal enum Password { + /// Submit + internal static let button = L10n.tr("Localizable", "account.update.email.require.password.button", fallback: "Submit") + /// For security reasons we require your PIA password to perform a change in your account. Please input your PIA password to proceed. + internal static let message = L10n.tr("Localizable", "account.update.email.require.password.message", fallback: "For security reasons we require your PIA password to perform a change in your account. Please input your PIA password to proceed.") + /// PIA Password Required + internal static let title = L10n.tr("Localizable", "account.update.email.require.password.title", fallback: "PIA Password Required") + } } } } - } - internal enum Username { - /// Username - internal static let caption = L10n.tr("Localizable", "account.username.caption") - } - } - - internal enum Card { - internal enum Wireguard { - /// It's a new, more efficient VPN protocol that offers better performance, lower CPU usage and longer battery life. - internal static let description = L10n.tr("Localizable", "card.wireguard.description") - /// Try WireGuard® today! - internal static let title = L10n.tr("Localizable", "card.wireguard.title") - internal enum Cta { - /// Try WireGuard® now - internal static let activate = L10n.tr("Localizable", "card.wireguard.cta.activate") - /// Learn more - internal static let learn = L10n.tr("Localizable", "card.wireguard.cta.learn") - /// Open Settings - internal static let settings = L10n.tr("Localizable", "card.wireguard.cta.settings") + internal enum Username { + /// Username + internal static let caption = L10n.tr("Localizable", "account.username.caption", fallback: "Username") } } - } - - internal enum ContentBlocker { - /// Safari Content Blocker - internal static let title = L10n.tr("Localizable", "content_blocker.title") - internal enum Body { - /// Please note: You do not need to be connected to the VPN for this Content Blocker to work, but it will only work while browsing with Safari. - internal static let footer = L10n.tr("Localizable", "content_blocker.body.footer") - /// To enable our Content Blocker for use with Safari please go to Settings > Safari, and under General touch Content Blockers toggle on PIA VPN. - internal static let subtitle = L10n.tr("Localizable", "content_blocker.body.subtitle") - } - } - - internal enum Dashboard { - internal enum Accessibility { - internal enum Vpn { - /// VPN Connection button - internal static let button = L10n.tr("Localizable", "dashboard.accessibility.vpn.button") - internal enum Button { - /// VPN Connection button. The VPN is currently disconnected - internal static let isOff = L10n.tr("Localizable", "dashboard.accessibility.vpn.button.isOff") - /// VPN Connection button. The VPN is currently connected - internal static let isOn = L10n.tr("Localizable", "dashboard.accessibility.vpn.button.isOn") + internal enum Card { + internal enum Wireguard { + /// It's a new, more efficient VPN protocol that offers better performance, lower CPU usage and longer battery life. + internal static let description = L10n.tr("Localizable", "card.wireguard.description", fallback: "It's a new, more efficient VPN protocol that offers better performance, lower CPU usage and longer battery life.") + /// Try WireGuard® today! + internal static let title = L10n.tr("Localizable", "card.wireguard.title", fallback: "Try WireGuard® today!") + internal enum Cta { + /// Try WireGuard® now + internal static let activate = L10n.tr("Localizable", "card.wireguard.cta.activate", fallback: "Try WireGuard® now") + /// Learn more + internal static let learn = L10n.tr("Localizable", "card.wireguard.cta.learn", fallback: "Learn more") + /// Open Settings + internal static let settings = L10n.tr("Localizable", "card.wireguard.cta.settings", fallback: "Open Settings") } } } - internal enum Connection { - internal enum Ip { - /// Internet unreachable - internal static let unreachable = L10n.tr("Localizable", "dashboard.connection.ip.unreachable") - } - } internal enum ContentBlocker { - internal enum Intro { - /// This version replaces MACE with our Safari Content Blocker.\n\nCheck it out in the 'Settings' section. - internal static let message = L10n.tr("Localizable", "dashboard.content_blocker.intro.message") + /// Safari Content Blocker + internal static let title = L10n.tr("Localizable", "content_blocker.title", fallback: "Safari Content Blocker") + internal enum Body { + /// Please note: You do not need to be connected to the VPN for this Content Blocker to work, but it will only work while browsing with Safari. + internal static let footer = L10n.tr("Localizable", "content_blocker.body.footer", fallback: "Please note: You do not need to be connected to the VPN for this Content Blocker to work, but it will only work while browsing with Safari.") + /// To enable our Content Blocker for use with Safari please go to Settings > Safari, and under General touch Content Blockers toggle on PIA VPN. + internal static let subtitle = L10n.tr("Localizable", "content_blocker.body.subtitle", fallback: "To enable our Content Blocker for use with Safari please go to Settings > Safari, and under General touch Content Blockers toggle on PIA VPN.") } } - internal enum Vpn { - /// Changing region... - internal static let changingRegion = L10n.tr("Localizable", "dashboard.vpn.changing_region") - /// Connected to VPN - internal static let connected = L10n.tr("Localizable", "dashboard.vpn.connected") - /// Connecting... - internal static let connecting = L10n.tr("Localizable", "dashboard.vpn.connecting") - /// Disconnected - internal static let disconnected = L10n.tr("Localizable", "dashboard.vpn.disconnected") - /// Disconnecting... - internal static let disconnecting = L10n.tr("Localizable", "dashboard.vpn.disconnecting") - /// VPN: ON - internal static let on = L10n.tr("Localizable", "dashboard.vpn.on") - internal enum Disconnect { - /// This network is untrusted. Do you really want to disconnect the VPN? - internal static let untrusted = L10n.tr("Localizable", "dashboard.vpn.disconnect.untrusted") - } - internal enum Leakprotection { - internal enum Alert { - /// Disable Now - internal static let cta1 = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.cta1", fallback: "Disable Now") - /// Learn more - internal static let cta2 = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.cta2", fallback: "Learn more") - /// Ignore - internal static let cta3 = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.cta3", fallback: "Ignore") - /// To prevent data leaks, tap Disable Now to turn off “Allow access to devices on local network" and automatically reconnect. - internal static let message = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.message", fallback: "To prevent data leaks, tap Disable Now to turn off “Allow access to devices on local network\" and automatically reconnect.") - /// Unsecured Wi-Fi detected - internal static let title = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.title", fallback: "Unsecured Wi-Fi detected") - internal enum IKEV2 { - /// Switch now - internal static let cta1 = L10n.tr("Localizable", "dashboard.vpn.leakprotection.ikev2.alert.cta1", fallback: "Switch Now") - /// To prevent data leaks, tap Switch Now to change to the IKEv2 VPN protocol and automatically reconnect. - internal static let message = L10n.tr("Localizable", "dashboard.vpn.leakprotection.ikev2.alert.message", fallback: "To prevent data leaks, tap Switch Now to change to the IKEv2 VPN protocol and automatically reconnect.") + internal enum Dashboard { + internal enum Accessibility { + internal enum Vpn { + /// VPN Connection button + internal static let button = L10n.tr("Localizable", "dashboard.accessibility.vpn.button", fallback: "VPN Connection button") + internal enum Button { + /// VPN Connection button. The VPN is currently disconnected + internal static let isOff = L10n.tr("Localizable", "dashboard.accessibility.vpn.button.isOff", fallback: "VPN Connection button. The VPN is currently disconnected") + /// VPN Connection button. The VPN is currently connected + internal static let isOn = L10n.tr("Localizable", "dashboard.accessibility.vpn.button.isOn", fallback: "VPN Connection button. The VPN is currently connected") } } } - } - } - - internal enum Dedicated { - internal enum Ip { - /// Are you sure you want to remove the selected region? - internal static let remove = L10n.tr("Localizable", "dedicated.ip.remove") - /// Dedicated IP - internal static let title = L10n.tr("Localizable", "dedicated.ip.title") - internal enum Activate { - internal enum Button { - /// Activate - internal static let title = L10n.tr("Localizable", "dedicated.ip.activate.button.title") + internal enum Connection { + internal enum Ip { + /// Internet unreachable + internal static let unreachable = L10n.tr("Localizable", "dashboard.connection.ip.unreachable", fallback: "Internet unreachable") } } - internal enum Activation { - /// Activate your Dedicated IP by pasting your token in the form below. If you've recently purchased a dedicated IP, you can generate the token by going to the PIA website. - internal static let description = L10n.tr("Localizable", "dedicated.ip.activation.description") + internal enum ContentBlocker { + internal enum Intro { + /// This version replaces MACE with our Safari Content Blocker. + /// + /// Check it out in the 'Settings' section. + internal static let message = L10n.tr("Localizable", "dashboard.content_blocker.intro.message", fallback: "This version replaces MACE with our Safari Content Blocker.\n\nCheck it out in the 'Settings' section.") + } } - internal enum Country { - internal enum Flag { - /// Country flag for %@ - internal static func accessibility(_ p1: Any) -> String { - return L10n.tr("Localizable", "dedicated.ip.country.flag.accessibility", String(describing: p1)) + internal enum Vpn { + /// Changing region... + internal static let changingRegion = L10n.tr("Localizable", "dashboard.vpn.changing_region", fallback: "Changing region...") + /// Connected to VPN + internal static let connected = L10n.tr("Localizable", "dashboard.vpn.connected", fallback: "Connected to VPN") + /// Connecting... + internal static let connecting = L10n.tr("Localizable", "dashboard.vpn.connecting", fallback: "Connecting...") + /// Disconnected + internal static let disconnected = L10n.tr("Localizable", "dashboard.vpn.disconnected", fallback: "Disconnected") + /// Disconnecting... + internal static let disconnecting = L10n.tr("Localizable", "dashboard.vpn.disconnecting", fallback: "Disconnecting...") + /// VPN: ON + internal static let on = L10n.tr("Localizable", "dashboard.vpn.on", fallback: "VPN: ON") + internal enum Disconnect { + /// This network is untrusted. Do you really want to disconnect the VPN? + internal static let untrusted = L10n.tr("Localizable", "dashboard.vpn.disconnect.untrusted", fallback: "This network is untrusted. Do you really want to disconnect the VPN?") + } + internal enum Leakprotection { + internal enum Alert { + /// Disable Now + internal static let cta1 = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.cta1", fallback: "Disable Now") + /// Learn more + internal static let cta2 = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.cta2", fallback: "Learn more") + /// Ignore + internal static let cta3 = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.cta3", fallback: "Ignore") + /// To prevent data leaks, tap Disable Now to turn off “Allow access to devices on local network" and automatically reconnect. + internal static let message = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.message", fallback: "To prevent data leaks, tap Disable Now to turn off “Allow access to devices on local network\" and automatically reconnect.") + /// Unsecured Wi-Fi detected + internal static let title = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.title", fallback: "Unsecured Wi-Fi detected") + } + internal enum Ikev2 { + internal enum Alert { + /// Switch Now + internal static let cta1 = L10n.tr("Localizable", "dashboard.vpn.leakprotection.ikev2.alert.cta1", fallback: "Switch Now") + /// To prevent data leaks, tap Switch Now to change to the IKEv2 VPN protocol and automatically reconnect. + internal static let message = L10n.tr("Localizable", "dashboard.vpn.leakprotection.ikev2.alert.message", fallback: "To prevent data leaks, tap Switch Now to change to the IKEv2 VPN protocol and automatically reconnect.") + } } } } - internal enum Limit { - /// Secure your remote connections to any asset with a dedicated IP from a country of your choice. During your subscription, this IP will be yours and yours alone, protecting your data transfers with the strongest encryption out there. - internal static let title = L10n.tr("Localizable", "dedicated.ip.limit.title") - } - internal enum Message { - internal enum Error { - /// Too many failed token activation requests. Please try again after %@ second(s). - internal static func retryafter(_ p1: Any) -> String { - return L10n.tr("Localizable", "dedicated.ip.message.error.retryafter", String(describing: p1)) + } + internal enum Dedicated { + internal enum Ip { + /// Are you sure you want to remove the selected region? + internal static let remove = L10n.tr("Localizable", "dedicated.ip.remove", fallback: "Are you sure you want to remove the selected region?") + /// Dedicated IP + internal static let title = L10n.tr("Localizable", "dedicated.ip.title", fallback: "Dedicated IP") + internal enum Activate { + internal enum Button { + /// Activate + internal static let title = L10n.tr("Localizable", "dedicated.ip.activate.button.title", fallback: "Activate") } - /// Your token is expired. Please generate a new one from your Account page on the website. - internal static let token = L10n.tr("Localizable", "dedicated.ip.message.error.token") } - internal enum Expired { - /// Your token is expired. Please generate a new one from your Account page on the website. - internal static let token = L10n.tr("Localizable", "dedicated.ip.message.expired.token") + internal enum Activation { + /// Activate your Dedicated IP by pasting your token in the form below. If you've recently purchased a dedicated IP, you can generate the token by going to the PIA website. + internal static let description = L10n.tr("Localizable", "dedicated.ip.activation.description", fallback: "Activate your Dedicated IP by pasting your token in the form below. If you've recently purchased a dedicated IP, you can generate the token by going to the PIA website.") } - internal enum Incorrect { - /// Please make sure you have entered the token correctly - internal static let token = L10n.tr("Localizable", "dedicated.ip.message.incorrect.token") + internal enum Country { + internal enum Flag { + /// Country flag for %@ + internal static func accessibility(_ p1: Any) -> String { + return L10n.tr("Localizable", "dedicated.ip.country.flag.accessibility", String(describing: p1), fallback: "Country flag for %@") + } + } } - internal enum Invalid { - /// Your token is invalid. Please make sure you have entered the token correctly. - internal static let token = L10n.tr("Localizable", "dedicated.ip.message.invalid.token") + internal enum Limit { + /// Secure your remote connections to any asset with a dedicated IP from a country of your choice. During your subscription, this IP will be yours and yours alone, protecting your data transfers with the strongest encryption out there. + internal static let title = L10n.tr("Localizable", "dedicated.ip.limit.title", fallback: "Secure your remote connections to any asset with a dedicated IP from a country of your choice. During your subscription, this IP will be yours and yours alone, protecting your data transfers with the strongest encryption out there.") } - internal enum Ip { - /// Your dedicated IP was updated - internal static let updated = L10n.tr("Localizable", "dedicated.ip.message.ip.updated") - } - internal enum Token { - /// Your dedicated IP will expire soon. Get a new one - internal static let willexpire = L10n.tr("Localizable", "dedicated.ip.message.token.willexpire") - internal enum Willexpire { - /// Get a new one - internal static let link = L10n.tr("Localizable", "dedicated.ip.message.token.willexpire.link") + internal enum Message { + internal enum Error { + /// Too many failed token activation requests. Please try again after %@ second(s). + internal static func retryafter(_ p1: Any) -> String { + return L10n.tr("Localizable", "dedicated.ip.message.error.retryafter", String(describing: p1), fallback: "Too many failed token activation requests. Please try again after %@ second(s).") + } + /// Your token is expired. Please generate a new one from your Account page on the website. + internal static let token = L10n.tr("Localizable", "dedicated.ip.message.error.token", fallback: "Your token is expired. Please generate a new one from your Account page on the website.") + } + internal enum Expired { + /// Your token is expired. Please generate a new one from your Account page on the website. + internal static let token = L10n.tr("Localizable", "dedicated.ip.message.expired.token", fallback: "Your token is expired. Please generate a new one from your Account page on the website.") + } + internal enum Incorrect { + /// Please make sure you have entered the token correctly + internal static let token = L10n.tr("Localizable", "dedicated.ip.message.incorrect.token", fallback: "Please make sure you have entered the token correctly") + } + internal enum Invalid { + /// Your token is invalid. Please make sure you have entered the token correctly. + internal static let token = L10n.tr("Localizable", "dedicated.ip.message.invalid.token", fallback: "Your token is invalid. Please make sure you have entered the token correctly.") + } + internal enum Ip { + /// Your dedicated IP was updated + internal static let updated = L10n.tr("Localizable", "dedicated.ip.message.ip.updated", fallback: "Your dedicated IP was updated") + } + internal enum Token { + /// Your dedicated IP will expire soon. Get a new one + internal static let willexpire = L10n.tr("Localizable", "dedicated.ip.message.token.willexpire", fallback: "Your dedicated IP will expire soon. Get a new one") + internal enum Willexpire { + /// Get a new one + internal static let link = L10n.tr("Localizable", "dedicated.ip.message.token.willexpire.link", fallback: "Get a new one") + } + } + internal enum Valid { + /// Your Dedicated IP has been activated successfully. It will be available in your Region selection list. + internal static let token = L10n.tr("Localizable", "dedicated.ip.message.valid.token", fallback: "Your Dedicated IP has been activated successfully. It will be available in your Region selection list.") } } - internal enum Valid { - /// Your Dedicated IP has been activated successfully. It will be available in your Region selection list. - internal static let token = L10n.tr("Localizable", "dedicated.ip.message.valid.token") + internal enum Plural { + /// Your Dedicated IPs + internal static let title = L10n.tr("Localizable", "dedicated.ip.plural.title", fallback: "Your Dedicated IPs") } - } - internal enum Plural { - /// Your Dedicated IPs - internal static let title = L10n.tr("Localizable", "dedicated.ip.plural.title") - } - internal enum Token { - internal enum Textfield { - /// The textfield to type the Dedicated IP token - internal static let accessibility = L10n.tr("Localizable", "dedicated.ip.token.textfield.accessibility") - /// Paste in your token here - internal static let placeholder = L10n.tr("Localizable", "dedicated.ip.token.textfield.placeholder") + internal enum Token { + internal enum Textfield { + /// The textfield to type the Dedicated IP token + internal static let accessibility = L10n.tr("Localizable", "dedicated.ip.token.textfield.accessibility", fallback: "The textfield to type the Dedicated IP token") + /// Paste in your token here + internal static let placeholder = L10n.tr("Localizable", "dedicated.ip.token.textfield.placeholder", fallback: "Paste in your token here") + } } } } - } - - internal enum Expiration { - /// Your subscription expires soon. Renew to stay protected. - internal static let message = L10n.tr("Localizable", "expiration.message") - /// Renewal - internal static let title = L10n.tr("Localizable", "expiration.title") - } - - internal enum Friend { - internal enum Referrals { - /// Full name - internal static let fullName = L10n.tr("Localizable", "friend.referrals.fullName") - /// Signed up - internal static let signedup = L10n.tr("Localizable", "friend.referrals.signedup") - /// Refer a Friend - internal static let title = L10n.tr("Localizable", "friend.referrals.title") - internal enum Days { - /// Free days acquired - internal static let acquired = L10n.tr("Localizable", "friend.referrals.days.acquired") - /// %d days - internal static func number(_ p1: Int) -> String { - return L10n.tr("Localizable", "friend.referrals.days.number", p1) + internal enum Expiration { + /// Your subscription expires soon. Renew to stay protected. + internal static let message = L10n.tr("Localizable", "expiration.message", fallback: "Your subscription expires soon. Renew to stay protected.") + /// Renewal + internal static let title = L10n.tr("Localizable", "expiration.title", fallback: "Renewal") + } + internal enum Friend { + internal enum Referrals { + /// Full name + internal static let fullName = L10n.tr("Localizable", "friend.referrals.fullName", fallback: "Full name") + /// Signed up + internal static let signedup = L10n.tr("Localizable", "friend.referrals.signedup", fallback: "Signed up") + /// Refer a Friend + internal static let title = L10n.tr("Localizable", "friend.referrals.title", fallback: "Refer a Friend") + internal enum Days { + /// Free days acquired + internal static let acquired = L10n.tr("Localizable", "friend.referrals.days.acquired", fallback: "Free days acquired") + /// %d days + internal static func number(_ p1: Int) -> String { + return L10n.tr("Localizable", "friend.referrals.days.number", p1, fallback: "%d days") + } } - } - internal enum Description { - /// REFER A FRIEND. GET 30 DAYS FREE. - internal static let short = L10n.tr("Localizable", "friend.referrals.description.short") - } - internal enum Email { - /// Invalid email. Please try again. - internal static let validation = L10n.tr("Localizable", "friend.referrals.email.validation") - } - internal enum Family { - internal enum Friends { - /// Family and Friends Referral Program - internal static let program = L10n.tr("Localizable", "friend.referrals.family.friends.program") + internal enum Description { + /// REFER A FRIEND. GET 30 DAYS FREE. + internal static let short = L10n.tr("Localizable", "friend.referrals.description.short", fallback: "REFER A FRIEND. GET 30 DAYS FREE.") + } + internal enum Email { + /// Invalid email. Please try again. + internal static let validation = L10n.tr("Localizable", "friend.referrals.email.validation", fallback: "Invalid email. Please try again.") } - } - internal enum Friends { internal enum Family { - /// Refer your friends and family. For every sign up we’ll give you both 30 days free. - internal static let title = L10n.tr("Localizable", "friend.referrals.friends.family.title") + internal enum Friends { + /// Family and Friends Referral Program + internal static let program = L10n.tr("Localizable", "friend.referrals.family.friends.program", fallback: "Family and Friends Referral Program") + } } - } - internal enum Invitation { - /// By sending this invitation, I agree to all of the terms and conditions of the Family and Friends Referral Program. - internal static let terms = L10n.tr("Localizable", "friend.referrals.invitation.terms") - } - internal enum Invite { - /// Could not resend invite. Try again later. - internal static let error = L10n.tr("Localizable", "friend.referrals.invite.error") - /// Invite sent successfully - internal static let success = L10n.tr("Localizable", "friend.referrals.invite.success") - } - internal enum Invites { - /// You have sent %d invites - internal static func number(_ p1: Int) -> String { - return L10n.tr("Localizable", "friend.referrals.invites.number", p1) + internal enum Friends { + internal enum Family { + /// Refer your friends and family. For every sign up we’ll give you both 30 days free. + internal static let title = L10n.tr("Localizable", "friend.referrals.friends.family.title", fallback: "Refer your friends and family. For every sign up we’ll give you both 30 days free. ") + } } - internal enum Sent { - /// Invites sent - internal static let title = L10n.tr("Localizable", "friend.referrals.invites.sent.title") + internal enum Invitation { + /// By sending this invitation, I agree to all of the terms and conditions of the Family and Friends Referral Program. + internal static let terms = L10n.tr("Localizable", "friend.referrals.invitation.terms", fallback: "By sending this invitation, I agree to all of the terms and conditions of the Family and Friends Referral Program.") } - } - internal enum Pending { - /// %d pending invites - internal static func invites(_ p1: Int) -> String { - return L10n.tr("Localizable", "friend.referrals.pending.invites", p1) + internal enum Invite { + /// Could not resend invite. Try again later. + internal static let error = L10n.tr("Localizable", "friend.referrals.invite.error", fallback: "Could not resend invite. Try again later.") + /// Invite sent successfully + internal static let success = L10n.tr("Localizable", "friend.referrals.invite.success", fallback: "Invite sent successfully") } internal enum Invites { - /// Pending invites - internal static let title = L10n.tr("Localizable", "friend.referrals.pending.invites.title") + /// You have sent %d invites + internal static func number(_ p1: Int) -> String { + return L10n.tr("Localizable", "friend.referrals.invites.number", p1, fallback: "You have sent %d invites") + } + internal enum Sent { + /// Invites sent + internal static let title = L10n.tr("Localizable", "friend.referrals.invites.sent.title", fallback: "Invites sent") + } } - } - internal enum Privacy { - /// Please note, for privacy reasons, all invites older than 30 days will be deleted. - internal static let note = L10n.tr("Localizable", "friend.referrals.privacy.note") - } - internal enum Reward { - /// Reward given - internal static let given = L10n.tr("Localizable", "friend.referrals.reward.given") - } - internal enum Send { - /// Send invite - internal static let invite = L10n.tr("Localizable", "friend.referrals.send.invite") - } - internal enum Share { - /// Share your unique referral link - internal static let link = L10n.tr("Localizable", "friend.referrals.share.link") - internal enum Link { - /// By sharing this link, you agree to all of the terms and conditions of the Family and Friends Referral Program. - internal static let terms = L10n.tr("Localizable", "friend.referrals.share.link.terms") + internal enum Pending { + /// %d pending invites + internal static func invites(_ p1: Int) -> String { + return L10n.tr("Localizable", "friend.referrals.pending.invites", p1, fallback: "%d pending invites") + } + internal enum Invites { + /// Pending invites + internal static let title = L10n.tr("Localizable", "friend.referrals.pending.invites.title", fallback: "Pending invites") + } } - } - internal enum Signups { - /// %d signups - internal static func number(_ p1: Int) -> String { - return L10n.tr("Localizable", "friend.referrals.signups.number", p1) + internal enum Privacy { + /// Please note, for privacy reasons, all invites older than 30 days will be deleted. + internal static let note = L10n.tr("Localizable", "friend.referrals.privacy.note", fallback: "Please note, for privacy reasons, all invites older than 30 days will be deleted.") } - } - internal enum View { - internal enum Invites { - /// View invites sent - internal static let sent = L10n.tr("Localizable", "friend.referrals.view.invites.sent") + internal enum Reward { + /// Reward given + internal static let given = L10n.tr("Localizable", "friend.referrals.reward.given", fallback: "Reward given") + } + internal enum Send { + /// Send invite + internal static let invite = L10n.tr("Localizable", "friend.referrals.send.invite", fallback: "Send invite") + } + internal enum Share { + /// Share your unique referral link + internal static let link = L10n.tr("Localizable", "friend.referrals.share.link", fallback: "Share your unique referral link") + internal enum Link { + /// By sharing this link, you agree to all of the terms and conditions of the Family and Friends Referral Program. + internal static let terms = L10n.tr("Localizable", "friend.referrals.share.link.terms", fallback: "By sharing this link, you agree to all of the terms and conditions of the Family and Friends Referral Program.") + } + } + internal enum Signups { + /// %d signups + internal static func number(_ p1: Int) -> String { + return L10n.tr("Localizable", "friend.referrals.signups.number", p1, fallback: "%d signups") + } + } + internal enum View { + internal enum Invites { + /// View invites sent + internal static let sent = L10n.tr("Localizable", "friend.referrals.view.invites.sent", fallback: "View invites sent") + } } } } - } - - internal enum Gdpr { - internal enum Accept { - internal enum Button { - /// Agree and continue - internal static let title = L10n.tr("Localizable", "gdpr.accept.button.title") - } - } - internal enum Collect { - internal enum Data { - /// E-mail Address for the purposes of account management and protection from abuse.\n\nE-mail address is used to send subscription information, payment confirmations, customer correspondence, and Private Internet Access promotional offers only. - internal static let description = L10n.tr("Localizable", "gdpr.collect.data.description") - /// Personal information we collect - internal static let title = L10n.tr("Localizable", "gdpr.collect.data.title") + internal enum Gdpr { + internal enum Accept { + internal enum Button { + /// Agree and continue + internal static let title = L10n.tr("Localizable", "gdpr.accept.button.title", fallback: "Agree and continue") + } } - } - } - - internal enum Global { - /// Add - internal static let add = L10n.tr("Localizable", "global.add") - /// Automatic - internal static let automatic = L10n.tr("Localizable", "global.automatic") - /// Cancel - internal static let cancel = L10n.tr("Localizable", "global.cancel") - /// Clear - internal static let clear = L10n.tr("Localizable", "global.clear") - /// Close - internal static let close = L10n.tr("Localizable", "global.close") - /// Copied to clipboard - internal static let copied = L10n.tr("Localizable", "global.copied") - /// Copy - internal static let copy = L10n.tr("Localizable", "global.copy") - /// Disable - internal static let disable = L10n.tr("Localizable", "global.disable") - /// Disabled - internal static let disabled = L10n.tr("Localizable", "global.disabled") - /// Edit - internal static let edit = L10n.tr("Localizable", "global.edit") - /// Empty - internal static let empty = L10n.tr("Localizable", "global.empty") - /// Enable - internal static let enable = L10n.tr("Localizable", "global.enable") - /// Enabled - internal static let enabled = L10n.tr("Localizable", "global.enabled") - /// Error - internal static let error = L10n.tr("Localizable", "global.error") - /// No - internal static let no = L10n.tr("Localizable", "global.no") - /// OK - internal static let ok = L10n.tr("Localizable", "global.ok") - /// Optional - internal static let `optional` = L10n.tr("Localizable", "global.optional") - /// or - internal static let or = L10n.tr("Localizable", "global.or") - /// Remove - internal static let remove = L10n.tr("Localizable", "global.remove") - /// Required - internal static let `required` = L10n.tr("Localizable", "global.required") - /// Share - internal static let share = L10n.tr("Localizable", "global.share") - /// No internet connection found. Please confirm that you have an internet connection. - internal static let unreachable = L10n.tr("Localizable", "global.unreachable") - /// Update - internal static let update = L10n.tr("Localizable", "global.update") - /// Version - internal static let version = L10n.tr("Localizable", "global.version") - /// Yes - internal static let yes = L10n.tr("Localizable", "global.yes") - internal enum General { - /// General Settings - internal static let settings = L10n.tr("Localizable", "global.general.settings") - } - internal enum Row { - /// Row selection - internal static let selection = L10n.tr("Localizable", "global.row.selection") - } - internal enum Vpn { - /// VPN Settings - internal static let settings = L10n.tr("Localizable", "global.vpn.settings") - } - } - - internal enum Hotspothelper { - internal enum Display { - /// 🔒 Activate VPN WiFi Protection in PIA Settings to secure this connection. - internal static let name = L10n.tr("Localizable", "hotspothelper.display.name") - internal enum Protected { - /// 🔒 PIA VPN WiFi Protection Enabled - We got your back. - internal static let name = L10n.tr("Localizable", "hotspothelper.display.protected.name") + internal enum Collect { + internal enum Data { + /// E-mail Address for the purposes of account management and protection from abuse. + /// + /// E-mail address is used to send subscription information, payment confirmations, customer correspondence, and Private Internet Access promotional offers only. + internal static let description = L10n.tr("Localizable", "gdpr.collect.data.description", fallback: "E-mail Address for the purposes of account management and protection from abuse.\n\nE-mail address is used to send subscription information, payment confirmations, customer correspondence, and Private Internet Access promotional offers only.") + /// Personal information we collect + internal static let title = L10n.tr("Localizable", "gdpr.collect.data.title", fallback: "Personal information we collect") + } } } - } - - internal enum Inapp { - internal enum Messages { - internal enum Settings { - /// Settings have been updated - internal static let updated = L10n.tr("Localizable", "inapp.messages.settings.updated") + internal enum Global { + /// Add + internal static let add = L10n.tr("Localizable", "global.add", fallback: "Add") + /// Automatic + internal static let automatic = L10n.tr("Localizable", "global.automatic", fallback: "Automatic") + /// Cancel + internal static let cancel = L10n.tr("Localizable", "global.cancel", fallback: "Cancel") + /// Clear + internal static let clear = L10n.tr("Localizable", "global.clear", fallback: "Clear") + /// Close + internal static let close = L10n.tr("Localizable", "global.close", fallback: "Close") + /// Copied to clipboard + internal static let copied = L10n.tr("Localizable", "global.copied", fallback: "Copied to clipboard") + /// Copy + internal static let copy = L10n.tr("Localizable", "global.copy", fallback: "Copy") + /// Disable + internal static let disable = L10n.tr("Localizable", "global.disable", fallback: "Disable") + /// Disabled + internal static let disabled = L10n.tr("Localizable", "global.disabled", fallback: "Disabled") + /// Edit + internal static let edit = L10n.tr("Localizable", "global.edit", fallback: "Edit") + /// Empty + internal static let empty = L10n.tr("Localizable", "global.empty", fallback: "Empty") + /// Enable + internal static let enable = L10n.tr("Localizable", "global.enable", fallback: "Enable") + /// Enabled + internal static let enabled = L10n.tr("Localizable", "global.enabled", fallback: "Enabled") + /// Error + internal static let error = L10n.tr("Localizable", "global.error", fallback: "Error") + /// No + internal static let no = L10n.tr("Localizable", "global.no", fallback: "No") + /// OK + internal static let ok = L10n.tr("Localizable", "global.ok", fallback: "OK") + /// Optional + internal static let `optional` = L10n.tr("Localizable", "global.optional", fallback: "Optional") + /// or + internal static let or = L10n.tr("Localizable", "global.or", fallback: "or") + /// Remove + internal static let remove = L10n.tr("Localizable", "global.remove", fallback: "Remove") + /// Required + internal static let `required` = L10n.tr("Localizable", "global.required", fallback: "Required") + /// Share + internal static let share = L10n.tr("Localizable", "global.share", fallback: "Share") + /// No internet connection found. Please confirm that you have an internet connection. + internal static let unreachable = L10n.tr("Localizable", "global.unreachable", fallback: "No internet connection found. Please confirm that you have an internet connection.") + /// Update + internal static let update = L10n.tr("Localizable", "global.update", fallback: "Update") + /// Version + internal static let version = L10n.tr("Localizable", "global.version", fallback: "Version") + /// Yes + internal static let yes = L10n.tr("Localizable", "global.yes", fallback: "Yes") + internal enum General { + /// General Settings + internal static let settings = L10n.tr("Localizable", "global.general.settings", fallback: "General Settings") + } + internal enum Row { + /// Row selection + internal static let selection = L10n.tr("Localizable", "global.row.selection", fallback: "Row selection") } - internal enum Toggle { - /// Show Service Communication Messages - internal static let title = L10n.tr("Localizable", "inapp.messages.toggle.title") - } - } - } - - internal enum LocalNotification { - internal enum NonCompliantWifi { - /// Tap here to secure your device - internal static let text = L10n.tr("Localizable", "local_notification.non_compliant_wifi.text", fallback: "Tap here to secure your device") - /// Unsecured Wi-Fi: %@ - internal static func title(_ p1: Any) -> String { - return L10n.tr("Localizable", "local_notification.non_compliant_wifi.title", String(describing: p1), fallback: "Unsecured Wi-Fi: \(p1)") + internal enum Vpn { + /// VPN Settings + internal static let settings = L10n.tr("Localizable", "global.vpn.settings", fallback: "VPN Settings") } } - } - - internal enum Menu { - internal enum Accessibility { - /// Menu - internal static let item = L10n.tr("Localizable", "menu.accessibility.item") - /// Logged in as %@ - internal static func loggedAs(_ p1: Any) -> String { - return L10n.tr("Localizable", "menu.accessibility.logged_as", String(describing: p1)) - } - internal enum Edit { - /// Edit - internal static let tile = L10n.tr("Localizable", "menu.accessibility.edit.tile") + internal enum Hotspothelper { + internal enum Display { + /// 🔒 Activate VPN WiFi Protection in PIA Settings to secure this connection. + internal static let name = L10n.tr("Localizable", "hotspothelper.display.name", fallback: "🔒 Activate VPN WiFi Protection in PIA Settings to secure this connection.") + internal enum Protected { + /// 🔒 PIA VPN WiFi Protection Enabled - We got your back. + internal static let name = L10n.tr("Localizable", "hotspothelper.display.protected.name", fallback: "🔒 PIA VPN WiFi Protection Enabled - We got your back.") + } } } - internal enum Expiration { - /// %d days - internal static func days(_ p1: Int) -> String { - return L10n.tr("Localizable", "menu.expiration.days", p1) - } - /// Subscription expires in - internal static let expiresIn = L10n.tr("Localizable", "menu.expiration.expires_in") - /// %d hours - internal static func hours(_ p1: Int) -> String { - return L10n.tr("Localizable", "menu.expiration.hours", p1) - } - /// one hour - internal static let oneHour = L10n.tr("Localizable", "menu.expiration.one_hour") - /// UPGRADE ACCOUNT - internal static let upgrade = L10n.tr("Localizable", "menu.expiration.upgrade") - } - internal enum Item { - /// About - internal static let about = L10n.tr("Localizable", "menu.item.about") - /// Account - internal static let account = L10n.tr("Localizable", "menu.item.account") - /// Log out - internal static let logout = L10n.tr("Localizable", "menu.item.logout") - /// Region selection - internal static let region = L10n.tr("Localizable", "menu.item.region") - /// Settings - internal static let settings = L10n.tr("Localizable", "menu.item.settings") - internal enum Web { - /// Home page - internal static let home = L10n.tr("Localizable", "menu.item.web.home") - /// Privacy policy - internal static let privacy = L10n.tr("Localizable", "menu.item.web.privacy") - /// Support - internal static let support = L10n.tr("Localizable", "menu.item.web.support") + internal enum Inapp { + internal enum Messages { + internal enum Settings { + /// Settings have been updated + internal static let updated = L10n.tr("Localizable", "inapp.messages.settings.updated", fallback: "Settings have been updated") + } + internal enum Toggle { + /// Show Service Communication Messages + internal static let title = L10n.tr("Localizable", "inapp.messages.toggle.title", fallback: "Show Service Communication Messages") + } } } - internal enum Logout { - /// Log out - internal static let confirm = L10n.tr("Localizable", "menu.logout.confirm") - /// Logging out will disable the VPN and leave you unprotected. - internal static let message = L10n.tr("Localizable", "menu.logout.message") - /// Log out - internal static let title = L10n.tr("Localizable", "menu.logout.title") - } - internal enum Renewal { - /// Purchase - internal static let purchase = L10n.tr("Localizable", "menu.renewal.purchase") - /// Renew - internal static let renew = L10n.tr("Localizable", "menu.renewal.renew") - /// Renewal - internal static let title = L10n.tr("Localizable", "menu.renewal.title") - internal enum Message { - /// Trial accounts are not eligible for renewal. Please purchase a new account upon expiry to continue service. - internal static let trial = L10n.tr("Localizable", "menu.renewal.message.trial") - /// Apple servers currently unavailable. Please try again later. - internal static let unavailable = L10n.tr("Localizable", "menu.renewal.message.unavailable") - /// Please use our website to renew your subscription. - internal static let website = L10n.tr("Localizable", "menu.renewal.message.website") + internal enum LocalNotification { + internal enum NonCompliantWifi { + /// Tap here to secure your device + internal static let text = L10n.tr("Localizable", "local_notification.non_compliant_wifi.text", fallback: "Tap here to secure your device") + /// Unsecured Wi-Fi: %@ + internal static func title(_ p1: Any) -> String { + return L10n.tr("Localizable", "local_notification.non_compliant_wifi.title", String(describing: p1), fallback: "Unsecured Wi-Fi: %@") + } } } - } - - internal enum Network { - internal enum Management { - internal enum Tool { - /// Your automation settings are configured to keep the VPN disconnected under the current network conditions. - internal static let alert = L10n.tr("Localizable", "network.management.tool.alert") - /// Disable Automation - internal static let disable = L10n.tr("Localizable", "network.management.tool.disable") - /// Manage Automation - internal static let title = L10n.tr("Localizable", "network.management.tool.title") - internal enum Add { - /// Add new rule - internal static let rule = L10n.tr("Localizable", "network.management.tool.add.rule") - } - internal enum Always { - /// Always connect VPN - internal static let connect = L10n.tr("Localizable", "network.management.tool.always.connect") - /// Always disconnect VPN - internal static let disconnect = L10n.tr("Localizable", "network.management.tool.always.disconnect") - } - internal enum Choose { - /// Choose a WiFi network to add a new rule. - internal static let wifi = L10n.tr("Localizable", "network.management.tool.choose.wifi") + internal enum Menu { + internal enum Accessibility { + /// Menu + internal static let item = L10n.tr("Localizable", "menu.accessibility.item", fallback: "Menu") + /// Logged in as %@ + internal static func loggedAs(_ p1: Any) -> String { + return L10n.tr("Localizable", "menu.accessibility.logged_as", String(describing: p1), fallback: "Logged in as %@") } - internal enum Enable { - /// Enable Automation - internal static let automation = L10n.tr("Localizable", "network.management.tool.enable.automation") + internal enum Edit { + /// Edit + internal static let tile = L10n.tr("Localizable", "menu.accessibility.edit.tile", fallback: "Edit") } - internal enum Mobile { - /// Mobile data - internal static let data = L10n.tr("Localizable", "network.management.tool.mobile.data") + } + internal enum Expiration { + /// %d days + internal static func days(_ p1: Int) -> String { + return L10n.tr("Localizable", "menu.expiration.days", p1, fallback: "%d days") } - internal enum Open { - /// Open WiFi - internal static let wifi = L10n.tr("Localizable", "network.management.tool.open.wifi") + /// Subscription expires in + internal static let expiresIn = L10n.tr("Localizable", "menu.expiration.expires_in", fallback: "Subscription expires in") + /// %d hours + internal static func hours(_ p1: Int) -> String { + return L10n.tr("Localizable", "menu.expiration.hours", p1, fallback: "%d hours") } - internal enum Retain { - /// Retain VPN State - internal static let state = L10n.tr("Localizable", "network.management.tool.retain.state") + /// one hour + internal static let oneHour = L10n.tr("Localizable", "menu.expiration.one_hour", fallback: "one hour") + /// UPGRADE ACCOUNT + internal static let upgrade = L10n.tr("Localizable", "menu.expiration.upgrade", fallback: "UPGRADE ACCOUNT") + } + internal enum Item { + /// About + internal static let about = L10n.tr("Localizable", "menu.item.about", fallback: "About") + /// Account + internal static let account = L10n.tr("Localizable", "menu.item.account", fallback: "Account") + /// Log out + internal static let logout = L10n.tr("Localizable", "menu.item.logout", fallback: "Log out") + /// Region selection + internal static let region = L10n.tr("Localizable", "menu.item.region", fallback: "Region selection") + /// Settings + internal static let settings = L10n.tr("Localizable", "menu.item.settings", fallback: "Settings") + internal enum Web { + /// Home page + internal static let home = L10n.tr("Localizable", "menu.item.web.home", fallback: "Home page") + /// Privacy policy + internal static let privacy = L10n.tr("Localizable", "menu.item.web.privacy", fallback: "Privacy policy") + /// Support + internal static let support = L10n.tr("Localizable", "menu.item.web.support", fallback: "Support") } - internal enum Secure { - /// Secure WiFi - internal static let wifi = L10n.tr("Localizable", "network.management.tool.secure.wifi") + } + internal enum Logout { + /// Log out + internal static let confirm = L10n.tr("Localizable", "menu.logout.confirm", fallback: "Log out") + /// Logging out will disable the VPN and leave you unprotected. + internal static let message = L10n.tr("Localizable", "menu.logout.message", fallback: "Logging out will disable the VPN and leave you unprotected.") + /// Log out + internal static let title = L10n.tr("Localizable", "menu.logout.title", fallback: "Log out") + } + internal enum Renewal { + /// Purchase + internal static let purchase = L10n.tr("Localizable", "menu.renewal.purchase", fallback: "Purchase") + /// Renew + internal static let renew = L10n.tr("Localizable", "menu.renewal.renew", fallback: "Renew") + /// Renewal + internal static let title = L10n.tr("Localizable", "menu.renewal.title", fallback: "Renewal") + internal enum Message { + /// Trial accounts are not eligible for renewal. Please purchase a new account upon expiry to continue service. + internal static let trial = L10n.tr("Localizable", "menu.renewal.message.trial", fallback: "Trial accounts are not eligible for renewal. Please purchase a new account upon expiry to continue service.") + /// Apple servers currently unavailable. Please try again later. + internal static let unavailable = L10n.tr("Localizable", "menu.renewal.message.unavailable", fallback: "Apple servers currently unavailable. Please try again later.") + /// Please use our website to renew your subscription. + internal static let website = L10n.tr("Localizable", "menu.renewal.message.website", fallback: "Please use our website to renew your subscription.") } } } - } - - internal enum Notifications { - internal enum Disabled { - /// Enable notifications to get a reminder to renew your subscription before it expires. - internal static let message = L10n.tr("Localizable", "notifications.disabled.message") - /// Settings - internal static let settings = L10n.tr("Localizable", "notifications.disabled.settings") - /// Notifications disabled - internal static let title = L10n.tr("Localizable", "notifications.disabled.title") - } - } - - internal enum Rating { - internal enum Alert { - internal enum Button { - /// No, thanks. - internal static let nothanks = L10n.tr("Localizable", "rating.alert.button.nothanks") - /// Not Really - internal static let notreally = L10n.tr("Localizable", "rating.alert.button.notreally") - /// Ok, sure! - internal static let oksure = L10n.tr("Localizable", "rating.alert.button.oksure") + internal enum Network { + internal enum Management { + internal enum Tool { + /// Your automation settings are configured to keep the VPN disconnected under the current network conditions. + internal static let alert = L10n.tr("Localizable", "network.management.tool.alert", fallback: "Your automation settings are configured to keep the VPN disconnected under the current network conditions.") + /// Disable Automation + internal static let disable = L10n.tr("Localizable", "network.management.tool.disable", fallback: "Disable Automation") + /// Manage Automation + internal static let title = L10n.tr("Localizable", "network.management.tool.title", fallback: "Manage Automation") + internal enum Add { + /// Add new rule + internal static let rule = L10n.tr("Localizable", "network.management.tool.add.rule", fallback: "Add new rule") + } + internal enum Always { + /// Always connect VPN + internal static let connect = L10n.tr("Localizable", "network.management.tool.always.connect", fallback: "Always connect VPN") + /// Always disconnect VPN + internal static let disconnect = L10n.tr("Localizable", "network.management.tool.always.disconnect", fallback: "Always disconnect VPN") + } + internal enum Choose { + /// Choose a WiFi network to add a new rule. + internal static let wifi = L10n.tr("Localizable", "network.management.tool.choose.wifi", fallback: "Choose a WiFi network to add a new rule. ") + } + internal enum Enable { + /// Enable Automation + internal static let automation = L10n.tr("Localizable", "network.management.tool.enable.automation", fallback: "Enable Automation") + } + internal enum Mobile { + /// Mobile data + internal static let data = L10n.tr("Localizable", "network.management.tool.mobile.data", fallback: "Mobile data") + } + internal enum Open { + /// Open WiFi + internal static let wifi = L10n.tr("Localizable", "network.management.tool.open.wifi", fallback: "Open WiFi") + } + internal enum Retain { + /// Retain VPN State + internal static let state = L10n.tr("Localizable", "network.management.tool.retain.state", fallback: "Retain VPN State") + } + internal enum Secure { + /// Secure WiFi + internal static let wifi = L10n.tr("Localizable", "network.management.tool.secure.wifi", fallback: "Secure WiFi") + } + } } } - internal enum Enjoy { - /// Are you enjoying PIA VPN? - internal static let question = L10n.tr("Localizable", "rating.enjoy.question") - /// We hope our VPN product is meeting your expectations - internal static let subtitle = L10n.tr("Localizable", "rating.enjoy.subtitle") - } - internal enum Error { - /// The connection couldn't be established - internal static let question = L10n.tr("Localizable", "rating.error.question") - /// You can try selecting a different region or letting us know about it by opening a support ticket. - internal static let subtitle = L10n.tr("Localizable", "rating.error.subtitle") - internal enum Button { - /// Send feedback - internal static let send = L10n.tr("Localizable", "rating.error.button.send") + internal enum Notifications { + internal enum Disabled { + /// Enable notifications to get a reminder to renew your subscription before it expires. + internal static let message = L10n.tr("Localizable", "notifications.disabled.message", fallback: "Enable notifications to get a reminder to renew your subscription before it expires.") + /// Settings + internal static let settings = L10n.tr("Localizable", "notifications.disabled.settings", fallback: "Settings") + /// Notifications disabled + internal static let title = L10n.tr("Localizable", "notifications.disabled.title", fallback: "Notifications disabled") } } - internal enum Problems { - /// What went wrong? - internal static let question = L10n.tr("Localizable", "rating.problems.question") - /// Do you want to give feedback? We can help you to improve your experience using PIA - internal static let subtitle = L10n.tr("Localizable", "rating.problems.subtitle") - } - internal enum Rate { - /// How about a rating on the AppStore? - internal static let question = L10n.tr("Localizable", "rating.rate.question") - /// We appreciate you sharing your experience - internal static let subtitle = L10n.tr("Localizable", "rating.rate.subtitle") - } - internal enum Review { - /// How about an AppStore review? - internal static let question = L10n.tr("Localizable", "rating.review.question") - } - } - - internal enum Region { - internal enum Accessibility { - /// Add a favorite region - internal static let favorite = L10n.tr("Localizable", "region.accessibility.favorite") - /// Filter - internal static let filter = L10n.tr("Localizable", "region.accessibility.filter") - /// Remove a favorite region - internal static let unfavorite = L10n.tr("Localizable", "region.accessibility.unfavorite") - } - internal enum Filter { - /// Favorites - internal static let favorites = L10n.tr("Localizable", "region.filter.favorites") - /// Latency - internal static let latency = L10n.tr("Localizable", "region.filter.latency") - /// Name - internal static let name = L10n.tr("Localizable", "region.filter.name") - /// Sort regions by - internal static let sortby = L10n.tr("Localizable", "region.filter.sortby") - } - internal enum Search { - /// Search for a region - internal static let placeholder = L10n.tr("Localizable", "region.search.placeholder") - } - } - - internal enum Renewal { - internal enum Failure { - /// Your purchase receipt couldn't be submitted, please retry at a later time. - internal static let message = L10n.tr("Localizable", "renewal.failure.message") - } - internal enum Success { - /// Your account was successfully renewed. - internal static let message = L10n.tr("Localizable", "renewal.success.message") - /// Thank you - internal static let title = L10n.tr("Localizable", "renewal.success.title") - } - } - - internal enum Server { - internal enum Reconnection { - internal enum Please { - /// Please wait... - internal static let wait = L10n.tr("Localizable", "server.reconnection.please.wait") + internal enum Rating { + internal enum Alert { + internal enum Button { + /// No, thanks. + internal static let nothanks = L10n.tr("Localizable", "rating.alert.button.nothanks", fallback: "No, thanks.") + /// Not Really + internal static let notreally = L10n.tr("Localizable", "rating.alert.button.notreally", fallback: "Not Really") + /// Ok, sure! + internal static let oksure = L10n.tr("Localizable", "rating.alert.button.oksure", fallback: "Ok, sure!") + } } - internal enum Still { - /// Still trying to connect... - internal static let connection = L10n.tr("Localizable", "server.reconnection.still.connection") + internal enum Enjoy { + /// Are you enjoying PIA VPN? + internal static let question = L10n.tr("Localizable", "rating.enjoy.question", fallback: "Are you enjoying PIA VPN?") + /// We hope our VPN product is meeting your expectations + internal static let subtitle = L10n.tr("Localizable", "rating.enjoy.subtitle", fallback: "We hope our VPN product is meeting your expectations") } - } - } - - internal enum Set { - internal enum Email { - /// We need your email to send your username and password. - internal static let why = L10n.tr("Localizable", "set.email.why") internal enum Error { - /// You must enter an email address. - internal static let validation = L10n.tr("Localizable", "set.email.error.validation") + /// The connection couldn't be established + internal static let question = L10n.tr("Localizable", "rating.error.question", fallback: "The connection couldn't be established") + /// You can try selecting a different region or letting us know about it by opening a support ticket. + internal static let subtitle = L10n.tr("Localizable", "rating.error.subtitle", fallback: "You can try selecting a different region or letting us know about it by opening a support ticket.") + internal enum Button { + /// Send feedback + internal static let send = L10n.tr("Localizable", "rating.error.button.send", fallback: "Send feedback") + } } - internal enum Form { - /// Enter your email address - internal static let email = L10n.tr("Localizable", "set.email.form.email") + internal enum Problems { + /// What went wrong? + internal static let question = L10n.tr("Localizable", "rating.problems.question", fallback: "What went wrong?") + /// Do you want to give feedback? We can help you to improve your experience using PIA + internal static let subtitle = L10n.tr("Localizable", "rating.problems.subtitle", fallback: "Do you want to give feedback? We can help you to improve your experience using PIA") } - internal enum Password { - /// Password - internal static let caption = L10n.tr("Localizable", "set.email.password.caption") + internal enum Rate { + /// How about a rating on the AppStore? + internal static let question = L10n.tr("Localizable", "rating.rate.question", fallback: "How about a rating on the AppStore?") + /// We appreciate you sharing your experience + internal static let subtitle = L10n.tr("Localizable", "rating.rate.subtitle", fallback: "We appreciate you sharing your experience") + } + internal enum Review { + /// How about an AppStore review? + internal static let question = L10n.tr("Localizable", "rating.review.question", fallback: "How about an AppStore review?") + } + } + internal enum Region { + internal enum Accessibility { + /// Add a favorite region + internal static let favorite = L10n.tr("Localizable", "region.accessibility.favorite", fallback: "Add a favorite region") + /// Filter + internal static let filter = L10n.tr("Localizable", "region.accessibility.filter", fallback: "Filter") + /// Remove a favorite region + internal static let unfavorite = L10n.tr("Localizable", "region.accessibility.unfavorite", fallback: "Remove a favorite region") + } + internal enum Filter { + /// Favorites + internal static let favorites = L10n.tr("Localizable", "region.filter.favorites", fallback: "Favorites") + /// Latency + internal static let latency = L10n.tr("Localizable", "region.filter.latency", fallback: "Latency") + /// Name + internal static let name = L10n.tr("Localizable", "region.filter.name", fallback: "Name") + /// Sort regions by + internal static let sortby = L10n.tr("Localizable", "region.filter.sortby", fallback: "Sort regions by") + } + internal enum Search { + /// Search for a region + internal static let placeholder = L10n.tr("Localizable", "region.search.placeholder", fallback: "Search for a region") + } + } + internal enum Renewal { + internal enum Failure { + /// Your purchase receipt couldn't be submitted, please retry at a later time. + internal static let message = L10n.tr("Localizable", "renewal.failure.message", fallback: "Your purchase receipt couldn't be submitted, please retry at a later time.") } internal enum Success { - /// We have sent your account username and password at your email address at %@ - internal static func messageFormat(_ p1: Any) -> String { - return L10n.tr("Localizable", "set.email.success.message_format", String(describing: p1)) + /// Your account was successfully renewed. + internal static let message = L10n.tr("Localizable", "renewal.success.message", fallback: "Your account was successfully renewed.") + /// Thank you + internal static let title = L10n.tr("Localizable", "renewal.success.title", fallback: "Thank you") + } + } + internal enum Server { + internal enum Reconnection { + internal enum Please { + /// Please wait... + internal static let wait = L10n.tr("Localizable", "server.reconnection.please.wait", fallback: "Please wait...") + } + internal enum Still { + /// Still trying to connect... + internal static let connection = L10n.tr("Localizable", "server.reconnection.still.connection", fallback: "Still trying to connect...") } } } - } - - internal enum Settings { - internal enum ApplicationInformation { - /// APPLICATION INFORMATION - internal static let title = L10n.tr("Localizable", "settings.application_information.title") - internal enum Debug { - /// Send Debug Log to support - internal static let title = L10n.tr("Localizable", "settings.application_information.debug.title") - internal enum Empty { - /// Debug information is empty, please attempt a connection before retrying submission. - internal static let message = L10n.tr("Localizable", "settings.application_information.debug.empty.message") - /// Empty debug information - internal static let title = L10n.tr("Localizable", "settings.application_information.debug.empty.title") + internal enum Set { + internal enum Email { + /// We need your email to send your username and password. + internal static let why = L10n.tr("Localizable", "set.email.why", fallback: "We need your email to send your username and password.") + internal enum Error { + /// You must enter an email address. + internal static let validation = L10n.tr("Localizable", "set.email.error.validation", fallback: "You must enter an email address.") } - internal enum Failure { - /// Debug information could not be submitted. - internal static let message = L10n.tr("Localizable", "settings.application_information.debug.failure.message") - /// Error during submission - internal static let title = L10n.tr("Localizable", "settings.application_information.debug.failure.title") + internal enum Form { + /// Enter your email address + internal static let email = L10n.tr("Localizable", "set.email.form.email", fallback: "Enter your email address") + } + internal enum Password { + /// Password + internal static let caption = L10n.tr("Localizable", "set.email.password.caption", fallback: "Password") } internal enum Success { - /// Debug information successfully submitted.\nID: %@\nPlease note this ID, as our support team will require this to locate your submission. - internal static func message(_ p1: Any) -> String { - return L10n.tr("Localizable", "settings.application_information.debug.success.message", String(describing: p1)) + /// We have sent your account username and password at your email address at %@ + internal static func messageFormat(_ p1: Any) -> String { + return L10n.tr("Localizable", "set.email.success.message_format", String(describing: p1), fallback: "We have sent your account username and password at your email address at %@") } - /// Debug information submitted - internal static let title = L10n.tr("Localizable", "settings.application_information.debug.success.title") } } } - internal enum ApplicationSettings { - /// APPLICATION SETTINGS - internal static let title = L10n.tr("Localizable", "settings.application_settings.title") - internal enum ActiveTheme { - /// Active theme - internal static let title = L10n.tr("Localizable", "settings.application_settings.active_theme.title") - } - internal enum DarkTheme { - /// Dark theme - internal static let title = L10n.tr("Localizable", "settings.application_settings.dark_theme.title") - } - internal enum KillSwitch { - /// The VPN kill switch prevents access to the Internet if the VPN connection is reconnecting. This excludes disconnecting manually. - internal static let footer = L10n.tr("Localizable", "settings.application_settings.kill_switch.footer") - /// VPN Kill Switch - internal static let title = L10n.tr("Localizable", "settings.application_settings.kill_switch.title") - } - internal enum LeakProtection { - /// iOS includes features designed to operate outside the VPN by default, such as AirDrop, CarPlay, AirPlay, and Personal Hotspots. Enabling custom leak protection routes this traffic through the VPN but may affect how these features function. More info - internal static let footer = L10n.tr("Localizable", "settings.application_settings.leak_protection.footer", fallback: "iOS includes features designed to operate outside the VPN by default, such as AirDrop, CarPlay, AirPlay, and Personal Hotspots. Enabling custom leak protection routes this traffic through the VPN but may affect how these features function. More info") - /// More info - internal static let moreInfo = L10n.tr("Localizable", "settings.application_settings.leak_protection.more_info", fallback: "More info") - /// Leak Protection - internal static let title = L10n.tr("Localizable", "settings.application_settings.leak_protection.title", fallback: "Leak Protection") - internal enum Alert { - /// Changes to the VPN Settings will take effect on the next connection - internal static let title = L10n.tr("Localizable", "settings.application_settings.leak_protection.alert.title", fallback: "Changes to the VPN Settings will take effect on the next connection") - } - } - internal enum AllowLocalNetwork { - /// Stay connected to local devices like printers or file servers while connected to the VPN. (Allow this only if you trust the people and devices on your network.) - internal static let footer = L10n.tr("Localizable", "settings.application_settings.allow_local_network.footer", fallback: "Stay connected to local devices like printers or file servers while connected to the VPN. (Allow this only if you trust the people and devices on your network.)") - /// Allow access to devices on local network - internal static let title = L10n.tr("Localizable", "settings.application_settings.allow_local_network.title", fallback: "Allow access to devices on local network") - } - internal enum Mace { - /// PIA MACE™ blocks ads, trackers, and malware while you're connected to the VPN. - internal static let footer = L10n.tr("Localizable", "settings.application_settings.mace.footer") - /// PIA MACE™ - internal static let title = L10n.tr("Localizable", "settings.application_settings.mace.title") - } - internal enum LeakProtectionAlert { - /// VPN Leak Protection update settings alert - internal static let title = L10n.tr("Localizable", "settings.application_settings.leak_protection.alert.title") + internal enum Settings { + internal enum ApplicationInformation { + /// APPLICATION INFORMATION + internal static let title = L10n.tr("Localizable", "settings.application_information.title", fallback: "APPLICATION INFORMATION") + internal enum Debug { + /// Send Debug Log to support + internal static let title = L10n.tr("Localizable", "settings.application_information.debug.title", fallback: "Send Debug Log to support") + internal enum Empty { + /// Debug information is empty, please attempt a connection before retrying submission. + internal static let message = L10n.tr("Localizable", "settings.application_information.debug.empty.message", fallback: "Debug information is empty, please attempt a connection before retrying submission.") + /// Empty debug information + internal static let title = L10n.tr("Localizable", "settings.application_information.debug.empty.title", fallback: "Empty debug information") + } + internal enum Failure { + /// Debug information could not be submitted. + internal static let message = L10n.tr("Localizable", "settings.application_information.debug.failure.message", fallback: "Debug information could not be submitted.") + /// Error during submission + internal static let title = L10n.tr("Localizable", "settings.application_information.debug.failure.title", fallback: "Error during submission") + } + internal enum Success { + /// Debug information successfully submitted. + /// ID: %@ + /// Please note this ID, as our support team will require this to locate your submission. + internal static func message(_ p1: Any) -> String { + return L10n.tr("Localizable", "settings.application_information.debug.success.message", String(describing: p1), fallback: "Debug information successfully submitted.\nID: %@\nPlease note this ID, as our support team will require this to locate your submission.") + } + /// Debug information submitted + internal static let title = L10n.tr("Localizable", "settings.application_information.debug.success.title", fallback: "Debug information submitted") + } } - } - internal enum Cards { - internal enum History { - /// Latest News - internal static let title = L10n.tr("Localizable", "settings.cards.history.title") } - } - internal enum Commit { - internal enum Buttons { - /// Later - internal static let later = L10n.tr("Localizable", "settings.commit.buttons.later") - /// Reconnect - internal static let reconnect = L10n.tr("Localizable", "settings.commit.buttons.reconnect") + internal enum ApplicationSettings { + /// APPLICATION SETTINGS + internal static let title = L10n.tr("Localizable", "settings.application_settings.title", fallback: "APPLICATION SETTINGS") + internal enum ActiveTheme { + /// Active theme + internal static let title = L10n.tr("Localizable", "settings.application_settings.active_theme.title", fallback: "Active theme") + } + internal enum AllowLocalNetwork { + /// Stay connected to local devices like printers or file servers while connected to the VPN. (Allow this only if you trust the people and devices on your network.) + internal static let footer = L10n.tr("Localizable", "settings.application_settings.allow_local_network.footer", fallback: "Stay connected to local devices like printers or file servers while connected to the VPN. (Allow this only if you trust the people and devices on your network.)") + /// Allow access to devices on local network + internal static let title = L10n.tr("Localizable", "settings.application_settings.allow_local_network.title", fallback: "Allow access to devices on local network") + } + internal enum DarkTheme { + /// Dark theme + internal static let title = L10n.tr("Localizable", "settings.application_settings.dark_theme.title", fallback: "Dark theme") + } + internal enum KillSwitch { + /// The VPN kill switch prevents access to the Internet if the VPN connection is reconnecting. This excludes disconnecting manually. + internal static let footer = L10n.tr("Localizable", "settings.application_settings.kill_switch.footer", fallback: "The VPN kill switch prevents access to the Internet if the VPN connection is reconnecting. This excludes disconnecting manually.") + /// VPN Kill Switch + internal static let title = L10n.tr("Localizable", "settings.application_settings.kill_switch.title", fallback: "VPN Kill Switch") + } + internal enum LeakProtection { + /// iOS includes features designed to operate outside the VPN by default, such as AirDrop, CarPlay, AirPlay, and Personal Hotspots. Enabling custom leak protection routes this traffic through the VPN but may affect how these features function. More info + internal static let footer = L10n.tr("Localizable", "settings.application_settings.leak_protection.footer", fallback: "iOS includes features designed to operate outside the VPN by default, such as AirDrop, CarPlay, AirPlay, and Personal Hotspots. Enabling custom leak protection routes this traffic through the VPN but may affect how these features function. More info") + /// More info + internal static let moreInfo = L10n.tr("Localizable", "settings.application_settings.leak_protection.more_info", fallback: "More info") + /// Leak Protection + internal static let title = L10n.tr("Localizable", "settings.application_settings.leak_protection.title", fallback: "Leak Protection") + internal enum Alert { + /// Changes to the VPN Settings will take effect on the next connection + internal static let title = L10n.tr("Localizable", "settings.application_settings.leak_protection.alert.title", fallback: "Changes to the VPN Settings will take effect on the next connection") + } + } + internal enum Mace { + /// PIA MACE™ blocks ads, trackers, and malware while you're connected to the VPN. + internal static let footer = L10n.tr("Localizable", "settings.application_settings.mace.footer", fallback: "PIA MACE™ blocks ads, trackers, and malware while you're connected to the VPN.") + /// PIA MACE™ + internal static let title = L10n.tr("Localizable", "settings.application_settings.mace.title", fallback: "PIA MACE™") + } } - internal enum Messages { - /// The VPN must reconnect for some changes to take effect. - internal static let mustDisconnect = L10n.tr("Localizable", "settings.commit.messages.must_disconnect") - /// Reconnect the VPN to apply changes. - internal static let shouldReconnect = L10n.tr("Localizable", "settings.commit.messages.should_reconnect") + internal enum Cards { + internal enum History { + /// Latest News + internal static let title = L10n.tr("Localizable", "settings.cards.history.title", fallback: "Latest News") + } } - } - internal enum Connection { - /// CONNECTION - internal static let title = L10n.tr("Localizable", "settings.connection.title") - internal enum RemotePort { - /// Remote Port - internal static let title = L10n.tr("Localizable", "settings.connection.remote_port.title") + internal enum Commit { + internal enum Buttons { + /// Later + internal static let later = L10n.tr("Localizable", "settings.commit.buttons.later", fallback: "Later") + /// Reconnect + internal static let reconnect = L10n.tr("Localizable", "settings.commit.buttons.reconnect", fallback: "Reconnect") + } + internal enum Messages { + /// The VPN must reconnect for some changes to take effect. + internal static let mustDisconnect = L10n.tr("Localizable", "settings.commit.messages.must_disconnect", fallback: "The VPN must reconnect for some changes to take effect.") + /// Reconnect the VPN to apply changes. + internal static let shouldReconnect = L10n.tr("Localizable", "settings.commit.messages.should_reconnect", fallback: "Reconnect the VPN to apply changes.") + } } - internal enum SocketProtocol { - /// Socket - internal static let title = L10n.tr("Localizable", "settings.connection.socket_protocol.title") + internal enum Connection { + /// CONNECTION + internal static let title = L10n.tr("Localizable", "settings.connection.title", fallback: "CONNECTION") + internal enum RemotePort { + /// Remote Port + internal static let title = L10n.tr("Localizable", "settings.connection.remote_port.title", fallback: "Remote Port") + } + internal enum SocketProtocol { + /// Socket + internal static let title = L10n.tr("Localizable", "settings.connection.socket_protocol.title", fallback: "Socket") + } + internal enum Transport { + /// Transport + internal static let title = L10n.tr("Localizable", "settings.connection.transport.title", fallback: "Transport") + } + internal enum VpnProtocol { + /// Protocol Selection + internal static let title = L10n.tr("Localizable", "settings.connection.vpn_protocol.title", fallback: "Protocol Selection") + } } - internal enum Transport { - /// Transport - internal static let title = L10n.tr("Localizable", "settings.connection.transport.title") + internal enum ContentBlocker { + /// To enable or disable Content Blocker go to Settings > Safari > Content Blockers and toggle PIA VPN. + internal static let footer = L10n.tr("Localizable", "settings.content_blocker.footer", fallback: "To enable or disable Content Blocker go to Settings > Safari > Content Blockers and toggle PIA VPN.") + /// Safari Content Blocker state + internal static let title = L10n.tr("Localizable", "settings.content_blocker.title", fallback: "Safari Content Blocker state") + internal enum Refresh { + /// Refresh block list + internal static let title = L10n.tr("Localizable", "settings.content_blocker.refresh.title", fallback: "Refresh block list") + } + internal enum State { + /// Current state + internal static let title = L10n.tr("Localizable", "settings.content_blocker.state.title", fallback: "Current state") + } } - internal enum VpnProtocol { - /// Protocol Selection - internal static let title = L10n.tr("Localizable", "settings.connection.vpn_protocol.title") + internal enum Dns { + /// Custom + internal static let custom = L10n.tr("Localizable", "settings.dns.custom", fallback: "Custom") + /// Primary DNS + internal static let primaryDNS = L10n.tr("Localizable", "settings.dns.primaryDNS", fallback: "Primary DNS") + /// Secondary DNS + internal static let secondaryDNS = L10n.tr("Localizable", "settings.dns.secondaryDNS", fallback: "Secondary DNS") + internal enum Alert { + internal enum Clear { + /// This will clear your custom DNS and default to PIA DNS. + internal static let message = L10n.tr("Localizable", "settings.dns.alert.clear.message", fallback: "This will clear your custom DNS and default to PIA DNS.") + /// Clear DNS + internal static let title = L10n.tr("Localizable", "settings.dns.alert.clear.title", fallback: "Clear DNS") + } + internal enum Create { + /// Using non PIA DNS could expose your DNS traffic to third parties and compromise your privacy. + internal static let message = L10n.tr("Localizable", "settings.dns.alert.create.message", fallback: "Using non PIA DNS could expose your DNS traffic to third parties and compromise your privacy.") + } + } + internal enum Custom { + /// Custom DNS + internal static let dns = L10n.tr("Localizable", "settings.dns.custom.dns", fallback: "Custom DNS") + } + internal enum Validation { + internal enum Primary { + /// Primary DNS is not valid. + internal static let invalid = L10n.tr("Localizable", "settings.dns.validation.primary.invalid", fallback: "Primary DNS is not valid.") + /// Primary DNS is mandatory. + internal static let mandatory = L10n.tr("Localizable", "settings.dns.validation.primary.mandatory", fallback: "Primary DNS is mandatory.") + } + internal enum Secondary { + /// Secondary DNS is not valid. + internal static let invalid = L10n.tr("Localizable", "settings.dns.validation.secondary.invalid", fallback: "Secondary DNS is not valid.") + } + } } - } - internal enum ContentBlocker { - /// To enable or disable Content Blocker go to Settings > Safari > Content Blockers and toggle PIA VPN. - internal static let footer = L10n.tr("Localizable", "settings.content_blocker.footer") - /// Safari Content Blocker state - internal static let title = L10n.tr("Localizable", "settings.content_blocker.title") - internal enum Refresh { - /// Refresh block list - internal static let title = L10n.tr("Localizable", "settings.content_blocker.refresh.title") - } - internal enum State { - /// Current state - internal static let title = L10n.tr("Localizable", "settings.content_blocker.state.title") + internal enum Encryption { + /// ENCRYPTION + internal static let title = L10n.tr("Localizable", "settings.encryption.title", fallback: "ENCRYPTION") + internal enum Cipher { + /// Data Encryption + internal static let title = L10n.tr("Localizable", "settings.encryption.cipher.title", fallback: "Data Encryption") + } + internal enum Digest { + /// Data Authentication + internal static let title = L10n.tr("Localizable", "settings.encryption.digest.title", fallback: "Data Authentication") + } + internal enum Handshake { + /// Handshake + internal static let title = L10n.tr("Localizable", "settings.encryption.handshake.title", fallback: "Handshake") + } } - } - internal enum Dns { - /// Custom - internal static let custom = L10n.tr("Localizable", "settings.dns.custom") - /// Primary DNS - internal static let primaryDNS = L10n.tr("Localizable", "settings.dns.primaryDNS") - /// Secondary DNS - internal static let secondaryDNS = L10n.tr("Localizable", "settings.dns.secondaryDNS") - internal enum Alert { - internal enum Clear { - /// This will clear your custom DNS and default to PIA DNS. - internal static let message = L10n.tr("Localizable", "settings.dns.alert.clear.message") - /// Clear DNS - internal static let title = L10n.tr("Localizable", "settings.dns.alert.clear.title") + internal enum Geo { + internal enum Servers { + /// Show Geo-located Regions + internal static let description = L10n.tr("Localizable", "settings.geo.servers.description", fallback: "Show Geo-located Regions") + } + } + internal enum Hotspothelper { + /// Configure how PIA will behaves on connection to WiFi or cellular networks. This excludes disconnecting manually. + internal static let description = L10n.tr("Localizable", "settings.hotspothelper.description", fallback: "Configure how PIA will behaves on connection to WiFi or cellular networks. This excludes disconnecting manually.") + /// Network management tool + internal static let title = L10n.tr("Localizable", "settings.hotspothelper.title", fallback: "Network management tool") + internal enum All { + /// VPN WiFi Protection will activate on all networks, including trusted networks. + internal static let description = L10n.tr("Localizable", "settings.hotspothelper.all.description", fallback: "VPN WiFi Protection will activate on all networks, including trusted networks.") + /// Protect all networks + internal static let title = L10n.tr("Localizable", "settings.hotspothelper.all.title", fallback: "Protect all networks") + } + internal enum Available { + /// To populate this list go to iOS Settings > WiFi. + internal static let help = L10n.tr("Localizable", "settings.hotspothelper.available.help", fallback: "To populate this list go to iOS Settings > WiFi.") + internal enum Add { + /// Tap + to add to Trusted networks. + internal static let help = L10n.tr("Localizable", "settings.hotspothelper.available.add.help", fallback: "Tap + to add to Trusted networks.") + } } - internal enum Create { - /// Using non PIA DNS could expose your DNS traffic to third parties and compromise your privacy. - internal static let message = L10n.tr("Localizable", "settings.dns.alert.create.message") + internal enum Cellular { + /// PIA automatically enables the VPN when connecting to cellular networks if this option is enabled. + internal static let description = L10n.tr("Localizable", "settings.hotspothelper.cellular.description", fallback: "PIA automatically enables the VPN when connecting to cellular networks if this option is enabled.") + /// Cellular networks + internal static let networks = L10n.tr("Localizable", "settings.hotspothelper.cellular.networks", fallback: "Cellular networks") + /// Protect over cellular networks + internal static let title = L10n.tr("Localizable", "settings.hotspothelper.cellular.title", fallback: "Protect over cellular networks") + } + internal enum Enable { + /// PIA automatically enables the VPN when connecting to untrusted WiFi networks if this option is enabled. + internal static let description = L10n.tr("Localizable", "settings.hotspothelper.enable.description", fallback: "PIA automatically enables the VPN when connecting to untrusted WiFi networks if this option is enabled.") + } + internal enum Rules { + /// Rules + internal static let title = L10n.tr("Localizable", "settings.hotspothelper.rules.title", fallback: "Rules") + } + internal enum Wifi { + /// WiFi networks + internal static let networks = L10n.tr("Localizable", "settings.hotspothelper.wifi.networks", fallback: "WiFi networks") + internal enum Trust { + /// VPN WiFi Protection + internal static let title = L10n.tr("Localizable", "settings.hotspothelper.wifi.trust.title", fallback: "VPN WiFi Protection") + } } } - internal enum Custom { - /// Custom DNS - internal static let dns = L10n.tr("Localizable", "settings.dns.custom.dns") + internal enum Log { + /// Save debug logs which can be submitted to technical support to help troubleshoot problems. + internal static let information = L10n.tr("Localizable", "settings.log.information", fallback: "Save debug logs which can be submitted to technical support to help troubleshoot problems.") + internal enum Connected { + /// A VPN connection is required. Please connect to the VPN and retry. + internal static let error = L10n.tr("Localizable", "settings.log.connected.error", fallback: "A VPN connection is required. Please connect to the VPN and retry.") + } } - internal enum Validation { - internal enum Primary { - /// Primary DNS is not valid. - internal static let invalid = L10n.tr("Localizable", "settings.dns.validation.primary.invalid") - /// Primary DNS is mandatory. - internal static let mandatory = L10n.tr("Localizable", "settings.dns.validation.primary.mandatory") + internal enum Nmt { + internal enum Killswitch { + /// The VPN kill switch is currently disabled. In order to ensure that the Network Management Tool is functioning, and that you are able to reconnect when switching networks, please enable the VPN kill switch in your settings. + internal static let disabled = L10n.tr("Localizable", "settings.nmt.killswitch.disabled", fallback: "The VPN kill switch is currently disabled. In order to ensure that the Network Management Tool is functioning, and that you are able to reconnect when switching networks, please enable the VPN kill switch in your settings.") } - internal enum Secondary { - /// Secondary DNS is not valid. - internal static let invalid = L10n.tr("Localizable", "settings.dns.validation.secondary.invalid") + internal enum Optout { + internal enum Disconnect { + /// Opt-out disconnect confirmation alert + internal static let alerts = L10n.tr("Localizable", "settings.nmt.optout.disconnect.alerts", fallback: "Opt-out disconnect confirmation alert") + internal enum Alerts { + /// Disables the warning alert when disconnecting from the VPN. + internal static let description = L10n.tr("Localizable", "settings.nmt.optout.disconnect.alerts.description", fallback: "Disables the warning alert when disconnecting from the VPN.") + } + } + } + internal enum Wireguard { + /// WireGuard® doesn't need to reconnect when you switch between different networks. It may be necessary to manually disconnect the VPN on trusted networks. + internal static let warning = L10n.tr("Localizable", "settings.nmt.wireguard.warning", fallback: "WireGuard® doesn't need to reconnect when you switch between different networks. It may be necessary to manually disconnect the VPN on trusted networks.") } } - } - internal enum Encryption { - /// ENCRYPTION - internal static let title = L10n.tr("Localizable", "settings.encryption.title") - internal enum Cipher { - /// Data Encryption - internal static let title = L10n.tr("Localizable", "settings.encryption.cipher.title") + internal enum Ovpn { + internal enum Migration { + /// We are updating our OpenVPN implementation, for more information, click here + internal static let footer = L10n.tr("Localizable", "settings.ovpn.migration.footer", fallback: "We are updating our OpenVPN implementation, for more information, click here") + internal enum Footer { + /// here + internal static let link = L10n.tr("Localizable", "settings.ovpn.migration.footer.link", fallback: "here") + } + } } - internal enum Digest { - /// Data Authentication - internal static let title = L10n.tr("Localizable", "settings.encryption.digest.title") + internal enum Preview { + /// Preview + internal static let title = L10n.tr("Localizable", "settings.preview.title", fallback: "Preview") + } + internal enum Reset { + /// This will reset all of the above settings to default. + internal static let footer = L10n.tr("Localizable", "settings.reset.footer", fallback: "This will reset all of the above settings to default.") + /// RESET + internal static let title = L10n.tr("Localizable", "settings.reset.title", fallback: "RESET") + internal enum Defaults { + /// Reset settings to default + internal static let title = L10n.tr("Localizable", "settings.reset.defaults.title", fallback: "Reset settings to default") + internal enum Confirm { + /// Reset + internal static let button = L10n.tr("Localizable", "settings.reset.defaults.confirm.button", fallback: "Reset") + /// This will bring the app back to default. You will lose all changes you have made. + internal static let message = L10n.tr("Localizable", "settings.reset.defaults.confirm.message", fallback: "This will bring the app back to default. You will lose all changes you have made.") + /// Reset settings + internal static let title = L10n.tr("Localizable", "settings.reset.defaults.confirm.title", fallback: "Reset settings") + } + } } - internal enum Handshake { - /// Handshake - internal static let title = L10n.tr("Localizable", "settings.encryption.handshake.title") + internal enum Section { + /// Automation + internal static let automation = L10n.tr("Localizable", "settings.section.automation", fallback: "Automation") + /// General + internal static let general = L10n.tr("Localizable", "settings.section.general", fallback: "General") + /// Help + internal static let help = L10n.tr("Localizable", "settings.section.help", fallback: "Help") + /// Network + internal static let network = L10n.tr("Localizable", "settings.section.network", fallback: "Network") + /// Privacy Features + internal static let privacyFeatures = L10n.tr("Localizable", "settings.section.privacyFeatures", fallback: "Privacy Features") + /// Protocols + internal static let protocols = L10n.tr("Localizable", "settings.section.protocols", fallback: "Protocols") + } + internal enum Server { + internal enum Network { + /// The VPN has to be disconnected to change the server network. + internal static let alert = L10n.tr("Localizable", "settings.server.network.alert", fallback: "The VPN has to be disconnected to change the server network.") + /// Next generation network + internal static let description = L10n.tr("Localizable", "settings.server.network.description", fallback: "Next generation network") + } } - } - internal enum Geo { - internal enum Servers { - /// Show Geo-located Regions - internal static let description = L10n.tr("Localizable", "settings.geo.servers.description") + internal enum Service { + internal enum Quality { + internal enum Share { + /// Help us improve by sharing VPN connection statistics. These reports never contain personally identifiable information. + internal static let description = L10n.tr("Localizable", "settings.service.quality.share.description", fallback: "Help us improve by sharing VPN connection statistics. These reports never contain personally identifiable information.") + /// Find out more + internal static let findoutmore = L10n.tr("Localizable", "settings.service.quality.share.findoutmore", fallback: "Find out more") + /// Help improve PIA + internal static let title = L10n.tr("Localizable", "settings.service.quality.share.title", fallback: "Help improve PIA") + } + internal enum Show { + /// Connection stats + internal static let title = L10n.tr("Localizable", "settings.service.quality.show.title", fallback: "Connection stats") + } + } + } + internal enum Small { + internal enum Packets { + /// Will slightly lower the IP packet size to improve compatibility with some routers and mobile networks. + internal static let description = L10n.tr("Localizable", "settings.small.packets.description", fallback: "Will slightly lower the IP packet size to improve compatibility with some routers and mobile networks.") + /// Use Small Packets + internal static let title = L10n.tr("Localizable", "settings.small.packets.title", fallback: "Use Small Packets") + } + } + internal enum Trusted { + internal enum Networks { + /// PIA won't automatically connect on these networks. + internal static let message = L10n.tr("Localizable", "settings.trusted.networks.message", fallback: "PIA won't automatically connect on these networks.") + internal enum Connect { + /// Protect this network by connecting to VPN? + internal static let message = L10n.tr("Localizable", "settings.trusted.networks.connect.message", fallback: "Protect this network by connecting to VPN?") + } + internal enum Sections { + /// Available networks + internal static let available = L10n.tr("Localizable", "settings.trusted.networks.sections.available", fallback: "Available networks") + /// Current network + internal static let current = L10n.tr("Localizable", "settings.trusted.networks.sections.current", fallback: "Current network") + /// Trusted networks + internal static let trusted = L10n.tr("Localizable", "settings.trusted.networks.sections.trusted", fallback: "Trusted networks") + /// Untrusted networks + internal static let untrusted = L10n.tr("Localizable", "settings.trusted.networks.sections.untrusted", fallback: "Untrusted networks") + internal enum Trusted { + internal enum Rule { + /// Disconnect from PIA VPN + internal static let action = L10n.tr("Localizable", "settings.trusted.networks.sections.trusted.rule.action", fallback: "Disconnect from PIA VPN") + /// Enable this feature, with the VPN kill switch enabled, to customize how PIA will behave on WiFi and cellular networks. Please be aware, functionality of the Network Management Tool will be disabled if you manually disconnect. + internal static let description = L10n.tr("Localizable", "settings.trusted.networks.sections.trusted.rule.description", fallback: "Enable this feature, with the VPN kill switch enabled, to customize how PIA will behave on WiFi and cellular networks. Please be aware, functionality of the Network Management Tool will be disabled if you manually disconnect.") + } + } + } + } } } - internal enum Hotspothelper { - /// Configure how PIA will behaves on connection to WiFi or cellular networks. This excludes disconnecting manually. - internal static let description = L10n.tr("Localizable", "settings.hotspothelper.description") - /// Network management tool - internal static let title = L10n.tr("Localizable", "settings.hotspothelper.title") - internal enum All { - /// VPN WiFi Protection will activate on all networks, including trusted networks. - internal static let description = L10n.tr("Localizable", "settings.hotspothelper.all.description") - /// Protect all networks - internal static let title = L10n.tr("Localizable", "settings.hotspothelper.all.title") - } - internal enum Available { - /// To populate this list go to iOS Settings > WiFi. - internal static let help = L10n.tr("Localizable", "settings.hotspothelper.available.help") + internal enum Shortcuts { + /// Connect + internal static let connect = L10n.tr("Localizable", "shortcuts.connect", fallback: "Connect") + /// Disconnect + internal static let disconnect = L10n.tr("Localizable", "shortcuts.disconnect", fallback: "Disconnect") + /// Select a region + internal static let selectRegion = L10n.tr("Localizable", "shortcuts.select_region", fallback: "Select a region") + } + internal enum Siri { + internal enum Shortcuts { internal enum Add { - /// Tap + to add to Trusted networks. - internal static let help = L10n.tr("Localizable", "settings.hotspothelper.available.add.help") + /// There was an error adding the Siri shortcut. Please, try it again. + internal static let error = L10n.tr("Localizable", "siri.shortcuts.add.error", fallback: "There was an error adding the Siri shortcut. Please, try it again.") + } + internal enum Connect { + /// Connect PIA VPN + internal static let title = L10n.tr("Localizable", "siri.shortcuts.connect.title", fallback: "Connect PIA VPN") + internal enum Row { + /// 'Connect' Siri Shortcut + internal static let title = L10n.tr("Localizable", "siri.shortcuts.connect.row.title", fallback: "'Connect' Siri Shortcut") + } + } + internal enum Disconnect { + /// Disconnect PIA VPN + internal static let title = L10n.tr("Localizable", "siri.shortcuts.disconnect.title", fallback: "Disconnect PIA VPN") + internal enum Row { + /// 'Disconnect' Siri Shortcut + internal static let title = L10n.tr("Localizable", "siri.shortcuts.disconnect.row.title", fallback: "'Disconnect' Siri Shortcut") + } } } - internal enum Cellular { - /// PIA automatically enables the VPN when connecting to cellular networks if this option is enabled. - internal static let description = L10n.tr("Localizable", "settings.hotspothelper.cellular.description") - /// Cellular networks - internal static let networks = L10n.tr("Localizable", "settings.hotspothelper.cellular.networks") - /// Protect over cellular networks - internal static let title = L10n.tr("Localizable", "settings.hotspothelper.cellular.title") - } - internal enum Enable { - /// PIA automatically enables the VPN when connecting to untrusted WiFi networks if this option is enabled. - internal static let description = L10n.tr("Localizable", "settings.hotspothelper.enable.description") + } + internal enum Tiles { + internal enum Accessibility { + internal enum Invisible { + internal enum Tile { + /// Tap to add this tile to the dashboard + internal static let action = L10n.tr("Localizable", "tiles.accessibility.invisible.tile.action", fallback: "Tap to add this tile to the dashboard") + } + } + internal enum Visible { + internal enum Tile { + /// Tap to remove this tile from the dashboard + internal static let action = L10n.tr("Localizable", "tiles.accessibility.visible.tile.action", fallback: "Tap to remove this tile from the dashboard") + } + } } - internal enum Rules { - /// Rules - internal static let title = L10n.tr("Localizable", "settings.hotspothelper.rules.title") + internal enum Favorite { + internal enum Servers { + /// Favorite servers + internal static let title = L10n.tr("Localizable", "tiles.favorite.servers.title", fallback: "Favorite servers") + } } - internal enum Wifi { - /// WiFi networks - internal static let networks = L10n.tr("Localizable", "settings.hotspothelper.wifi.networks") - internal enum Trust { - /// VPN WiFi Protection - internal static let title = L10n.tr("Localizable", "settings.hotspothelper.wifi.trust.title") + internal enum Nmt { + /// Cellular + internal static let cellular = L10n.tr("Localizable", "tiles.nmt.cellular", fallback: "Cellular") + internal enum Accessibility { + /// Trusted network + internal static let trusted = L10n.tr("Localizable", "tiles.nmt.accessibility.trusted", fallback: "Trusted network") + /// Untrusted network + internal static let untrusted = L10n.tr("Localizable", "tiles.nmt.accessibility.untrusted", fallback: "Untrusted network") } } - } - internal enum Log { - /// Save debug logs which can be submitted to technical support to help troubleshoot problems. - internal static let information = L10n.tr("Localizable", "settings.log.information") - internal enum Connected { - /// A VPN connection is required. Please connect to the VPN and retry. - internal static let error = L10n.tr("Localizable", "settings.log.connected.error") + internal enum Quick { + internal enum Connect { + /// Quick connect + internal static let title = L10n.tr("Localizable", "tiles.quick.connect.title", fallback: "Quick connect") + } } - } - internal enum Nmt { - internal enum Killswitch { - /// The VPN kill switch is currently disabled. In order to ensure that the Network Management Tool is functioning, and that you are able to reconnect when switching networks, please enable the VPN kill switch in your settings. - internal static let disabled = L10n.tr("Localizable", "settings.nmt.killswitch.disabled") + internal enum Quicksetting { + internal enum Nmt { + /// Network Management + internal static let title = L10n.tr("Localizable", "tiles.quicksetting.nmt.title", fallback: "Network Management") + } + internal enum Private { + internal enum Browser { + /// Private Browser + internal static let title = L10n.tr("Localizable", "tiles.quicksetting.private.browser.title", fallback: "Private Browser") + } + } } - internal enum Optout { - internal enum Disconnect { - /// Opt-out disconnect confirmation alert - internal static let alerts = L10n.tr("Localizable", "settings.nmt.optout.disconnect.alerts") - internal enum Alerts { - /// Disables the warning alert when disconnecting from the VPN. - internal static let description = L10n.tr("Localizable", "settings.nmt.optout.disconnect.alerts.description") + internal enum Quicksettings { + /// Quick settings + internal static let title = L10n.tr("Localizable", "tiles.quicksettings.title", fallback: "Quick settings") + internal enum Min { + internal enum Elements { + /// You should keep at least one element visible in the Quick Settings Tile + internal static let message = L10n.tr("Localizable", "tiles.quicksettings.min.elements.message", fallback: "You should keep at least one element visible in the Quick Settings Tile") } } } - internal enum Wireguard { - /// WireGuard® doesn't need to reconnect when you switch between different networks. It may be necessary to manually disconnect the VPN on trusted networks. - internal static let warning = L10n.tr("Localizable", "settings.nmt.wireguard.warning") + internal enum Region { + /// VPN Server + internal static let title = L10n.tr("Localizable", "tiles.region.title", fallback: "VPN Server") + } + internal enum Subscription { + /// Monthly + internal static let monthly = L10n.tr("Localizable", "tiles.subscription.monthly", fallback: "Monthly") + /// Subscription + internal static let title = L10n.tr("Localizable", "tiles.subscription.title", fallback: "Subscription") + /// Trial + internal static let trial = L10n.tr("Localizable", "tiles.subscription.trial", fallback: "Trial") + /// Yearly + internal static let yearly = L10n.tr("Localizable", "tiles.subscription.yearly", fallback: "Yearly") + internal enum Days { + /// (%d days left) + internal static func `left`(_ p1: Int) -> String { + return L10n.tr("Localizable", "tiles.subscription.days.left", p1, fallback: "(%d days left)") + } + } } - } - internal enum Ovpn { - internal enum Migration { - /// We are updating our OpenVPN implementation, for more information, click here - internal static let footer = L10n.tr("Localizable", "settings.ovpn.migration.footer") - internal enum Footer { - /// here - internal static let link = L10n.tr("Localizable", "settings.ovpn.migration.footer.link") + internal enum Usage { + /// Download + internal static let download = L10n.tr("Localizable", "tiles.usage.download", fallback: "Download") + /// Usage + internal static let title = L10n.tr("Localizable", "tiles.usage.title", fallback: "Usage") + /// Upload + internal static let upload = L10n.tr("Localizable", "tiles.usage.upload", fallback: "Upload") + internal enum Ipsec { + /// USAGE (Not available on IKEv2) + internal static let title = L10n.tr("Localizable", "tiles.usage.ipsec.title", fallback: "USAGE (Not available on IKEv2)") } } } - internal enum Preview { - /// Preview - internal static let title = L10n.tr("Localizable", "settings.preview.title") + internal enum Today { + internal enum Widget { + /// Login + internal static let login = L10n.tr("Localizable", "today.widget.login", fallback: "Login") + } } - internal enum Reset { - /// This will reset all of the above settings to default. - internal static let footer = L10n.tr("Localizable", "settings.reset.footer") - /// RESET - internal static let title = L10n.tr("Localizable", "settings.reset.title") - internal enum Defaults { - /// Reset settings to default - internal static let title = L10n.tr("Localizable", "settings.reset.defaults.title") - internal enum Confirm { - /// Reset - internal static let button = L10n.tr("Localizable", "settings.reset.defaults.confirm.button") - /// This will bring the app back to default. You will lose all changes you have made. - internal static let message = L10n.tr("Localizable", "settings.reset.defaults.confirm.message") - /// Reset settings - internal static let title = L10n.tr("Localizable", "settings.reset.defaults.confirm.title") + internal enum VpnPermission { + /// PIA + internal static let title = L10n.tr("Localizable", "vpn_permission.title", fallback: "PIA") + internal enum Body { + /// We don’t monitor, filter or log any network activity. + internal static let footer = L10n.tr("Localizable", "vpn_permission.body.footer", fallback: "We don’t monitor, filter or log any network activity.") + /// You’ll see a prompt for PIA VPN and need to allow access to VPN configurations. + /// To proceed tap on “%@”. + internal static func subtitle(_ p1: Any) -> String { + return L10n.tr("Localizable", "vpn_permission.body.subtitle", String(describing: p1), fallback: "You’ll see a prompt for PIA VPN and need to allow access to VPN configurations.\nTo proceed tap on “%@”.") + } + /// PIA needs access to your VPN profiles to secure your traffic + internal static let title = L10n.tr("Localizable", "vpn_permission.body.title", fallback: "PIA needs access to your VPN profiles to secure your traffic") + } + internal enum Disallow { + /// Contact + internal static let contact = L10n.tr("Localizable", "vpn_permission.disallow.contact", fallback: "Contact") + internal enum Message { + /// We need this permission for the application to function. + internal static let basic = L10n.tr("Localizable", "vpn_permission.disallow.message.basic", fallback: "We need this permission for the application to function.") + /// You can also get in touch with customer support if you need assistance. + internal static let support = L10n.tr("Localizable", "vpn_permission.disallow.message.support", fallback: "You can also get in touch with customer support if you need assistance.") } } } - internal enum Section { - /// Automation - internal static let automation = L10n.tr("Localizable", "settings.section.automation") - /// General - internal static let general = L10n.tr("Localizable", "settings.section.general") - /// Help - internal static let help = L10n.tr("Localizable", "settings.section.help") - /// Network - internal static let network = L10n.tr("Localizable", "settings.section.network") - /// Privacy Features - internal static let privacyFeatures = L10n.tr("Localizable", "settings.section.privacyFeatures") - /// Protocols - internal static let protocols = L10n.tr("Localizable", "settings.section.protocols") - } - internal enum Server { - internal enum Network { - /// The VPN has to be disconnected to change the server network. - internal static let alert = L10n.tr("Localizable", "settings.server.network.alert") - /// Next generation network - internal static let description = L10n.tr("Localizable", "settings.server.network.description") + internal enum Widget { + internal enum LiveActivity { + internal enum SelectedProtocol { + /// Protocol + internal static let title = L10n.tr("Localizable", "widget.liveActivity.protocol.title", fallback: "Protocol") + } + internal enum Region { + /// Region + internal static let title = L10n.tr("Localizable", "widget.liveActivity.region.title", fallback: "Region") + } } } - internal enum Service { - internal enum Quality { - internal enum Share { - /// Help us improve by sharing VPN connection statistics. These reports never contain personally identifiable information. - internal static let description = L10n.tr("Localizable", "settings.service.quality.share.description") - /// Find out more - internal static let findoutmore = L10n.tr("Localizable", "settings.service.quality.share.findoutmore") - /// Help improve PIA - internal static let title = L10n.tr("Localizable", "settings.service.quality.share.title") + } + internal enum Signup { + internal enum Failure { + /// We're unable to create an account at this time. Please try again later. Reopening the app will re-attempt to create an account. + internal static let message = L10n.tr("Signup", "failure.message", fallback: "We're unable to create an account at this time. Please try again later. Reopening the app will re-attempt to create an account.") + /// GO BACK + internal static let submit = L10n.tr("Signup", "failure.submit", fallback: "GO BACK") + /// Account creation failed + internal static let title = L10n.tr("Signup", "failure.title", fallback: "Account creation failed") + /// Sign-up failed + internal static let vcTitle = L10n.tr("Signup", "failure.vc_title", fallback: "Sign-up failed") + internal enum Purchase { + internal enum Sandbox { + /// The selected sandbox subscription is not available in production. + internal static let message = L10n.tr("Signup", "failure.purchase.sandbox.message", fallback: "The selected sandbox subscription is not available in production.") + } + } + internal enum Redeem { + internal enum Claimed { + /// Looks like this card has already been claimed by another account. You can try entering a different PIN. + internal static let message = L10n.tr("Signup", "failure.redeem.claimed.message", fallback: "Looks like this card has already been claimed by another account. You can try entering a different PIN.") + /// Card claimed already + internal static let title = L10n.tr("Signup", "failure.redeem.claimed.title", fallback: "Card claimed already") } - internal enum Show { - /// Connection stats - internal static let title = L10n.tr("Localizable", "settings.service.quality.show.title") + internal enum Invalid { + /// Looks like you entered an invalid card PIN. Please try again. + internal static let message = L10n.tr("Signup", "failure.redeem.invalid.message", fallback: "Looks like you entered an invalid card PIN. Please try again.") + /// Invalid card PIN + internal static let title = L10n.tr("Signup", "failure.redeem.invalid.title", fallback: "Invalid card PIN") } } } - internal enum Small { - internal enum Packets { - /// Will slightly lower the IP packet size to improve compatibility with some routers and mobile networks. - internal static let description = L10n.tr("Localizable", "settings.small.packets.description") - /// Use Small Packets - internal static let title = L10n.tr("Localizable", "settings.small.packets.title") + internal enum InProgress { + /// We're confirming your purchase with our system. It could take a moment so hang in there. + internal static let message = L10n.tr("Signup", "in_progress.message", fallback: "We're confirming your purchase with our system. It could take a moment so hang in there.") + /// Signup.strings + /// PIALibrary + /// + /// Created by Davide De Rosa on 12/7/17. + /// Copyright © 2017 London Trust Media. All rights reserved. + internal static let title = L10n.tr("Signup", "in_progress.title", fallback: "Confirm sign-up") + internal enum Redeem { + /// We're confirming your card PIN with our system. It could take a moment so hang in there. + internal static let message = L10n.tr("Signup", "in_progress.redeem.message", fallback: "We're confirming your card PIN with our system. It could take a moment so hang in there.") + } + } + internal enum Purchase { + internal enum Subscribe { + /// Subscribe now + internal static let now = L10n.tr("Signup", "purchase.subscribe.now", fallback: "Subscribe now") + } + internal enum Trials { + /// Browse anonymously and hide your ip. + internal static let anonymous = L10n.tr("Signup", "purchase.trials.anonymous", fallback: "Browse anonymously and hide your ip.") + /// Support 10 devices at once + internal static let devices = L10n.tr("Signup", "purchase.trials.devices", fallback: "Support 10 devices at once") + /// Start your 7-day free trial + internal static let intro = L10n.tr("Signup", "purchase.trials.intro", fallback: "Start your 7-day free trial") + /// Connect to any region easily + internal static let region = L10n.tr("Signup", "purchase.trials.region", fallback: "Connect to any region easily") + /// More than 3300 servers in 32 countries + internal static let servers = L10n.tr("Signup", "purchase.trials.servers", fallback: "More than 3300 servers in 32 countries") + /// Start subscription + internal static let start = L10n.tr("Signup", "purchase.trials.start", fallback: "Start subscription") + internal enum _1year { + /// 1 year of privacy and identity protection + internal static let protection = L10n.tr("Signup", "purchase.trials.1year.protection", fallback: "1 year of privacy and identity protection") + } + internal enum All { + /// See all available plans + internal static let plans = L10n.tr("Signup", "purchase.trials.all.plans", fallback: "See all available plans") + } + internal enum Devices { + /// Protect yourself on up to 10 devices at a time. + internal static let description = L10n.tr("Signup", "purchase.trials.devices.description", fallback: "Protect yourself on up to 10 devices at a time.") + } + internal enum Money { + /// 30 day money back guarantee + internal static let back = L10n.tr("Signup", "purchase.trials.money.back", fallback: "30 day money back guarantee") + } + internal enum Price { + /// Then %@ + internal static func after(_ p1: Any) -> String { + return L10n.tr("Signup", "purchase.trials.price.after", String(describing: p1), fallback: "Then %@") + } + } + } + internal enum Uncredited { + internal enum Alert { + /// You have uncredited transactions. Do you want to recover your account details? + internal static let message = L10n.tr("Signup", "purchase.uncredited.alert.message", fallback: "You have uncredited transactions. Do you want to recover your account details?") + internal enum Button { + /// Cancel + internal static let cancel = L10n.tr("Signup", "purchase.uncredited.alert.button.cancel", fallback: "Cancel") + /// Recover account + internal static let recover = L10n.tr("Signup", "purchase.uncredited.alert.button.recover", fallback: "Recover account") + } + } } } - internal enum Trusted { - internal enum Networks { - /// PIA won't automatically connect on these networks. - internal static let message = L10n.tr("Localizable", "settings.trusted.networks.message") - internal enum Connect { - /// Protect this network by connecting to VPN? - internal static let message = L10n.tr("Localizable", "settings.trusted.networks.connect.message") - } - internal enum Sections { - /// Available networks - internal static let available = L10n.tr("Localizable", "settings.trusted.networks.sections.available") - /// Current network - internal static let current = L10n.tr("Localizable", "settings.trusted.networks.sections.current") - /// Trusted networks - internal static let trusted = L10n.tr("Localizable", "settings.trusted.networks.sections.trusted") - /// Untrusted networks - internal static let untrusted = L10n.tr("Localizable", "settings.trusted.networks.sections.untrusted") - internal enum Trusted { - internal enum Rule { - /// Disconnect from PIA VPN - internal static let action = L10n.tr("Localizable", "settings.trusted.networks.sections.trusted.rule.action") - /// Enable this feature, with the VPN kill switch enabled, to customize how PIA will behave on WiFi and cellular networks. Please be aware, functionality of the Network Management Tool will be disabled if you manually disconnect. - internal static let description = L10n.tr("Localizable", "settings.trusted.networks.sections.trusted.rule.description") - } + internal enum Share { + internal enum Data { + internal enum Buttons { + /// Accept + internal static let accept = L10n.tr("Signup", "share.data.buttons.accept", fallback: "Accept") + /// No, thanks + internal static let noThanks = L10n.tr("Signup", "share.data.buttons.noThanks", fallback: "No, thanks") + /// Read more + internal static let readMore = L10n.tr("Signup", "share.data.buttons.readMore", fallback: "Read more") + } + internal enum ReadMore { + internal enum Text { + /// This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + /// + /// We will collect information about the following events: + /// + /// - Connection Attempt + /// - Connection Canceled + /// - Connection Established + /// + /// For all of these events, we will collect the following information: + /// - Platform + /// - App version + /// - App type (pre-release or not) + /// - Protocol used + /// - Connection source (manual or using automation) + /// - Time To Connect (time between connecting and connected state) + /// + /// All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + /// + /// You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time. + internal static let description = L10n.tr("Signup", "share.data.readMore.text.description", fallback: "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default.\n\nWe will collect information about the following events:\n\n - Connection Attempt\n - Connection Canceled\n - Connection Established\n\nFor all of these events, we will collect the following information:\n - Platform\n - App version\n - App type (pre-release or not)\n - Protocol used\n - Connection source (manual or using automation)\n - Time To Connect (time between connecting and connected state)\n\nAll events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes.\n\nYou will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time.") } } + internal enum Text { + /// To help us ensure our service's connection performance, you can anonymously share your connection stats with us. These reports do not include any personally identifiable information. + internal static let description = L10n.tr("Signup", "share.data.text.description", fallback: "To help us ensure our service's connection performance, you can anonymously share your connection stats with us. These reports do not include any personally identifiable information.") + /// You can always control this from your settings + internal static let footer = L10n.tr("Signup", "share.data.text.footer", fallback: "You can always control this from your settings") + /// Please help us improve our service + internal static let title = L10n.tr("Signup", "share.data.text.title", fallback: "Please help us improve our service") + } + } + } + internal enum Success { + /// Thank you for signing up with us. We have sent your account username and password at your email address at %@ + internal static func messageFormat(_ p1: Any) -> String { + return L10n.tr("Signup", "success.message_format", String(describing: p1), fallback: "Thank you for signing up with us. We have sent your account username and password at your email address at %@") + } + /// GET STARTED + internal static let submit = L10n.tr("Signup", "success.submit", fallback: "GET STARTED") + /// Purchase complete + internal static let title = L10n.tr("Signup", "success.title", fallback: "Purchase complete") + internal enum Password { + /// Password + internal static let caption = L10n.tr("Signup", "success.password.caption", fallback: "Password") + } + internal enum Redeem { + /// You will receive an email shortly with your username and password. + /// + /// Your login details + internal static let message = L10n.tr("Signup", "success.redeem.message", fallback: "You will receive an email shortly with your username and password.\n\nYour login details") + /// Card redeemed successfully + internal static let title = L10n.tr("Signup", "success.redeem.title", fallback: "Card redeemed successfully") + } + internal enum Username { + /// Username + internal static let caption = L10n.tr("Signup", "success.username.caption", fallback: "Username") + } + } + internal enum Unreachable { + /// No internet connection found. Please confirm that you have an internet connection and hit retry below. + /// + /// You can come back to the app later to finish the process. + internal static let message = L10n.tr("Signup", "unreachable.message", fallback: "No internet connection found. Please confirm that you have an internet connection and hit retry below.\n\nYou can come back to the app later to finish the process.") + /// TRY AGAIN + internal static let submit = L10n.tr("Signup", "unreachable.submit", fallback: "TRY AGAIN") + /// Whoops! + internal static let title = L10n.tr("Signup", "unreachable.title", fallback: "Whoops!") + /// Error + internal static let vcTitle = L10n.tr("Signup", "unreachable.vc_title", fallback: "Error") + } + internal enum Walkthrough { + internal enum Action { + /// DONE + internal static let done = L10n.tr("Signup", "walkthrough.action.done", fallback: "DONE") + /// NEXT + internal static let next = L10n.tr("Signup", "walkthrough.action.next", fallback: "NEXT") + /// SKIP + internal static let skip = L10n.tr("Signup", "walkthrough.action.skip", fallback: "SKIP") + } + internal enum Page { + internal enum _1 { + /// Protect yourself on up to 10 devices at a time. + internal static let description = L10n.tr("Signup", "walkthrough.page.1.description", fallback: "Protect yourself on up to 10 devices at a time.") + /// Support 10 devices at once + internal static let title = L10n.tr("Signup", "walkthrough.page.1.title", fallback: "Support 10 devices at once") + } + internal enum _2 { + /// With servers around the globe, you are always under protection. + internal static let description = L10n.tr("Signup", "walkthrough.page.2.description", fallback: "With servers around the globe, you are always under protection.") + /// Connect to any region easily + internal static let title = L10n.tr("Signup", "walkthrough.page.2.title", fallback: "Connect to any region easily") + } + internal enum _3 { + /// Enabling our Content Blocker prevents ads from showing in Safari. + internal static let description = L10n.tr("Signup", "walkthrough.page.3.description", fallback: "Enabling our Content Blocker prevents ads from showing in Safari.") + /// Protect yourself from ads + internal static let title = L10n.tr("Signup", "walkthrough.page.3.title", fallback: "Protect yourself from ads") + } } } } - - internal enum Shortcuts { - /// Connect - internal static let connect = L10n.tr("Localizable", "shortcuts.connect") - /// Disconnect - internal static let disconnect = L10n.tr("Localizable", "shortcuts.disconnect") - /// Select a region - internal static let selectRegion = L10n.tr("Localizable", "shortcuts.select_region") + internal enum Ui { + internal enum Global { + /// Cancel + internal static let cancel = L10n.tr("UI", "global.cancel", fallback: "Cancel") + /// Close + internal static let close = L10n.tr("UI", "global.close", fallback: "Close") + /// OK + internal static let ok = L10n.tr("UI", "global.ok", fallback: "OK") + internal enum Version { + /// Version %@ (%@) + internal static func format(_ p1: Any, _ p2: Any) -> String { + return L10n.tr("UI", "global.version.format", String(describing: p1), String(describing: p2), fallback: "Version %@ (%@)") + } + } + } } - - internal enum Siri { - internal enum Shortcuts { - internal enum Add { - /// There was an error adding the Siri shortcut. Please, try it again. - internal static let error = L10n.tr("Localizable", "siri.shortcuts.add.error") + internal enum Welcome { + internal enum Agreement { + /// After the 7 days free trial this subscription automatically renews for %@ unless it is canceled at least 24 hours before the end of the trial period. Your Apple ID account will be charged for renewal within 24 hours before the end of the trial period. You can manage and cancel your subscriptions by going to your App Store account settings after purchase. 7-days trial offer is limited to one 7-days trial offer per user. Any unused portion of a free trial period, if offered, will be forfeited when the user purchases a subscription. All prices include applicable local sales taxes. + /// + /// Signing up constitutes acceptance of the $1 and the $2. + internal static func message(_ p1: Any) -> String { + return L10n.tr("Welcome", "agreement.message", String(describing: p1), fallback: "After the 7 days free trial this subscription automatically renews for %@ unless it is canceled at least 24 hours before the end of the trial period. Your Apple ID account will be charged for renewal within 24 hours before the end of the trial period. You can manage and cancel your subscriptions by going to your App Store account settings after purchase. 7-days trial offer is limited to one 7-days trial offer per user. Any unused portion of a free trial period, if offered, will be forfeited when the user purchases a subscription. All prices include applicable local sales taxes.\n\nSigning up constitutes acceptance of the $1 and the $2.") } - internal enum Connect { - /// Connect PIA VPN - internal static let title = L10n.tr("Localizable", "siri.shortcuts.connect.title") - internal enum Row { - /// 'Connect' Siri Shortcut - internal static let title = L10n.tr("Localizable", "siri.shortcuts.connect.row.title") + internal enum Message { + /// Privacy Policy + internal static let privacy = L10n.tr("Welcome", "agreement.message.privacy", fallback: "Privacy Policy") + /// Terms of Service + internal static let tos = L10n.tr("Welcome", "agreement.message.tos", fallback: "Terms of Service") + } + internal enum Trials { + /// Payment will be charged to your Apple ID account at the confirmation of purchase. Subscription automatically renews unless it is canceled at least 24 hours before the end of the current period. Your account will be charged for renewal within 24 hours prior to the end of the current period. You can manage and cancel your subscriptions by going to your account settings on the App Store after purchase. + /// + /// Certain Paid Subscriptions may offer a free trial prior to charging your payment method. If you decide to unsubscribe from a Paid Subscription before we start charging your payment method, cancel the subscription at least 24 hours before the free trial ends. + /// + /// Free trials are only available to new users, and are at our sole discretion, and if you attempt to sign up for an additional free trial, you will be immediately charged with the standard Subscription Fee. + /// + /// We reserve the right to revoke your free trial at any time. + /// + /// Any unused portion of your free trial period will be forfeited upon purchase of a subscription. + /// + /// Signing up constitutes acceptance of this terms and conditions. + internal static let message = L10n.tr("Welcome", "agreement.trials.message", fallback: "Payment will be charged to your Apple ID account at the confirmation of purchase. Subscription automatically renews unless it is canceled at least 24 hours before the end of the current period. Your account will be charged for renewal within 24 hours prior to the end of the current period. You can manage and cancel your subscriptions by going to your account settings on the App Store after purchase.\n\nCertain Paid Subscriptions may offer a free trial prior to charging your payment method. If you decide to unsubscribe from a Paid Subscription before we start charging your payment method, cancel the subscription at least 24 hours before the free trial ends.\n\nFree trials are only available to new users, and are at our sole discretion, and if you attempt to sign up for an additional free trial, you will be immediately charged with the standard Subscription Fee.\n\nWe reserve the right to revoke your free trial at any time.\n\nAny unused portion of your free trial period will be forfeited upon purchase of a subscription.\n\nSigning up constitutes acceptance of this terms and conditions.") + /// Free trials terms and conditions + internal static let title = L10n.tr("Welcome", "agreement.trials.title", fallback: "Free trials terms and conditions") + internal enum Monthly { + /// month + internal static let plan = L10n.tr("Welcome", "agreement.trials.monthly.plan", fallback: "month") } - } - internal enum Disconnect { - /// Disconnect PIA VPN - internal static let title = L10n.tr("Localizable", "siri.shortcuts.disconnect.title") - internal enum Row { - /// 'Disconnect' Siri Shortcut - internal static let title = L10n.tr("Localizable", "siri.shortcuts.disconnect.row.title") + internal enum Yearly { + /// year + internal static let plan = L10n.tr("Welcome", "agreement.trials.yearly.plan", fallback: "year") } } } - } - - internal enum Tiles { - internal enum Accessibility { - internal enum Invisible { - internal enum Tile { - /// Tap to add this tile to the dashboard - internal static let action = L10n.tr("Localizable", "tiles.accessibility.invisible.tile.action") + internal enum Gdpr { + internal enum Accept { + internal enum Button { + /// Agree and continue + internal static let title = L10n.tr("Welcome", "gdpr.accept.button.title", fallback: "Agree and continue") + } + } + internal enum Collect { + internal enum Data { + /// E-mail Address for the purposes of account management and protection from abuse. + internal static let description = L10n.tr("Welcome", "gdpr.collect.data.description", fallback: "E-mail Address for the purposes of account management and protection from abuse.") + /// Personal information we collect + internal static let title = L10n.tr("Welcome", "gdpr.collect.data.title", fallback: "Personal information we collect") } } - internal enum Visible { - internal enum Tile { - /// Tap to remove this tile from the dashboard - internal static let action = L10n.tr("Localizable", "tiles.accessibility.visible.tile.action") + internal enum Usage { + internal enum Data { + /// E-mail address is used to send subscription information, payment confirmations, customer correspondence, and Private Internet Access promotional offers only. + internal static let description = L10n.tr("Welcome", "gdpr.usage.data.description", fallback: "E-mail address is used to send subscription information, payment confirmations, customer correspondence, and Private Internet Access promotional offers only.") + /// Uses of personal information collected by us + internal static let title = L10n.tr("Welcome", "gdpr.usage.data.title", fallback: "Uses of personal information collected by us") } } } - internal enum Favorite { - internal enum Servers { - /// Favorite servers - internal static let title = L10n.tr("Localizable", "tiles.favorite.servers.title") + internal enum Getstarted { + internal enum Buttons { + /// Buy account + internal static let buyaccount = L10n.tr("Welcome", "getstarted.buttons.buyaccount", fallback: "Buy account") } } - internal enum Nmt { - /// Cellular - internal static let cellular = L10n.tr("Localizable", "tiles.nmt.cellular") - internal enum Accessibility { - /// Trusted network - internal static let trusted = L10n.tr("Localizable", "tiles.nmt.accessibility.trusted") - /// Untrusted network - internal static let untrusted = L10n.tr("Localizable", "tiles.nmt.accessibility.untrusted") + internal enum Iap { + internal enum Error { + /// Error + internal static let title = L10n.tr("Welcome", "iap.error.title", fallback: "Error") + internal enum Message { + /// Apple servers currently unavailable. Please try again later. + internal static let unavailable = L10n.tr("Welcome", "iap.error.message.unavailable", fallback: "Apple servers currently unavailable. Please try again later.") + } } } - internal enum Quick { - internal enum Connect { - /// Quick connect - internal static let title = L10n.tr("Localizable", "tiles.quick.connect.title") + internal enum Login { + /// LOGIN + internal static let submit = L10n.tr("Welcome", "login.submit", fallback: "LOGIN") + /// Welcome.strings + /// PIALibrary + /// + /// Created by Davide De Rosa on 12/7/17. + /// Copyright © 2017 London Trust Media. All rights reserved. + internal static let title = L10n.tr("Welcome", "login.title", fallback: "Sign in to your account") + internal enum Error { + /// Too many failed login attempts with this username. Please try again after %@ second(s). + internal static func throttled(_ p1: Any) -> String { + return L10n.tr("Welcome", "login.error.throttled", String(describing: p1), fallback: "Too many failed login attempts with this username. Please try again after %@ second(s).") + } + /// Log in + internal static let title = L10n.tr("Welcome", "login.error.title", fallback: "Log in") + /// Your username or password is incorrect. + internal static let unauthorized = L10n.tr("Welcome", "login.error.unauthorized", fallback: "Your username or password is incorrect.") + /// You must enter a username and password. + internal static let validation = L10n.tr("Welcome", "login.error.validation", fallback: "You must enter a username and password.") + } + internal enum Magic { + internal enum Link { + /// Please check your e-mail for a login link. + internal static let response = L10n.tr("Welcome", "login.magic.link.response", fallback: "Please check your e-mail for a login link.") + /// Send Link + internal static let send = L10n.tr("Welcome", "login.magic.link.send", fallback: "Send Link") + /// Login using magic email link + internal static let title = L10n.tr("Welcome", "login.magic.link.title", fallback: "Login using magic email link") + internal enum Invalid { + /// Invalid email. Please try again. + internal static let email = L10n.tr("Welcome", "login.magic.link.invalid.email", fallback: "Invalid email. Please try again.") + } + } + } + internal enum Password { + /// Password + internal static let placeholder = L10n.tr("Welcome", "login.password.placeholder", fallback: "Password") + } + internal enum Receipt { + /// Login using purchase receipt + internal static let button = L10n.tr("Welcome", "login.receipt.button", fallback: "Login using purchase receipt") + } + internal enum Restore { + /// Didn't receive account details? + internal static let button = L10n.tr("Welcome", "login.restore.button", fallback: "Didn't receive account details?") + } + internal enum Username { + /// Username (p1234567) + internal static let placeholder = L10n.tr("Welcome", "login.username.placeholder", fallback: "Username (p1234567)") } } - internal enum Quicksetting { - internal enum Nmt { - /// Network Management - internal static let title = L10n.tr("Localizable", "tiles.quicksetting.nmt.title") + internal enum Plan { + /// Best value + internal static let bestValue = L10n.tr("Welcome", "plan.best_value", fallback: "Best value") + /// %@/mo + internal static func priceFormat(_ p1: Any) -> String { + return L10n.tr("Welcome", "plan.price_format", String(describing: p1), fallback: "%@/mo") + } + internal enum Accessibility { + /// per month + internal static let perMonth = L10n.tr("Welcome", "plan.accessibility.per_month", fallback: "per month") } - internal enum Private { - internal enum Browser { - /// Private Browser - internal static let title = L10n.tr("Localizable", "tiles.quicksetting.private.browser.title") + internal enum Monthly { + /// Monthly + internal static let title = L10n.tr("Welcome", "plan.monthly.title", fallback: "Monthly") + } + internal enum Yearly { + /// %@%@ per year + internal static func detailFormat(_ p1: Any, _ p2: Any) -> String { + return L10n.tr("Welcome", "plan.yearly.detail_format", String(describing: p1), String(describing: p2), fallback: "%@%@ per year") + } + /// Yearly + internal static let title = L10n.tr("Welcome", "plan.yearly.title", fallback: "Yearly") + } + } + internal enum Purchase { + /// Continue + internal static let `continue` = L10n.tr("Welcome", "purchase.continue", fallback: "Continue") + /// or + internal static let or = L10n.tr("Welcome", "purchase.or", fallback: "or") + /// Submit + internal static let submit = L10n.tr("Welcome", "purchase.submit", fallback: "Submit") + /// 30-day money back guarantee + internal static let subtitle = L10n.tr("Welcome", "purchase.subtitle", fallback: "30-day money back guarantee") + /// Select a VPN plan + internal static let title = L10n.tr("Welcome", "purchase.title", fallback: "Select a VPN plan") + internal enum Confirm { + /// You are purchasing the %@ plan + internal static func plan(_ p1: Any) -> String { + return L10n.tr("Welcome", "purchase.confirm.plan", String(describing: p1), fallback: "You are purchasing the %@ plan") + } + internal enum Form { + /// Enter your email address + internal static let email = L10n.tr("Welcome", "purchase.confirm.form.email", fallback: "Enter your email address") } } - } - internal enum Quicksettings { - /// Quick settings - internal static let title = L10n.tr("Localizable", "tiles.quicksettings.title") - internal enum Min { - internal enum Elements { - /// You should keep at least one element visible in the Quick Settings Tile - internal static let message = L10n.tr("Localizable", "tiles.quicksettings.min.elements.message") + internal enum Email { + /// Email address + internal static let placeholder = L10n.tr("Welcome", "purchase.email.placeholder", fallback: "Email address") + /// We need your email to send your username and password. + internal static let why = L10n.tr("Welcome", "purchase.email.why", fallback: "We need your email to send your username and password.") + } + internal enum Error { + /// Purchase + internal static let title = L10n.tr("Welcome", "purchase.error.title", fallback: "Purchase") + /// You must enter an email address. + internal static let validation = L10n.tr("Welcome", "purchase.error.validation", fallback: "You must enter an email address.") + internal enum Connectivity { + /// We are unable to reach Private Internet Access. This may due to poor internet or our service is blocked in your country. + internal static let description = L10n.tr("Welcome", "purchase.error.connectivity.description", fallback: "We are unable to reach Private Internet Access. This may due to poor internet or our service is blocked in your country.") + /// Connection Failure + internal static let title = L10n.tr("Welcome", "purchase.error.connectivity.title", fallback: "Connection Failure") } } + internal enum Login { + /// Sign in + internal static let button = L10n.tr("Welcome", "purchase.login.button", fallback: "Sign in") + /// Already have an account? + internal static let footer = L10n.tr("Welcome", "purchase.login.footer", fallback: "Already have an account?") + } } - internal enum Region { - /// VPN Server - internal static let title = L10n.tr("Localizable", "tiles.region.title") - } - internal enum Subscription { - /// Monthly - internal static let monthly = L10n.tr("Localizable", "tiles.subscription.monthly") - /// Subscription - internal static let title = L10n.tr("Localizable", "tiles.subscription.title") - /// Trial - internal static let trial = L10n.tr("Localizable", "tiles.subscription.trial") - /// Yearly - internal static let yearly = L10n.tr("Localizable", "tiles.subscription.yearly") - internal enum Days { - /// (%d days left) - internal static func `left`(_ p1: Int) -> String { - return L10n.tr("Localizable", "tiles.subscription.days.left", p1) + internal enum Redeem { + /// SUBMIT + internal static let submit = L10n.tr("Welcome", "redeem.submit", fallback: "SUBMIT") + /// Type in your email address and the %lu digit PIN from your gift card or trial card below. + internal static func subtitle(_ p1: Int) -> String { + return L10n.tr("Welcome", "redeem.subtitle", p1, fallback: "Type in your email address and the %lu digit PIN from your gift card or trial card below.") + } + /// Redeem gift card + internal static let title = L10n.tr("Welcome", "redeem.title", fallback: "Redeem gift card") + internal enum Accessibility { + /// Back + internal static let back = L10n.tr("Welcome", "redeem.accessibility.back", fallback: "Back") + } + internal enum Email { + /// Email address + internal static let placeholder = L10n.tr("Welcome", "redeem.email.placeholder", fallback: "Email address") + } + internal enum Error { + /// Please type in your email and card PIN. + internal static let allfields = L10n.tr("Welcome", "redeem.error.allfields", fallback: "Please type in your email and card PIN.") + /// Code must be %lu numeric digits. + internal static func code(_ p1: Int) -> String { + return L10n.tr("Welcome", "redeem.error.code", p1, fallback: "Code must be %lu numeric digits.") } + /// Redeem + internal static let title = L10n.tr("Welcome", "redeem.error.title", fallback: "Redeem") } - } - internal enum Usage { - /// Download - internal static let download = L10n.tr("Localizable", "tiles.usage.download") - /// Usage - internal static let title = L10n.tr("Localizable", "tiles.usage.title") - /// Upload - internal static let upload = L10n.tr("Localizable", "tiles.usage.upload") - internal enum Ipsec { - /// USAGE (Not available on IKEv2) - internal static let title = L10n.tr("Localizable", "tiles.usage.ipsec.title") + internal enum Giftcard { + /// Gift card PIN + internal static let placeholder = L10n.tr("Welcome", "redeem.giftcard.placeholder", fallback: "Gift card PIN") } } - } - - internal enum Today { - internal enum Widget { - /// Login - internal static let login = L10n.tr("Localizable", "today.widget.login") - } - } - - internal enum VpnPermission { - /// PIA - internal static let title = L10n.tr("Localizable", "vpn_permission.title") - internal enum Body { - /// We don’t monitor, filter or log any network activity. - internal static let footer = L10n.tr("Localizable", "vpn_permission.body.footer") - /// You’ll see a prompt for PIA VPN and need to allow access to VPN configurations.\nTo proceed tap on “%@”. - internal static func subtitle(_ p1: Any) -> String { - return L10n.tr("Localizable", "vpn_permission.body.subtitle", String(describing: p1)) - } - /// PIA needs access to your VPN profiles to secure your traffic - internal static let title = L10n.tr("Localizable", "vpn_permission.body.title") - } - internal enum Disallow { - /// Contact - internal static let contact = L10n.tr("Localizable", "vpn_permission.disallow.contact") - internal enum Message { - /// We need this permission for the application to function. - internal static let basic = L10n.tr("Localizable", "vpn_permission.disallow.message.basic") - /// You can also get in touch with customer support if you need assistance. - internal static let support = L10n.tr("Localizable", "vpn_permission.disallow.message.support") + internal enum Restore { + /// CONFIRM + internal static let submit = L10n.tr("Welcome", "restore.submit", fallback: "CONFIRM") + /// If you purchased a plan through this app and didn't receive your credentials, you can send them again from here. You will not be charged during this process. + internal static let subtitle = L10n.tr("Welcome", "restore.subtitle", fallback: "If you purchased a plan through this app and didn't receive your credentials, you can send them again from here. You will not be charged during this process.") + /// Restore uncredited purchase + internal static let title = L10n.tr("Welcome", "restore.title", fallback: "Restore uncredited purchase") + internal enum Email { + /// Email address + internal static let placeholder = L10n.tr("Welcome", "restore.email.placeholder", fallback: "Email address") } } - } - - internal enum Widget { - internal enum LiveActivity { - internal enum SelectedProtocol { - /// Protocol - internal static let title = L10n.tr("Localizable", "widget.liveActivity.protocol.title", fallback: "Protocol") + internal enum Update { + internal enum Account { + internal enum Email { + /// Failed to modify account email + internal static let error = L10n.tr("Welcome", "update.account.email.error", fallback: "Failed to modify account email") + } } - internal enum Region { - /// Region - internal static let title = L10n.tr("Localizable", "widget.liveActivity.region.title", fallback: "Region") + } + internal enum Upgrade { + /// Welcome Back! + internal static let header = L10n.tr("Welcome", "upgrade.header", fallback: "Welcome Back!") + /// In order to use Private Internet Access, you’ll need to renew your subscription. + internal static let title = L10n.tr("Welcome", "upgrade.title", fallback: "In order to use Private Internet Access, you’ll need to renew your subscription.") + internal enum Renew { + /// Renew now + internal static let now = L10n.tr("Welcome", "upgrade.renew.now", fallback: "Renew now") } } } - } // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces @@ -1337,7 +1807,7 @@ internal enum L10n { // MARK: - Implementation Details extension L10n { - private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String? = nil) -> String { + private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String { let format = BundleToken.bundle.localizedString(forKey: key, value: value, table: table) return String(format: format, locale: Locale.current, arguments: args) } diff --git a/PIA VPN/TermsAndConditionsViewController.swift b/PIA VPN/TermsAndConditionsViewController.swift new file mode 100644 index 000000000..b5b7bc691 --- /dev/null +++ b/PIA VPN/TermsAndConditionsViewController.swift @@ -0,0 +1,63 @@ +// +// TermsAndConditionsViewController.swift +// PIALibrary-iOS +// +// Created by Jose Antonio Blaya Garcia on 08/08/2019. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit +import PIALibrary + +class TermsAndConditionsViewController: AutolayoutViewController, BrandableNavigationBar { + + @IBOutlet private weak var termsTitleLabel: UILabel! + @IBOutlet private weak var termsLabel: UILabel! + var termsAndConditionsTitle: String! + var termsAndConditions: String! + + override func viewDidLoad() { + super.viewDidLoad() + if #available(iOS 13.0, *) { + self.modalPresentationStyle = .automatic + } + self.termsTitleLabel.text = self.termsAndConditionsTitle + self.termsLabel.text = self.termsAndConditions + self.navigationItem.leftBarButtonItem = UIBarButtonItem( + image: Theme.current.palette.navigationBarBackIcon?.withRenderingMode(.alwaysOriginal), + style: .plain, + target: self, + action: #selector(back(_:)) + ) + self.navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Welcome.Redeem.Accessibility.back + } + + // MARK: Restylable + + override func viewShouldRestyle() { + super.viewShouldRestyle() + navigationItem.titleView = NavigationLogoView() + Theme.current.applyNavigationBarStyle(to: self) + Theme.current.applyPrincipalBackground(view) + Theme.current.applyBigTitle(termsTitleLabel, appearance: .dark) + Theme.current.applySmallSubtitle(termsLabel) + } + + @IBAction func close(_ sender: Any) { + dismissModal() + } +} diff --git a/PIA VPN/Theme+App.swift b/PIA VPN/Theme+App.swift index 304c42bc2..b50c0af6f 100644 --- a/PIA VPN/Theme+App.swift +++ b/PIA VPN/Theme+App.swift @@ -167,9 +167,9 @@ extension Theme { public func applyFavoriteUnselectedImage(_ imageView: UIImageView) { if palette.appearance == .dark { - imageView.image = Asset.Piax.Global.favoriteUnselectedDark.image + imageView.image = Asset.Images.Piax.Global.favoriteUnselectedDark.image } else { - imageView.image = Asset.Piax.Global.favoriteUnselected.image + imageView.image = Asset.Images.Piax.Global.favoriteUnselected.image } } @@ -191,8 +191,8 @@ extension Theme { public func noResultsImage() -> UIImage { return palette.appearance == .dark ? - Asset.Piax.Regions.noResultsDark.image : - Asset.Piax.Regions.noResultsLight.image + Asset.Images.Piax.Regions.noResultsDark.image : + Asset.Images.Piax.Regions.noResultsLight.image } public func mapImage() -> UIImage? { @@ -209,24 +209,24 @@ extension Theme { public func dragDropImage() -> UIImage { return palette.appearance == .dark ? - Asset.Piax.Global.dragDropIndicatorDark.image : - Asset.Piax.Global.dragDropIndicatorLight.image + Asset.Images.Piax.Global.dragDropIndicatorDark.image : + Asset.Images.Piax.Global.dragDropIndicatorLight.image } public func activeEyeImage() -> UIImage { return palette.appearance == .dark ? - Asset.Piax.Global.eyeActiveDark.image : - Asset.Piax.Global.eyeActiveLight.image + Asset.Images.Piax.Global.eyeActiveDark.image : + Asset.Images.Piax.Global.eyeActiveLight.image } public func inactiveEyeImage() -> UIImage { return palette.appearance == .dark ? - Asset.Piax.Global.eyeInactiveDark.image : - Asset.Piax.Global.eyeInactiveLight.image + Asset.Images.Piax.Global.eyeInactiveDark.image : + Asset.Images.Piax.Global.eyeInactiveLight.image } public func trashIconImage() -> UIImage { - return palette.appearance == .dark ? Asset.iconTrashDark.image : Asset.iconTrash.image + return palette.appearance == .dark ? Asset.Images.iconTrashDark.image : Asset.Images.iconTrash.image } public func applyLicenseMonospaceFontAndColor(_ textView: UITextView, diff --git a/PIA VPN/Theme+DarkPalette.swift b/PIA VPN/Theme+DarkPalette.swift index 47b8fcb80..cc09056dd 100644 --- a/PIA VPN/Theme+DarkPalette.swift +++ b/PIA VPN/Theme+DarkPalette.swift @@ -30,10 +30,10 @@ extension Theme.Palette { let palette = Theme.Palette() palette.appearance = Theme.Appearance.dark - palette.logo = Asset.navLogoWhite.image + palette.logo = Asset.Images.navLogoWhite.image palette.secondaryColor = UIColor.piaGrey10 palette.textfieldButtonBackgroundColor = UIColor.black - palette.navigationBarBackIcon = Asset.Piax.Global.iconBack.image + palette.navigationBarBackIcon = Asset.Images.Piax.Global.iconBack.image palette.brandBackground = lightPalette.brandBackground palette.secondaryBackground = .piaGrey5 palette.lineColor = .white diff --git a/PIA VPN/Theme+LightPalette.swift b/PIA VPN/Theme+LightPalette.swift new file mode 100644 index 000000000..c9ace9a24 --- /dev/null +++ b/PIA VPN/Theme+LightPalette.swift @@ -0,0 +1,48 @@ +// +// Theme+LightPalette.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 1/23/18. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import Foundation +import UIKit +import PIALibrary + +extension Theme.Palette { + + /// Light theme. + public static var light: Theme.Palette { + let palette = Theme.Palette() + palette.appearance = Theme.Appearance.light + palette.logo = Asset.Images.navLogo.image + palette.secondaryColor = .white + palette.textfieldButtonBackgroundColor = .white + palette.navigationBarBackIcon = Asset.Ui.Piax.Global.iconBack.image + palette.brandBackground = Macros.color(hex: 0x009a18, alpha: 0xff) + palette.secondaryBackground = .white + palette.principalBackground = .piaGrey1 + palette.lineColor = .piaGreenDark20 + palette.emphasis = Macros.color(hex: 0x29cc41, alpha: 0xff) + palette.accent1 = UIColor.piaOrange + palette.accent2 = Macros.color(hex: 0xe60924, alpha: 0xff) + palette.divider = UIColor(white: 0.0, alpha: 0.2) + palette.overlayAlpha = 0.3 + return palette + } +} diff --git a/PIA VPN/Theme.swift b/PIA VPN/Theme.swift new file mode 100644 index 000000000..65633443a --- /dev/null +++ b/PIA VPN/Theme.swift @@ -0,0 +1,819 @@ +// +// Theme.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 10/19/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import Foundation +import UIKit +import __PIALibraryNative +import PIALibrary + + +/// Defines the look and feel of the client UI. +public class Theme { + + /// Semantic appearance of an accent. + public enum Appearance { + + /// Dark accent. + case dark + + /// Light accent. + case light + + /// Emphasis accent. + case emphasis + } + + /// Defines theme values related to colors. + public final class Palette { + + /// The appearance type theme + public var appearance: Appearance? + + /// The logo image. + public var logo: UIImage? + + /// The navigation bar back image. + public var navigationBarBackIcon: UIImage? + + /// The light background color. + public var secondaryColor: UIColor + + /// The background color of the buttons inside textfields. + public var textfieldButtonBackgroundColor: UIColor + + /// The brand background color. + public var brandBackground: UIColor + + /// The secondary background color. + public var secondaryBackground: UIColor + + /// The solid light background color. + public var principalBackground: UIColor + + /// The emphasis accent color. + public var emphasis: UIColor + + /// The primary accent color. + public var accent1: UIColor + + /// The secondary accent color. + public var accent2: UIColor + + private var darkText: UIColor + + private var darkTextArray: [UIColor] + + private var lightText: UIColor + + private var lightTextArray: [UIColor] + + /// The solid text color for buttons. + public var solidButtonText: UIColor + + /// The divider color. + public var divider: UIColor + + /// The error color. + public var errorColor: UIColor + + /// The overlay alpha value. + public var overlayAlpha: CGFloat + + /// The line color. + public var lineColor: UIColor + + /// :nodoc: + public init() { + appearance = .light + brandBackground = .green + secondaryColor = .piaGrey10 + secondaryBackground = .white + principalBackground = .piaGrey1 + textfieldButtonBackgroundColor = .white + lineColor = .piaGreenDark20 +// primary = .black + emphasis = .green + accent1 = .piaOrange + accent2 = .red + + darkText = .black + darkTextArray = [ + darkText, + darkText, + darkText + ] + lightText = .white + lightTextArray = [ + lightText, + lightText, + lightText + ] + solidButtonText = .white + divider = .piaGrey1 + errorColor = .piaRed + overlayAlpha = 0.3 + } + + /** + Returns the color for the given relevance and appearance. + + - Precondition: `relevance` lies between 1 and 3 (included). + - Parameter relevance: The color relevance between 1 and 3 (included). + - Parameter appearance: An `Appearance` value. + - Returns: The resulting text color. + */ + public func textColor(forRelevance relevance: Int, appearance: Appearance) -> UIColor { + precondition(relevance >= 1) + precondition(relevance <= 3) + + switch appearance { + case .dark: + return darkTextArray[relevance - 1] + + case .light: + return lightTextArray[relevance - 1] + + case .emphasis: + return emphasis + } + } + } + + /// Defines a theme font typeface. + public final class Typeface { + + /// The font name for regular weight. Defaults to system font. + public var regularName: String? + + /// The font name for medium weight. Defaults to system font. + public var mediumName: String? + + /// The font name for monospace text. + public var monospaceName: String + + /// :nodoc: + public init() { + monospaceName = "Courier New" + } + + private func safeFont(name: String, size: CGFloat) -> UIFont { + guard let font = UIFont(name: name, size: size) else { + fatalError("Cannot load font '\(name)'") + } + return font + } + + /** + Returns a regular-weighted font. + + - Parameter size: The size of the font. + - Returns: A regular-weighted font of the given size. + */ + public func regularFont(size: CGFloat) -> UIFont { + guard let name = regularName else { + return UIFont.systemFont(ofSize: size, weight: .regular) + } + return safeFont(name: name, size: size) + } + + /** + Returns a medium-weighted font. + + - Parameter size: The size of the font. + - Returns: A medium-weighted font of the given size. + */ + public func mediumFont(size: CGFloat) -> UIFont { + guard let name = mediumName else { + return UIFont.systemFont(ofSize: size, weight: .medium) + } + return safeFont(name: name, size: size) + } + + /** + Returns a monospace font. + + - Parameter size: The size of the font. + - Returns: A monospace font of the given size. + */ + public func monospaceFont(size: CGFloat) -> UIFont { + return safeFont(name: monospaceName, size: size) + } + } + + /// The current UI theme. + public static let current = Theme() + + /// The `Palette` holding the theme colors. + public var palette: Palette + + /// The `Typeface` holding the theme fonts. + public var typeface: Typeface + + /// The `ThemeStrategy` for dynamic styling. + public var strategy: ThemeStrategy + + private let cornerRadius: CGFloat + +// private let dotSpacing: CGFloat + + private init() { + palette = .light + typeface = Typeface() + strategy = DefaultThemeStrategy() + + cornerRadius = 4.0 +// dotSpacing = 6.0 + } + + /** + Reloads the theme, every observer of `Notification.Name.PIAThemeDidChange` is notified for refreshing. + + - Postcondition: Posts `Notification.Name.PIAThemeDidChange` notification. + */ + public func reload() { + Macros.postNotification(.PIAThemeDidChange) + } + + // MARK: Backgrounds + + /// :nodoc: + public func applySecondaryBackground(_ view: UIView) { + view.backgroundColor = palette.appearance == .dark ? + palette.secondaryBackground.withAlphaComponent(0.3) : + palette.secondaryBackground + } + + /// :nodoc: + public func applyPrincipalBackground(_ view: UIView) { + view.backgroundColor = palette.principalBackground + } + + /// :nodoc: + public func applyRegionSolidLightBackground(_ view: UIView) { + view.backgroundColor = palette.appearance == .dark ? UIColor.piaGrey6 : palette.principalBackground + } + + /// :nodoc: + public func applyWarningBackground(_ view: UIView) { + view.backgroundColor = palette.accent1 + } + + /// :nodoc: + public func applyMessagesBackground(_ view: UIView) { + view.backgroundColor = palette.appearance == .dark ? UIColor.piaGrey8 : UIColor.piaGrey2 + } + + // MARK: Table View Utils + + /// :nodoc: + public func applyDivider(_ view: UIView) { + view.backgroundColor = palette.divider + } + + /// :nodoc: + public func applyDividerToSeparator(_ tableView: UITableView) { + tableView.separatorColor = palette.divider + } + + // MARK: Images + + /// :nodoc: + public func applyCenteredMap(_ imageView: UIImageView) { + imageView.image = palette.appearance == .dark ? + Asset.Ui.Piax.Global.centeredDarkMap.image : Asset.Ui.Piax.Global.centeredLightMap.image + } + + // MARK: Navigation bar + + /// :nodoc: + public func applyBrandNavigationBar(_ navigationBar: UINavigationBar) { + navigationBar.tintColor = palette.textColor(forRelevance: 1, appearance: .light) + navigationBar.setBackgroundAppearenceColor(palette.brandBackground) + } + + // MARK: Typography + /// :nodoc: + public func applyButtonLabelStyle(_ button: UIButton) { + if palette.appearance == Appearance.light { + button.style(style: TextStyle.textStyle9) + } else { + button.style(style: TextStyle.textStyle6) + } + } + + public func applyButtonLabelMediumStyle(_ button: UIButton) { + if palette.appearance == Appearance.light { + button.style(style: TextStyle.textStyle9Medium) + } else { + button.style(style: TextStyle.textStyle6Medium) + } + } + + public func applyVersionNumberStyle(_ label: UILabel) { + label.style(style: TextStyle.versionNumberStyle) + } + + /// :nodoc: + public func applyTitle(_ label: UILabel, appearance: Appearance) { + if palette.appearance == Appearance.light { + label.style(style: TextStyle.textStyle2) + } else { + label.style(style: TextStyle.textStyle1) + } + } + + /// :nodoc: + public func applyBigTitle(_ label: UILabel, appearance: Appearance) { + if palette.appearance == Appearance.light { + label.style(style: TextStyle.textStyle23) + } else { + label.style(style: TextStyle.textStyle22) + } + } + + /// :nodoc: + public func applySubtitle(_ label: UILabel) { + let textAlignment = label.textAlignment + label.style(style: TextStyle.textStyle8) + label.textAlignment = textAlignment + } + + public func applySmallSubtitle(_ label: UILabel) { + let textAlignment = label.textAlignment + label.style(style: TextStyle.textStyle21) + label.textAlignment = textAlignment + } + + /// :nodoc: + public func applyBody1Monospace(_ textView: UITextView, appearance: Appearance) { + textView.font = typeface.monospaceFont(size: 14.0) + textView.textColor = palette.textColor(forRelevance: 2, appearance: appearance) + } + + /// :nodoc: + public func applySmallInfo(_ label: UILabel, appearance: Appearance) { + if palette.appearance == Appearance.light { + label.style(style: TextStyle.textStyle12) + } else { + label.style(style: TextStyle.textStyle11) + } + } + + /// Method to apply a second style for the same UILabel + /// label.text should be previously set + public func makeSmallLabelToStandOut(_ label: UILabel, + withTextToStandOut textToStandOut: String, + andAppearance appearance: Appearance = Appearance.dark) { + + if let text = label.text { + let rangeSecondText = (text as NSString).range(of: textToStandOut) + let attributedString = NSMutableAttributedString(string: text) + + var foregroundColor = TextStyle.textStyle1.color! + if palette.appearance == Appearance.light { + foregroundColor = TextStyle.textStyle2.color! + } + + attributedString.addAttribute(.foregroundColor, + value: foregroundColor, + range: rangeSecondText) + + label.attributedText = attributedString + } + + } + + /// :nodoc: + public func applyTag(_ label: UILabel, appearance: Appearance) { + label.font = typeface.regularFont(size: 12.0) + label.textColor = palette.textColor(forRelevance: 1, appearance: appearance) + } + + /// :nodoc: + public func applyBlackLabelInBox(_ label: UILabel) { + label.font = typeface.regularFont(size: 12.0) + label.textColor = .black + } + + /// :nodoc: + public func applyList(_ label: UILabel, appearance: Appearance) { + applyList(label, appearance: appearance, relevance: 2) + } + + /// :nodoc: + public func applyList(_ label: UILabel, appearance: Appearance, relevance: Int) { + label.font = typeface.regularFont(size: 15.0) + label.textColor = palette.textColor(forRelevance: relevance, appearance: appearance) + } + + // MARK: Textfields + + /// :nodoc: + public func applyInput(_ textField: UITextField) { // hint is placeholder + + textField.style(style: TextStyle.textStyle8) + textField.backgroundColor = Theme.current.palette.secondaryColor + + if let borderedTextField = textField as? BorderedTextField { + borderedTextField.borderColor = palette.divider + borderedTextField.highlightedBorderColor = palette.emphasis + borderedTextField.highlightsWhileEditing = true + } + } + + /// :nodoc: + public func applyInputError(_ textField: UITextField) { // hint is placeholder + + textField.style(style: TextStyle.textStyle8) + textField.backgroundColor = Theme.current.palette.secondaryColor + + if let borderedTextField = textField as? BorderedTextField { + borderedTextField.borderColor = palette.errorColor + borderedTextField.highlightedBorderColor = palette.errorColor + borderedTextField.highlightsWhileEditing = true + } + } + + // MARK: Buttons + + /// :nodoc: + public func applyTransparentButton(_ button: PIAButton, + withSize size: CGFloat) { + button.setBorder(withSize: size, + andColor: palette.lineColor) + button.setTitleColor(palette.lineColor, + for: .normal) + } + + /// :nodoc: + public func applyActivityIndicator(_ activityIndicator: UIActivityIndicatorView) { + activityIndicator.color = palette.appearance == Appearance.light ? .piaGreen : .piaWhite + } + + /// :nodoc: + public func applyActionButton(_ button: ActivityButton) { + button.font = typeface.mediumFont(size: 15.0) + button.backgroundColor = palette.emphasis + button.textColor = palette.solidButtonText + button.cornerRadius = cornerRadius + } + + /// :nodoc: + public func applyCancelButton(_ button: UIButton, appearance: Appearance) { + button.setTitle("×", for: .normal) + button.titleLabel?.font = typeface.mediumFont(size: 36.0) + button.setTitleColor(palette.textColor(forRelevance: 1, appearance: appearance), for: .normal) + } + + public func applyUnderlinedSubtitleButton(_ button: UIButton) { + button.style(style: TextStyle.textStyle8) + let attributes: [NSAttributedString.Key: Any] = [ + .underlineStyle: NSUnderlineStyle.single.rawValue + ] + let title = button.title(for: .normal) ?? "" + + let attributedString = NSAttributedString(string: title, attributes: attributes) + button.setAttributedTitle(attributedString, for: .normal) + + } + + public func applyUnderline(_ label: UILabel, with text: String) { + label.style(style: TextStyle.textStyle10) + let attributes: [NSAttributedString.Key: Any] = [ + .underlineStyle: NSUnderlineStyle.single.rawValue + ] + + let attributedString = NSAttributedString(string: text, attributes: attributes) + label.attributedText = attributedString + } + + /// :nodoc: + public func agreementText(withMessage message: String, tos: String, tosUrl: String, privacy: String, privacyUrl: String) -> NSAttributedString { + let plain = message.replacingOccurrences( + of: "$1", + with: tos + ).replacingOccurrences( + of: "$2", + with: privacy + ) as NSString + + let attributed = NSMutableAttributedString(string: plain as String) + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .center + paragraph.minimumLineHeight = 16 + let fullRange = NSMakeRange(0, plain.length) + attributed.addAttribute(.font, value: UIFont.regularFontWith(size: 12), range: fullRange) + attributed.addAttribute(.foregroundColor, value: UIColor.piaGrey4, range: fullRange) + attributed.addAttribute(.paragraphStyle, value: paragraph, range: fullRange) + let range1 = plain.range(of: tos) + let range2 = plain.range(of: privacy) + attributed.addAttribute(.link, value: tosUrl, range: range1) + attributed.addAttribute(.link, value: privacyUrl, range: range2) + return attributed + } + + public func messageWithLinkText(withMessage message: String, link: String) -> NSAttributedString { + let plain = message.replacingOccurrences( + of: "$1", + with: link + ) as NSString + + let attributed = NSMutableAttributedString(string: plain as String) + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .center + paragraph.minimumLineHeight = 16 + let fullRange = NSMakeRange(0, plain.length) + attributed.addAttribute(.font, value: UIFont.mediumFontWith(size: 14), range: fullRange) + if Theme.current.palette.appearance == .dark { + attributed.addAttribute(.foregroundColor, value: UIColor.white, range: fullRange) + } else { + attributed.addAttribute(.foregroundColor, value: UIColor.piaGrey6, range: fullRange) + } + attributed.addAttribute(.paragraphStyle, value: paragraph, range: fullRange) + let range1 = plain.range(of: link) + attributed.addAttribute(.link, value: link, range: range1) + return attributed + } + + + // MARK: Composite + + /// :nodoc: + public func applyAppearance() { + let navBarAppearance = UINavigationBar.appearance() + let switchAppearance = UISwitch.appearance() + let activityIndicatorAppearance = UIActivityIndicatorView.appearance() + let pageControlAppearance = UIPageControl.appearance() + + switchAppearance.onTintColor = palette.emphasis + activityIndicatorAppearance.color = palette.textColor(forRelevance: 2, appearance: .dark) + pageControlAppearance.pageIndicatorTintColor = UIColor.groupTableViewBackground + pageControlAppearance.currentPageIndicatorTintColor = palette.emphasis + + navBarAppearance.barStyle = .black + navBarAppearance.isTranslucent = false + navBarAppearance.setBackgroundImage(UIImage(), for: .default) + navBarAppearance.shadowImage = UIImage() + } + + /// :nodoc: + public func applyTableSectionHeader(_ view: UIView) { + guard let hfv = view as? UITableViewHeaderFooterView, let label = hfv.textLabel else { + return + } + label.style(style: TextStyle.textStyle14) + } + + /// :nodoc: + public func applyTableSectionFooter(_ view: UIView) { + guard let hfv = view as? UITableViewHeaderFooterView, let label = hfv.textLabel else { + return + } + label.style(style: TextStyle.textStyle21) + } + + /// :nodoc: + public func applyDetailTableCell(_ cell: UITableViewCell) { + if let label = cell.textLabel { + applyList(label, appearance: .dark, relevance: 2) + } + if let detail = cell.detailTextLabel { + applyList(detail, appearance: .dark, relevance: 3) + } + } + + /// :nodoc: + public func applyCorner(_ view: UIView) { + applyCorner(view, factor: 1.0) + } + + /// :nodoc: + public func applyCorner(_ view: UIView, factor: CGFloat) { + view.layer.cornerRadius = cornerRadius * factor +// view.layer.masksToBounds = true + } + + /// :nodoc: + public func applyBorder(_ view: UIView, selected: Bool) { + applyBorder(view, selected: selected, factor: 1.0) + } + + /// :nodoc: + public func applyBorder(_ view: UIView, selected: Bool, factor: CGFloat) { + view.layer.cornerRadius = cornerRadius * factor + view.layer.borderWidth = 1.0 + view.layer.borderColor = (selected ? palette.emphasis : palette.divider).cgColor + view.clipsToBounds = true + } + + /// :nodoc: + public func applyCircleProgressView(_ circleProgressView: CircleProgressView) { + circleProgressView.outerColor = palette.brandBackground + circleProgressView.innerColor = palette.divider + circleProgressView.fixedColor = palette.emphasis + } + + /// :nodoc: + public func applyLinkAttributes(_ textView: UITextView) { + textView.tintColor = palette.lineColor + } + + public func applyMessageLinkAttributes(_ textView: UITextView, withColor color: UIColor) { + textView.tintColor = color + } + + /// :nodoc: + public func applyScrollableMap(_ imageView: UIImageView) { + imageView.image = palette.appearance == .dark ? + Asset.Ui.Piax.Global.scrollableMapDark.image : Asset.Ui.Piax.Global.scrollableMapLight.image + } + + /// :nodoc: + func applyPageControl(_ pageControl: FXPageControl) { + pageControl.dotSpacing = 6.0 + pageControl.selectedDotImage = Asset.Ui.Piax.Global.pagecontrolSelectedDot.image + pageControl.dotImage = Asset.Ui.Piax.Global.pagecontrolUnselectedDot.image + } + + + // MARK: Strategy + + /// :nodoc: + func applyNavigationBarStyle(to viewController: AutolayoutViewController) { + strategy.applyNavigationBarStyle(to: viewController, theme: self) + } + + /// :nodoc: + func statusBarAppearance(for viewController: AutolayoutViewController) -> UIStatusBarStyle { + return strategy.statusBarAppearance(for: viewController) + } + + /// :nodoc: + func autolayoutContainerMargins(for mask: UIInterfaceOrientationMask) -> UIEdgeInsets { + return strategy.autolayoutContainerMargins(for: mask) + } + + // MARK: Navigation bar + + public func applyLightNavigationBar(_ navigationBar: UINavigationBar) { + navigationBar.setBackgroundAppearenceColor(palette.principalBackground) + navigationBar.tintColor = UIColor.piaGrey4 + + } + + // MARK: Refresh control + public func applyRefreshControlStyle(_ refreshControl: UIRefreshControl) { + if palette.appearance == Appearance.light { + refreshControl.style(style: ViewStyle.refreshControlLight) + } else { + refreshControl.style(style: ViewStyle.refreshControlDark) + } + } + + /** + Set color values for a custom navigation bar. + + - Parameter navigationBar: The navigationBar where the changes are going to be applied. + - Parameter tintColor: The tintColor for the navigationBar. If nil: self.palette.textColor(forRelevance: 1, appearance: .dark) + - Parameter barTintColors: Array of colors for the background of the navigationBar. If the array contains 2 colors, it will generate a gradient. If the array contains more than 2 colors or nil, it will set the default value: self.palette.secondaryBackground. If the array only contains 1 color, a solid background color will be set. + */ + public func applyCustomNavigationBar(_ navigationBar: UINavigationBar, + withTintColor tintColor: UIColor?, + andBarTintColors barTintColors: [UIColor]?) { + + UIView.animate(withDuration: 0.3) { + if let tintColor = tintColor { + navigationBar.tintColor = tintColor + } else { + navigationBar.tintColor = UIColor.piaGrey4 + } + + if let barTintColors = barTintColors, + barTintColors.count > 0, + barTintColors.count <= 2 { + if barTintColors.count == 1 { + navigationBar.setBackgroundAppearenceColor(barTintColors.first) + navigationBar.setBackgroundAppearenceImage(nil) + } else { + var updatedFrame = navigationBar.bounds + updatedFrame.size.height += navigationBar.frame.origin.y + let gradientLayer = CAGradientLayer(frame: updatedFrame, colors: barTintColors) + navigationBar.setBackgroundAppearenceImage(gradientLayer.createGradientImage()) + } + } else { + navigationBar.setBackgroundAppearenceColor(self.palette.principalBackground) + navigationBar.setBackgroundAppearenceImage(nil) + } + navigationBar.setNeedsLayout() + } + + } + + + public func applyLightBrandLogoNavigationBar(_ navigationBar: UINavigationBar) { + navigationBar.tintColor = palette.textColor(forRelevance: 1, appearance: .dark) + navigationBar.setBackgroundAppearenceColor(palette.principalBackground) + } + + //MARK: Cell + /// :nodoc: + public func applySettingsCellTitle(_ label: UILabel, appearance: Appearance) { + if palette.appearance == Appearance.light { + label.style(style: TextStyle.textStyle7) + } else { + label.style(style: TextStyle.textStyle6) + } + } + + /// :nodoc: + public func applyRegionIPCell(_ label: UILabel, appearance: Appearance) { + label.style(style: TextStyle.ipTextStyle) + } + + public func applyRegionIPTitleCell(_ label: UILabel, appearance: Appearance) { + label.style(style: TextStyle.ipTitleTextStyle) + } + + //MARK: Tile Usage + /// :nodoc: + public func applySubtitleTileUsage(_ label: UILabel, appearance: Appearance) { + if palette.appearance == Appearance.dark { + label.style(style: TextStyle.textStyle16) + } else { + label.style(style: TextStyle.textStyle17) + } + } + +} + +/// Defines a dynamic strategy for complex styles. +/// +/// - Seealso: `Theme.strategy` +public protocol ThemeStrategy { + + /** + Applies a style to a `UINavigation` based on an input `AutolayoutViewController`, in order to provide per-controller navigation bar styling. + + - Parameter viewController: The target `AutolayoutViewController` to apply the navigation bar style to. + - Parameter theme: The `Theme` to apply. + */ + func applyNavigationBarStyle(to viewController: AutolayoutViewController, theme: Theme) + + /** + Returns a `UIStatusBarStyle` based on an input `AutolayoutViewController`, in order to provide per-controller styling. + + - Parameter viewController: The target `AutolayoutViewController` to apply the status bar style to. + - Returns: The desired `UIStatusBarStyle` on the given view controller. + */ + func statusBarAppearance(for viewController: AutolayoutViewController) -> UIStatusBarStyle + + /** + Returns a set of margins to apply to `AutolayoutViewController.viewContainer` in a specific orientation mask. + + - Parameter mask: The current `UIInterfaceOrientationMask`. + - Returns: The desired `UIEdgeInsets` margins to apply to `AutolayoutViewController.viewContainer`. + */ + func autolayoutContainerMargins(for mask: UIInterfaceOrientationMask) -> UIEdgeInsets +} + +private struct DefaultThemeStrategy: ThemeStrategy { + func applyNavigationBarStyle(to viewController: AutolayoutViewController, theme: Theme) { + guard let navigationBar = viewController.navigationController?.navigationBar else { + return + } + theme.applyBrandNavigationBar(navigationBar) + } + + func statusBarAppearance(for viewController: AutolayoutViewController) -> UIStatusBarStyle { + if let _ = viewController as? PIAWelcomeViewController { + return .default + } + if let _ = viewController as? GetStartedViewController { + return .default + } + return .lightContent + } + + func autolayoutContainerMargins(for mask: UIInterfaceOrientationMask) -> UIEdgeInsets { + return .zero + } +} diff --git a/PIA VPN/Tiles/ConnectionTile.swift b/PIA VPN/Tiles/ConnectionTile.swift index 227360069..1bca483e2 100644 --- a/PIA VPN/Tiles/ConnectionTile.swift +++ b/PIA VPN/Tiles/ConnectionTile.swift @@ -69,7 +69,7 @@ class ConnectionTile: UIView, Tileable { setConnectionValues() viewShouldRestyle() - self.tileTitle.text = L10n.Settings.Connection.title.uppercased() + self.tileTitle.text = L10n.Localizable.Settings.Connection.title.uppercased() } @@ -103,12 +103,12 @@ class ConnectionTile: UIView, Tileable { self.socketLabel.text = "---" self.handshakeLabel.text = "---" - self.protocolLabel.accessibilityLabel = L10n.Global.empty - self.portLabel.accessibilityLabel = L10n.Global.empty - self.authenticationLabel.accessibilityLabel = L10n.Global.empty - self.encryptionLabel.accessibilityLabel = L10n.Global.empty - self.socketLabel.accessibilityLabel = L10n.Global.empty - self.handshakeLabel.accessibilityLabel = L10n.Global.empty + self.protocolLabel.accessibilityLabel = L10n.Localizable.Global.empty + self.portLabel.accessibilityLabel = L10n.Localizable.Global.empty + self.authenticationLabel.accessibilityLabel = L10n.Localizable.Global.empty + self.encryptionLabel.accessibilityLabel = L10n.Localizable.Global.empty + self.socketLabel.accessibilityLabel = L10n.Localizable.Global.empty + self.handshakeLabel.accessibilityLabel = L10n.Localizable.Global.empty } @objc private func viewShouldRestyle() { diff --git a/PIA VPN/Tiles/ConnectionTileCollectionViewCell.swift b/PIA VPN/Tiles/ConnectionTileCollectionViewCell.swift index 2310513e2..f589d7026 100644 --- a/PIA VPN/Tiles/ConnectionTileCollectionViewCell.swift +++ b/PIA VPN/Tiles/ConnectionTileCollectionViewCell.swift @@ -62,11 +62,11 @@ class ConnectionTileCollectionViewCell: UICollectionViewCell, TileableCell { if Client.providers.tileProvider.visibleTiles.contains(tileType) { accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Visible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Visible.Tile.action } else { accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Invisible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Invisible.Tile.action } } diff --git a/PIA VPN/Tiles/FavoriteServersTile.swift b/PIA VPN/Tiles/FavoriteServersTile.swift index dd94a7ab4..b25b09fe3 100644 --- a/PIA VPN/Tiles/FavoriteServersTile.swift +++ b/PIA VPN/Tiles/FavoriteServersTile.swift @@ -63,7 +63,7 @@ class FavoriteServersTile: UIView, Tileable { nc.addObserver(self, selector: #selector(updateFavoriteList), name: .PIAServerHasBeenUpdated, object: nil) viewShouldRestyle() - self.tileTitle.text = L10n.Tiles.Favorite.Servers.title.uppercased() + self.tileTitle.text = L10n.Localizable.Tiles.Favorite.Servers.title.uppercased() updateFavoriteList() } @@ -78,18 +78,18 @@ class FavoriteServersTile: UIView, Tileable { currentServers.append(Server.automatic) for containerView in stackView.subviews { if let button = containerView.subviews.first as? ServerButton { - button.setImage(Theme.current.palette.appearance == .light ? Asset.Piax.Tiles.quickConnectPlaceholderLight.image : - Asset.Piax.Tiles.quickConnectPlaceholderDark.image, for: .normal) + button.setImage(Theme.current.palette.appearance == .light ? Asset.Images.Piax.Tiles.quickConnectPlaceholderLight.image : + Asset.Images.Piax.Tiles.quickConnectPlaceholderDark.image, for: .normal) button.imageView?.contentMode = .scaleAspectFit button.isUserInteractionEnabled = false - button.accessibilityLabel = L10n.Global.empty + button.accessibilityLabel = L10n.Localizable.Global.empty } } for label in labelsStackView.subviews { if let label = label as? UILabel { label.text = "" - label.accessibilityLabel = L10n.Global.empty + label.accessibilityLabel = L10n.Localizable.Global.empty } } diff --git a/PIA VPN/Tiles/FavoriteServersTileCollectionViewCell.swift b/PIA VPN/Tiles/FavoriteServersTileCollectionViewCell.swift index 859b0332b..20d7336b9 100644 --- a/PIA VPN/Tiles/FavoriteServersTileCollectionViewCell.swift +++ b/PIA VPN/Tiles/FavoriteServersTileCollectionViewCell.swift @@ -73,11 +73,11 @@ class FavoriteServersTileCollectionViewCell: UICollectionViewCell, TileableCell, if Client.providers.tileProvider.visibleTiles.contains(tileType) { accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Visible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Visible.Tile.action } else { accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Invisible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Invisible.Tile.action } } diff --git a/PIA VPN/Tiles/IPTile.swift b/PIA VPN/Tiles/IPTile.swift index ae68bfe85..2777bec48 100644 --- a/PIA VPN/Tiles/IPTile.swift +++ b/PIA VPN/Tiles/IPTile.swift @@ -66,9 +66,9 @@ class IPTile: UIView, Tileable { self.localIpTitle.text = "IP" self.vpnIpTitle.text = "VPN IP" self.localIpValue.text = Client.daemons.publicIP ?? emptyIPValue - self.localIpValue.accessibilityLabel = Client.daemons.publicIP ?? L10n.Global.empty + self.localIpValue.accessibilityLabel = Client.daemons.publicIP ?? L10n.Localizable.Global.empty self.vpnIpValue.text = emptyIPValue - self.vpnIpValue.accessibilityLabel = L10n.Global.empty + self.vpnIpValue.accessibilityLabel = L10n.Localizable.Global.empty } @@ -85,9 +85,9 @@ class IPTile: UIView, Tileable { let vpn = Client.providers.vpnProvider if (vpn.vpnStatus == .connected) { self.vpnIpValue.text = Client.daemons.vpnIP ?? self.emptyIPValue - self.vpnIpValue.accessibilityLabel = Client.daemons.vpnIP ?? L10n.Global.empty + self.vpnIpValue.accessibilityLabel = Client.daemons.vpnIP ?? L10n.Localizable.Global.empty } else if (!Client.daemons.isInternetReachable && (vpn.vpnStatus == .disconnected)) { - self.vpnIpValue.text = L10n.Dashboard.Connection.Ip.unreachable + self.vpnIpValue.text = L10n.Localizable.Dashboard.Connection.Ip.unreachable } } @@ -96,7 +96,7 @@ class IPTile: UIView, Tileable { switch vpn.vpnStatus { case .connecting, .disconnecting, .disconnected: self.vpnIpValue.text = self.emptyIPValue - self.vpnIpValue.accessibilityLabel = L10n.Global.empty + self.vpnIpValue.accessibilityLabel = L10n.Localizable.Global.empty default: break } diff --git a/PIA VPN/Tiles/IPTileCollectionViewCell.swift b/PIA VPN/Tiles/IPTileCollectionViewCell.swift index b7fdf2ac2..9ed24b9e4 100644 --- a/PIA VPN/Tiles/IPTileCollectionViewCell.swift +++ b/PIA VPN/Tiles/IPTileCollectionViewCell.swift @@ -64,11 +64,11 @@ class IPTileCollectionViewCell: UICollectionViewCell, TileableCell { if Client.providers.tileProvider.visibleTiles.contains(tileType) { accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Visible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Visible.Tile.action } else { accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Invisible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Invisible.Tile.action } } diff --git a/PIA VPN/Tiles/MessagesTile.swift b/PIA VPN/Tiles/MessagesTile.swift index 230b54b01..29700b8af 100644 --- a/PIA VPN/Tiles/MessagesTile.swift +++ b/PIA VPN/Tiles/MessagesTile.swift @@ -58,7 +58,7 @@ class MessagesTile: UIView, Tileable { nc.addObserver(self, selector: #selector(viewShouldRestyle), name: .PIAThemeDidChange, object: nil) viewShouldRestyle() - self.alertIcon.image = Asset.iconAlert.image.withRenderingMode(.alwaysTemplate) + self.alertIcon.image = Asset.Images.iconAlert.image.withRenderingMode(.alwaysTemplate) if let message = MessagesManager.shared.availableMessage(), let _ = message.linkMessage { let tap = UITapGestureRecognizer(target: self, action: #selector(openLink)) diff --git a/PIA VPN/Tiles/QuickConnectTile.swift b/PIA VPN/Tiles/QuickConnectTile.swift index f039d3322..93efb14d0 100644 --- a/PIA VPN/Tiles/QuickConnectTile.swift +++ b/PIA VPN/Tiles/QuickConnectTile.swift @@ -66,7 +66,7 @@ class QuickConnectTile: UIView, Tileable { nc.addObserver(self, selector: #selector(updateQuickConnectList), name: .PIAServerHasBeenUpdated, object: nil) nc.addObserver(self, selector: #selector(updateQuickConnectList), name: .PIADaemonsDidPingServers, object: nil) viewShouldRestyle() - self.tileTitle.text = L10n.Tiles.Quick.Connect.title.uppercased() + self.tileTitle.text = L10n.Localizable.Tiles.Quick.Connect.title.uppercased() updateQuickConnectList() } @@ -113,11 +113,11 @@ class QuickConnectTile: UIView, Tileable { for containerView in stackView.subviews { if let button = containerView.subviews.first as? ServerButton, let favoriteImage = containerView.subviews.last as? UIImageView { - button.setImage(Theme.current.palette.appearance == .light ? Asset.Piax.Tiles.quickConnectPlaceholderLight.image : - Asset.Piax.Tiles.quickConnectPlaceholderDark.image, for: .normal) + button.setImage(Theme.current.palette.appearance == .light ? Asset.Images.Piax.Tiles.quickConnectPlaceholderLight.image : + Asset.Images.Piax.Tiles.quickConnectPlaceholderDark.image, for: .normal) button.imageView?.contentMode = .scaleAspectFit button.isUserInteractionEnabled = false - button.accessibilityLabel = L10n.Global.empty + button.accessibilityLabel = L10n.Localizable.Global.empty favoriteImage.isHidden = true } } @@ -125,7 +125,7 @@ class QuickConnectTile: UIView, Tileable { for label in labelsStackView.subviews { if let label = label as? UILabel { label.text = "" - label.accessibilityLabel = L10n.Global.empty + label.accessibilityLabel = L10n.Localizable.Global.empty } } diff --git a/PIA VPN/Tiles/QuickConnectTileCollectionViewCell.swift b/PIA VPN/Tiles/QuickConnectTileCollectionViewCell.swift index 7b6a7fc23..df1a56ee8 100644 --- a/PIA VPN/Tiles/QuickConnectTileCollectionViewCell.swift +++ b/PIA VPN/Tiles/QuickConnectTileCollectionViewCell.swift @@ -73,11 +73,11 @@ class QuickConnectTileCollectionViewCell: UICollectionViewCell, TileableCell, Se if Client.providers.tileProvider.visibleTiles.contains(tileType) { accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Visible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Visible.Tile.action } else { accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Invisible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Invisible.Tile.action } } diff --git a/PIA VPN/Tiles/QuickSettingsTile.swift b/PIA VPN/Tiles/QuickSettingsTile.swift index 8d59d89ca..90f138034 100644 --- a/PIA VPN/Tiles/QuickSettingsTile.swift +++ b/PIA VPN/Tiles/QuickSettingsTile.swift @@ -68,7 +68,7 @@ class QuickSettingsTile: UIView, Tileable { nc.addObserver(self, selector: #selector(setupButtons), name: .PIAQuickSettingsHaveChanged, object: nil) nc.addObserver(self, selector: #selector(setupButtons), name: .PIATilesDidChange, object: nil) - self.tileTitle.text = L10n.Tiles.Quicksettings.title.uppercased() + self.tileTitle.text = L10n.Localizable.Tiles.Quicksettings.title.uppercased() setupButtons() viewShouldRestyle() @@ -91,42 +91,42 @@ class QuickSettingsTile: UIView, Tileable { @objc private func updateButtons() { - killSwitchButton.accessibilityLabel = L10n.Settings.ApplicationSettings.KillSwitch.title - nmtButton.accessibilityLabel = L10n.Tiles.Quicksetting.Nmt.title - browserButton.accessibilityLabel = L10n.Tiles.Quicksetting.Private.Browser.title + killSwitchButton.accessibilityLabel = L10n.Localizable.Settings.ApplicationSettings.KillSwitch.title + nmtButton.accessibilityLabel = L10n.Localizable.Tiles.Quicksetting.Nmt.title + browserButton.accessibilityLabel = L10n.Localizable.Tiles.Quicksetting.Private.Browser.title if Flags.shared.enablesThemeSwitch { - themeButton.accessibilityLabel = L10n.Settings.ApplicationSettings.ActiveTheme.title + themeButton.accessibilityLabel = L10n.Localizable.Settings.ApplicationSettings.ActiveTheme.title if AppPreferences.shared.currentThemeCode == ThemeCode.light { - themeButton.setImage(Theme.current.palette.appearance == .light ? Asset.Piax.Global.themeLightActive.image : - Asset.Piax.Global.themeDarkActive.image, for: []) + themeButton.setImage(Theme.current.palette.appearance == .light ? Asset.Images.Piax.Global.themeLightActive.image : + Asset.Images.Piax.Global.themeDarkActive.image, for: []) } else { - themeButton.setImage(Theme.current.palette.appearance == .light ? Asset.Piax.Global.themeLightInactive.image : - Asset.Piax.Global.themeDarkInactive.image, for: []) + themeButton.setImage(Theme.current.palette.appearance == .light ? Asset.Images.Piax.Global.themeLightInactive.image : + Asset.Images.Piax.Global.themeDarkInactive.image, for: []) } } if Client.preferences.isPersistentConnection { - killSwitchButton.accessibilityLabel = L10n.Global.disable + " " + L10n.Settings.ApplicationSettings.KillSwitch.title - killSwitchButton.setImage(Asset.Piax.Global.killswitchDarkActive.image, for: []) + killSwitchButton.accessibilityLabel = L10n.Localizable.Global.disable + " " + L10n.Localizable.Settings.ApplicationSettings.KillSwitch.title + killSwitchButton.setImage(Asset.Images.Piax.Global.killswitchDarkActive.image, for: []) } else { - killSwitchButton.accessibilityLabel = L10n.Global.enable + " " + L10n.Settings.ApplicationSettings.KillSwitch.title - killSwitchButton.setImage(Theme.current.palette.appearance == .light ? Asset.Piax.Global.killswitchLightInactive.image : - Asset.Piax.Global.killswitchDarkInactive.image, for: []) + killSwitchButton.accessibilityLabel = L10n.Localizable.Global.enable + " " + L10n.Localizable.Settings.ApplicationSettings.KillSwitch.title + killSwitchButton.setImage(Theme.current.palette.appearance == .light ? Asset.Images.Piax.Global.killswitchLightInactive.image : + Asset.Images.Piax.Global.killswitchDarkInactive.image, for: []) } if Client.preferences.nmtRulesEnabled { - nmtButton.accessibilityLabel = L10n.Global.disable + " " + L10n.Tiles.Quicksetting.Nmt.title - nmtButton.setImage(Theme.current.palette.appearance == .light ? Asset.Piax.Global.nmtLightActive.image : - Asset.Piax.Global.nmtDarkActive.image, for: []) + nmtButton.accessibilityLabel = L10n.Localizable.Global.disable + " " + L10n.Localizable.Tiles.Quicksetting.Nmt.title + nmtButton.setImage(Theme.current.palette.appearance == .light ? Asset.Images.Piax.Global.nmtLightActive.image : + Asset.Images.Piax.Global.nmtDarkActive.image, for: []) } else { - nmtButton.accessibilityLabel = L10n.Global.enable + " " + L10n.Tiles.Quicksetting.Nmt.title - nmtButton.setImage(Theme.current.palette.appearance == .light ? Asset.Piax.Global.nmtLightInactive.image : - Asset.Piax.Global.nmtDarkInactive.image, for: []) + nmtButton.accessibilityLabel = L10n.Localizable.Global.enable + " " + L10n.Localizable.Tiles.Quicksetting.Nmt.title + nmtButton.setImage(Theme.current.palette.appearance == .light ? Asset.Images.Piax.Global.nmtLightInactive.image : + Asset.Images.Piax.Global.nmtDarkInactive.image, for: []) } - browserButton.setImage(Theme.current.palette.appearance == .light ? Asset.Piax.Global.browserLightInactive.image : - Asset.Piax.Global.browserDarkInactive.image, for: []) + browserButton.setImage(Theme.current.palette.appearance == .light ? Asset.Images.Piax.Global.browserLightInactive.image : + Asset.Images.Piax.Global.browserDarkInactive.image, for: []) } diff --git a/PIA VPN/Tiles/QuickSettingsTileCollectionViewCell.swift b/PIA VPN/Tiles/QuickSettingsTileCollectionViewCell.swift index 6437f4ba5..e50c27763 100644 --- a/PIA VPN/Tiles/QuickSettingsTileCollectionViewCell.swift +++ b/PIA VPN/Tiles/QuickSettingsTileCollectionViewCell.swift @@ -57,7 +57,7 @@ class QuickSettingsTileCollectionViewCell: UICollectionViewCell, TileableCell { case .normal: self.accessibilityTraits = UIAccessibilityTraits.button self.isAccessibilityElement = true - self.accessoryImageRight.image = Asset.Piax.Tiles.openTileDetails.image + self.accessoryImageRight.image = Asset.Images.Piax.Tiles.openTileDetails.image self.tileLeftConstraint.constant = 0 self.accessoryButtonLeft.isHidden = true case .edit: @@ -77,11 +77,11 @@ class QuickSettingsTileCollectionViewCell: UICollectionViewCell, TileableCell { if Client.providers.tileProvider.visibleTiles.contains(tileType) { accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Visible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Visible.Tile.action } else { accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Invisible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Invisible.Tile.action } } diff --git a/PIA VPN/Tiles/RegionTile.swift b/PIA VPN/Tiles/RegionTile.swift index 17d0f3af2..4cdb77d8c 100644 --- a/PIA VPN/Tiles/RegionTile.swift +++ b/PIA VPN/Tiles/RegionTile.swift @@ -77,7 +77,7 @@ class RegionTile: UIView, Tileable { nc.addObserver(self, selector: #selector(updateServer), name: .PIAServerHasBeenUpdated, object: nil) viewShouldRestyle() - self.tileTitle.text = L10n.Tiles.Region.title.uppercased() + self.tileTitle.text = L10n.Localizable.Tiles.Region.title.uppercased() self.updateServer() } @@ -105,7 +105,7 @@ class RegionTile: UIView, Tileable { labelDedicatedIPTitle.isHidden = false viewIP.isHidden = false labelIP.text = server.wireGuardAddressesForUDP?.first?.ip ?? "" - labelDedicatedIPTitle.text = L10n.Dedicated.Ip.title.uppercased() + labelDedicatedIPTitle.text = L10n.Localizable.Dedicated.Ip.title.uppercased() viewIP.layer.cornerRadius = 10.0 viewIP.layer.borderWidth = 0.5 viewIP.layer.borderColor = UIColor.piaGrey4.cgColor diff --git a/PIA VPN/Tiles/RegionTileCollectionViewCell.swift b/PIA VPN/Tiles/RegionTileCollectionViewCell.swift index 64a9289f5..5724b5c7b 100644 --- a/PIA VPN/Tiles/RegionTileCollectionViewCell.swift +++ b/PIA VPN/Tiles/RegionTileCollectionViewCell.swift @@ -46,7 +46,7 @@ class RegionTileCollectionViewCell: UICollectionViewCell, TileableCell { } func setupCellForStatus(_ status: TileStatus) { - self.accessibilityLabel = L10n.Tiles.Region.title + self.accessibilityLabel = L10n.Localizable.Tiles.Region.title Theme.current.applyPrincipalBackground(self) Theme.current.applyPrincipalBackground(self.contentView) tile.status = status @@ -56,7 +56,7 @@ class RegionTileCollectionViewCell: UICollectionViewCell, TileableCell { case .normal: self.accessibilityTraits = UIAccessibilityTraits.button self.isAccessibilityElement = true - self.accessoryImageRight.image = Asset.Piax.Tiles.openTileDetails.image + self.accessoryImageRight.image = Asset.Images.Piax.Tiles.openTileDetails.image self.tileLeftConstraint.constant = 0 self.accessoryButtonLeft.isHidden = true case .edit: @@ -93,11 +93,11 @@ class RegionTileCollectionViewCell: UICollectionViewCell, TileableCell { if Client.providers.tileProvider.visibleTiles.contains(tileType) { accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Visible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Visible.Tile.action } else { accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Invisible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Invisible.Tile.action } } diff --git a/PIA VPN/Tiles/SubscriptionTile.swift b/PIA VPN/Tiles/SubscriptionTile.swift index a9bdb6314..390a59aeb 100644 --- a/PIA VPN/Tiles/SubscriptionTile.swift +++ b/PIA VPN/Tiles/SubscriptionTile.swift @@ -55,7 +55,7 @@ class SubscriptionTile: UIView, Tileable { nc.addObserver(self, selector: #selector(displayAccountInformation), name: .PIAAccountDidRefresh, object: nil) viewShouldRestyle() - self.subscriptionTitle.text = L10n.Tiles.Subscription.title.uppercased() + self.subscriptionTitle.text = L10n.Localizable.Tiles.Subscription.title.uppercased() displayAccountInformation() } @@ -71,11 +71,11 @@ class SubscriptionTile: UIView, Tileable { if let userInfo = currentUser?.info { if userInfo.isExpired { - self.subscriptionValue.text = L10n.Account.ExpiryDate.expired + self.subscriptionValue.text = L10n.Localizable.Account.ExpiryDate.expired } else { - var value = L10n.Account.ExpiryDate.information(userInfo.humanReadableExpirationDate()) + var value = L10n.Localizable.Account.ExpiryDate.information(userInfo.humanReadableExpirationDate()) if let days = daysLeftFromAccountInfo(userInfo) { - let daysLeft = L10n.Tiles.Subscription.Days.left(days) + let daysLeft = L10n.Localizable.Tiles.Subscription.Days.left(days) let plan = planDescriptionFromPlan(userInfo.plan) if plan != "" { value = plan + " " + daysLeft @@ -91,9 +91,9 @@ class SubscriptionTile: UIView, Tileable { private func planDescriptionFromPlan(_ plan: Plan) -> String { switch plan { - case .trial: return L10n.Tiles.Subscription.trial - case .monthly: return L10n.Tiles.Subscription.monthly - case .yearly: return L10n.Tiles.Subscription.yearly + case .trial: return L10n.Localizable.Tiles.Subscription.trial + case .monthly: return L10n.Localizable.Tiles.Subscription.monthly + case .yearly: return L10n.Localizable.Tiles.Subscription.yearly default: return "" } } diff --git a/PIA VPN/Tiles/SubscriptionTileCollectionViewCell.swift b/PIA VPN/Tiles/SubscriptionTileCollectionViewCell.swift index 2ade8e565..c183163d9 100644 --- a/PIA VPN/Tiles/SubscriptionTileCollectionViewCell.swift +++ b/PIA VPN/Tiles/SubscriptionTileCollectionViewCell.swift @@ -64,11 +64,11 @@ class SubscriptionTileCollectionViewCell: UICollectionViewCell, TileableCell { if Client.providers.tileProvider.visibleTiles.contains(tileType) { accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Visible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Visible.Tile.action } else { accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Invisible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Invisible.Tile.action } } diff --git a/PIA VPN/Tiles/UsageTile.swift b/PIA VPN/Tiles/UsageTile.swift index 4067f1cfb..49244f205 100644 --- a/PIA VPN/Tiles/UsageTile.swift +++ b/PIA VPN/Tiles/UsageTile.swift @@ -59,9 +59,9 @@ class UsageTile: UIView, Tileable { nc.addObserver(self, selector: #selector(displayUsageInformation), name: .PIAVPNUsageUpdate, object: nil) viewShouldRestyle() - self.usageTitle.text = L10n.Tiles.Usage.title.uppercased() - self.uploadTitle.text = L10n.Tiles.Usage.upload - self.downloadTitle.text = L10n.Tiles.Usage.download + self.usageTitle.text = L10n.Localizable.Tiles.Usage.title.uppercased() + self.uploadTitle.text = L10n.Localizable.Tiles.Usage.upload + self.downloadTitle.text = L10n.Localizable.Tiles.Usage.download displayUsageInformation() } @@ -102,9 +102,9 @@ class UsageTile: UIView, Tileable { countStyle: .file) self.uploadValue.alpha = 0.2 self.downloadValue.alpha = 0.2 - self.usageTitle.text = L10n.Tiles.Usage.Ipsec.title + self.usageTitle.text = L10n.Localizable.Tiles.Usage.Ipsec.title } else { - self.usageTitle.text = L10n.Tiles.Usage.title.uppercased() + self.usageTitle.text = L10n.Localizable.Tiles.Usage.title.uppercased() self.uploadValue.alpha = 1 self.downloadValue.alpha = 1 } diff --git a/PIA VPN/Tiles/UsageTileCollectionViewCell.swift b/PIA VPN/Tiles/UsageTileCollectionViewCell.swift index 6edce932b..f6d2b157b 100644 --- a/PIA VPN/Tiles/UsageTileCollectionViewCell.swift +++ b/PIA VPN/Tiles/UsageTileCollectionViewCell.swift @@ -64,11 +64,11 @@ class UsageTileCollectionViewCell: UICollectionViewCell, TileableCell { if Client.providers.tileProvider.visibleTiles.contains(tileType) { accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Visible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Visible.Tile.action } else { accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .normal) accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .highlighted) - accessoryButtonLeft.accessibilityLabel = L10n.Tiles.Accessibility.Invisible.Tile.action + accessoryButtonLeft.accessibilityLabel = L10n.Localizable.Tiles.Accessibility.Invisible.Tile.action } } diff --git a/PIA VPN/TrustedNetworksViewController.swift b/PIA VPN/TrustedNetworksViewController.swift index 18376fbee..252ccf87e 100644 --- a/PIA VPN/TrustedNetworksViewController.swift +++ b/PIA VPN/TrustedNetworksViewController.swift @@ -106,9 +106,9 @@ class TrustedNetworksViewController: AutolayoutViewController { // MARK: Private Methods private func presentKillSwitchAlert() { - let alert = Macros.alert(nil, L10n.Settings.Nmt.Killswitch.disabled) - alert.addCancelAction(L10n.Global.close) - alert.addActionWithTitle(L10n.Global.enable) { + let alert = Macros.alert(nil, L10n.Localizable.Settings.Nmt.Killswitch.disabled) + alert.addCancelAction(L10n.Localizable.Global.close) + alert.addActionWithTitle(L10n.Localizable.Global.enable) { let preferences = Client.preferences.editable() preferences.isPersistentConnection = true preferences.commit() @@ -217,7 +217,7 @@ extension TrustedNetworksViewController: UICollectionViewDelegateFlowLayout, UIC case UICollectionView.elementKindSectionHeader: let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: Cells.header, for: indexPath) as! PIAHeaderCollectionViewCell - headerView.setup(withTitle: L10n.Network.Management.Tool.title, andSubtitle: L10n.Settings.Hotspothelper.description) + headerView.setup(withTitle: L10n.Localizable.Network.Management.Tool.title, andSubtitle: L10n.Localizable.Settings.Hotspothelper.description) return headerView default: diff --git a/PIA VPN/UIDevice+WIFI.swift b/PIA VPN/UIDevice+WIFI.swift new file mode 100644 index 000000000..f02918a22 --- /dev/null +++ b/PIA VPN/UIDevice+WIFI.swift @@ -0,0 +1,24 @@ +// +// UIDevice+WIFI.swift +// PIA VPN +// +// Created by Said Rehouni on 7/11/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import Foundation +import SystemConfiguration +import NetworkExtension +import UIKit + +extension UIDevice { + var WiFiSSID: String? { + guard let interfaces = NEHotspotHelper.supportedNetworkInterfaces(), + interfaces.count > 0, + let interface = interfaces[0] as? NEHotspotNetwork, + !interface.ssid.isEmpty else { + return nil + } + return interface.ssid + } +} diff --git a/PIA VPN/UIViewLoading.swift b/PIA VPN/UIViewLoading.swift new file mode 100644 index 000000000..4fe18d27f --- /dev/null +++ b/PIA VPN/UIViewLoading.swift @@ -0,0 +1,117 @@ +// +// UIViewLoading.swift +// PIALibrary-iOS +// +// Created by Jose Antonio Blaya Garcia on 04/12/2018. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import Foundation +import Lottie +import UIKit + +extension UIView: AnimatingLoadingDelegate { + + private struct LottieRepos { + static var graphLoad: AnimationView? + } + + var graphLoad: AnimationView? { + get { + return objc_getAssociatedObject(self, &LottieRepos.graphLoad) as? AnimationView + } + set { + if let unwrappedValue = newValue { + objc_setAssociatedObject(self, &LottieRepos.graphLoad, unwrappedValue as AnimationView?, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + } + + public func showLoadingAnimation() { + if graphLoad == nil { + graphLoad = AnimationView(name: "pia-spinner") + } + addLoadingAnimation() + } + + private func addLoadingAnimation() { + graphLoad?.loopMode = .loop + if let graphLoad = graphLoad { + self.addSubview(graphLoad) + graphLoad.play() + } + setLoadingConstraints() + } + + public func hideLoadingAnimation() { + graphLoad?.stop() + graphLoad?.removeFromSuperview() + } + + public func adjustLottieSize() { + let lottieWidth = self.frame.width/2 + graphLoad?.frame = CGRect( + x: lottieWidth/2, + y: (self.frame.height - lottieWidth)/2, + width: lottieWidth, + height: lottieWidth + ) + } + + private func setLoadingConstraints() { + if let graphLoad = graphLoad { + + graphLoad.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint(item: graphLoad, + attribute: .centerX, + relatedBy: .equal, + toItem: self, + attribute: .centerX, + multiplier: 1.0, + constant: 0.0).isActive = true + + NSLayoutConstraint(item: graphLoad, + attribute: .centerY, + relatedBy: .equal, + toItem: self, + attribute: .centerY, + multiplier: 1.0, + constant: 0.0).isActive = true + + let lottieWidth = self.frame.width/2 + + NSLayoutConstraint(item: graphLoad, + attribute: .width, + relatedBy: .equal, + toItem: nil, + attribute: .width, + multiplier: 1.0, + constant: lottieWidth).isActive = true + + NSLayoutConstraint(item: graphLoad, + attribute: .height, + relatedBy: .equal, + toItem: nil, + attribute: .height, + multiplier: 1.0, + constant: lottieWidth).isActive = true + + } + } + +} diff --git a/PIA VPN/VPNPermissionViewController.swift b/PIA VPN/VPNPermissionViewController.swift index a34176fa1..7bcbe2353 100644 --- a/PIA VPN/VPNPermissionViewController.swift +++ b/PIA VPN/VPNPermissionViewController.swift @@ -44,14 +44,14 @@ class VPNPermissionViewController: AutolayoutViewController { override func viewDidLoad() { super.viewDidLoad() - title = L10n.VpnPermission.title + title = L10n.Localizable.VpnPermission.title navigationItem.hidesBackButton = true self.view.accessibilityIdentifier = AccessibilityId.VPNPermission.screen - imvPicture.image = Asset.imageVpnAllow.image - labelTitle.text = L10n.VpnPermission.Body.title - labelMessage.text = L10n.VpnPermission.Body.subtitle(L10n.Global.ok) - labelFooter.text = L10n.VpnPermission.Body.footer + imvPicture.image = Asset.Images.imageVpnAllow.image + labelTitle.text = L10n.Localizable.VpnPermission.Body.title + labelMessage.text = L10n.Localizable.VpnPermission.Body.subtitle(L10n.Localizable.Global.ok) + labelFooter.text = L10n.Localizable.VpnPermission.Body.footer styleSubmitButton() } @@ -74,17 +74,17 @@ class VPNPermissionViewController: AutolayoutViewController { } private func alertRequiredPermission() { - var message = L10n.VpnPermission.Disallow.Message.basic + var message = L10n.Localizable.VpnPermission.Disallow.Message.basic if MFMailComposeViewController.canSendMail() { - message += "\n" + L10n.VpnPermission.Disallow.Message.support + message += "\n" + L10n.Localizable.VpnPermission.Disallow.Message.support } - let alert = Macros.alert(L10n.VpnPermission.title, message) + let alert = Macros.alert(L10n.Localizable.VpnPermission.title, message) if MFMailComposeViewController.canSendMail() { - alert.addActionWithTitle(L10n.VpnPermission.Disallow.contact) { + alert.addActionWithTitle(L10n.Localizable.VpnPermission.Disallow.contact) { self.contactCustomerSupport() } } - alert.addCancelActionWithTitle(L10n.Global.ok) { + alert.addCancelActionWithTitle(L10n.Localizable.Global.ok) { self.submit() } present(alert, animated: true, completion: nil) @@ -111,7 +111,7 @@ class VPNPermissionViewController: AutolayoutViewController { private func styleSubmitButton() { buttonSubmit.setRounded() buttonSubmit.style(style: TextStyle.Buttons.piaGreenButton) - buttonSubmit.setTitle(L10n.Global.ok.uppercased(), + buttonSubmit.setTitle(L10n.Localizable.Global.ok.uppercased(), for: []) buttonSubmit.accessibilityIdentifier = AccessibilityId.VPNPermission.submit } diff --git a/PIA VPN/WalkthroughPageView.swift b/PIA VPN/WalkthroughPageView.swift new file mode 100644 index 000000000..14680868d --- /dev/null +++ b/PIA VPN/WalkthroughPageView.swift @@ -0,0 +1,109 @@ +// +// WalkthroughPageView.swift +// PIA VPN +// +// Created by Davide De Rosa on 12/8/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit +import PIALibrary +import DeviceCheck + +class WalkthroughPageView: UIView { + struct PageData { + let title: String + + let detail: String + + let image: UIImage? + } + + private let data: PageData + + private(set) var labelTitle = UILabel() + private(set) var labelDetail = UILabel() + + required init?(coder aDecoder: NSCoder) { + data = PageData(title: "", detail: "", image: nil) + super.init(coder: aDecoder) + } + + init(data: PageData) { + self.data = data + super.init(frame: .zero) + + configure() + } + + private func configure() { + let imvImage = UIImageView() + + addSubview(imvImage) + addSubview(labelTitle) + addSubview(labelDetail) + + translatesAutoresizingMaskIntoConstraints = false + imvImage.translatesAutoresizingMaskIntoConstraints = false + labelTitle.translatesAutoresizingMaskIntoConstraints = false + labelDetail.translatesAutoresizingMaskIntoConstraints = false + + var imageMultiplier: CGFloat = 0.55 + var labelSpaceMargin: CGFloat = 5.0 + var textTopMargin: CGFloat = 19.0 + + switch UIDevice().type { + case .iPhoneSE, .iPhone5, .iPhone5S: + imageMultiplier = 0.40 + labelSpaceMargin = 2.0 + textTopMargin = 5.0 + default: break + } + + NSLayoutConstraint.activate([ + imvImage.topAnchor.constraint(equalTo: topAnchor), + imvImage.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor, multiplier: imageMultiplier), + imvImage.centerXAnchor.constraint(equalTo: centerXAnchor), + labelTitle.topAnchor.constraint(equalTo: imvImage.bottomAnchor, constant: textTopMargin), + labelTitle.leftAnchor.constraint(equalTo: leftAnchor, constant: 40.0), + labelTitle.rightAnchor.constraint(equalTo: rightAnchor, constant: -40.0), + labelDetail.topAnchor.constraint(equalTo: labelTitle.bottomAnchor, constant: labelSpaceMargin), + labelDetail.leftAnchor.constraint(equalTo: labelTitle.leftAnchor), + labelDetail.rightAnchor.constraint(equalTo: labelTitle.rightAnchor), + labelDetail.bottomAnchor.constraint(equalTo: bottomAnchor) + ]) + + imvImage.contentMode = .scaleAspectFit + imvImage.setContentCompressionResistancePriority(.defaultLow, for: .vertical) + labelTitle.numberOfLines = 0 + labelDetail.numberOfLines = 0 + + labelTitle.text = data.title + labelDetail.text = data.detail + imvImage.image = data.image + + labelTitle.textAlignment = .center + labelDetail.textAlignment = .center + + } + + func applyStyles() { + Theme.current.applySubtitle(labelDetail) + Theme.current.applyTitle(labelTitle, appearance: .dark) + } + +} diff --git a/PIA VPN/WelcomePageViewController.swift b/PIA VPN/WelcomePageViewController.swift new file mode 100644 index 000000000..fac49bf46 --- /dev/null +++ b/PIA VPN/WelcomePageViewController.swift @@ -0,0 +1,159 @@ +// +// WelcomePageViewController.swift +// PIALibrary-iOS +// +// Created by Davide De Rosa on 10/19/17. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// The Private Internet Access iOS Client is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The Private Internet Access iOS Client is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with the Private +// Internet Access iOS Client. If not, see . +// + +import UIKit +import PIALibrary + +class WelcomePageViewController: UIPageViewController { + private var source = [UIViewController]() + + var preset: Preset? + + var selectedPlanIndex: Int? + + var allPlans: [PurchasePlan]? + + weak var completionDelegate: WelcomeCompletionDelegate? + + override func viewDidLoad() { + super.viewDidLoad() + + guard let preset = self.preset else { + fatalError("Preset not propagated") + } + if preset.pages.contains(.login) { + let vc = StoryboardScene.Welcome.loginViewController.instantiate() + source.append(vc) + } + if preset.pages.contains(.purchase) { + let vc = StoryboardScene.Welcome.purchaseViewController.instantiate() + source.append(vc) + } + if preset.pages.contains(.restore) { + let vc = StoryboardScene.Welcome.restoreSignupViewController.instantiate() + source.append(vc) + } + dataSource = self + + guard !source.isEmpty else { + fatalError("Source controllers are empty") + } + let isSinglePage = (source.count == 1) + guard isSinglePage || (preset.pages == .all) else { + fatalError("Currently supports all pages or a single page, not a subset") + } + + for vc in source { + guard let child = vc as? WelcomeChild else { + fatalError("Source element must be a WelcomeChild") + } + child.preset = preset + child.omitsSiblingLink = !isSinglePage + child.completionDelegate = completionDelegate + } + + setViewControllers([source.first!], direction: .forward, animated: false, completion: nil) + + if let scrollView = self.view.subviews.filter({ + $0.isKind(of: UIScrollView.self) + }).first as? UIScrollView { + scrollView.isScrollEnabled = false + } + + } + + func show(page: Pages) { + + // XXX: quick temp solution for log2 + let index: Int + switch page { + case .login: + index = 0 + + case .purchase: + index = 1 + + case .restore: + index = 3 + + default: + return + } + + guard (index < source.count) else { + fatalError("Page \(index) beyond source controllers (\(source.count))") + } + guard let currentIndex = source.index(of: viewControllers!.first!) else { + fatalError("No page displayed yet") + } + let controller = source[index] + let direction: UIPageViewController.NavigationDirection = (index > currentIndex) ? .forward : .reverse + setViewControllers([controller], direction: direction, animated: true, completion: nil) + } + + // MARK: Unwind + + @IBAction private func unwoundSignupFailure(segue: UIStoryboardSegue) { + } + + // MARK: Size classes + + public override func overrideTraitCollection(forChild childViewController: UIViewController) -> UITraitCollection? { + guard let window = view.window else { + return super.traitCollection + } + let isLandscape = (window.bounds.size.width > window.bounds.size.height) + let minHeight: CGFloat + if let _ = childViewController as? LoginViewController { + minHeight = 568.0 + } else { + minHeight = 667.0 + } + if !Macros.isDevicePad && (isLandscape || (window.bounds.size.height < minHeight)) { + return UITraitCollection(verticalSizeClass: .compact) + } else { + return UITraitCollection(verticalSizeClass: .regular) + } + } +} + +extension WelcomePageViewController: UIPageViewControllerDataSource { + func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { + guard let index = source.index(of: viewController) else { + fatalError("Cannot find view controller") + } + if (index == 0) { + return nil + } + return source[index - 1] + } + + func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { + guard let index = source.index(of: viewController) else { + fatalError("Cannot find view controller") + } + if (index == source.count - 1) { + return nil + } + return source[index + 1] + } +} diff --git a/PIA-VPN_E2E_Tests/PIA-VPN-e2e-simulator.xctestplan b/PIA-VPN_E2E_Tests/PIA-VPN-e2e-simulator.xctestplan index 8750f1781..c1a05911c 100644 --- a/PIA-VPN_E2E_Tests/PIA-VPN-e2e-simulator.xctestplan +++ b/PIA-VPN_E2E_Tests/PIA-VPN-e2e-simulator.xctestplan @@ -29,6 +29,7 @@ }, "testTargets" : [ { + "parallelizable" : true, "target" : { "containerPath" : "container:PIA VPN.xcodeproj", "identifier" : "69B70AAF2ACBF51C0072A09D", diff --git a/PIA-VPN_E2E_Tests/PIAOnboardingVPNPermissionE2ETests.swift b/PIA-VPN_E2E_Tests/PIAOnboardingVPNPermissionE2ETests.swift new file mode 100644 index 000000000..b765bc7b3 --- /dev/null +++ b/PIA-VPN_E2E_Tests/PIAOnboardingVPNPermissionE2ETests.swift @@ -0,0 +1,112 @@ +// +// PIAOnboardingVPNPermissionE2ETests.swift +// PIA-VPN_E2E_Tests +// +// Created by Laura S on 10/5/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import XCTest + +final class PIAOnboardingVPNPermissionE2ETests: XCTestCase { + + private var app: XCUIApplication! + + override func setUpWithError() throws { + continueAfterFailure = false + app = XCUIApplication(bundleIdentifier: "com.privateinternetaccess.ios.PIA-VPN") + launchAppAndAuthenticate() + } + + override func tearDownWithError() throws { + app.terminate() + } + + private func launchAppAndAuthenticate() { + dismissNotificationsPermissionAlertIfNeeded() + app.launch() + app.logOutIfNeeded() + app.navigateToLoginScreenIfNeeded() + app.fillLoginScreen(with: CredentialsUtil.credentials(type: .valid)) + + guard app.loginButton.exists else { + XCTFail("XCUIApplication: failed to find LOGIN button in screen to authenticate user") + return + } + + app.loginButton.tap() + + guard app.vpnPermissionScreen.waitForExistence(timeout: app.defaultTimeout) else { + XCTFail("XCUIApplication: login failed") + return + } + } + + private func allowVPNProfileInstallationWhenRequested() { + addUIInterruptionMonitor(withDescription: "Vpn permission dialog") { element in + let vpnPermissionAlertText = "“PIA VPN dev” Would Like to Add VPN Configurations" + let isVPNPermissionAlert = element.elementType == .alert && + element.staticTexts[vpnPermissionAlertText].exists + + let allowButton = element.buttons["Allow"].firstMatch + if isVPNPermissionAlert && allowButton.exists { + allowButton.tap() + return true + } else { + return false + } + } + } + + func testAllowVPNProfileInstallation() throws { + let vpnPermissionScreen = app.view(with: AccessibilityId.VPNPermission.screen) + let vpnPermissionButton = app.button(with: AccessibilityId.VPNPermission.submit) + + XCTContext.runActivity(named: "GIVEN that the Vpn Permission screen is shown") { _ in + XCTAssertTrue(vpnPermissionScreen.exists) + XCTAssertTrue(vpnPermissionButton.exists) + } + + XCTContext.runActivity(named: "WHEN giving consent to install the VPN profile") { _ in + allowVPNProfileInstallationWhenRequested() + vpnPermissionButton.tap() + + // Do some action on the app so that the system alert is handled + app.swipeUp() + } + + XCTContext.runActivity(named: "THEN the Home screen is shown") { _ in + let dashboardMenuButtonExists = app.dashboardMenuButton.waitForExistence(timeout: app.defaultTimeout) + + XCTAssertTrue(dashboardMenuButtonExists) + } + + XCTContext.runActivity(named: "AND the VPN Permission screen is NOT shown") { _ in + XCTAssertFalse(app.vpnPermissionScreen.exists) + } + } + +} + +extension PIAOnboardingVPNPermissionE2ETests { + /* Dismisses only the system alert about sending Notifications Permission. + Sometimes this alert appears unexpectedly + when the app launches. + This makes not possible to continue with the test + since the rest of the UI becomes not interactable */ + private func dismissNotificationsPermissionAlertIfNeeded() { + addUIInterruptionMonitor(withDescription: "Notifications permission alert") { element in + let vpnPermissionAlertText = "“PIA VPN dev” Would Like to Send You Notifications" + let isNotificationsPermissionAlert = element.elementType == .alert && + element.staticTexts[vpnPermissionAlertText].exists + + let allowButton = element.buttons["Allow"].firstMatch + if isNotificationsPermissionAlert && allowButton.exists { + allowButton.tap() + return true + } else { + return false + } + } + } +} diff --git a/PIA-VPN_E2E_Tests/PIASignInE2ETests.swift b/PIA-VPN_E2E_Tests/PIASignInE2ETests.swift new file mode 100644 index 000000000..333983297 --- /dev/null +++ b/PIA-VPN_E2E_Tests/PIASignInE2ETests.swift @@ -0,0 +1,72 @@ +// +// PIASignInE2ETests.swift +// PIASignInE2ETests +// +// Created by Laura S on 10/3/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import XCTest + + +final class PIASignInE2ETests: XCTestCase { + private var app: XCUIApplication! + + override func setUpWithError() throws { + continueAfterFailure = false + app = XCUIApplication() + + // Handles any possible interruption in the test + // due to the appearence of a permission system alert + // (like Notifications permission, VPN profile installation, etc.) + app.dismissAnyPermissionSystemAlert(from: self) + + app.launch() + + app.logOutIfNeeded() + app.navigateToLoginScreenIfNeeded() + } + + override func tearDownWithError() throws { + app.terminate() + } + + func testSignInWithValidCredentials() throws { + XCTContext.runActivity(named: "GIVEN that valid credentials are provided in the login screen") { _ in + app.fillLoginScreen(with: CredentialsUtil.credentials(type: .valid)) + } + + XCTContext.runActivity(named: "WHEN tapping the 'Login' button") { _ in + app.loginButton.tap() + } + + XCTContext.runActivity(named: "THEN the VPN Permission screen will appear") { _ in + XCTAssertTrue(app.vpnPermissionScreen.waitForExistence(timeout: app.defaultTimeout)) + } + + XCTContext.runActivity(named: "AND no login error banner is displayed") { _ in + XCTAssertFalse(app.loginErrorMessage.exists) + } + + } + + func testSignInWithInvalidCredentials() throws { + XCTContext.runActivity(named: "GIVEN that invalid credentials are provided in the login screen") { _ in + app.fillLoginScreen(with: CredentialsUtil.credentials(type: .invalid)) + } + + XCTContext.runActivity(named: "WHEN tapping the 'Login' button") { _ in + app.loginButton.tap() + } + + XCTContext.runActivity(named: "THEN the login error banner is displayed") { _ in + XCTAssertTrue(app.loginErrorMessage.waitForExistence(timeout: app.shortTimeout)) + } + + XCTContext.runActivity(named: "AND the app does NOT display the Vpn Permission Screen") { _ in + XCTAssertFalse(app.vpnPermissionScreen.exists) + } + } + +} + diff --git a/PIA-VPN_E2E_Tests/Tests/BaseTest.swift b/PIA-VPN_E2E_Tests/Tests/BaseTest.swift new file mode 100644 index 000000000..933452d64 --- /dev/null +++ b/PIA-VPN_E2E_Tests/Tests/BaseTest.swift @@ -0,0 +1,29 @@ +// +// BaseTest.swift +// PIA-VPN_E2E_Tests +// +// Created by Geneva Parayno on 17/10/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import Quick +import Nimble +import XCTest +import Foundation + +class BaseTest: QuickSpec{ + static var app: XCUIApplication! + + override class func spec(){ + beforeEach { + app = XCUIApplication() + app.launch() + app.logOutIfNeeded() + app.navigateToLoginScreenIfNeeded() + } + + afterEach { + app.terminate() + } + } +} diff --git a/PIA-VPN_E2E_Tests/XCUIApplication+Extensions/XCUIApplication+Global.swift b/PIA-VPN_E2E_Tests/XCUIApplication+Extensions/XCUIApplication+Global.swift new file mode 100644 index 000000000..63b2d6f57 --- /dev/null +++ b/PIA-VPN_E2E_Tests/XCUIApplication+Extensions/XCUIApplication+Global.swift @@ -0,0 +1,169 @@ + +import XCTest + +extension XCUIApplication { + var defaultTimeout: TimeInterval { 10.0 } + var shortTimeout: TimeInterval { 5.0 } + + func view(with id: String) -> XCUIElement { + return otherElements[id] + } + + func button(with id: String) -> XCUIElement { + return buttons[id] + } + + func textField(with id: String) -> XCUIElement { + return textFields[id] + } + + func cell(with id: String) -> XCUIElement { + return cells[id] + } + + func secureTextField(with id: String) -> XCUIElement { + return secureTextFields[id] + } +} + + +extension XCUIApplication { + // This method navigates to the login screen from the Welcome screen + func navigateToLoginScreenIfNeeded() { + if goToLoginScreenButton.waitForExistence(timeout: shortTimeout) { + goToLoginScreenButton.tap() + } else { + if goToLoginScreenButtonOldVersion.waitForExistence(timeout: shortTimeout) { + goToLoginScreenButtonOldVersion.tap() + } + } + } + + + /// This method authenticates the user and installs the VPN profile + /// Use this method when we are testing flows where the app has to be logged in already. + /// In such cases, it is recommended to call this method from the `setUp` of each `XCTestCase` class + /// NOTE: the app must be already in the Login Screen for this method to work + /// So before calling this method, make sure to navigate to the login screen + func loginAndInstallVPNProfile(from test: XCTestCase) { + // Listens to any interruption due to a system alert permission + // and presses the 'Allow' button + // (like the VPN Permission system alert) + dismissAnyPermissionSystemAlert(from: test) + + // Logs Out if needed + logOutIfNeeded() + + // Goes To the Login Screen + navigateToLoginScreenIfNeeded() + + // Fills in valid credentials in the login screen + // The credentials are set in Environment Variables + let credentials = CredentialsUtil.credentials(type: .valid) + + let usernameTextField = textField(with: PIALibraryAccessibility.Id.Login.username) + + let passwordTextField = secureTextField(with: PIALibraryAccessibility.Id.Login.password) + + guard usernameTextField.exists, passwordTextField.exists else { + XCTFail("XCUIApplication: failed to find username and password fields in screen to authenticate user") + return + } + + guard loginButton.exists else { + XCTFail("XCUIApplication: failed to find LOGIN button in screen to authenticate user") + return + } + + // Type username + usernameTextField.tap() + usernameTextField.typeText(credentials.username) + + // Type password + passwordTextField.tap() + passwordTextField.typeText(credentials.password) + + loginButton.tap() + + guard vpnPermissionScreen.waitForExistence(timeout: app.defaultTimeout) else { + XCTFail("XCUIApplication: login failed") + return + } + + let vpnPermissionButton = button(with: AccessibilityId.VPNPermission.submit) + + guard vpnPermissionButton.exists else { + XCTFail("XCUIApplication: can't find the VPN permission submit button") + return + } + + vpnPermissionButton.tap() + + swipeUp() + + let connectionButtonExists = connectionButton.waitForExistence(timeout: defaultTimeout) + XCTAssertTrue(connectionButtonExists) + XCTAssertTrue(connectionButton.isHittable) + } + + + /// Sometimes a system alert to request permissions about Notifications or VPN profile installation can appear + /// at any time when the app is running + /// This makes not possible to contitnue with the test unless the alert is dismissed + /// This method dismisses any system alert by pressing 'Allow' button + /// It is adviced that we call this method from all the `setUp` method of the tests from the authentication flow + /// For tests where we require the app to be authenticated, + /// use the method `loginAndInstallVPNProfile(from test: XCTestCase)` from the `setUp` method of the `XCTestCase` + func dismissAnyPermissionSystemAlert(from test: XCTestCase) { + test.addUIInterruptionMonitor(withDescription: "Any system permission alert") { element in + + let allowButton = element.buttons["Allow"].firstMatch + if element.elementType == .alert && allowButton.exists { + allowButton.tap() + return true + } else { + return false + } + } + } + + // Logs out from the Dashboard screen (Home screen) + // If the app is showing other view, then this flow does not work + func logOutIfNeeded() { + guard dashboardMenuButton.exists else { return } + + dashboardMenuButton.tap() + + let logOutButton = cell(with: PIALibraryAccessibility.Id.Menu.logout).firstMatch + + if logOutButton.waitForExistence(timeout: defaultTimeout) { + logOutButton.tap() + + let confirmationDialogButton = button(with: PIALibraryAccessibility.Id.Dialog.destructive) + + if confirmationDialogButton.waitForExistence(timeout: shortTimeout) { + confirmationDialogButton.tap() + } + + let welcomeScreen = goToLoginScreenButton + + welcomeScreen.waitForExistence(timeout: defaultTimeout) + + } + } + + // This method asumes that the app is already in the login screen + func fillLoginScreen(with credentials: Credentials) { + if loginUsernameTextField.exists && loginPasswordTextField.exists { + // Type username + loginUsernameTextField.tap() + loginUsernameTextField.typeText(credentials.username) + + // Type password + loginPasswordTextField.tap() + loginPasswordTextField.typeText(credentials.password) + } else { + XCTFail("PIASigninE2ETests: Username and Password text fields on LoginViewController are either not identifiable or are moved") + } + } +} diff --git a/PIA-VPN_E2E_Tests/XCUIApplication+Extensions/XCUIApplication+WelcomeScreen.swift b/PIA-VPN_E2E_Tests/XCUIApplication+Extensions/XCUIApplication+WelcomeScreen.swift new file mode 100644 index 000000000..6364b3366 --- /dev/null +++ b/PIA-VPN_E2E_Tests/XCUIApplication+Extensions/XCUIApplication+WelcomeScreen.swift @@ -0,0 +1,22 @@ +// +// XCUIApplication+WelcomeScreen.swift +// PIA-VPN_E2E_Tests +// +// Created by Laura S on 10/5/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import Foundation +import XCTest + +// MARK: XCUIApplication + Welcome Screen UI elements + +extension XCUIApplication { + var goToLoginScreenButton: XCUIElement { + button(with: PIALibraryAccessibility.Id.Login.submitNew) + } + + var goToLoginScreenButtonOldVersion: XCUIElement { + button(with: PIALibraryAccessibility.Id.Login.submit) + } +} diff --git a/PIAWidget/Domain/UI/PIAConnectionView.swift b/PIAWidget/Domain/UI/PIAConnectionView.swift index e947e9017..7cd3ccc95 100644 --- a/PIAWidget/Domain/UI/PIAConnectionView.swift +++ b/PIAWidget/Domain/UI/PIAConnectionView.swift @@ -7,8 +7,8 @@ internal struct PIAConnectionView: View { internal let context: ActivityViewContext internal let showProtocol: Bool - let localizedRegionText = L10n.Widget.LiveActivity.Region.title - let localizedProtocolText = L10n.Widget.LiveActivity.SelectedProtocol.title + let localizedRegionText = L10n.Localizable.Widget.LiveActivity.Region.title + let localizedProtocolText = L10n.Localizable.Widget.LiveActivity.SelectedProtocol.title init(context: ActivityViewContext, showProtocol: Bool = false) { self.context = context diff --git a/PIAWidget/Domain/Widget/PIAConnectionActivityWidget.swift b/PIAWidget/Domain/Widget/PIAConnectionActivityWidget.swift index 1bd6eb2dc..5db0fc13f 100644 --- a/PIAWidget/Domain/Widget/PIAConnectionActivityWidget.swift +++ b/PIAWidget/Domain/Widget/PIAConnectionActivityWidget.swift @@ -4,7 +4,7 @@ import SwiftUI @available(iOSApplicationExtension 16.1, *) struct PIAConnectionActivityWidget: Widget { - let localizedRegionText = L10n.Widget.LiveActivity.Region.title + let localizedRegionText = L10n.Localizable.Widget.LiveActivity.Region.title var body: some WidgetConfiguration { ActivityConfiguration(for: PIAConnectionAttributes.self) { context in diff --git a/Resources/Resources/Shared/ar.lproj/UI.strings b/Resources/Resources/Shared/ar.lproj/UI.strings new file mode 100644 index 000000000..d93d9b375 --- /dev/null +++ b/Resources/Resources/Shared/ar.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "الإصدار %@ (%@)"; +"global.close" = "إغلاق"; +"global.ok" = "موافق"; +"global.cancel" = "إلغاء"; diff --git a/Resources/Resources/Shared/da.lproj/UI.strings b/Resources/Resources/Shared/da.lproj/UI.strings new file mode 100644 index 000000000..ef42d7faf --- /dev/null +++ b/Resources/Resources/Shared/da.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "Version %@ (%@)"; +"global.close" = "Luk"; +"global.ok" = "OK"; +"global.cancel" = "Annuller"; diff --git a/Resources/Resources/Shared/de.lproj/UI.strings b/Resources/Resources/Shared/de.lproj/UI.strings new file mode 100644 index 000000000..4268a80ca --- /dev/null +++ b/Resources/Resources/Shared/de.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "Version %@ (%@)"; +"global.close" = "Schließen"; +"global.ok" = "OK"; +"global.cancel" = "Abbrechen"; diff --git a/Resources/Resources/Shared/en.lproj/UI.strings b/Resources/Resources/Shared/en.lproj/UI.strings new file mode 100644 index 000000000..98ec3d5e2 --- /dev/null +++ b/Resources/Resources/Shared/en.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "Version %@ (%@)"; +"global.close" = "Close"; +"global.ok" = "OK"; +"global.cancel" = "Cancel"; diff --git a/Resources/Resources/Shared/es-MX.lproj/UI.strings b/Resources/Resources/Shared/es-MX.lproj/UI.strings new file mode 100644 index 000000000..e645f72b5 --- /dev/null +++ b/Resources/Resources/Shared/es-MX.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "Versión %@ (%@)"; +"global.close" = "Cerrar"; +"global.ok" = "Aceptar"; +"global.cancel" = "Cancelar"; diff --git a/Resources/Resources/Shared/fr.lproj/UI.strings b/Resources/Resources/Shared/fr.lproj/UI.strings new file mode 100644 index 000000000..fffdf519f --- /dev/null +++ b/Resources/Resources/Shared/fr.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "Version %@ (%@)"; +"global.close" = "Fermer"; +"global.ok" = "OK"; +"global.cancel" = "Annuler"; diff --git a/Resources/Resources/Shared/it.lproj/UI.strings b/Resources/Resources/Shared/it.lproj/UI.strings new file mode 100644 index 000000000..fc2c6c196 --- /dev/null +++ b/Resources/Resources/Shared/it.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "Versione %@ (%@)"; +"global.close" = "Chiudi"; +"global.ok" = "OK"; +"global.cancel" = "Annulla"; diff --git a/Resources/Resources/Shared/ja.lproj/UI.strings b/Resources/Resources/Shared/ja.lproj/UI.strings new file mode 100644 index 000000000..1751458e6 --- /dev/null +++ b/Resources/Resources/Shared/ja.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "バージョン%@ (%@)"; +"global.close" = "閉じる"; +"global.ok" = "OK"; +"global.cancel" = "キャンセル"; diff --git a/Resources/Resources/Shared/ko.lproj/UI.strings b/Resources/Resources/Shared/ko.lproj/UI.strings new file mode 100644 index 000000000..1c9f1e509 --- /dev/null +++ b/Resources/Resources/Shared/ko.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "버전 %@ (%@)"; +"global.close" = "닫기"; +"global.ok" = "확인"; +"global.cancel" = "취소"; diff --git a/Resources/Resources/Shared/nb.lproj/UI.strings b/Resources/Resources/Shared/nb.lproj/UI.strings new file mode 100644 index 000000000..5ccb0d7dc --- /dev/null +++ b/Resources/Resources/Shared/nb.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "Versjon %@ (%@)"; +"global.close" = "Lukk"; +"global.ok" = "OK"; +"global.cancel" = "Avbryt"; diff --git a/Resources/Resources/Shared/nl.lproj/UI.strings b/Resources/Resources/Shared/nl.lproj/UI.strings new file mode 100644 index 000000000..0131eaac3 --- /dev/null +++ b/Resources/Resources/Shared/nl.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "Versie %@ (%@)"; +"global.close" = "Sluiten"; +"global.ok" = "OK"; +"global.cancel" = "Annuleren"; diff --git a/Resources/Resources/Shared/pl.lproj/UI.strings b/Resources/Resources/Shared/pl.lproj/UI.strings new file mode 100644 index 000000000..fccbc42bf --- /dev/null +++ b/Resources/Resources/Shared/pl.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "Wersja %@ (%@)"; +"global.close" = "Zamknij"; +"global.ok" = "OK"; +"global.cancel" = "Anuluj"; diff --git a/Resources/Resources/Shared/pt-BR.lproj/UI.strings b/Resources/Resources/Shared/pt-BR.lproj/UI.strings new file mode 100644 index 000000000..32e6d7e28 --- /dev/null +++ b/Resources/Resources/Shared/pt-BR.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "Versão %@ (%@)"; +"global.close" = "Fechar"; +"global.ok" = "OK"; +"global.cancel" = "Cancelar"; diff --git a/Resources/Resources/Shared/ru.lproj/UI.strings b/Resources/Resources/Shared/ru.lproj/UI.strings new file mode 100644 index 000000000..2d1416365 --- /dev/null +++ b/Resources/Resources/Shared/ru.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "Версия %@ (%@)"; +"global.close" = "Закрыть"; +"global.ok" = "ОК"; +"global.cancel" = "Отмена"; diff --git a/Resources/Resources/Shared/th.lproj/UI.strings b/Resources/Resources/Shared/th.lproj/UI.strings new file mode 100644 index 000000000..3069ecff4 --- /dev/null +++ b/Resources/Resources/Shared/th.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "เวอร์ชั่น %@ (%@)"; +"global.close" = "ปิด"; +"global.ok" = "ตกลง"; +"global.cancel" = "ยกเลิก"; diff --git a/Resources/Resources/Shared/tr.lproj/UI.strings b/Resources/Resources/Shared/tr.lproj/UI.strings new file mode 100644 index 000000000..fc6795eb9 --- /dev/null +++ b/Resources/Resources/Shared/tr.lproj/UI.strings @@ -0,0 +1,12 @@ +/* (No Comment) */ +"global.cancel" = "İptal"; + +/* (No Comment) */ +"global.close" = "Kapat"; + +/* (No Comment) */ +"global.ok" = "Tamam"; + +/* (No Comment) */ +"global.version.format" = "Sürüm %@ (%@)"; + diff --git a/Resources/Resources/Shared/zh-Hans.lproj/UI.strings b/Resources/Resources/Shared/zh-Hans.lproj/UI.strings new file mode 100644 index 000000000..e6f012399 --- /dev/null +++ b/Resources/Resources/Shared/zh-Hans.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "版本 %@ (%@)"; +"global.close" = "关闭"; +"global.ok" = "确定"; +"global.cancel" = "取消"; diff --git a/Resources/Resources/Shared/zh-Hant.lproj/UI.strings b/Resources/Resources/Shared/zh-Hant.lproj/UI.strings new file mode 100644 index 000000000..7d68e2272 --- /dev/null +++ b/Resources/Resources/Shared/zh-Hant.lproj/UI.strings @@ -0,0 +1,4 @@ +"global.version.format" = "版本 %@ (%@)"; +"global.close" = "關閉"; +"global.ok" = "確認"; +"global.cancel" = "取消"; diff --git a/Resources/Resources/iOS/UI.xcassets/Contents.json b/Resources/Resources/iOS/UI.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Contents.json new file mode 100644 index 000000000..da4a164c9 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/centered-dark-map.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/centered-dark-map.imageset/Contents.json new file mode 100644 index 000000000..cb11bdd29 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/centered-dark-map.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "darkDotsCopy2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/centered-dark-map.imageset/darkDotsCopy2.pdf b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/centered-dark-map.imageset/darkDotsCopy2.pdf new file mode 100644 index 000000000..fa02be2ae Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/centered-dark-map.imageset/darkDotsCopy2.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/centered-light-map.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/centered-light-map.imageset/Contents.json new file mode 100644 index 000000000..2e6412708 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/centered-light-map.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "group.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/centered-light-map.imageset/group.pdf b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/centered-light-map.imageset/group.pdf new file mode 100644 index 000000000..225c9533b Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/centered-light-map.imageset/group.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/computer-icon.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/computer-icon.imageset/Contents.json new file mode 100644 index 000000000..3be8cd667 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/computer-icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "computer-7.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "computer-7@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "computer-7@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/computer-icon.imageset/computer-7.png b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/computer-icon.imageset/computer-7.png new file mode 100644 index 000000000..c655b4382 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/computer-icon.imageset/computer-7.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/computer-icon.imageset/computer-7@2x.png b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/computer-icon.imageset/computer-7@2x.png new file mode 100644 index 000000000..832367a12 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/computer-icon.imageset/computer-7@2x.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/computer-icon.imageset/computer-7@3x.png b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/computer-icon.imageset/computer-7@3x.png new file mode 100644 index 000000000..56aac76eb Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/computer-icon.imageset/computer-7@3x.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/globe-icon.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/globe-icon.imageset/Contents.json new file mode 100644 index 000000000..f65cff056 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/globe-icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "globe-turn-7.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "globe-turn-7@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "globe-turn-7@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/globe-icon.imageset/globe-turn-7.png b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/globe-icon.imageset/globe-turn-7.png new file mode 100644 index 000000000..7b578a284 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/globe-icon.imageset/globe-turn-7.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/globe-icon.imageset/globe-turn-7@2x.png b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/globe-icon.imageset/globe-turn-7@2x.png new file mode 100644 index 000000000..9c178500c Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/globe-icon.imageset/globe-turn-7@2x.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/globe-icon.imageset/globe-turn-7@3x.png b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/globe-icon.imageset/globe-turn-7@3x.png new file mode 100644 index 000000000..35ff13f83 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/globe-icon.imageset/globe-turn-7@3x.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-back.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-back.imageset/Contents.json new file mode 100644 index 000000000..6679f0b09 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-back.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "backIconCopy-2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-back.imageset/backIconCopy-2.pdf b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-back.imageset/backIconCopy-2.pdf new file mode 100644 index 000000000..b70bce618 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-back.imageset/backIconCopy-2.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-camera.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-camera.imageset/Contents.json new file mode 100644 index 000000000..bce0a6471 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-camera.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "scanIcon.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-camera.imageset/scanIcon.pdf b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-camera.imageset/scanIcon.pdf new file mode 100644 index 000000000..8745d7a04 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-camera.imageset/scanIcon.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-close.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-close.imageset/Contents.json new file mode 100644 index 000000000..a8f99392e --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-close.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "group104Copy.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-close.imageset/group104Copy.pdf b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-close.imageset/group104Copy.pdf new file mode 100644 index 000000000..2d3748c0f Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-close.imageset/group104Copy.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-warning.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-warning.imageset/Contents.json new file mode 100644 index 000000000..c8ddbf654 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-warning.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "warningIconCopy4.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-warning.imageset/warningIconCopy4.pdf b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-warning.imageset/warningIconCopy4.pdf new file mode 100644 index 000000000..0bf4d9890 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/icon-warning.imageset/warningIconCopy4.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/pagecontrol-selected-dot.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/pagecontrol-selected-dot.imageset/Contents.json new file mode 100644 index 000000000..c3b980050 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/pagecontrol-selected-dot.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "group4.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/pagecontrol-selected-dot.imageset/group4.pdf b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/pagecontrol-selected-dot.imageset/group4.pdf new file mode 100644 index 000000000..7664ef04f Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/pagecontrol-selected-dot.imageset/group4.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/pagecontrol-unselected-dot.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/pagecontrol-unselected-dot.imageset/Contents.json new file mode 100644 index 000000000..1ed8a5b8f --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/pagecontrol-unselected-dot.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "oval2Copy.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/pagecontrol-unselected-dot.imageset/oval2Copy.pdf b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/pagecontrol-unselected-dot.imageset/oval2Copy.pdf new file mode 100644 index 000000000..66e36df5f Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/pagecontrol-unselected-dot.imageset/oval2Copy.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/plan-selected.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/plan-selected.imageset/Contents.json new file mode 100644 index 000000000..fecd64dd0 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/plan-selected.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "group3.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/plan-selected.imageset/group3.pdf b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/plan-selected.imageset/group3.pdf new file mode 100644 index 000000000..7c72ff40f Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/plan-selected.imageset/group3.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/plan-unselected.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/plan-unselected.imageset/Contents.json new file mode 100644 index 000000000..7d4eb15e4 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/plan-unselected.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "oval3.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/plan-unselected.imageset/oval3.pdf b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/plan-unselected.imageset/oval3.pdf new file mode 100644 index 000000000..39296d491 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/plan-unselected.imageset/oval3.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/scrollableMap-dark.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/scrollableMap-dark.imageset/Contents.json new file mode 100644 index 000000000..24094a461 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/scrollableMap-dark.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "map-dark-walkthrough.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/scrollableMap-dark.imageset/map-dark-walkthrough.pdf b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/scrollableMap-dark.imageset/map-dark-walkthrough.pdf new file mode 100644 index 000000000..598eb2127 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/scrollableMap-dark.imageset/map-dark-walkthrough.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/scrollableMap-light.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/scrollableMap-light.imageset/Contents.json new file mode 100644 index 000000000..24d22d23a --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/scrollableMap-light.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "map-light-walkthrough.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/scrollableMap-light.imageset/map-light-walkthrough.pdf b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/scrollableMap-light.imageset/map-light-walkthrough.pdf new file mode 100644 index 000000000..9f7cd1cf2 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/scrollableMap-light.imageset/map-light-walkthrough.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/shield-icon.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/shield-icon.imageset/Contents.json new file mode 100644 index 000000000..ea0da821e --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/shield-icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "shield-7.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "shield-7@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "shield-7@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/shield-icon.imageset/shield-7.png b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/shield-icon.imageset/shield-7.png new file mode 100644 index 000000000..9d5a9b9b9 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/shield-icon.imageset/shield-7.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/shield-icon.imageset/shield-7@2x.png b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/shield-icon.imageset/shield-7@2x.png new file mode 100644 index 000000000..35489829c Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/shield-icon.imageset/shield-7@2x.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/PIAX/Global/shield-icon.imageset/shield-7@3x.png b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/shield-icon.imageset/shield-7@3x.png new file mode 100644 index 000000000..fcfa17137 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/PIAX/Global/shield-icon.imageset/shield-7@3x.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/close-icon.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/close-icon.imageset/Contents.json new file mode 100644 index 000000000..063263d21 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/close-icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "close_button@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "close_button@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/close-icon.imageset/close_button@2x.png b/Resources/Resources/iOS/UI.xcassets/close-icon.imageset/close_button@2x.png new file mode 100644 index 000000000..5b23b62c9 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/close-icon.imageset/close_button@2x.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/close-icon.imageset/close_button@3x.png b/Resources/Resources/iOS/UI.xcassets/close-icon.imageset/close_button@3x.png new file mode 100644 index 000000000..160a6c060 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/close-icon.imageset/close_button@3x.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/image-account-failed.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/image-account-failed.imageset/Contents.json new file mode 100644 index 000000000..36439e32c --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/image-account-failed.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "signup-error.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Resources/Resources/iOS/UI.xcassets/image-account-failed.imageset/signup-error.pdf b/Resources/Resources/iOS/UI.xcassets/image-account-failed.imageset/signup-error.pdf new file mode 100644 index 000000000..711c9d689 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/image-account-failed.imageset/signup-error.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/image-document-consent.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/image-document-consent.imageset/Contents.json new file mode 100644 index 000000000..53ef85ddc --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/image-document-consent.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "consent-icon.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Resources/Resources/iOS/UI.xcassets/image-document-consent.imageset/consent-icon.pdf b/Resources/Resources/iOS/UI.xcassets/image-document-consent.imageset/consent-icon.pdf new file mode 100644 index 000000000..b91347418 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/image-document-consent.imageset/consent-icon.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/image-no-internet.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/image-no-internet.imageset/Contents.json new file mode 100644 index 000000000..828c33b9a --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/image-no-internet.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "no-internet-dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Resources/Resources/iOS/UI.xcassets/image-no-internet.imageset/no-internet-dark.pdf b/Resources/Resources/iOS/UI.xcassets/image-no-internet.imageset/no-internet-dark.pdf new file mode 100644 index 000000000..c7d799ecc Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/image-no-internet.imageset/no-internet-dark.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/image-purchase-success.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/image-purchase-success.imageset/Contents.json new file mode 100644 index 000000000..713ce5e6b --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/image-purchase-success.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "image-purchase-success@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "image-purchase-success@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/image-purchase-success.imageset/image-purchase-success@2x.png b/Resources/Resources/iOS/UI.xcassets/image-purchase-success.imageset/image-purchase-success@2x.png new file mode 100644 index 000000000..61beeb699 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/image-purchase-success.imageset/image-purchase-success@2x.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/image-purchase-success.imageset/image-purchase-success@3x.png b/Resources/Resources/iOS/UI.xcassets/image-purchase-success.imageset/image-purchase-success@3x.png new file mode 100644 index 000000000..a1dc10d72 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/image-purchase-success.imageset/image-purchase-success@3x.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/image-receipt-background.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/image-receipt-background.imageset/Contents.json new file mode 100644 index 000000000..978980d7e --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/image-receipt-background.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "image-receipt-background@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "image-receipt-background@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/image-receipt-background.imageset/image-receipt-background@2x.png b/Resources/Resources/iOS/UI.xcassets/image-receipt-background.imageset/image-receipt-background@2x.png new file mode 100644 index 000000000..dc7214046 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/image-receipt-background.imageset/image-receipt-background@2x.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/image-receipt-background.imageset/image-receipt-background@3x.png b/Resources/Resources/iOS/UI.xcassets/image-receipt-background.imageset/image-receipt-background@3x.png new file mode 100644 index 000000000..31969f7fb Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/image-receipt-background.imageset/image-receipt-background@3x.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/image-redeem-claimed.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/image-redeem-claimed.imageset/Contents.json new file mode 100644 index 000000000..488339966 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/image-redeem-claimed.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "image-redeem-claimed@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "image-redeem-claimed@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/image-redeem-claimed.imageset/image-redeem-claimed@2x.png b/Resources/Resources/iOS/UI.xcassets/image-redeem-claimed.imageset/image-redeem-claimed@2x.png new file mode 100644 index 000000000..b5a3fa9ef Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/image-redeem-claimed.imageset/image-redeem-claimed@2x.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/image-redeem-claimed.imageset/image-redeem-claimed@3x.png b/Resources/Resources/iOS/UI.xcassets/image-redeem-claimed.imageset/image-redeem-claimed@3x.png new file mode 100644 index 000000000..8f47cf215 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/image-redeem-claimed.imageset/image-redeem-claimed@3x.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/image-redeem-invalid.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/image-redeem-invalid.imageset/Contents.json new file mode 100644 index 000000000..d85f8073a --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/image-redeem-invalid.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "gift-card-error.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Resources/Resources/iOS/UI.xcassets/image-redeem-invalid.imageset/gift-card-error.pdf b/Resources/Resources/iOS/UI.xcassets/image-redeem-invalid.imageset/gift-card-error.pdf new file mode 100644 index 000000000..7109baad6 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/image-redeem-invalid.imageset/gift-card-error.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/image-redeem-success.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/image-redeem-success.imageset/Contents.json new file mode 100644 index 000000000..327612fa0 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/image-redeem-success.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "sign-up-email.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Resources/Resources/iOS/UI.xcassets/image-redeem-success.imageset/sign-up-email.pdf b/Resources/Resources/iOS/UI.xcassets/image-redeem-success.imageset/sign-up-email.pdf new file mode 100644 index 000000000..0a76e648f Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/image-redeem-success.imageset/sign-up-email.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/image-walkthrough-1.imageset/01IllustrationWalkthroughScreen.pdf b/Resources/Resources/iOS/UI.xcassets/image-walkthrough-1.imageset/01IllustrationWalkthroughScreen.pdf new file mode 100644 index 000000000..73621d8e3 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/image-walkthrough-1.imageset/01IllustrationWalkthroughScreen.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/image-walkthrough-1.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/image-walkthrough-1.imageset/Contents.json new file mode 100644 index 000000000..09d6c52ff --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/image-walkthrough-1.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "01IllustrationWalkthroughScreen.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/image-walkthrough-2.imageset/02IllustrationWalkthroughGlobe.pdf b/Resources/Resources/iOS/UI.xcassets/image-walkthrough-2.imageset/02IllustrationWalkthroughGlobe.pdf new file mode 100644 index 000000000..4b3a393cb Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/image-walkthrough-2.imageset/02IllustrationWalkthroughGlobe.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/image-walkthrough-2.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/image-walkthrough-2.imageset/Contents.json new file mode 100644 index 000000000..3f5478ed4 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/image-walkthrough-2.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "02IllustrationWalkthroughGlobe.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/image-walkthrough-3.imageset/03IllustrationWalkthroughShield.pdf b/Resources/Resources/iOS/UI.xcassets/image-walkthrough-3.imageset/03IllustrationWalkthroughShield.pdf new file mode 100644 index 000000000..13136e585 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/image-walkthrough-3.imageset/03IllustrationWalkthroughShield.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/image-walkthrough-3.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/image-walkthrough-3.imageset/Contents.json new file mode 100644 index 000000000..5dc39a9d7 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/image-walkthrough-3.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "03IllustrationWalkthroughShield.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/nav-logo.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/nav-logo.imageset/Contents.json new file mode 100644 index 000000000..9dee98208 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/nav-logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "pia-logo-light-horizontal-128.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Resources/Resources/iOS/UI.xcassets/nav-logo.imageset/pia-logo-light-horizontal-128.pdf b/Resources/Resources/iOS/UI.xcassets/nav-logo.imageset/pia-logo-light-horizontal-128.pdf new file mode 100644 index 000000000..e6dd96640 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/nav-logo.imageset/pia-logo-light-horizontal-128.pdf differ diff --git a/Resources/Resources/iOS/UI.xcassets/qr-code.imageset/Contents.json b/Resources/Resources/iOS/UI.xcassets/qr-code.imageset/Contents.json new file mode 100644 index 000000000..5f2018ed7 --- /dev/null +++ b/Resources/Resources/iOS/UI.xcassets/qr-code.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_qr_code@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "ic_qr_code@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Resources/Resources/iOS/UI.xcassets/qr-code.imageset/ic_qr_code@2x.png b/Resources/Resources/iOS/UI.xcassets/qr-code.imageset/ic_qr_code@2x.png new file mode 100644 index 000000000..6e729dc91 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/qr-code.imageset/ic_qr_code@2x.png differ diff --git a/Resources/Resources/iOS/UI.xcassets/qr-code.imageset/ic_qr_code@3x.png b/Resources/Resources/iOS/UI.xcassets/qr-code.imageset/ic_qr_code@3x.png new file mode 100644 index 000000000..ec768c4d2 Binary files /dev/null and b/Resources/Resources/iOS/UI.xcassets/qr-code.imageset/ic_qr_code@3x.png differ diff --git a/Resources/Resources/iOS/ar.lproj/Signup.strings b/Resources/Resources/iOS/ar.lproj/Signup.strings new file mode 100644 index 000000000..ce7d9f39d --- /dev/null +++ b/Resources/Resources/iOS/ar.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "تأكيد التسجيل"; +"in_progress.message" = "جارٍ تأكيد عملية الشراء مع نظامنا. يمكن أن يستغرق ذلك لحظة لذلك انتظر قليلًا."; +"in_progress.redeem.message" = "جارٍ تأكيد رمز بطاقتك مع نظامنا. قد يستغرق ذلك بضع لحظات، انتظر قليلًا."; + +"success.title" = "تم الشراء"; +"success.message_format" = "شضكراً على اشتراكك معنا. لقد أرسلنا اسم المستخدم وكلمة المرور الخاصين بك إلى بريدك الإلكتروني %@"; +"success.redeem.title" = "تم استبدال البطاقة بنجاح"; +"success.redeem.message" = "ستصلك رسالة عبر البريد الإلكتروني تتضمن اسم المستخدم وكلمة المرور.\n\nتفاصيل تسجيل الدخول"; +"success.username.caption" = "اسم المستخدم"; +"success.password.caption" = "كلمة المرور"; +"success.submit" = "كيف تبدأ"; + +"failure.vc_title" = "فشل تسجيل الآن"; +"failure.title" = "فشل إنشاء الحساب"; +"failure.message" = "يتعذر علينا إنشاء حساب في الوقت الحالي. يرجى إعادة المحاولة لاحقًا. \n\nعند إعادة فتح التطبيق سيتم إعادة محاولة إنشاء حساب."; +"failure.purchase.sandbox.message" = "اشتراك الصندوق المحدد غير متاح في الإنتاج."; +"failure.redeem.invalid.title" = "رمز بطاقة غير صالح"; +"failure.redeem.invalid.message" = "يبدو أنك أدخلت رمز بطاقة غير صالح. يرجى إعادة المحاولة."; +"failure.redeem.claimed.title" = "تم استخدام هذه البطاقة بالفعل"; +"failure.redeem.claimed.message" = "يبدو أن هذه البطاقة استُخدمت في حساب آخر. حاول إدخال رمز جديد."; +"failure.submit" = "عودة"; + +"unreachable.vc_title" = "خطأ"; +"unreachable.title" = "أوبس!"; +"unreachable.message" = "لم يتم العثور على اتصال بالإنترنت. يرجى التأكد من اتصالك بالإنترنت وضغط زر إعادة المحاولة أدناه.\n\nيمكنك العودة إلى التطبيق في وقت لاحق لإنهاء العملية."; +"unreachable.submit" = "أعد المحاولة"; + +"purchase.uncredited.alert.message" = "لديك معاملات غير معتمدة. هل تريد استعادة تفاصيل حسابك؟"; +"purchase.uncredited.alert.button.cancel" = "إلغاء"; +"purchase.uncredited.alert.button.recover" = "استعادة الحساب"; + +"purchase.trials.intro" = "ابدأ تجربتك المجانية لمدة 7 ايام"; +"purchase.trials.price.after" = "ثم %@"; +"purchase.trials.money.back" = "ضمان استرداد المال لمدة 30 أيام"; +"purchase.trials.1year.protection" = "1 سنة من الخصوصية وحماية الهوية"; +"purchase.trials.anonymous" = "تصفح مجهول الهوية وإخفاء عنوان IP."; +"purchase.trials.devices" = "يدعم 10 أجهزة في المرة الواحدة"; +"purchase.trials.devices.description" = "احمي نفسك على ما يصل إلى 10 أجهزة في وقت واحد."; +"purchase.trials.region" = "اتصل بأي منطقة بسهولة"; +"purchase.trials.servers" = "أكثر من 3300 خادم في 32 دولة"; +"purchase.trials.start" = "ابدأ الاشتراك"; +"purchase.trials.all.plans" = "مشاهدة جميع الخطط المتاحة"; + +"purchase.subscribe.now" = "اشتراك الآن"; + +// WALKTHROUGH + +"walkthrough.action.next" = "التالي"; +"walkthrough.action.done" = "تم"; +"walkthrough.action.skip" = "تخطي"; + +"walkthrough.page.1.title" = "يدعم 10 أجهزة في المرة الواحدة"; +"walkthrough.page.1.description" = "احمي نفسك على ما يصل إلى 10 أجهزة في وقت واحد."; +"walkthrough.page.2.title" = "اتصل بأي منطقة بسهولة"; +"walkthrough.page.2.description" = "مع خوادم في جميع أنحاء العالم، أنت دائما تحت الحماية."; +"walkthrough.page.3.title" = "احمي نفسك من الإعلانات"; +"walkthrough.page.3.description" = "تمكين ميزة حظر المحتوى يمنع الإعلانات من الظهور في Safari."; + +"share.data.buttons.accept" = "قبول"; +"share.data.buttons.noThanks" = "لا، شكرًا"; +"share.data.buttons.readMore" = "قراءة المزيد"; +"share.data.text.title" = "يرجى مساعدتنا في تحسين خدمتنا"; +"share.data.text.description" = "لمساعدتنا في ضمان أداء اتصال خدمتنا، يمكنك مشاركة إحصائيات اتصالك معنا دون الكشف عن هويتك. لا تتضمن هذه التقارير أي معلومات محددة للشخصية."; +"share.data.text.footer" = "يمكنك دائمًا التحكم في ذلك من إعداداتك"; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/ar.lproj/Welcome.strings b/Resources/Resources/iOS/ar.lproj/Welcome.strings new file mode 100644 index 000000000..75d5562b2 --- /dev/null +++ b/Resources/Resources/iOS/ar.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "سجل الدخول إلى حسابك"; +"login.username.placeholder" = "اسم المستخدم (p1234567)"; +"login.password.placeholder" = "كلمة المرور"; +"login.submit" = "تسجيل الدخول"; +"login.restore.button" = "لم تحصل على تفاصيل الحساب؟"; +"login.error.title" = "تسجيل الدخول"; +"login.error.validation" = "يجب إدخال اسم المستخدم وكلمة المرور"; +"login.error.unauthorized" = "اسم المستخدم أو كلمة المرور غير صحيحة."; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "تسجيل الدخول باستخدام إيصال الشراء"; +"login.magic.link.title" = "تسجيل الدخول باستخدام رابط البريد الإلكتروني السحري"; +"login.magic.link.response" = "يرجى إلقاء نظرة على بريدك الإلكتروني للحصول على رابط تسجيل الدخول."; +"login.magic.link.send" = "إرسال الرابط"; +"login.magic.link.invalid.email" = "البريد الإلكتروني غير صحيح. يرجى إعادة المحاولة."; + +"purchase.title" = "حدد خطة VPN"; +"purchase.subtitle" = "ضمان استرداد المال لمدة 7 أيام"; +"purchase.email.placeholder" = "البريد الإلكتروني"; +"purchase.continue" = "متابعة"; +"purchase.login.footer" = "لديك حساب بالفعل؟"; +"purchase.login.button" = "تسجيل الدخول"; +"purchase.error.title" = "شراء"; +"purchase.error.validation" = "يجب إدخال عنوان بريد إلكتروني."; +"purchase.error.connectivity.title" = "فشل الاتصال"; +"purchase.error.connectivity.description" = "لم نتمكن من الوصول إلى منفذ الإنترنت الخاص. ربما بسبب اتصال ضعيف بالإنترنت أو خدماتنا موقوفة في بلدك."; +"purchase.confirm.form.email" = "يرجى إدخال بريدك الإلكتروني"; +"purchase.confirm.plan" = "أنت تشتري الآن خطة %@"; +"purchase.email.why" = "نحتاج إلى بريدك الإلكتروني لإرسال اسم المستخدم وكلمة المرور."; +"purchase.submit" = "إرسال"; +"purchase.or" = "أو"; + +"upgrade.header" = "مرحبًا بعودتك!"; +"upgrade.title" = "لاستخدام Private Internet Access، تحتاج إلى تجديد اشتراكك."; +"upgrade.renew.now" = "تجديد الآن"; + + + +"redeem.title" = "استلم محتويات بطاقة هدية"; +"redeem.subtitle" = "أدخل بريدك الإلكتروني ورمز بطاقة الهدية أو بطاقة التجربة المكون من %lu خانات."; +"redeem.email.placeholder" = "البريد الإلكتروني"; +"redeem.submit" = "إرسال"; +"redeem.error.title" = "استلام"; +"redeem.error.code" = "يجب أن يتكون الرمز من %lu خانات رقمية."; +"redeem.error.allfields" = "يرجى كتابة بريدك الإلكتروني ورمز PIN الخاص بالبطاقة."; +"redeem.accessibility.back" = "عودة"; +"redeem.giftcard.placeholder" = "رمز PIN الخاص بالبطاقة."; + +"plan.monthly.title" = "شهريًا"; +"plan.yearly.title" = "سنويًا"; +"plan.yearly.detail_format" = "%@%@ في السنة"; +"plan.price_format" = "%@ / شهر"; +"plan.best_value" = "أفضل قيمة"; +"plan.accessibility.per_month" = "في الشهر"; + +"restore.title" = "استرداد الشراء غير المقيد في الحساب"; +"restore.subtitle" = "إذا اشتريت خطة من خلال هذا التطبيق ولم تصلك بيانات تسجيل دخولك، يمكنك إرسالها مرة أخرى من هنا. لن يتم تحصيل رسوم منك أثناء هذه العملية."; +"restore.email.placeholder" = "البريد الإلكتروني"; +"restore.submit" = "تأكيد"; + +"iap.error.message.unavailable" = "خوادم أبل غير متاحة حاليًا. يرجى إعادة المحاولة لاحقًا."; +"iap.error.title" = "خطأ"; + +"agreement.trials.title" = "شروط وأحكام الفترات التجريبية المجانية"; +"agreement.trials.message" = "سيتم تحصيل مبلغ الدفع من حساب Apple ID الخاص بك عند تأكيد الشراء. يتم تجديد الاشتراك تلقائيًا ما لم يتم إلغاؤه قبل نهاية الفترة الحالية بمدة 24 ساعة على الأقل. سيتم محاسبتك على التجديد خلال 24 ساعة قبل نهاية الفترة الحالية. يمكنك إدارة وإلغاء اشتراكاتك عن طريق الانتقال إلى إعدادات حسابك في متجر التطبيقات بعد الشراء.\n\nقد توفر بعض الاشتراكات المدفوعة فترة تجريبية مجانية قبل تحصيل المبلغ من طريقة الدفع. إذا قررت إلغاء الاشتراك من اشتراك مدفوع قبل البدء في تحصيل الرسوم من طريقة الدفع، قم بإلغاء الاشتراك قبل انتهاء الفترة التجريبية المجانية بمدة 24 ساعة على الأقل.\n\nلا يتم توفير التجارب المجانية إلا للمستخدمين الجدد فقط، وهي وفقًا لتقديرنا الخاص، وإذا حاولت التسجيل للحصول على فترة تجريبية مجانية إضافية، فستتم محاسبتك فورًا على رسوم الاشتراك القياسية.\n\nنحن نحتفظ بالحق في إلغاء الفترة التجريبية المجانية في أي وقت.\n\nسيتم مصادرة أي جزء غير مستخدم من الفترة التجريبية المجانية عند شراء اشتراك.\n\nيمثل التسجيل قبولًا لهذه الشروط والأحكام."; +"agreement.message" = "بعد انتهاء الـ 7 أيام الخاصة بالفترة التجريبية المجانية، يتم تجديد هذا الاشتراك تلقائيًا مقابل %@ ما لم يتم إلغاؤه قبل 24 ساعة على الأقل من نهاية الفترة التجريبية. ستتم محاسبة حساب Apple ID الخاص بك على رسوم التجديد في غضون 24 ساعة قبل نهاية الفترة التجريبية. يمكنك إدارة وإلغاء اشتراكاتك عن طريق الانتقال إلى إعدادات حسابك في App Store بعد الشراء. يقتصر عرض الفترة التجريبية لمدة 7 أيام على عرض فترة تجريبية واحد لمدة 7 أيام لكل مستخدم. أي جزء غير مستخدم من الفترة التجريبية المجانية، إذا تم عرضها، سيتم مصادرته عندما يشتري المستخدم اشتراكًا. تشمل جميع الأسعار ضرائب المبيعات المحلية المطبقة.\n\nيعتبر الاشتراك بمثابة قبول $1 و$2."; +"agreement.trials.yearly.plan" = "سنة"; +"agreement.trials.monthly.plan" = "شهر"; + +"agreement.message.tos" = "شروط الخدمة"; +"agreement.message.privacy" = "سياسة الخصوصية"; + +"getstarted.buttons.buyaccount" = "شراء حساب"; + +"gdpr.collect.data.title" = "المعلومات الشخصية التي نجمعها"; +"gdpr.collect.data.description" = "عنوان البريد الإلكتروني لأغراض إدارة الحساب والحماية من إساءة الاستخدام."; +"gdpr.usage.data.title" = "استخدامات المعلومات الشخصية التي نجمعها"; +"gdpr.usage.data.description" = "يتم استخدام عنوان البريد الإلكتروني لإرسال معلومات الاشتراك وتأكيد الدفع ومراسلات العملاء والعروض الترويجية الخاصة بـ Private Internet Access فقط."; +"gdpr.accept.button.title" = "موافق ومتابعة"; + +"update.account.email.error" = "تعذَّر تعديل البريد الإلكتروني للحساب"; diff --git a/Resources/Resources/iOS/da.lproj/Signup.strings b/Resources/Resources/iOS/da.lproj/Signup.strings new file mode 100644 index 000000000..44c000115 --- /dev/null +++ b/Resources/Resources/iOS/da.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "Bekræft tilmelding"; +"in_progress.message" = "Vi bekræfter dit køb i vores system. Det kan tage et øjeblik, så bliv her venligst."; +"in_progress.redeem.message" = "Vi bekræfter din kort-pinkode i vores system. Det kan tage et øjeblik, så bliv her venligst."; + +"success.title" = "Køb fuldført"; +"success.message_format" = "Tak fordi du tilmeldte dig hos os. Vi har sendt dit brugernavn og kodeord til din e-mailadresse %@"; +"success.redeem.title" = "Kort indløst med succes"; +"success.redeem.message" = "Du modtager snart en e-mail med dit brugernavn og adgangskode.\n\nDine loginoplysninger"; +"success.username.caption" = "Brugernavn"; +"success.password.caption" = "Kodeord"; +"success.submit" = "Kom i gang"; + +"failure.vc_title" = "Tilmelding mislykkedes"; +"failure.title" = "Kunne ikke oprette konto"; +"failure.message" = "Vi kan ikke oprette en konto på nuværende tidspunkt. Prøv igen senere.\n\nGenåbning af appen vil genoptage forsøget på at oprette en konto."; +"failure.purchase.sandbox.message" = "Det valgte sandbox-abonnement er ikke tilgængeligt i produktionen."; +"failure.redeem.invalid.title" = "Ugyldig kort-pinkode"; +"failure.redeem.invalid.message" = "Det ser ud til, at du har indtastet en ugyldig kort-pinkode. Prøv igen."; +"failure.redeem.claimed.title" = "Kort allerede taget"; +"failure.redeem.claimed.message" = "Det ser ud til, at dette kort allerede er taget af en anden konto. Prøv at indtaste en anden pinkode."; +"failure.submit" = "GÅ TILBAGE"; + +"unreachable.vc_title" = "Fejl"; +"unreachable.title" = "Ups!"; +"unreachable.message" = "Ingen internetforbindelse fundet. Bekræft venligst, at du har en internetforbindelse, og tryk på \"prøv igen\" herunder.\n\nDu kan vende tilbage til appen senere for at afslutte processen."; +"unreachable.submit" = "PRØV IGEN"; + +"purchase.uncredited.alert.message" = "Du har ukrediterede transaktioner. Vil du gendanne dine kontooplysninger?"; +"purchase.uncredited.alert.button.cancel" = "Annuller"; +"purchase.uncredited.alert.button.recover" = "Gendan konto"; + +"purchase.trials.intro" = "Start din 7-dages gratis prøveperiode"; +"purchase.trials.price.after" = "Derefter %@"; +"purchase.trials.money.back" = "30-dages pengene tilbage garanti"; +"purchase.trials.1year.protection" = "1 års beskyttelse af personlige oplysninger og identitet"; +"purchase.trials.anonymous" = "Gennemse anonymt og skjul din ip."; +"purchase.trials.devices" = "Understøtter 10 enheder på én gang"; +"purchase.trials.devices.description" = "Beskyt dig selv på op til 10 enheder ad gangen."; +"purchase.trials.region" = "Forbind nemt til en hvilken som helst region"; +"purchase.trials.servers" = "Mere end 3300 servere i 32 lande"; +"purchase.trials.start" = "Start abonnement"; +"purchase.trials.all.plans" = "Se alle tilgængelige planer"; + +"purchase.subscribe.now" = "Tilmeld nu"; + +// WALKTHROUGH + +"walkthrough.action.next" = "NÆSTE"; +"walkthrough.action.done" = "UDFØRT"; +"walkthrough.action.skip" = "SPRING OVER"; + +"walkthrough.page.1.title" = "Understøtter 10 enheder på en gang"; +"walkthrough.page.1.description" = "Beskyt dig selv på op til 10 enheder ad gangen."; +"walkthrough.page.2.title" = "Forbind nemt til en hvilken som helst region"; +"walkthrough.page.2.description" = "Med servere rundt om i verden er du altid beskyttet."; +"walkthrough.page.3.title" = "Beskyt dig mod annoncer"; +"walkthrough.page.3.description" = "Aktivering af vores Indholdsblokering forhindrer reklamer i at blive vist i Safari."; + +"share.data.buttons.accept" = "Accepter"; +"share.data.buttons.noThanks" = "Nej tak"; +"share.data.buttons.readMore" = "Læs mere"; +"share.data.text.title" = "Hjælp os med at forbedre vores service"; +"share.data.text.description" = "For at hjælpe os med at sikre vores tjenestes forbindelsesydelse, kan du anonymt dele dine forbindelsesstatistikker med os. Disse rapporter inkluderer ikke personligt identificerbare oplysninger."; +"share.data.text.footer" = "Du kan altid kontrollere dette fra dine indstillinger"; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/da.lproj/Welcome.strings b/Resources/Resources/iOS/da.lproj/Welcome.strings new file mode 100644 index 000000000..6e179a032 --- /dev/null +++ b/Resources/Resources/iOS/da.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "Log ind på din konto"; +"login.username.placeholder" = "Brugernavn (p1234567)"; +"login.password.placeholder" = "Kodeord"; +"login.submit" = "LOG IND"; +"login.restore.button" = "Modtog du ikke dine kontodetaljer?"; +"login.error.title" = "Log ind"; +"login.error.validation" = "Du skal indtaste et brugernavn og et kodeord."; +"login.error.unauthorized" = "Dit brugernavn eller kodeord er forkert."; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "Log på med købskvittering"; +"login.magic.link.title" = "Log ind ved hjælp af magisk e-maillink"; +"login.magic.link.response" = "Se i din e-mail for et login-link."; +"login.magic.link.send" = "Send link"; +"login.magic.link.invalid.email" = "Ugyldig e-mail. Prøv igen."; + +"purchase.title" = "Vælg en VPN-plan"; +"purchase.subtitle" = "30-dages pengene tilbage garanti"; +"purchase.email.placeholder" = "E-mailadresse"; +"purchase.continue" = "Fortsæt"; +"purchase.login.footer" = "Har du allerede en konto?"; +"purchase.login.button" = "Log ind"; +"purchase.error.title" = "Køb"; +"purchase.error.validation" = "Du skal indtaste en e-mailadresse"; +"purchase.error.connectivity.title" = "Forbindelsesfejl"; +"purchase.error.connectivity.description" = "Vi kan ikke nå Private Internet Access. Dette kan skyldes dårlig internet eller at vores service er blokeret i dit land."; +"purchase.confirm.form.email" = "Indtast din e-mailadresse"; +"purchase.confirm.plan" = "Du køber %@-planen"; +"purchase.email.why" = "Vi har brug for din e-mail for at sende dit brugernavn og din adgangskode."; +"purchase.submit" = "Indsend"; +"purchase.or" = "eller"; + +"upgrade.header" = "Velkommen tilbage!"; +"upgrade.title" = "For at bruge Private Internet Access skal du forny dit abonnement."; +"upgrade.renew.now" = "Forny nu"; + + + +"redeem.title" = "Indløs gavekort"; +"redeem.subtitle" = "Indtast din e-mailadresse, og %lu-cifrede pinkode frra dit gavekort eller prøvekort herunder."; +"redeem.email.placeholder" = "E-mailadresse"; +"redeem.submit" = "INDSEND"; +"redeem.error.title" = "Indløs"; +"redeem.error.code" = "Koden skal være %lu numeriske cifre."; +"redeem.error.allfields" = "Indtast venligst din e-mail og dit korts PIN-kode."; +"redeem.accessibility.back" = "Tilbage"; +"redeem.giftcard.placeholder" = "Gavekortets PIN-kode"; + +"plan.monthly.title" = "Månedligt"; +"plan.yearly.title" = "Årligt"; +"plan.yearly.detail_format" = "%@%@ per år"; +"plan.price_format" = "%@/mdr"; +"plan.best_value" = "Bedste værdi"; +"plan.accessibility.per_month" = "per måned"; + +"restore.title" = "Gendan ukrediteret køb"; +"restore.subtitle" = "Hvis du har købt en plan gennem denne app og ikke har modtaget dine legitimationsoplysninger, kan du anmode om at sende dem igen herfra. Du vil ikke blive opkrævet i løbet af denne proces."; +"restore.email.placeholder" = "E-mailadresse"; +"restore.submit" = "BEKRÆFT"; + +"iap.error.message.unavailable" = "Apple-servere er ikke tilgængelige i øjeblikket. Prøv venligst igen senere."; +"iap.error.title" = "Fejl"; + +"agreement.trials.title" = "Vilkår og betingelser for gratis prøveperioder"; +"agreement.trials.message" = "Betaling debiteres din Apple ID-konto ved bekræftelsen af ​​købet. Abonnementet fornyes automatisk, medmindre det annulleres mindst 24 timer inden udgangen af ​​den aktuelle periode. Din konto bliver debiteret for fornyelse inden for 24 timer inden udgangen af ​​den aktuelle periode. Du kan administrere og annullere dine abonnementer ved at gå til dine kontoindstillinger i App Store efter køb.\n\nVisse betalte abonnementer kan muligvis tilbyde en gratis prøveperiode, inden de opkræver din betalingsmetode. Hvis du beslutter at afmelde dig et betalt abonnement, før vi begynder at opkræve din betalingsmetode, skal du annullere abonnementet mindst 24 timer før den gratis prøveperiode afsluttes.\n\nGratis prøveperioder er kun tilgængelige for nye brugere og er efter vores eget skøn, og hvis du forsøger at tilmelde dig en ekstra gratis prøveperiode, bliver du straks debiteret for det almindelige abonnementsgebyr.\n\nVi forbeholder os retten til at tilbagekalde din gratis prøveperiode til enhver tid.\n\nEnhver ubrugt del af din gratis prøveperiode fortabes ved køb af et abonnement.\n\nTilmelding udgør accept af disse vilkår og betingelser."; +"agreement.message" = "Efter den gratis prøveperiode på 7 dage, fornyes dette abonnement automatisk for %@, medmindre det annulleres mindst 24 timer før prøveperioden er afsluttet. Din Apple ID-konto bliver debiteret for fornyelse inden for 24 timer inden udløbet af prøveperioden. Du kan administrere og annullere dine abonnementer ved at gå til din App Store-kontoindstillinger efter købet. Tilbudet med en 7-dages prøveperiode er begrænset til en 7-dages prøveperiode pr. bruger. Enhver ubrugt del af en gratis prøveperiode, hvis den tilbydes, fortabes, når brugeren køber et abonnement. Alle priser inkluderer gældende lokal moms.\n\nSigning up constitutes acceptance of the $1 and the $2."; +"agreement.trials.yearly.plan" = "år"; +"agreement.trials.monthly.plan" = "måned"; + +"agreement.message.tos" = "Vilkår for brug"; +"agreement.message.privacy" = "Fortrolighedspolitik"; + +"getstarted.buttons.buyaccount" = "Køb konto"; + +"gdpr.collect.data.title" = "Personlige oplysninger, vi indsamler"; +"gdpr.collect.data.description" = "E-mailadresse med henblik på kontohåndtering og beskyttelse mod misbrug."; +"gdpr.usage.data.title" = "Anvendelse af personlige oplysninger indsamlet af os"; +"gdpr.usage.data.description" = "E-mailadresse bruges kun til at sende abonnementsoplysninger, betalingsbekræftelser, kundekorrespondance og salgsfremmende tilbud fra Private Internet Access."; +"gdpr.accept.button.title" = "Accepter og fortsæt"; + +"update.account.email.error" = "Lykkedes ikke at ændre konto-e-mail"; diff --git a/Resources/Resources/iOS/de.lproj/Signup.strings b/Resources/Resources/iOS/de.lproj/Signup.strings new file mode 100644 index 000000000..ba5d2b2b5 --- /dev/null +++ b/Resources/Resources/iOS/de.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "Registrierung bestätigen"; +"in_progress.message" = "Wir bestätigen deinen Kauf in unserem System. Es kann einen Moment dauern, also gedulde dich bitte etwas."; +"in_progress.redeem.message" = "Wir überprüfen derzeit Ihre Karten-PIN in unserem System. Dies kann einen Moment dauern. Bitte haben Sie etwas Geduld."; + +"success.title" = "Kauf abgeschlossen"; +"success.message_format" = "Vielen Dank für deine Registrierung. Wir haben dir deinen Benutzernamen und dein Passwort an deine E-Mail-Adresse unter %@ gesendet."; +"success.redeem.title" = "Karte erfolgreich eingelöst!"; +"success.redeem.message" = "Sie werden in Kürze eine E-Mail mit Ihrem Benutzernamen und Passwort erhalten.\n\nIhre Zugangsdaten"; +"success.username.caption" = "Benutzername"; +"success.password.caption" = "Passwort"; +"success.submit" = "Erste Schritte"; + +"failure.vc_title" = "Registrierung fehlgeschlagen"; +"failure.title" = "Konto nicht erstellt"; +"failure.message" = "Zur Zeit können wir kein Konto erstellen. Bitte später erneut versuchen.\n\nBeim erneuten Öffnen der App wird wieder versucht, ein Konto zu erstellen."; +"failure.purchase.sandbox.message" = "Das ausgewählte Sandbox-Abonnement ist in der Produktion nicht verfügbar."; +"failure.redeem.invalid.title" = "Ungültige Karten-PIN"; +"failure.redeem.invalid.message" = "Anscheinend haben Sie eine ungültige PIN eingegeben. Bitte erneut versuchen."; +"failure.redeem.claimed.title" = "Karte bereits eingelöst"; +"failure.redeem.claimed.message" = "Anscheinend wurde diese Karte bereits über ein anderes Konto eingelöst. Geben Sie eine andere PIN ein."; +"failure.submit" = "ZURÜCK"; + +"unreachable.vc_title" = "Fehler"; +"unreachable.title" = "Ups!"; +"unreachable.message" = "Keine Internetverbindung gefunden. Bitte deine Internetverbindung überprüfen und erneut versuchen.\n\nDu kannst die App später erneut aufrufen, um den Vorgang abzuschließen."; +"unreachable.submit" = "WIEDERHOLEN"; + +"purchase.uncredited.alert.message" = "Sie haben Transaktionen, die noch nicht gutgeschrieben wurden. Möchten Sie Ihre Kontodaten wiederherstellen?"; +"purchase.uncredited.alert.button.cancel" = "Abbrechen"; +"purchase.uncredited.alert.button.recover" = "Konto wiederherstellen"; + +"purchase.trials.intro" = "Jetzt 7 Tage gratis testen"; +"purchase.trials.price.after" = "Dann %@"; +"purchase.trials.money.back" = "30-Tage-Geld-zurück-Garantie"; +"purchase.trials.1year.protection" = "1 Jahr Daten- und Identitätsschutz"; +"purchase.trials.anonymous" = "Anonym surfen und Ihre IP-Adresse verbergen"; +"purchase.trials.devices" = "Unterstützung für 10 Geräte"; +"purchase.trials.devices.description" = "Schützen Sie sich auf bis zu 10 Geräten gleichzeitig."; +"purchase.trials.region" = "Einfache Verbindung zu jeder Region"; +"purchase.trials.servers" = "Mehr als 3300 Server in 32 Ländern"; +"purchase.trials.start" = "Abonnement beginnen"; +"purchase.trials.all.plans" = "Alle verfügbaren Pläne anzeigen"; + +"purchase.subscribe.now" = "Jetzt abonnieren"; + +// WALKTHROUGH + +"walkthrough.action.next" = "WEITER"; +"walkthrough.action.done" = "FERTIG"; +"walkthrough.action.skip" = "ÜBERSPRINGEN"; + +"walkthrough.page.1.title" = "Unterstützung für 10 Geräte"; +"walkthrough.page.1.description" = "Schützen Sie sich auf bis zu 10 Geräten gleichzeitig."; +"walkthrough.page.2.title" = "Bequem mit jeder beliebigen Region verbinden"; +"walkthrough.page.2.description" = "Mit Servern auf der ganzen Welt bist du immer geschützt."; +"walkthrough.page.3.title" = "Schütze dich vor Werbung"; +"walkthrough.page.3.description" = "Mit der Aktivierung unseres Inhalts-Blockers sehen Sie keine Anzeigen in Safari mehr."; + +"share.data.buttons.accept" = "Akzeptieren"; +"share.data.buttons.noThanks" = "Nein danke"; +"share.data.buttons.readMore" = "Mehr erfahren"; +"share.data.text.title" = "Bitte helfen Sie uns, unseren Service zu verbessern"; +"share.data.text.description" = "Um uns zu helfen, die Verbindungsleistung unseres Dienstes sicherzustellen, können Sie Ihre Verbindungsstatistiken anonym mit uns teilen. Diese Berichte enthalten keine persönlich identifizierbaren Informationen."; +"share.data.text.footer" = "Sie können dies jederzeit über Ihre Einstellungen steuern"; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/de.lproj/Welcome.strings b/Resources/Resources/iOS/de.lproj/Welcome.strings new file mode 100644 index 000000000..ed13eb4ab --- /dev/null +++ b/Resources/Resources/iOS/de.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "An deinem Konto anmelden"; +"login.username.placeholder" = "Benutzername (p1234567)"; +"login.password.placeholder" = "Passwort"; +"login.submit" = "ANMELDEN"; +"login.restore.button" = "Keine Kontodaten erhalten?"; +"login.error.title" = "Anmelden"; +"login.error.validation" = "Du musst einen Benutzernamen und ein Passwort angeben."; +"login.error.unauthorized" = "Dein Benutzername oder Passwort ist falsch."; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "Anmeldung mit Kaufbeleg"; +"login.magic.link.title" = "Anmeldung mit magischem E-Mail-Link"; +"login.magic.link.response" = "Bitte überprüfen Sie Ihre E-Mail auf einen Login-Link."; +"login.magic.link.send" = "Link senden"; +"login.magic.link.invalid.email" = "Ungültige E-Mail-Adresse. Bitte erneut versuchen."; + +"purchase.title" = "VPN-Tarif auswählen"; +"purchase.subtitle" = "30-Tage-Geld-zurück-Garantie"; +"purchase.email.placeholder" = "E-Mail-Adresse"; +"purchase.continue" = "Weiter"; +"purchase.login.footer" = "Bereits ein Konto?"; +"purchase.login.button" = "Anmelden"; +"purchase.error.title" = "Kaufen"; +"purchase.error.validation" = "Sie müssen eine E-Mail-Adresse angeben."; +"purchase.error.connectivity.title" = "Verbindungsfehler"; +"purchase.error.connectivity.description" = "Wir können Private Internet Access nicht erreichen. Dies kann auf eine schlechte Internetverbindung zurückzuführen sein oder unser Service ist in deinem Land blockiert."; +"purchase.confirm.form.email" = "E-Mail-Adresse eingeben"; +"purchase.confirm.plan" = "Sie erwerben den %@-Tarif."; +"purchase.email.why" = "Wir benötigen Ihre E-Mail, um Ihren Benutzernamen und Ihr Passwort zu senden."; +"purchase.submit" = "Senden"; +"purchase.or" = "oder"; + +"upgrade.header" = "Willkommen zurück!"; +"upgrade.title" = "Sie müssen Ihr Abonnement erneuern, um Private Internet Access nutzen zu können."; +"upgrade.renew.now" = "Jetzt erneuern"; + + + +"redeem.title" = "Geschenkkarte einlösen"; +"redeem.subtitle" = "Geben Sie unten Ihre E-Mail-Adresse und die %lu-stellige PIN von Ihrer Geschenk- oder Testkarte ein."; +"redeem.email.placeholder" = "E-Mail-Adresse"; +"redeem.submit" = "SENDEN"; +"redeem.error.title" = "Einlösen"; +"redeem.error.code" = "Der Code muss aus %lu Ziffern bestehen."; +"redeem.error.allfields" = "Bitte geben Sie Ihre E-Mail-Adresse und Karten-PIN ein."; +"redeem.accessibility.back" = "Zurück"; +"redeem.giftcard.placeholder" = "PIN der Geschenkkarte"; + +"plan.monthly.title" = "Monatlich"; +"plan.yearly.title" = "Jährlich"; +"plan.yearly.detail_format" = "%@%@ pro Jahr"; +"plan.price_format" = "%@/Monat"; +"plan.best_value" = "Bestpreis"; +"plan.accessibility.per_month" = "pro Monat"; + +"restore.title" = "Nicht gutgeschriebenen Kauf wiederherstellen"; +"restore.subtitle" = "Wenn Sie einen Tarif über diese App erworben haben und Ihre Zugangsdaten nicht erhalten haben, können Sie sie hier erneut anfordern. Sie müssen dann nicht erneut bezahlen."; +"restore.email.placeholder" = "E-Mail-Adresse"; +"restore.submit" = "BESTÄTIGEN"; + +"iap.error.message.unavailable" = "Die Apple-Server sind momentan nicht verfügbar. Bitte später erneut versuchen."; +"iap.error.title" = "Fehler"; + +"agreement.trials.title" = "Nutzungsbedingungen für kostenlose Testversionen"; +"agreement.trials.message" = "Die Zahlung wird Ihrem Apple ID-Konto bei der Kaufbestätigung belastet. Das Abonnement verlängert sich automatisch, wenn es nicht mindestens 24 Stunden vor Ablauf der aktuellen Periode gekündigt wird. Die Verlängerung wird Ihrem Konto innerhalb von 24 Stunden vor Ablauf der aktuellen Periode in Rechnung gestellt. Sie können Ihre Abonnements verwalten und kündigen, indem Sie nach dem Kauf zu Ihren Kontoeinstellungen im App Store aufrufen.\n\nBestimmte kostenpflichtige Abonnements können eine kostenlose Testversion anbieten, bevor Sie Ihre Zahlungsmethode berechnen. Wenn Sie sich entscheiden, sich von einem kostenpflichtigen Abonnement abzumelden, bevor wir mit der Berechnung Ihrer Zahlungsmethode beginnen, kündigen Sie das Abonnement mindestens 24 Stunden vor Ablauf der kostenlosen Probezeit.\n\nKostenlose Testversionen sind nur für neue Benutzer verfügbar und liegen in unserem alleinigen Ermessen, und wenn Sie versuchen, sich für eine zusätzliche kostenlose Testversion anzumelden, wird Ihnen sofort die Standard-Abonnementgebühr in Rechnung gestellt.\n\nWir behalten uns das Recht vor, Ihre kostenlose Testversion jederzeit zu widerrufen.\n\nJeder ungenutzte Teil Ihrer kostenlosen Probezeit verfällt mit dem Kauf eines Abonnements.\n\nMit der Registrierung akzeptieren Sie diese Nutzungsbedingungen."; +"agreement.message" = "Nach der 7-tägigen kostenlosen Testperiode verlängert sich dieses Abonnement automatisch für %@, sofern es nicht mindestens 24 Stunden vor Ablauf der Testperiode gekündigt wird. Ihr Apple-ID-Konto wird für die Verlängerung innerhalb von 24 Stunden vor Ablauf des Testzeitraums belastet. Sie können Ihre Abonnements nach dem Kauf in den Einstellungen Ihres App Store-Kontos verwalten und kündigen. Jeder Benutzer kann die 7-tägige kostenlose Testperiode nur einmal in Anspruch nehmen. Jeder nicht genutzte Teil einer kostenlosen Testperiode, falls angeboten, verfällt beim Kauf eines Abonnements durch den Benutzer. Alle Preise enthalten die örtlich geltenden Verkaufssteuern.\n\nMit der Anmeldung akzeptieren Sie die $1 und $2."; +"agreement.trials.yearly.plan" = "Jahr"; +"agreement.trials.monthly.plan" = "Monat"; + +"agreement.message.tos" = "Nutzungsbedingungen"; +"agreement.message.privacy" = "Datenschutzrichtlinien"; + +"getstarted.buttons.buyaccount" = "Konto kaufen"; + +"gdpr.collect.data.title" = "Art der personenbezogenen Daten, die wir erfassen"; +"gdpr.collect.data.description" = "E-Mail-Adresse zum Zwecke der Kontoverwaltung und zum Schutz vor Missbrauch."; +"gdpr.usage.data.title" = "Verwendungszwecke für personenbezogene Daten, die von uns erfasst wurden"; +"gdpr.usage.data.description" = "Die E-Mail-Adresse wird lediglich zum Senden von Abonnementinformationen, Zahlungsbestätigungen, Kundenkorrespondenz und Sonderangeboten zu Private Internet Access verwendet."; +"gdpr.accept.button.title" = "Zustimmen und fortfahren"; + +"update.account.email.error" = "Konto-E-Mail nicht geändert"; diff --git a/Resources/Resources/iOS/en.lproj/Signup.strings b/Resources/Resources/iOS/en.lproj/Signup.strings new file mode 100644 index 000000000..dacff3d57 --- /dev/null +++ b/Resources/Resources/iOS/en.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "Confirm sign-up"; +"in_progress.message" = "We're confirming your purchase with our system. It could take a moment so hang in there."; +"in_progress.redeem.message" = "We're confirming your card PIN with our system. It could take a moment so hang in there."; + +"success.title" = "Purchase complete"; +"success.message_format" = "Thank you for signing up with us. We have sent your account username and password at your email address at %@"; +"success.redeem.title" = "Card redeemed successfully"; +"success.redeem.message" = "You will receive an email shortly with your username and password.\n\nYour login details"; +"success.username.caption" = "Username"; +"success.password.caption" = "Password"; +"success.submit" = "GET STARTED"; + +"failure.vc_title" = "Sign-up failed"; +"failure.title" = "Account creation failed"; +"failure.message" = "We're unable to create an account at this time. Please try again later. Reopening the app will re-attempt to create an account."; +"failure.purchase.sandbox.message" = "The selected sandbox subscription is not available in production."; +"failure.redeem.invalid.title" = "Invalid card PIN"; +"failure.redeem.invalid.message" = "Looks like you entered an invalid card PIN. Please try again."; +"failure.redeem.claimed.title" = "Card claimed already"; +"failure.redeem.claimed.message" = "Looks like this card has already been claimed by another account. You can try entering a different PIN."; +"failure.submit" = "GO BACK"; + +"unreachable.vc_title" = "Error"; +"unreachable.title" = "Whoops!"; +"unreachable.message" = "No internet connection found. Please confirm that you have an internet connection and hit retry below.\n\nYou can come back to the app later to finish the process."; +"unreachable.submit" = "TRY AGAIN"; + +"purchase.uncredited.alert.message" = "You have uncredited transactions. Do you want to recover your account details?"; +"purchase.uncredited.alert.button.cancel" = "Cancel"; +"purchase.uncredited.alert.button.recover" = "Recover account"; + +"purchase.trials.intro" = "Start your 7-day free trial"; +"purchase.trials.price.after" = "Then %@"; +"purchase.trials.money.back" = "30 day money back guarantee"; +"purchase.trials.1year.protection" = "1 year of privacy and identity protection"; +"purchase.trials.anonymous" = "Browse anonymously and hide your ip."; +"purchase.trials.devices" = "Support 10 devices at once"; +"purchase.trials.devices.description" = "Protect yourself on up to 10 devices at a time."; +"purchase.trials.region" = "Connect to any region easily"; +"purchase.trials.servers" = "More than 3300 servers in 32 countries"; +"purchase.trials.start" = "Start subscription"; +"purchase.trials.all.plans" = "See all available plans"; + +"purchase.subscribe.now" = "Subscribe now"; + +// WALKTHROUGH + +"walkthrough.action.next" = "NEXT"; +"walkthrough.action.done" = "DONE"; +"walkthrough.action.skip" = "SKIP"; + +"walkthrough.page.1.title" = "Support 10 devices at once"; +"walkthrough.page.1.description" = "Protect yourself on up to 10 devices at a time."; +"walkthrough.page.2.title" = "Connect to any region easily"; +"walkthrough.page.2.description" = "With servers around the globe, you are always under protection."; +"walkthrough.page.3.title" = "Protect yourself from ads"; +"walkthrough.page.3.description" = "Enabling our Content Blocker prevents ads from showing in Safari."; + +"share.data.buttons.accept" = "Accept"; +"share.data.buttons.noThanks" = "No, thanks"; +"share.data.buttons.readMore" = "Read more"; +"share.data.text.title" = "Please help us improve our service"; +"share.data.text.description" = "To help us ensure our service's connection performance, you can anonymously share your connection stats with us. These reports do not include any personally identifiable information."; +"share.data.text.footer" = "You can always control this from your settings"; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/en.lproj/Welcome.strings b/Resources/Resources/iOS/en.lproj/Welcome.strings new file mode 100644 index 000000000..91c5c4c53 --- /dev/null +++ b/Resources/Resources/iOS/en.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "Sign in to your account"; +"login.username.placeholder" = "Username (p1234567)"; +"login.password.placeholder" = "Password"; +"login.submit" = "LOGIN"; +"login.restore.button" = "Didn't receive account details?"; +"login.error.title" = "Log in"; +"login.error.validation" = "You must enter a username and password."; +"login.error.unauthorized" = "Your username or password is incorrect."; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "Login using purchase receipt"; +"login.magic.link.title" = "Login using magic email link"; +"login.magic.link.response" = "Please check your e-mail for a login link."; +"login.magic.link.send" = "Send Link"; +"login.magic.link.invalid.email" = "Invalid email. Please try again."; + +"purchase.title" = "Select a VPN plan"; +"purchase.subtitle" = "30-day money back guarantee"; +"purchase.email.placeholder" = "Email address"; +"purchase.continue" = "Continue"; +"purchase.login.footer" = "Already have an account?"; +"purchase.login.button" = "Sign in"; +"purchase.error.title" = "Purchase"; +"purchase.error.validation" = "You must enter an email address."; +"purchase.error.connectivity.title" = "Connection Failure"; +"purchase.error.connectivity.description" = "We are unable to reach Private Internet Access. This may due to poor internet or our service is blocked in your country."; +"purchase.confirm.form.email" = "Enter your email address"; +"purchase.confirm.plan" = "You are purchasing the %@ plan"; +"purchase.email.why" = "We need your email to send your username and password."; +"purchase.submit" = "Submit"; +"purchase.or" = "or"; + +"upgrade.header" = "Welcome Back!"; +"upgrade.title" = "In order to use Private Internet Access, you’ll need to renew your subscription."; +"upgrade.renew.now" = "Renew now"; + + + +"redeem.title" = "Redeem gift card"; +"redeem.subtitle" = "Type in your email address and the %lu digit PIN from your gift card or trial card below."; +"redeem.email.placeholder" = "Email address"; +"redeem.submit" = "SUBMIT"; +"redeem.error.title" = "Redeem"; +"redeem.error.code" = "Code must be %lu numeric digits."; +"redeem.error.allfields"="Please type in your email and card PIN."; +"redeem.accessibility.back" = "Back"; +"redeem.giftcard.placeholder" = "Gift card PIN"; + +"plan.monthly.title" = "Monthly"; +"plan.yearly.title" = "Yearly"; +"plan.yearly.detail_format" = "%@%@ per year"; +"plan.price_format" = "%@/mo"; +"plan.best_value" = "Best value"; +"plan.accessibility.per_month" = "per month"; + +"restore.title" = "Restore uncredited purchase"; +"restore.subtitle" = "If you purchased a plan through this app and didn't receive your credentials, you can send them again from here. You will not be charged during this process."; +"restore.email.placeholder" = "Email address"; +"restore.submit" = "CONFIRM"; + +"iap.error.message.unavailable" = "Apple servers currently unavailable. Please try again later."; +"iap.error.title" = "Error"; + +"agreement.trials.title" = "Free trials terms and conditions"; +"agreement.trials.message" = "Payment will be charged to your Apple ID account at the confirmation of purchase. Subscription automatically renews unless it is canceled at least 24 hours before the end of the current period. Your account will be charged for renewal within 24 hours prior to the end of the current period. You can manage and cancel your subscriptions by going to your account settings on the App Store after purchase.\n\nCertain Paid Subscriptions may offer a free trial prior to charging your payment method. If you decide to unsubscribe from a Paid Subscription before we start charging your payment method, cancel the subscription at least 24 hours before the free trial ends.\n\nFree trials are only available to new users, and are at our sole discretion, and if you attempt to sign up for an additional free trial, you will be immediately charged with the standard Subscription Fee.\n\nWe reserve the right to revoke your free trial at any time.\n\nAny unused portion of your free trial period will be forfeited upon purchase of a subscription.\n\nSigning up constitutes acceptance of this terms and conditions."; +"agreement.message" = "After the 7 days free trial this subscription automatically renews for %@ unless it is canceled at least 24 hours before the end of the trial period. Your Apple ID account will be charged for renewal within 24 hours before the end of the trial period. You can manage and cancel your subscriptions by going to your App Store account settings after purchase. 7-days trial offer is limited to one 7-days trial offer per user. Any unused portion of a free trial period, if offered, will be forfeited when the user purchases a subscription. All prices include applicable local sales taxes.\n\nSigning up constitutes acceptance of the $1 and the $2."; +"agreement.trials.yearly.plan" = "year"; +"agreement.trials.monthly.plan" = "month"; + +"agreement.message.tos" = "Terms of Service"; +"agreement.message.privacy" = "Privacy Policy"; + +"getstarted.buttons.buyaccount" = "Buy account"; + +"gdpr.collect.data.title" = "Personal information we collect"; +"gdpr.collect.data.description" = "E-mail Address for the purposes of account management and protection from abuse."; +"gdpr.usage.data.title" = "Uses of personal information collected by us"; +"gdpr.usage.data.description" = "E-mail address is used to send subscription information, payment confirmations, customer correspondence, and Private Internet Access promotional offers only."; +"gdpr.accept.button.title" = "Agree and continue"; + +"update.account.email.error" = "Failed to modify account email"; diff --git a/Resources/Resources/iOS/es-MX.lproj/Signup.strings b/Resources/Resources/iOS/es-MX.lproj/Signup.strings new file mode 100644 index 000000000..3ebdb9fd3 --- /dev/null +++ b/Resources/Resources/iOS/es-MX.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "Confirmar registro"; +"in_progress.message" = "Estamos confirmando la compra en el sistema. Podrías tardar unos instantes, así que espera."; +"in_progress.redeem.message" = "Estamos confirmando el PIN de tu tarjeta en nuestro sistema. Puede tardar un momento, así que espera un poco."; + +"success.title" = "Compra completa"; +"success.message_format" = "Gracias por registrarte con nosotros. Te enviamos el nombre de usuario y contraseña de tu cuenta a tu dirección de email en %@"; +"success.redeem.title" = "Tarjeta canjeada correctamente"; +"success.redeem.message" = "Recibirás un mensaje de correo electrónico dentro de poco con tu nombre de usuario y contraseña.\n\nTu información de inicio de sesión"; +"success.username.caption" = "Nombre de usuario"; +"success.password.caption" = "Contraseña "; +"success.submit" = "Empieza"; + +"failure.vc_title" = "Falló el registro"; +"failure.title" = "Falló la creación de la cuenta"; +"failure.message" = "No pudimos crear una cuenta en este momento. Por favor, inténtalo de nuevo más tarde. \nSi vuelves a abrir la aplicación intentaremos crear una cuenta otra vez."; +"failure.purchase.sandbox.message" = "La suscripción del entorno aislado seleccionado no está disponible en la producción."; +"failure.redeem.invalid.title" = "El PIN de la tarjeta no es válido"; +"failure.redeem.invalid.message" = "Parece que ingresaste un PIN de tarjeta inválido. Por favor, inténtalo de nuevo."; +"failure.redeem.claimed.title" = "La tarjeta ya fue reclamada"; +"failure.redeem.claimed.message" = "Parece que esta tarjeta ha sido reclamada por otra cuenta. Puedes intentar ingresar un PIN diferente."; +"failure.submit" = "ATRÁS"; + +"unreachable.vc_title" = "Error"; +"unreachable.title" = "¡Ups!"; +"unreachable.message" = "No se encontró conexión a Internet. Por favor, confirma que tienes una conexión a Internet y toca en intentar de nuevo más abajo.\n\nPuedes regresar después a la aplicación para terminar el proceso."; +"unreachable.submit" = "VOLVER A INTENTAR"; + +"purchase.uncredited.alert.message" = "Tienes transacciones sin acreditar. ¿Seguro que quieres recuperar los detalles de tu cuenta?"; +"purchase.uncredited.alert.button.cancel" = "Cancelar"; +"purchase.uncredited.alert.button.recover" = "Recuperar cuenta"; + +"purchase.trials.intro" = "Inicia tu prueba gratuita de 7 días"; +"purchase.trials.price.after" = "Después, %@"; +"purchase.trials.money.back" = "Garantía de devolución de 30 días"; +"purchase.trials.1year.protection" = "1 año de privacidad y protección de la identidad."; +"purchase.trials.anonymous" = "Navega de forma anónima y oculta tu IP."; +"purchase.trials.devices" = "Admite 10 dispositivos a la vez."; +"purchase.trials.devices.description" = "Protégete en hasta 10 dispositivos a la vez."; +"purchase.trials.region" = "Conéctate a cualquier región con facilidad."; +"purchase.trials.servers" = "Más de 3300 servidores en 32 países."; +"purchase.trials.start" = "Iniciar suscripción"; +"purchase.trials.all.plans" = "Ver todos los planes disponibles."; + +"purchase.subscribe.now" = "Suscríbete ahora"; + +// WALKTHROUGH + +"walkthrough.action.next" = "SIGUIENTE"; +"walkthrough.action.done" = "TERMINADO"; +"walkthrough.action.skip" = "OMITIR"; + +"walkthrough.page.1.title" = "Admite 10 dispositivos a la vez"; +"walkthrough.page.1.description" = "Protégete en hasta 10 dispositivos a la vez."; +"walkthrough.page.2.title" = "Conéctate a cualquier región con facilidad"; +"walkthrough.page.2.description" = "Con servidores en todo el mundo, siempre estarás protegido."; +"walkthrough.page.3.title" = "Protégete de la publicidad"; +"walkthrough.page.3.description" = "Habilita nuestro Bloqueador de contenido para impedir que aparezca publicidad en Safari."; + +"share.data.buttons.accept" = "Aceptar"; +"share.data.buttons.noThanks" = "No, gracias"; +"share.data.buttons.readMore" = "Más información"; +"share.data.text.title" = "Ayúdanos a mejorar nuestro servicio."; +"share.data.text.description" = "Para ayudarnos a garantizar el rendimiento de la conexión de nuestro servicio, puedes compartir con nosotros tus estadísticas de conexión de forma anónima. Estos informes no contienen ninguna información personal identificable."; +"share.data.text.footer" = "Siempre puedes controlarlo desde tus ajustes."; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/es-MX.lproj/Welcome.strings b/Resources/Resources/iOS/es-MX.lproj/Welcome.strings new file mode 100644 index 000000000..3c47f73e3 --- /dev/null +++ b/Resources/Resources/iOS/es-MX.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "Inicia sesión en tu cuenta"; +"login.username.placeholder" = "Nombre de usuario (p1234567)"; +"login.password.placeholder" = "Contraseña "; +"login.submit" = "INICIAR SESIÓN"; +"login.restore.button" = "¿No has recibido los detalles de la cuenta?"; +"login.error.title" = "Iniciar sesión"; +"login.error.validation" = "Debe ingresar un nombre de usuario y una contraseña."; +"login.error.unauthorized" = "Tu nombre de usuario o contraseña son incorrectos."; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "Inicia sesión con el recibo de compra"; +"login.magic.link.title" = "Inicia sesión con el vínculo mágico del correo electrónico."; +"login.magic.link.response" = "Busca en tu correo electrónico un enlace de inicio de sesión."; +"login.magic.link.send" = "Enviar enlace"; +"login.magic.link.invalid.email" = "Correo electrónico no válido. Vuelve a intentarlo."; + +"purchase.title" = "Selecciona un plan VPN"; +"purchase.subtitle" = "Garantía de devolución de 30 días"; +"purchase.email.placeholder" = "Dirección de correo electrónico"; +"purchase.continue" = "Continuar"; +"purchase.login.footer" = "¿Ya tienes una cuenta?"; +"purchase.login.button" = "Inicia sesión"; +"purchase.error.title" = "Comprar"; +"purchase.error.validation" = "Debes ingresar una dirección de email."; +"purchase.error.connectivity.title" = "Falla en la conexión"; +"purchase.error.connectivity.description" = "No pudimos localizar a Private Internet Access. Esto puede ser debido a una mala conexión con Internet o a que nuestro servicio está bloqueado en su país."; +"purchase.confirm.form.email" = "Introduce tu dirección de correo electrónico"; +"purchase.confirm.plan" = "Estás comprando el plan %@."; +"purchase.email.why" = "Necesitamos tu dirección de correo electrónico para enviar tu nombre de usuario y tu contraseña."; +"purchase.submit" = "Enviar"; +"purchase.or" = "o"; + +"upgrade.header" = "¡Hola otra vez!"; +"upgrade.title" = "Para usar Private Internet Access debes renovar tu suscripción."; +"upgrade.renew.now" = "Renovar ahora"; + + + +"redeem.title" = "Canjear tarjeta de regalo"; +"redeem.subtitle" = "Escribe abajo tu dirección de correo electrónico y el PIN de %lu dígitos de tu tarjeta de regalo o tarjeta de prueba."; +"redeem.email.placeholder" = "Dirección de correo electrónico"; +"redeem.submit" = "ENVIAR"; +"redeem.error.title" = "Canjear"; +"redeem.error.code" = "El código debe tener %lu dígitos numéricos."; +"redeem.error.allfields" = "Escribe tu dirección de correo electrónico y el PIN de tu tarjeta."; +"redeem.accessibility.back" = "Volver"; +"redeem.giftcard.placeholder" = "PIN de tarjeta regalo"; + +"plan.monthly.title" = "Mensual"; +"plan.yearly.title" = "Anual"; +"plan.yearly.detail_format" = "%@%@ al año"; +"plan.price_format" = "%@/m"; +"plan.best_value" = "Mejor oferta"; +"plan.accessibility.per_month" = "por mes"; + +"restore.title" = "Restablecer compra no acreditada"; +"restore.subtitle" = "Si compraste un plan a través de esta aplicación y no recibes tus credenciales, puedes reiniciar la renovación desde aquí. No se realizarán cargos durante este proceso. \n"; +"restore.email.placeholder" = "Correo electrónico"; +"restore.submit" = "CONFIRMAR"; + +"iap.error.message.unavailable" = "Actualmente, los servidores de Apple no están disponibles. Por favor, inténtalo de nuevo más tarde."; +"iap.error.title" = "Error"; + +"agreement.trials.title" = "Términos y condiciones de la prueba gratuita."; +"agreement.trials.message" = "Se realizará un cobro en tu cuenta de Apple ID en el momento de confirmar la compra. Las suscripciones se renuevan automáticamente a menos que se cancelen como mínimo 24 horas antes de la finalización del periodo actual. Se cobrará la cantidad de la renovación en un plazo de 24 horas antes de la finalización del período actual. Tu mismo podrás gestionar y cancelar las suscripciones en los ajustes de App Store después de la compra. \n\nAlgunas suscripciones de pago pueden ofrecer una prueba gratuita antes de realizar cobros según tu método de pago. Si decides darte de baja de una suscripción de pago antes de que comencemos a cobrar tu método de pago, cancela la suscripción al menos 24 horas antes de que finalice la prueba gratuita.\n\nLas pruebas gratuitas solo están disponibles para nuevos usuarios y quedan a nuestra entera discreción. Si intentas registrarte para obtener una prueba gratuita adicional, se te cobrará de inmediato la tarifa de suscripción estándar.\n\nNos reservamos el derecho de revocar tu prueba gratuita en cualquier momento.\n\nLas secciones sin usar del periodo de prueba gratuito se perderán si el usuario compra una suscripción.\n\nSi te registras, aceptas estos términos y condiciones."; +"agreement.message" = "Después de los 7 días de prueba gratuita, esta suscripción se renueva automáticamente por %@ a menos que se cancele al menos 24 horas antes del final del período de prueba. Se cobrará la renovación en tu cuenta de ID de Apple en un plazo de 24 horas antes de que finalice el periodo de prueba. Puedes gestionar y cancelar tus suscripciones accediendo a los ajustes de tu cuenta de App Store después de la compra. La oferta de prueba de 7 días se limita a una oferta de prueba de 7 días por usuario. Cualquier parte no utilizada de un período de prueba gratuito, si se ofrece, se perderá cuando el usuario compre una suscripción. Todos los precios incluyen los impuestos de venta locales aplicables.\n\nSi te registras, aceptas los $1 y la $2."; +"agreement.trials.yearly.plan" = "año"; +"agreement.trials.monthly.plan" = "mes"; + +"agreement.message.tos" = "Términos de servicio"; +"agreement.message.privacy" = "Política de privacidad"; + +"getstarted.buttons.buyaccount" = "Comprar cuenta"; + +"gdpr.collect.data.title" = "Información personal que recopilamos"; +"gdpr.collect.data.description" = "Dirección de correo electrónico con fines de gestión de cuenta y protección frente a abusos."; +"gdpr.usage.data.title" = "Usos de la información personal que recopilamos"; +"gdpr.usage.data.description" = "La dirección de correo electrónico solo se usa para enviar información de suscripción, confirmaciones de pago, correspondencia al cliente y ofertas promocionales de Private Internet Access."; +"gdpr.accept.button.title" = "Aceptar y continuar"; + +"update.account.email.error" = "Error al modificar el correo electrónico de la cuenta"; diff --git a/Resources/Resources/iOS/fr.lproj/Signup.strings b/Resources/Resources/iOS/fr.lproj/Signup.strings new file mode 100644 index 000000000..a99c30197 --- /dev/null +++ b/Resources/Resources/iOS/fr.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "Confirmation d'inscription"; +"in_progress.message" = "Notre système est en train de confirmer votre achat. Cela pourrait prendre un moment, nous vous prions donc de patienter."; +"in_progress.redeem.message" = "Notre système est en train de confirmer le code PIN de votre carte. Cela pourrait prendre un moment, nous vous prions donc de patienter."; + +"success.title" = "Achat terminé"; +"success.message_format" = "Merci pour votre inscription. L'identifiant et le mot de passe de votre compte ont été envoyés à votre adresse e-mail %@"; +"success.redeem.title" = "Carte échangée avec succès"; +"success.redeem.message" = "Vous allez bientôt recevoir un e-mail contenant votre nom d'utilisateur et votre mot de passe.\n\nDétails de vos identifiants"; +"success.username.caption" = "Nom d'utilisateur"; +"success.password.caption" = "Mot de passe"; +"success.submit" = "Commencer"; + +"failure.vc_title" = "Échec de la connexion"; +"failure.title" = "La création du compte a échoué"; +"failure.message" = "Nous ne parvenons pas à créer un compte pour l'instant. Veuillez réessayer plus tard. \n\nRouvrir l'application engendrera une nouvelle tentative de création de compte."; +"failure.purchase.sandbox.message" = "L'abonnement sandbox sélectionné n'est pas disponible en production."; +"failure.redeem.invalid.title" = "Code PIN de la carte invalide"; +"failure.redeem.invalid.message" = "Il semblerait que le code PIN de la carte saisie soit invalide. Veuillez réessayer."; +"failure.redeem.claimed.title" = "Carte déjà utilisée"; +"failure.redeem.claimed.message" = "Il semblerait que cette carte soit déjà utilisée sur un autre compte. Vous pouvez essayer de saisir un code PIN différent."; +"failure.submit" = "REVENIR"; + +"unreachable.vc_title" = "Erreur"; +"unreachable.title" = "Oups !"; +"unreachable.message" = "Aucune connexion Internet trouvée. Veuillez confirmer que vous disposez d'une connexion Internet et cliquez de nouveau sur « Réessayer » ci-dessous.\n\nVous pourrez revenir dans l'application plus tard pour terminer le processus."; +"unreachable.submit" = "RÉESSAYER"; + +"purchase.uncredited.alert.message" = "Vous avez des transaction non créditées. Voulez-vous restaurer les détails de votre compte ?"; +"purchase.uncredited.alert.button.cancel" = "Annuler"; +"purchase.uncredited.alert.button.recover" = "Restaurer le compte"; + +"purchase.trials.intro" = "Démarrez votre essai gratuit de 7 jours"; +"purchase.trials.price.after" = "Ensuite %@"; +"purchase.trials.money.back" = "Garantie satisfait ou remboursé sur 30 jours"; +"purchase.trials.1year.protection" = "1 an de confidentialité et de protection de l'identité"; +"purchase.trials.anonymous" = "Surfez anonymement et masquez votre IP."; +"purchase.trials.devices" = "Prise en charge de 10 appareils en même temps"; +"purchase.trials.devices.description" = "Protégez-vous sur jusqu'à 10 appareils en même temps."; +"purchase.trials.region" = "Connectez-vous facilement à n'importe quelle région"; +"purchase.trials.servers" = "Plus de 3300 serveurs dans 32 pays"; +"purchase.trials.start" = "Commencer l'abonnement"; +"purchase.trials.all.plans" = "Voir tous les forfaits disponibles"; + +"purchase.subscribe.now" = "S'abonner maintenant"; + +// WALKTHROUGH + +"walkthrough.action.next" = "SUIVANT"; +"walkthrough.action.done" = "TERMINÉ"; +"walkthrough.action.skip" = "PASSER"; + +"walkthrough.page.1.title" = "Prend en charge 10 appareils en même temps"; +"walkthrough.page.1.description" = "Protégez-vous sur jusqu'à 10 appareils à la fois."; +"walkthrough.page.2.title" = "Connectez-vous facilement à n'importe quelle région"; +"walkthrough.page.2.description" = "Avec des serveurs partout dans le monde, vous êtes toujours sous protection."; +"walkthrough.page.3.title" = "Protégez-vous contre les publicités"; +"walkthrough.page.3.description" = "Activer notre bloqueur de contenu empêche les publicités de s'afficher dans Safari."; + +"share.data.buttons.accept" = "Accepter"; +"share.data.buttons.noThanks" = "Non, merci"; +"share.data.buttons.readMore" = "Lire plus"; +"share.data.text.title" = "Merci de nous aider à améliorer notre service"; +"share.data.text.description" = "Pour nous aider à garantir les performances de connexion de notre service, vous pouvez partager vos statistiques de connexion de manière anonyme avec nous. Ces rapports ne contiennent aucune information personnellement identifiable."; +"share.data.text.footer" = "Vous pouvez toujours contrôler cela à partir de vos paramètres."; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/fr.lproj/Welcome.strings b/Resources/Resources/iOS/fr.lproj/Welcome.strings new file mode 100644 index 000000000..623c17c3a --- /dev/null +++ b/Resources/Resources/iOS/fr.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "Connectez-vous à votre compte"; +"login.username.placeholder" = "Nom d'utilisateur (p1234567)"; +"login.password.placeholder" = "Mot de passe"; +"login.submit" = "CONNEXION"; +"login.restore.button" = "Vous n'avez pas reçu les détails de votre compte ?"; +"login.error.title" = "Connexion"; +"login.error.validation" = "Vous devez saisir un nom d'utilisateur et un mot de passe."; +"login.error.unauthorized" = "Votre nom d'utilisateur ou mot de passe est incorrect."; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "Connectez-vous à l'aide du reçu d'achat"; +"login.magic.link.title" = "Connectez-vous à l'aide du lien e-mail magique"; +"login.magic.link.response" = "Veuillez vérifier vos e-mails pour trouver un lien de connexion."; +"login.magic.link.send" = "Envoyer un lien"; +"login.magic.link.invalid.email" = "E-mail invalide. Veuillez réessayer."; + +"purchase.title" = "Sélectionnez un forfait VPN"; +"purchase.subtitle" = "Garantie satisfait ou remboursé sur 30 jours"; +"purchase.email.placeholder" = "Adresse e-mail"; +"purchase.continue" = "Continuer"; +"purchase.login.footer" = "Vous avez déjà un compte ?"; +"purchase.login.button" = "Connectez-vous"; +"purchase.error.title" = "Acheter"; +"purchase.error.validation" = "Vous devez entrer une adresse e-mail."; +"purchase.error.connectivity.title" = "Échec de la connexion"; +"purchase.error.connectivity.description" = "Nous n'arrivons pas à joindre Private Internet Access. Cela peut être dû à une connexion Internet de faible qualité ou parce que notre service est bloqué dans votre pays."; +"purchase.confirm.form.email" = "Saisissez votre adresse e-mail"; +"purchase.confirm.plan" = "Vous achetez le forfait %@"; +"purchase.email.why" = "Nous avons besoin de votre e-mail pour envoyer votre nom d'utilisateur et votre mot de passe."; +"purchase.submit" = "Envoyer"; +"purchase.or" = "ou"; + +"upgrade.header" = "Bienvenue !"; +"upgrade.title" = "Afin d'utiliser Private Internet Access, vous devrez renouveler votre abonnement."; +"upgrade.renew.now" = "Renouveler maintenant"; + + + +"redeem.title" = "Échanger carte cadeau"; +"redeem.subtitle" = "Veuillez saisir ci-dessous votre adresse e-mail et le code PIN à %lu chiffres de votre carte cadeau ou carte d'essai."; +"redeem.email.placeholder" = "Adresse e-mail"; +"redeem.submit" = "ENVOYER"; +"redeem.error.title" = "Échanger"; +"redeem.error.code" = "Le code doit contenir %lu chiffres numériques."; +"redeem.error.allfields" = "Saisissez votre e-mail et le code PIN de votre carte."; +"redeem.accessibility.back" = "Retour"; +"redeem.giftcard.placeholder" = "Code PIN de la carte"; + +"plan.monthly.title" = "Mensuellement"; +"plan.yearly.title" = "Annuellement"; +"plan.yearly.detail_format" = "%@%@ par an"; +"plan.price_format" = "%@/mois"; +"plan.best_value" = "Économique"; +"plan.accessibility.per_month" = "par mois"; + +"restore.title" = "Restaurer l'achat non crédité"; +"restore.subtitle" = "Si vous avez acheté un forfait via cette application et n'avez pas reçu vos identifiants, il est possible de les renvoyer à partir d'ici. Cette action ne vous sera pas facturée."; +"restore.email.placeholder" = "Adresse e-mail"; +"restore.submit" = "CONFIRMER"; + +"iap.error.message.unavailable" = "Les serveurs Apple ne sont pas disponibles actuellement. Veuillez réessayer plus tard."; +"iap.error.title" = "Erreur"; + +"agreement.trials.title" = "Termes et conditions des essais gratuits"; +"agreement.trials.message" = "Le paiement sera débité de votre compte Apple ID au moment de la confirmation de l'achat. L'abonnement se renouvelle automatiquement à moins qu'il ne soit annulé au moins 24 heures avant la fin de la période en cours. Votre compte sera débité du renouvellement dans les 24 heures précédant la fin de la période actuelle. Vous pouvez gérer et annuler vos abonnements en accédant aux paramètres du compte sur l'App Store après l'achat.\n\nCertains abonnements payants peuvent offrir un essai gratuit avant de débiter votre méthode de paiement. Si vous décidez de vous désabonner d'un abonnement payant avant que nous commencions à débiter votre méthode de paiement, annulez l'abonnement au moins 24 heures avant la fin de l'essai.\n\nLes essais gratuits ne sont disponibles que pour les nouveaux utilisateurs et sont à notre entière discrétion et si vous tentez de vous inscrire pour un autre essai gratuit, vous serez immédiatement débité des frais d'abonnement standards.\n\nNous nous réservons le droit de révoquer votre essai gratuit à tout moment.\n\nToute partie non utilisée de votre période d'essai gratuit sera abandonnée au moment de l'achat d'un abonnement.\n\nL'inscription constitue l'acceptation de ces termes et conditions."; +"agreement.message" = "Après l'essai gratuit de 7 jour, cet abonnement est renouvelé automatiquement pour %@, sauf s'il est annulé au moins 24 heures avant la fin de la période d'essai. Votre compte de l'identifiant Apple sera facturé pour le renouvellement 24 heures avant la fin de la période d'essai. Vous pouvez gérer et annuler vos abonnements en accédant aux paramètres de votre compte de l'App Store après l'achat. L'offre de l'essai de 7 jours est limité à une seule offre d'essai de 7 jours par utilisateur. Toute partie non utilisée d'une période d'essai (le cas échéant) sera abandonnée lorsque l'utilisateur achète une abonnement. Tous les prix comprennent les taxes locales applicables.\n\nL'abonnement signifie que vous acceptez les $1 et la $2."; +"agreement.trials.yearly.plan" = "an"; +"agreement.trials.monthly.plan" = "mois"; + +"agreement.message.tos" = "Conditions d'utilisation"; +"agreement.message.privacy" = "Politique de confidentialité"; + +"getstarted.buttons.buyaccount" = "Acheter un compte"; + +"gdpr.collect.data.title" = "Informations personnelles que nous collectons"; +"gdpr.collect.data.description" = "Adresse e-mail dans le but de gérer le compte et de protéger des abus."; +"gdpr.usage.data.title" = "Utilisation des informations personnelles que nous collectons"; +"gdpr.usage.data.description" = "L'adresse e-mail est utilisée pour envoyer les informations d'abonnement, les confirmation de paiement, la correspondance avec le client, et les offres promotionnelles de Private Internet Access uniquement."; +"gdpr.accept.button.title" = "Accepter et continuer"; + +"update.account.email.error" = "Échec de la modification de l'e-mail du compte"; diff --git a/Resources/Resources/iOS/it.lproj/Signup.strings b/Resources/Resources/iOS/it.lproj/Signup.strings new file mode 100644 index 000000000..b60ca1b4e --- /dev/null +++ b/Resources/Resources/iOS/it.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "Conferma registrazione"; +"in_progress.message" = "Conferma dell'acquisto col sistema in corso. Potrebbe impiegare qualche secondo. Attendi."; +"in_progress.redeem.message" = "Conferma del PIN della tua carta col sistema in corso. Potrebbe impiegare qualche secondo. Attendi."; + +"success.title" = "Acquisto completato"; +"success.message_format" = "Grazie per la registrazione. Ti abbiamo inviato nome utente e password del tuo account all'indirizzo e-mail %@"; +"success.redeem.title" = "Carta riscossa"; +"success.redeem.message" = "Riceverai un'email a breve con nome utente e password.\n\nI tuoi dati d'accesso"; +"success.username.caption" = "Nome utente"; +"success.password.caption" = "Password"; +"success.submit" = "Inizia"; + +"failure.vc_title" = "Registrazione non riuscita"; +"failure.title" = "Errore di creazione account"; +"failure.message" = "Impossibile creare un account in questo momento. Riprova più tardi. \n\nRiaprendo l'app si riproverà a creare un account."; +"failure.purchase.sandbox.message" = "L'abbonamento del sandbox selezionato on è disponibile in produzione."; +"failure.redeem.invalid.title" = "PIN della carta non valido"; +"failure.redeem.invalid.message" = "Hai inserito un PIN della carta non valido. Riprova."; +"failure.redeem.claimed.title" = "Carta già riscattata"; +"failure.redeem.claimed.message" = "Questa carta è già stata riscattata da un altro account. Prova a inserire un PIN diverso."; +"failure.submit" = "INDIETRO"; + +"unreachable.vc_title" = "Errore"; +"unreachable.title" = "Ops!"; +"unreachable.message" = "Nessuna connessione Internet trovata. Verifica la connessione Internet e premi Riprova di seguito.\n\nPuoi tornare all'app più tardi per terminare il processo."; +"unreachable.submit" = "RIPROVA"; + +"purchase.uncredited.alert.message" = "Hai transazioni non accreditate. Vuoi recuperare i dati del tuo account?"; +"purchase.uncredited.alert.button.cancel" = "Annulla"; +"purchase.uncredited.alert.button.recover" = "Recupera account"; + +"purchase.trials.intro" = "Inizia la tua prova gratuita da 7 giorni"; +"purchase.trials.price.after" = "Poi a %@"; +"purchase.trials.money.back" = "Garanzia di rimborso entro 30 giorni"; +"purchase.trials.1year.protection" = "1 anno di privacy e protezione d'indentità"; +"purchase.trials.anonymous" = "Sfoglia anonimamente e nascondi il tuo IP."; +"purchase.trials.devices" = "Supporta 10 dispositivi alla volta"; +"purchase.trials.devices.description" = "Proteggi un massimo di 10 dispositivi alla volta."; +"purchase.trials.region" = "Connettiti facilmente a qualsiasi regione"; +"purchase.trials.servers" = "Oltre 3300 server in 32 Paesi"; +"purchase.trials.start" = "Inizia abbonamento"; +"purchase.trials.all.plans" = "Vedi tutti i piani disponibili"; + +"purchase.subscribe.now" = "Iscriviti ora"; + +// WALKTHROUGH + +"walkthrough.action.next" = "AVANTI"; +"walkthrough.action.done" = "FATTO"; +"walkthrough.action.skip" = "SALTA"; + +"walkthrough.page.1.title" = "Supporta 10 dispositivi alla volta"; +"walkthrough.page.1.description" = "Proteggi te stesso su un massimo di 10 dispositivi alla volta."; +"walkthrough.page.2.title" = "Connettiti facilmente a qualsiasi regione"; +"walkthrough.page.2.description" = "Con server in tutto il mondo, sei sempre protetto."; +"walkthrough.page.3.title" = "Proteggiti dalle pubblicità"; +"walkthrough.page.3.description" = "Abilitando il nostro Blocco dei contenuti non visualizzerai la pubblicità mentre navighi con Safari."; + +"share.data.buttons.accept" = "Accetta"; +"share.data.buttons.noThanks" = "No, grazie"; +"share.data.buttons.readMore" = "Leggi di più"; +"share.data.text.title" = "Aiutaci a migliorare il tuo servizio"; +"share.data.text.description" = "Per aiutarci a garantire le prestazioni di connessione del nostro servizio, puoi condividere in modo anonimo le tue statistiche di connessione con noi. Questi rapporti non contengono informazioni d'identificazione personale."; +"share.data.text.footer" = "Puoi sempre controllare questa funzione dalle tue impostazioni"; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/it.lproj/Welcome.strings b/Resources/Resources/iOS/it.lproj/Welcome.strings new file mode 100644 index 000000000..197265683 --- /dev/null +++ b/Resources/Resources/iOS/it.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "Accedi al tuo account"; +"login.username.placeholder" = "Nome utente (p1234567)"; +"login.password.placeholder" = "Password"; +"login.submit" = "ACCEDI"; +"login.restore.button" = "Non hai ancora ricevuto i dettagli dell'account?"; +"login.error.title" = "Accedi"; +"login.error.validation" = "Devi inserire un nome utente e una password."; +"login.error.unauthorized" = "Nome utente o password non valida"; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "Accedi mediante ricevuta d'acquisto"; +"login.magic.link.title" = "Accedi tramite il link magico della mail"; +"login.magic.link.response" = "Controlla la tua mail per ottenere il link d'accesso."; +"login.magic.link.send" = "Invia link"; +"login.magic.link.invalid.email" = "Indirizzo email non valido. Riprova."; + +"purchase.title" = "Seleziona un piano VPN"; +"purchase.subtitle" = "Garanzia di rimborso entro 30 giorni"; +"purchase.email.placeholder" = "Indirizzo email"; +"purchase.continue" = "Continua"; +"purchase.login.footer" = "Possiedi già un account?"; +"purchase.login.button" = "Accedi"; +"purchase.error.title" = "Acquista"; +"purchase.error.validation" = "Devi indicare un indirizzo e-mail."; +"purchase.error.connectivity.title" = "Errore di connessione"; +"purchase.error.connectivity.description" = "Non siamo in grado di stabilire l'accesso a una rete Internet privata. Ciò potrebbe essere dovuto a una scarsa qualità della rete o a un blocco dei nostri servizi nel tuo paese."; +"purchase.confirm.form.email" = "Inserisci il tuo indirizzo email"; +"purchase.confirm.plan" = "Stai acquistando il piano %@"; +"purchase.email.why" = "Per inviarti nome utente e password, abbiamo bisogno del tuo indirizzo email."; +"purchase.submit" = "Invia"; +"purchase.or" = "o"; + +"upgrade.header" = "Bentornato!"; +"upgrade.title" = "Per usare Private Internet Access, devi rinnovare l'abbonamento."; +"upgrade.renew.now" = "Rinnova adesso"; + + + +"redeem.title" = "Riscatta la carta regalo"; +"redeem.subtitle" = "Digita qui sotto il tuo indirizzo email e le %lu cifre del PIN della carta regalo o carta di prova."; +"redeem.email.placeholder" = "Indirizzo email"; +"redeem.submit" = "INVIA"; +"redeem.error.title" = "Riscatta"; +"redeem.error.code" = "Il codice dev'essere di %lu cifre."; +"redeem.error.allfields" = "Digita il tuo indirizzo email e PIN della carta."; +"redeem.accessibility.back" = "Indietro"; +"redeem.giftcard.placeholder" = "PIN carta regalo"; + +"plan.monthly.title" = "Mensile"; +"plan.yearly.title" = "Annuale"; +"plan.yearly.detail_format" = "%@%@ all'anno"; +"plan.price_format" = "%@ al mese"; +"plan.best_value" = "Valore migliore"; +"plan.accessibility.per_month" = "al mese"; + +"restore.title" = "Ripristina acquisto non accreditato"; +"restore.subtitle" = "Se hai acquistato un piano mediante questa app ma non hai ricevuto le tue credenziali, puoi inviarle nuovamente da qui.\nNon verrà effettuato alcun addebito durante la procedura."; +"restore.email.placeholder" = "Indirizzo email"; +"restore.submit" = "CONFERMA"; + +"iap.error.message.unavailable" = "Server Apple attualmente non disponibili. Riprova più tardi."; +"iap.error.title" = "Errore"; + +"agreement.trials.title" = "Termini e condizioni della prova gratuita"; +"agreement.trials.message" = "Il pagamento verrà addebitato sul tuo account ID Apple alla conferma dell'acquisto. L'abbonamento si rinnova automaticamente a meno che non venga annullato entro 24 ore dalla fine del periodo attuale. Il tuo account verrà addebitato per il rinnovo entro 24 ore dalla fine del periodo attuale. Puoi gestire e cancellare i tuoi abbonamenti dalle impostazioni dell'account sull'App Store dopo l'acquisto.\n\nAlcuni abbonamenti a pagamento possono offrire una prova gratuita prima di addebitare il metodo di pagamento. Se decidi di annullare l'iscrizione a un abbonamento a pagamento prima dell'addebito, dovrai annullarla entro 24 ore dalla scadenza della prova gratuita.\n\nLe prove gratuite sono disponibili solo per i nuovi utenti e sono a nostra esclusiva discrezione. In caso di registrazione per ottenere una prova gratuita aggiuntiva, l'addebito dell'importo standard dell'abbonamento verrà effettuato immediatamente.\n\nCi riserviamo il diritto di revocare la prova gratuita in qualsiasi momento.\n\nQualsiasi parte inutilizzata del periodo di prova gratuito andrà persa al momento dell'acquisto di un abbonamento.\n\nLa registrazione implica l'accettazione dei presenti termini e condizioni."; +"agreement.message" = "Dopo 7 giorni di prova gratuita, l'abbonamento si rinnova automaticamente per % @ a meno che non venga annullato entro 24 ore dalla fine del periodo di prova. Il tuo account ID Apple verrà addebitato per il rinnovo entro 24 ore dalla fine del periodo di prova. Puoi gestire e cancellare i tuoi abbonamenti andando sulle impostazioni del tuo account App Store dopo l'acquisto. L'offerta di prova di 7 giorni è limitata a un'unica offerta da 7 giorni per utente. Ogni parte inutilizzata di un periodo di prova gratuito, se offerto, andrà perduta quando l'utente acquista un abbonamento. Tutti i prezzi includono le tasse locali applicabili.\n\nLa registrazione implica l'accettazione di $1 e $2."; +"agreement.trials.yearly.plan" = "anno"; +"agreement.trials.monthly.plan" = "mese"; + +"agreement.message.tos" = "Termini di servizio"; +"agreement.message.privacy" = "Informativa sulla Privacy"; + +"getstarted.buttons.buyaccount" = "Acquista account"; + +"gdpr.collect.data.title" = "Dati personali da noi raccolti"; +"gdpr.collect.data.description" = "Indirizzo email ai fini di gestione dell'account e della protezione dall'uso improprio."; +"gdpr.usage.data.title" = "Usi delle informazioni personali da noi richieste"; +"gdpr.usage.data.description" = "L'indirizzo email viene utilizzato solo per inviare informazioni sull'abbonamento, conferme di pagamento, corrispondenza col cliente e offerte promozionali di Private Internet Access."; +"gdpr.accept.button.title" = "Accetta e continua"; + +"update.account.email.error" = "Modifica email account non riuscita"; diff --git a/Resources/Resources/iOS/ja.lproj/Signup.strings b/Resources/Resources/iOS/ja.lproj/Signup.strings new file mode 100644 index 000000000..886508935 --- /dev/null +++ b/Resources/Resources/iOS/ja.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "サインアップを確定"; +"in_progress.message" = "システムがご購入を確定しています。しばらく時間がかかることがありますので、そのままお待ちください。"; +"in_progress.redeem.message" = "システムがご購入を確定しています。しばらく時間がかかることがありますので、そのままお待ちください。"; + +"success.title" = "購入完了"; +"success.message_format" = "サインアップありがとうございます。アカウントのユーザー名とパスワードをお客様のメールアドレス(%@)に送信いたしました。"; +"success.redeem.title" = "カードの引き換えが完了しました"; +"success.redeem.message" = "ユーザーネームとパスワードを記載したメールがまもなく届きます。\n\nお客様のログイン詳細"; +"success.username.caption" = "ユーザー名"; +"success.password.caption" = "パスワード"; +"success.submit" = "始める"; + +"failure.vc_title" = "サインアップできませんでした"; +"failure.title" = "アカウントの作成に失敗しました"; +"failure.message" = "ただ今アカウントを作成できません。後ほどもう一度お試しください。\n\nアプリを再起動すると、アカウント作成が再試行されます。"; +"failure.purchase.sandbox.message" = "選択されたサンドボックスのサブスクリプションは生産中のため利用できません。"; +"failure.redeem.invalid.title" = "無効なカードPIN"; +"failure.redeem.invalid.message" = "無効なカードPINを入力したようです。もう一度お試しください。"; +"failure.redeem.claimed.title" = "すでに獲得されたカードです"; +"failure.redeem.claimed.message" = "このカードは、すでに別のアカウントが獲得したようです。別のPINを入力してください。"; +"failure.submit" = "戻る"; + +"unreachable.vc_title" = "エラー"; +"unreachable.title" = "おっと!"; +"unreachable.message" = "インターネット接続が見つかりません。インターネットに接続していることを確認してから下の再試行をタップしてください。\n\n後ほどアプリでこのプロセスを完了することができます。"; +"unreachable.submit" = "再試行"; + +"purchase.uncredited.alert.message" = "反映されていない取引があります。アカウントの詳細を回復しますか?"; +"purchase.uncredited.alert.button.cancel" = "キャンセル"; +"purchase.uncredited.alert.button.recover" = "アカウントを回復"; + +"purchase.trials.intro" = "7日間無料トライアルを開始"; +"purchase.trials.price.after" = "以後%@"; +"purchase.trials.money.back" = "30日間返金保証"; +"purchase.trials.1year.protection" = "1年間のプライバシーおよび個人情報の保護"; +"purchase.trials.anonymous" = "ウェブの匿名利用でIPを非表示にします。"; +"purchase.trials.devices" = "一度に10台の端末をサポート"; +"purchase.trials.devices.description" = "一度に最大10台の端末を保護して自分を守ることができます。"; +"purchase.trials.region" = "すべての地域に簡単に接続"; +"purchase.trials.servers" = "32か国の3300以上のサーバー"; +"purchase.trials.start" = "サブスクリプションを開始"; +"purchase.trials.all.plans" = "利用可能なプランをすべて見る"; + +"purchase.subscribe.now" = "今すぐ定期購読を購入"; + +// WALKTHROUGH + +"walkthrough.action.next" = "次へ"; +"walkthrough.action.done" = "完了"; +"walkthrough.action.skip" = "スキップ"; + +"walkthrough.page.1.title" = "一度に10台の端末をサポート"; +"walkthrough.page.1.description" = "一度に最大10台の端末を保護して自分を守ることができます。"; +"walkthrough.page.2.title" = "あらゆる地域に簡単に接続"; +"walkthrough.page.2.description" = "世界中にサーバがあるので、常に保護された状態でいることができます。"; +"walkthrough.page.3.title" = "広告から自分を守りましょう"; +"walkthrough.page.3.description" = "コンテンツブロッカーを有効にすると、Safariで広告表示をブロックできます。"; + +"share.data.buttons.accept" = "同意する"; +"share.data.buttons.noThanks" = "いいえ、結構です"; +"share.data.buttons.readMore" = "もっと読む"; +"share.data.text.title" = "弊社のサービス改善にご協力ください"; +"share.data.text.description" = "弊社のサービスの接続パフォーマンス確保にご協力いただくには、接続データを匿名で弊社と共有してください。これらのレポートには、個人を特定できる情報は含まれません。"; +"share.data.text.footer" = "これは、設定からいつでもコントロール可能です"; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/ja.lproj/Welcome.strings b/Resources/Resources/iOS/ja.lproj/Welcome.strings new file mode 100644 index 000000000..98aa77a8f --- /dev/null +++ b/Resources/Resources/iOS/ja.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "アカウントにサインイン"; +"login.username.placeholder" = "ユーザー名 (p1234567)"; +"login.password.placeholder" = "パスワード"; +"login.submit" = "ログイン"; +"login.restore.button" = "アカウント詳細を受信しませんでしたか?"; +"login.error.title" = "ログイン"; +"login.error.validation" = "ユーザー名とパスワードを入力してください。"; +"login.error.unauthorized" = "ユーザー名またはパスワードが間違っています。"; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "購入領収書を使用してログイン"; +"login.magic.link.title" = "魔法のメールリンクを使用してログイン"; +"login.magic.link.response" = "ログインリンクについては、メールを確認してください。"; +"login.magic.link.send" = "リンクを送信"; +"login.magic.link.invalid.email" = "無効なメールアドレス。もう一度お試しください。"; + +"purchase.title" = "VPNプランを選択"; +"purchase.subtitle" = "30日間返金保証"; +"purchase.email.placeholder" = "メールアドレス"; +"purchase.continue" = "続行"; +"purchase.login.footer" = "既にアカウントをお持ちですか?"; +"purchase.login.button" = "サインイン"; +"purchase.error.title" = "購入"; +"purchase.error.validation" = "必ずメールアドレスを入力してください。"; +"purchase.error.connectivity.title" = "接続エラー"; +"purchase.error.connectivity.description" = "Private Internet Accessに接続できませんでした。インターネットの接続が不安定、もしくはお住まいの国で当社サービスがブロックされている可能性があります。"; +"purchase.confirm.form.email" = "メールアドレスを入力してください"; +"purchase.confirm.plan" = "お客様は%@プランを購入しようとしています"; +"purchase.email.why" = "ユーザー名とパスワードを送信するためのメールアドレスを入力してください。"; +"purchase.submit" = "送信"; +"purchase.or" = "または"; + +"upgrade.header" = "お帰りなさい!"; +"upgrade.title" = "Private Internet Accessのご利用を継続するには、サブスクリプションを更新する必要があります。"; +"upgrade.renew.now" = "今すぐ更新"; + + + +"redeem.title" = "ギフトカード引換え"; +"redeem.subtitle" = "メールアドレスと、以下のギフトカードまたはトライアルカードの%lu桁のPINを入力してください。"; +"redeem.email.placeholder" = "メールアドレス"; +"redeem.submit" = "送信"; +"redeem.error.title" = "引換え"; +"redeem.error.code" = "コードは%lu桁でなければなりません。"; +"redeem.error.allfields" = "メールアドレスとカードのPINを入力してください。"; +"redeem.accessibility.back" = "戻る"; +"redeem.giftcard.placeholder" = "ギフトカードのPIN"; + +"plan.monthly.title" = "月間"; +"plan.yearly.title" = "年間"; +"plan.yearly.detail_format" = "年間%@%@"; +"plan.price_format" = "%@/月"; +"plan.best_value" = "最もお得"; +"plan.accessibility.per_month" = "月々"; + +"restore.title" = "追加されていない更新を復元"; +"restore.subtitle" = "このアプリでプランを購入した後、認証情報を受け取っていない方は、こちらから認証情報を再度送信することができます。この処理の実行中、課金は発生しません。"; +"restore.email.placeholder" = "メールアドレス"; +"restore.submit" = "確定"; + +"iap.error.message.unavailable" = "Appleサーバーが現在ご利用いただけません。後でもう一度お試しください。"; +"iap.error.title" = "エラー"; + +"agreement.trials.title" = "無料トライアル利用規約"; +"agreement.trials.message" = "ご購入確定時にお使いのApple IDアカウント支払額が請求されます。サブスクリプションは現在の期間が終了する24時間前までにキャンセルされない限り自動的に更新されます。更新料は現在の期間終了前の24時間以内にお使いのアカウントに請求されます。サブスクリプションはご購入後にApp Storeのアカウント設定からいつでも管理およびキャンセルすることができます。\n\n一部の有料サブスクリプションでは、ご希望の支払方法による請求が実施される前に無料トライアルが提供されている場合があります。ご選択の支払方法による請求が実施される前に有料サブスクリプションの解約をする場合は、無料トライアルが終了する24時間前までにサブスクリプションをキャンセルしてください。\n\n無料トライアルをご利用いただけるのは新規ユーザーのみとなり、無料トライアルを使用する目的で新たに追加のアカウントをサインアップした場合、弊社の裁量によって、通常のサブスクリプション料金が即時に請求されます。\n\n弊社は無料トライアル期間をいつでも無効にする権利を保持します。\n\nサブスクリプションご購入後、未使用分の無料トライアル期間は無効となります。\n\nサインアップすることで、$1と$2に同意したことになります。"; +"agreement.message" = "トライアル期間終了の少なくとも24時間前にキャンセルされない限り、7日間の無料トライアル後、この定期購読は自動的に%@で更新されます。更新料は、トライアル期間終了前の24時間以内にご利用のApple IDアカウントに請求されます。定期購読は、定期購読購入後にApp Storeアカウント設定から管理およびキャンセルすることができます。7日間のトライアルオファーは、ユーザー1人あたり1回の7日間トライアルに限られています。無料トライアルを利用した場合、無料トライアル期間の未使用分は、ユーザーが定期購読を購入した時点で無効となります。すべての料金には、適用可能な場合現地の消費税が含まれます。\n\nサインアップすることにより、$1および$2に同意したことになります。"; +"agreement.trials.yearly.plan" = "年"; +"agreement.trials.monthly.plan" = "月"; + +"agreement.message.tos" = "利用規約"; +"agreement.message.privacy" = "プライバシーポリシー"; + +"getstarted.buttons.buyaccount" = "アカウントを購入する"; + +"gdpr.collect.data.title" = "弊社が収集する個人情報"; +"gdpr.collect.data.description" = "メールアドレスは、アカウント管理、および乱用からの保護を目的とします。"; +"gdpr.usage.data.title" = "弊社により収集される個人情報の使用"; +"gdpr.usage.data.description" = "メールアドレスはサブスクリプション情報、お支払確認、お客様とのやり取り、およびPrivate Internet Accessのプロモーションオファーを送信するためにのみ使用されます。"; +"gdpr.accept.button.title" = "同意して続行する"; + +"update.account.email.error" = "アカウントのメールを変更できませんでした"; diff --git a/Resources/Resources/iOS/ko.lproj/Signup.strings b/Resources/Resources/iOS/ko.lproj/Signup.strings new file mode 100644 index 000000000..cfd62453f --- /dev/null +++ b/Resources/Resources/iOS/ko.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "회원 가입 확인"; +"in_progress.message" = "저희 시스템에서 고객님의 구매를 확인하고 있습니다. 약간 시간이 소요될 수 있으므로 양해 부탁드립니다."; +"in_progress.redeem.message" = "저희 시스템에서 고객님의 카드 PIN을 확인하고 있습니다. 약간 시간이 소요될 수 있으므로 양해 부탁드립니다."; + +"success.title" = "구매 완료"; +"success.message_format" = "회원으로 가입해 주셔서 감사합니다. 계정 사용자명과 비밀번호를 귀하의 이메일 주소(%@)로 발송했습니다."; +"success.redeem.title" = "카드 사용 성공"; +"success.redeem.message" = "사용자 이름 및 비밀번호가 담긴 이메일을 곧 보내드리겠습니다.\n\n귀하의 로그인 정보"; +"success.username.caption" = "사용자 이름"; +"success.password.caption" = "비밀번호"; +"success.submit" = "시작하기"; + +"failure.vc_title" = "회원 가입 실패"; +"failure.title" = "계정 생성 실패"; +"failure.message" = "지금은 계정을 생성할 수 없습니다. 나중에 다시 시도해주십시오.\n\n 앱을 다시 열면 계정 생성을 다시 시도합니다."; +"failure.purchase.sandbox.message" = "선택하신 샌드박스 구독은 생산에 사용할 수 없습니다."; +"failure.redeem.invalid.title" = "유효하지 않은 카드 PIN"; +"failure.redeem.invalid.message" = "유효하지 않은 카드 PIN을 입력한 것 같습니다. 다시 시도하세요."; +"failure.redeem.claimed.title" = "이미 청구한 카드"; +"failure.redeem.claimed.message" = "이 카드는 이미 다른 계정에서 청구한 것 같습니다. 다른 PIN을 입력해 보세요."; +"failure.submit" = "뒤로"; + +"unreachable.vc_title" = "오류"; +"unreachable.title" = "앗!"; +"unreachable.message" = "인터넷에 연결되지 않았습니다. 인터넷에 연결되어 있는지 확인하신 후 아래에서 다시 시도를 누르십시오.\n\n나중에 앱에 다시 돌아와서 이 과정을 완료할 수 있습니다."; +"unreachable.submit" = "다시 시도"; + +"purchase.uncredited.alert.message" = "처리되지 않은 거래가 있습니다. 계정 정보를 복구하시겠습니까?"; +"purchase.uncredited.alert.button.cancel" = "취소"; +"purchase.uncredited.alert.button.recover" = "계정 복구"; + +"purchase.trials.intro" = "7일 무료 체험 시작"; +"purchase.trials.price.after" = "그 후 %@"; +"purchase.trials.money.back" = "30일 이내 환불 보장"; +"purchase.trials.1year.protection" = "1년간 프라이버시 및 신원 보호"; +"purchase.trials.anonymous" = "익명으로 검색하고 IP를 숨기세요."; +"purchase.trials.devices" = "동시에 10대의 장치 지원"; +"purchase.trials.devices.description" = "한 번에 최대 10대의 장치에서 보호를 받으세요."; +"purchase.trials.region" = "모든 지역에 쉽게 연결"; +"purchase.trials.servers" = "32개국 3300개 이상의 서버"; +"purchase.trials.start" = "구독 시작"; +"purchase.trials.all.plans" = "이용 가능한 모든 플랜 보기"; + +"purchase.subscribe.now" = "지금 구독"; + +// WALKTHROUGH + +"walkthrough.action.next" = "다음"; +"walkthrough.action.done" = "완료"; +"walkthrough.action.skip" = "건너뛰기"; + +"walkthrough.page.1.title" = "한 번의 10대의 장치 지원"; +"walkthrough.page.1.description" = "한 번에 최대 10대의 장치에서 보호를 받으세요."; +"walkthrough.page.2.title" = "모든 지역에 쉽게 연결"; +"walkthrough.page.2.description" = "전 세계에 있는 서버를 통해 언제나 보호를 받습니다."; +"walkthrough.page.3.title" = "광고로부터 보호"; +"walkthrough.page.3.description" = "Content Blocker를 활성화하면 Safari에서 광고가 표시되지 않습니다."; + +"share.data.buttons.accept" = "수락"; +"share.data.buttons.noThanks" = "아니요"; +"share.data.buttons.readMore" = "자세히 보기"; +"share.data.text.title" = "저희 서비스를 개선하도록 도와 주세요"; +"share.data.text.description" = "연결 상태를 저희와 익명으로 공유해 주시면 저희 서비스의 연결 성능을 개선하는 데 도움이 됩니다. 이 보고서에는 개인 식별 정보가 포함되지 않습니다."; +"share.data.text.footer" = "언제든지 설정에서 이 옵션을 관리할 수 있습니다."; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/ko.lproj/Welcome.strings b/Resources/Resources/iOS/ko.lproj/Welcome.strings new file mode 100644 index 000000000..2d29b5f83 --- /dev/null +++ b/Resources/Resources/iOS/ko.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "계정에 로그인"; +"login.username.placeholder" = "사용자 이름 (p1234567)"; +"login.password.placeholder" = "비밀번호"; +"login.submit" = "로그인"; +"login.restore.button" = "계정 정보를 받지 못하셨습니까?"; +"login.error.title" = "로그인"; +"login.error.validation" = "사용자 이름과 비밀번호를 입력하셔야 합니다."; +"login.error.unauthorized" = "사용자명 또는 비밀번호가 틀립니다."; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "구매 영수증을 사용해 로그인"; +"login.magic.link.title" = "이메일 링크를 사용해 간편하게 로그인"; +"login.magic.link.response" = "로그인 링크가 담긴 이메일을 확인하세요."; +"login.magic.link.send" = "링크 보내기"; +"login.magic.link.invalid.email" = "이메일이 유효하지 않음. 다시 시도하세요."; + +"purchase.title" = "VPN 플랜 선택"; +"purchase.subtitle" = "30일 이내 환불 보장"; +"purchase.email.placeholder" = "이메일 주소"; +"purchase.continue" = "계속"; +"purchase.login.footer" = "이미 계정이 있으세요?"; +"purchase.login.button" = "로그인"; +"purchase.error.title" = "구매"; +"purchase.error.validation" = "이메일 주소를 입력하셔야 합니다."; +"purchase.error.connectivity.title" = "연결 실패"; +"purchase.error.connectivity.description" = "Private Internet Access에 접속할 수 없습니다. 인터넷 연결 상태가 좋지 않거나 귀하의 국가에서 당사의 서비스가 차단된 것 같습니다."; +"purchase.confirm.form.email" = "이메일 주소를 입력하세요"; +"purchase.confirm.plan" = "%@ 플랜을 구매합니다"; +"purchase.email.why" = "사용자 이름 및 비밀번호를 보내 드리면 고객님의 이메일 주소가 필요합니다."; +"purchase.submit" = "제출"; +"purchase.or" = "또는"; + +"upgrade.header" = "다시 오신 걸 환영합니다!"; +"upgrade.title" = "Private Internet Access를 사용하려면 구독을 갱신하셔야 합니다."; +"upgrade.renew.now" = "지금 갱신"; + + + +"redeem.title" = "기프트 카드 청구"; +"redeem.subtitle" = "기프트 카드 또는 체험 카드의 %lu자리 PIN과 이메일 주소를 입력하세요."; +"redeem.email.placeholder" = "이메일 주소"; +"redeem.submit" = "제출"; +"redeem.error.title" = "청구"; +"redeem.error.code" = "코드는 %lu자리 숫자여야 합니다."; +"redeem.error.allfields" = "이메일 및 카드 PIN을 입력하세요."; +"redeem.accessibility.back" = "뒤로"; +"redeem.giftcard.placeholder" = "기프트 카드 PIN"; + +"plan.monthly.title" = "월간"; +"plan.yearly.title" = "연간"; +"plan.yearly.detail_format" = "매년 %@%@"; +"plan.price_format" = "%@/월"; +"plan.best_value" = "최저가"; +"plan.accessibility.per_month" = "매월"; + +"restore.title" = "인정되지 않은 구매 항목 복원"; +"restore.subtitle" = "이 앱을 통해 요금 플랜을 구매하셨는데 자격 증명 정보를 받지 못하신 경우 이곳에서 다시 보내실 수 있습니다. 이 과정 중 요금이 부과되지 않습니다.\n"; +"restore.email.placeholder" = "이메일 주소"; +"restore.submit" = "확인"; + +"iap.error.message.unavailable" = "현재 Apple 서버를 이용할 수 없습니다. 나중에 다시 시도해주십시오."; +"iap.error.title" = "오류"; + +"agreement.trials.title" = "무료 체험 계약 조건"; +"agreement.trials.message" = "구매 확인 시 사용자의 Apple ID 계정으로 요금이 청구됩니다. 현재 기간이 종료하기 24시간 전에 취소하지 않으면 구독은 자동으로 갱신됩니다. 현재 기간이 종료하기 전 24시간 이내에 갱신 요금이 계정으로 청구됩니다. 구매 후 App Store의 계정 설정에서 구독을 관리하고 취소할 수 있습니다.\n\n일부 유료 구독은 사용자의 결제 수단으로 청구하기 전에 무료 체험을 제공할 수 있습니다. 결제 수단으로 청구가 시작되기 전에 유료 구독을 취소하려면, 무료 체험 기간이 종료하기 24시간 전에 구독을 취소하십시오.\n\n무료 체험은 신규 사용자만 이용할 수 있으며, 당사의 단독 재량으로 제공됩니다. 무료 체험을 추가로 이용하기 위해 가입한 경우 표준 구독 요금이 즉시 청구됩니다.\n\n당사는 언제든지 무료 체험을 철회할 권리를 보유합니다.\n\n구독 구매 시 무료 체험 기간의 미사용분은 소멸됩니다.\n\n가입 시 본 계약 조건에 동의하신 것으로 간주됩니다."; +"agreement.message" = "체험 기간이 종료하기 24시간 전에 구독을 취소하지 않으면, 7일 무료 체험 후 이 구독은 %@에 자동으로 갱신됩니다. 체험 기간이 종료하기 전 24시간 이내에 사용자의 Apple ID로 갱신 요금이 청구됩니다. 구매 후 App Store 계정으로 이동하여 구독을 관리하고 취소할 수 있습니다. 7일 체험 혜택은 사용자당 한 번으로 제한됩니다. 사용자가 구독을 구매할 경우 무료 체험 기간의 미사용분은 소멸됩니다. 모든 가격에는 현지에서 적용되는 판매세가 포함됩니다.\n\n가입 시 $1 및 $2에 동의하신 것으로 간주됩니다."; +"agreement.trials.yearly.plan" = "년"; +"agreement.trials.monthly.plan" = "개월"; + +"agreement.message.tos" = "서비스 약관"; +"agreement.message.privacy" = "개인정보 취급방침"; + +"getstarted.buttons.buyaccount" = "계정 구입"; + +"gdpr.collect.data.title" = "당사가 수집하는 개인 정보"; +"gdpr.collect.data.description" = "계정 관리 및 악용 방지를 위한 이메일 주소."; +"gdpr.usage.data.title" = "수집된 개인 정보의 사용"; +"gdpr.usage.data.description" = "이메일 주소는 구독 정보, 결제 확인, 고객 공지 사항 및 Private Internet Access 프로모션 정보를 보내는 데에만 사용됩니다."; +"gdpr.accept.button.title" = "동의 및 계속"; + +"update.account.email.error" = "계정 이메일을 수정하지 못했습니다"; diff --git a/Resources/Resources/iOS/nb.lproj/Signup.strings b/Resources/Resources/iOS/nb.lproj/Signup.strings new file mode 100644 index 000000000..300d513ed --- /dev/null +++ b/Resources/Resources/iOS/nb.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "Bekrefter registrering"; +"in_progress.message" = "Vi bekrefter kjøpet i systemet vårt. Dette kan ta litt tid."; +"in_progress.redeem.message" = "Vi bekrefter kort-PIN-en din i systemet vårt. Det kan ta et øyeblikk, så vennligst vent."; + +"success.title" = "Kjøp fullført"; +"success.message_format" = "Takk for at du registrerte deg. Vi har sendt deg kontonavnet og passordet ditt til e-postadressen din %@"; +"success.redeem.title" = "Kortet har blitt innløst"; +"success.redeem.message" = "Du mottar en e-post med brukernavn og passord.\n\nPåloggingsinformasjonen din"; +"success.username.caption" = "Brukernavn"; +"success.password.caption" = "Passord"; +"success.submit" = "Komme i gang"; + +"failure.vc_title" = "Registrering mislyktes"; +"failure.title" = "Kunne ikke opprette kontoen"; +"failure.message" = "Vi kunne ikke opprette en konto nå. Prøv på nytt senere.\n\nNår du åpner appen igjen, vil den prøve å opprette kontoen på nytt."; +"failure.purchase.sandbox.message" = "Det valgte sandboksabonnementet er ikke tilgjengelig i produksjon."; +"failure.redeem.invalid.title" = "Ugyldig kort-PIN"; +"failure.redeem.invalid.message" = "Ser ut til at du brukte en ugyldig kort-PIN. Prøv igjen"; +"failure.redeem.claimed.title" = "Kortet er allerede brukt"; +"failure.redeem.claimed.message" = "Ser ut til at kortet allerede er brukt av en annen konto. Du kan prøve med en annen PIN."; +"failure.submit" = "GÅ TILBAKE"; + +"unreachable.vc_title" = "Feil"; +"unreachable.title" = "Ups!"; +"unreachable.message" = "Ingen internettilkobling. Bekreft at du er koblet til Internett og trykk på Prøv igjen nedenfor.\n\nDu kan komme tilbake til appen senere for å fullføre prosessen."; +"unreachable.submit" = "PRØV PÅ NYTT"; + +"purchase.uncredited.alert.message" = "Du har ikke krediterte transaksjoner. Vil du gjenopprette kontoinformasjonen din?"; +"purchase.uncredited.alert.button.cancel" = "Abryt"; +"purchase.uncredited.alert.button.recover" = "Gjenopprett konto"; + +"purchase.trials.intro" = "Start din gratis 7-dagers prøveperiode"; +"purchase.trials.price.after" = "Deretter %@"; +"purchase.trials.money.back" = "30 dagers pengene-tilbake-garanti"; +"purchase.trials.1year.protection" = "Et års personverns- og identitetsbeskyttelse"; +"purchase.trials.anonymous" = "Surf anonymt og skjul IP-adressen din."; +"purchase.trials.devices" = "Støtter ti enheter om gangen"; +"purchase.trials.devices.description" = "Beskytt deg på opptil ti enheter samtidig."; +"purchase.trials.region" = "Koble til hvilken som helst region på en enkel måte"; +"purchase.trials.servers" = "Over 3300 servere i 32 land"; +"purchase.trials.start" = "Start abonnementet"; +"purchase.trials.all.plans" = "Vis alle tilgjengelige abonnement"; + +"purchase.subscribe.now" = "Abonner nå"; + +// WALKTHROUGH + +"walkthrough.action.next" = "NESTE"; +"walkthrough.action.done" = "FERDIG"; +"walkthrough.action.skip" = "HOPP OVER"; + +"walkthrough.page.1.title" = "Støtter ti enheter samtidig"; +"walkthrough.page.1.description" = "Beskytt deg på opptil ti enheter samtidig."; +"walkthrough.page.2.title" = "Koble til hvilken som helst region på en enkel måte"; +"walkthrough.page.2.description" = "Med serverer rundt om i hele verden, er du alltid beskyttet."; +"walkthrough.page.3.title" = "Beskytt deg selv mot reklame"; +"walkthrough.page.3.description" = "Aktivering av innholdsblokkereren sikrer at reklame ikke blir vist når du bruker Safari."; + +"share.data.buttons.accept" = "Godta"; +"share.data.buttons.noThanks" = "Nei takk"; +"share.data.buttons.readMore" = "Les mer"; +"share.data.text.title" = "Hjelp oss med å forbedre tjenesten vår"; +"share.data.text.description" = "For å hjelpe oss med å sikre tjenestens tilkoblingsytelse, kan du anonymt dele tilkoblingsstatistikken din med oss. Disse rapportene inkluderer informasjon som ikke er personlig identifiserbar."; +"share.data.text.footer" = "Du kan kontrollere dette fra innstillingene dine"; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/nb.lproj/Welcome.strings b/Resources/Resources/iOS/nb.lproj/Welcome.strings new file mode 100644 index 000000000..80faed974 --- /dev/null +++ b/Resources/Resources/iOS/nb.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "Logg inn på kontoen din"; +"login.username.placeholder" = "Brukernavn (p1234567)"; +"login.password.placeholder" = "Passord"; +"login.submit" = "LOGG INN"; +"login.restore.button" = "Har du ikke fått kontodetaljene?"; +"login.error.title" = "Logg på"; +"login.error.validation" = "Du må oppgi et brukernavn og passord."; +"login.error.unauthorized" = "Brukernavnet eller passordet ditt er feil."; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "Logg på med kjøpsbevis"; +"login.magic.link.title" = "Pålogging med magisk e-postkobling"; +"login.magic.link.response" = "Sjekk e-posten din for å finne påloggingskoblingen."; +"login.magic.link.send" = "Send kobling"; +"login.magic.link.invalid.email" = "Ugyldig e-post. Prøv igjen."; + +"purchase.title" = "Velg et VPN-abonnement"; +"purchase.subtitle" = "30 dagers pengene-tilbake-garanti"; +"purchase.email.placeholder" = "E-postadresse"; +"purchase.continue" = "Fortsett"; +"purchase.login.footer" = "Har du allerede en konto?"; +"purchase.login.button" = "Logg inn"; +"purchase.error.title" = "Kjøp"; +"purchase.error.validation" = "Du må angi en e-postadresse."; +"purchase.error.connectivity.title" = "Tilkoblingsfeil"; +"purchase.error.connectivity.description" = "Vi kunne ikke nå Private Internet Access. Dette kan skyldes dårlig Internett eller at tjenesten vår er blokkert i landet ditt."; +"purchase.confirm.form.email" = "Angi e-postadressen din"; +"purchase.confirm.plan" = "Du kjøper %@-abonnementet"; +"purchase.email.why" = "Vi trenger e-posten din for å sende deg brukernavnet og passordet ditt."; +"purchase.submit" = "Send inn"; +"purchase.or" = "eller"; + +"upgrade.header" = "Velkommen tilbake!"; +"upgrade.title" = "For å bruke en privat internettilgang, må du fornye abonnementet ditt."; +"upgrade.renew.now" = "Forny nå"; + + + +"redeem.title" = "Løs inn gavekort"; +"redeem.subtitle" = "Angi e-postadressen og den %lu-sifrede PIN-koden fra gavekortet eller prøvekortet nedenfor."; +"redeem.email.placeholder" = "E-postadresse"; +"redeem.submit" = "SEND"; +"redeem.error.title" = "Løs inn"; +"redeem.error.code" = "Koden må bestå av %lu siffer."; +"redeem.error.allfields" = "Angi e-postadressen din og kortet PIN-kode."; +"redeem.accessibility.back" = "Tilbake"; +"redeem.giftcard.placeholder" = "PIN-kode for gavekort"; + +"plan.monthly.title" = "Månedlig"; +"plan.yearly.title" = "Årlig"; +"plan.yearly.detail_format" = "%@%@ per år"; +"plan.price_format" = "%@/mnd"; +"plan.best_value" = "Mest for pengene"; +"plan.accessibility.per_month" = "per måned"; + +"restore.title" = "Gjenopprett ukreditert kjøp"; +"restore.subtitle" = "Hvis du har kjøpt en plan via appen og ikke har mottatt opplysningene dine, kan du sende dem på nytt herfra.\nDu belastes ikke under denne prosessen."; +"restore.email.placeholder" = "E-postadresse"; +"restore.submit" = "BEKREFT"; + +"iap.error.message.unavailable" = "Apple-serverne er for øyeblikket ikke tilgjengelige. Prøv igjen senere."; +"iap.error.title" = "Feil"; + +"agreement.trials.title" = "Vilkår og betingelser for gratis prøveperiode"; +"agreement.trials.message" = "Betalingen belastes Apple ID-kontoen din når du bekrefter kjøpet. Abonnementet fornyes automatisk med mindre det kanselleres minst 24 timer før slutten av den inneværende perioden. Kontoen blir belastet for fornyelse innen 24 timer før slutten av den inneværende perioden. Du kan administrere og kansellere abonnementene dine ved besøke kontoinnstillingene på App Store etter kjøpet.\n\nEnkelte betalte abonnementer kan tilby en gratis prøveperiode før det belaster betalingsmetoden din. Hvis du bestemmer deg for å avslutte et betalt abonnement før vi begynner å belaste betalingsmetoden din, må du kansellere abonnementet minst 24 timer før prøveperioden er slutt.\n\nGratis prøveperioder er kun tilgjengelig for nye brukere og utføres etter vårt eget skjønn. Hvis du forsøker å registrere deg med flere gratis prøveperioder, blir du øyeblikkelig belastet standard abonnementsavgift.\n\nVi forbeholder oss retten til å tilbakekalle din gratis prøveperiode når som helst.\n\nEventuell ubrukt del av en gratis prøveperiode går tapt ved kjøpe av et abonnement.\n\nVed å registrere deg samtykker du til disse vilkårene og betingelsene."; +"agreement.message" = "Etter den gratis 7-dagers prøveperioden fornyes abonnementet automatisk for %@, med mindre det kanselleres minst 24 timer før slutten av prøveperioden. Din Apple ID-konto belastes for fornyingen innen 24 timer før slutten av prøveperioden. Du kan administrere og avbryte abonnementene dine ved å gå til App Store-kontoinnstillingene etter kjøp. Det 7-dagers prøvetilbudet er begrenset til én 7-dagers prøveperiode per bruker. Eventuell ubrukt tid av den gratis prøveperioden går tapt hvis brukeren kjøper et abonnement. Alle priser inkluderer gjeldende lokale avgifter.\n\nVed å signere godtar du $1 og $2."; +"agreement.trials.yearly.plan" = "år"; +"agreement.trials.monthly.plan" = "måned"; + +"agreement.message.tos" = "Tjenestevilkår"; +"agreement.message.privacy" = "REntingslinjer om personvern"; + +"getstarted.buttons.buyaccount" = "Kjøpskonto"; + +"gdpr.collect.data.title" = "Personlig informasjon vi tar vare på"; +"gdpr.collect.data.description" = "E-postadresse for kontoadministrasjon og beskyttelse mot misbruk."; +"gdpr.usage.data.title" = "Bruk av personlig informasjon samlet inn av oss"; +"gdpr.usage.data.description" = "E-postadresse blir kun brukt for å sende ut informasjon om abonnementet, betalingsbekreftelser, kundekorrespondanse og tilbud om privat internettilgang."; +"gdpr.accept.button.title" = "Godta og fortsett"; + +"update.account.email.error" = "Kunne ikke endre kontoens e-post"; diff --git a/Resources/Resources/iOS/nl.lproj/Signup.strings b/Resources/Resources/iOS/nl.lproj/Signup.strings new file mode 100644 index 000000000..97c63fd93 --- /dev/null +++ b/Resources/Resources/iOS/nl.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "Aanmelding bevestigen"; +"in_progress.message" = "We zijn uw aankoop aan het verifiëren in ons systeem. Dit kan eventjes duren, daarom vragen we u vriendelijk om geduld."; +"in_progress.redeem.message" = "We zijn uw pincode aan het verifiëren in ons systeem. Dit kan eventjes duren, daarom vragen we u vriendelijk om geduld."; + +"success.title" = "Aankoop voltooid"; +"success.message_format" = "Bedankt voor uw aanmelding. We hebben de gebruikersnaam en het wachtwoord voor uw account naar het volgende e-mailadres gestuurd: %@"; +"success.redeem.title" = "Kaart ingewisseld"; +"success.redeem.message" = "U ontvangt zo een e-mail met uw gebruikersnaam en wachtwoord."; +"success.username.caption" = "Gebruikersnaam"; +"success.password.caption" = "Wachtwoord"; +"success.submit" = "Aan de slag"; + +"failure.vc_title" = "Aanmelden mislukt"; +"failure.title" = "Account aanmaken mislukt"; +"failure.message" = "We kunnen op dit moment geen account maken. Probeer het later opnieuw. \n\nAls u de app opnieuw opent, wordt opnieuw geprobeerd een account te maken."; +"failure.purchase.sandbox.message" = "Het geselecteerde sandbox-abonnement is niet beschikbaar in productie."; +"failure.redeem.invalid.title" = "Ongeldige pincode"; +"failure.redeem.invalid.message" = "U heeft een ongeldige pincode ingevoerd. Probeer het opnieuw."; +"failure.redeem.claimed.title" = "Kaart al geclaimd"; +"failure.redeem.claimed.message" = "Deze kaart is door een ander account geclaimd. Probeer een andere pincode."; +"failure.submit" = "GA TERUG"; + +"unreachable.vc_title" = "Fout"; +"unreachable.title" = "Oeps!"; +"unreachable.message" = "Geen internetverbinding gevonden. Controleer uw internetverbinding en probeer het hieronder opnieuw.\n\nU kunt later naar de app terugkeren om het proces te voltooien."; +"unreachable.submit" = "OPNIEUW PROBEREN"; + +"purchase.uncredited.alert.message" = "U heeft niet-gecrediteerde transacties. Wilt u uw accountgegevens herstellen?"; +"purchase.uncredited.alert.button.cancel" = "Annuleren"; +"purchase.uncredited.alert.button.recover" = "Account herstellen"; + +"purchase.trials.intro" = "Start je gratis proefabonnement van 7 dagen"; +"purchase.trials.price.after" = "Daarna %@"; +"purchase.trials.money.back" = "30 dagen lang niet-goed-geld-teruggarantie"; +"purchase.trials.1year.protection" = "1 jaar aan privacy- en identiteitsbescherming"; +"purchase.trials.anonymous" = "Browse anoniem en verberg uw ip."; +"purchase.trials.devices" = "Ondersteun tien apparaten tegelijk"; +"purchase.trials.devices.description" = "Bescherm uzelf op tien apparaten tegelijk."; +"purchase.trials.region" = "Maak eenvoudig verbinding met elke regio"; +"purchase.trials.servers" = "Meer dan 3300 servers in 32 landen"; +"purchase.trials.start" = "Abonnement starten"; +"purchase.trials.all.plans" = "Bekijk de beschikbare abonnementen"; + +"purchase.subscribe.now" = "Nu abonneren"; + +// WALKTHROUGH + +"walkthrough.action.next" = "VOLGENDE"; +"walkthrough.action.done" = "KLAAR"; +"walkthrough.action.skip" = "OVERSLAAN"; + +"walkthrough.page.1.title" = "Ondersteun tien apparaten tegelijk"; +"walkthrough.page.1.description" = "Bescherm uzelf op tien apparaten tegelijk."; +"walkthrough.page.2.title" = "Maak eenvoudig verbinding met elke regio"; +"walkthrough.page.2.description" = "Met servers over de hele wereld wordt u altijd beschermd."; +"walkthrough.page.3.title" = "Bescherm uzelf tegen advertenties"; +"walkthrough.page.3.description" = "Als u onze Content Blocker inschakelt, krijgt u geen advertenties meer te zien in Safari."; + +"share.data.buttons.accept" = "Accepteren"; +"share.data.buttons.noThanks" = "Nee, bedankt"; +"share.data.buttons.readMore" = "Meer informatie"; +"share.data.text.title" = "Help ons onze service te verbeteren"; +"share.data.text.description" = "Om ons te helpen de verbindingsprestaties van onze dienst te verbeteren, kunt u uw verbindingsstatistieken anoniem met ons delen. Deze rapporten bevatten geen persoonsgegevens."; +"share.data.text.footer" = "U kunt dit via de Instellingen beheren"; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/nl.lproj/Welcome.strings b/Resources/Resources/iOS/nl.lproj/Welcome.strings new file mode 100644 index 000000000..3c4007de5 --- /dev/null +++ b/Resources/Resources/iOS/nl.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "Aanmelden bij uw account"; +"login.username.placeholder" = "Gebruikersnaam (p1234567)"; +"login.password.placeholder" = "Wachtwoord"; +"login.submit" = "INLOGGEN"; +"login.restore.button" = "Geen accountgegevens ontvangen?"; +"login.error.title" = "Inloggen"; +"login.error.validation" = "U moet een gebruikersnaam en wachtwoord invoeren."; +"login.error.unauthorized" = "Uw gebruikersnaam of wachtwoord is onjuist."; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "Inloggen met aankoopbewijs"; +"login.magic.link.title" = "Log in met de magische link in uw e-mail"; +"login.magic.link.response" = "Controleer uw e-mail voor een inloglink."; +"login.magic.link.send" = "Link verzenden"; +"login.magic.link.invalid.email" = "Ongeldige e-mail. Probeer het opnieuw."; + +"purchase.title" = "Selecteer een VPN-abonnement"; +"purchase.subtitle" = "30 dagen lang niet-goed-geld-teruggarantie"; +"purchase.email.placeholder" = "E-mailadres"; +"purchase.continue" = "Doorgaan"; +"purchase.login.footer" = "Heeft u al een account?"; +"purchase.login.button" = "Aanmelden"; +"purchase.error.title" = "Kopen"; +"purchase.error.validation" = "U moet een e-mailadres invoeren"; +"purchase.error.connectivity.title" = "Verbindingsfout"; +"purchase.error.connectivity.description" = "We kunnen Private Internet Access niet bereiken. Dit kan komen door een slechte internetverbinding of omdat onze dienst in uw land is geblokkeerd."; +"purchase.confirm.form.email" = "Voer uw e-mailadres in"; +"purchase.confirm.plan" = "U koop het %@-abonnement"; +"purchase.email.why" = "We hebben uw e-mailadres nodig om uw gebruikersnaam en wachtwoord te versturen."; +"purchase.submit" = "Versturen"; +"purchase.or" = "of"; + +"upgrade.header" = "Welkom terug!"; +"upgrade.title" = "U moet uw abonnement vernieuwen om Private Internet Access te kunnen gebruiken."; +"upgrade.renew.now" = "Nu vernieuwen"; + + + +"redeem.title" = "Cadeaubon inwisselen"; +"redeem.subtitle" = "Voer uw e-mailadres en de %lu-cijferige pincode van uw cadeaubon of proefperiodekaart hieronder in."; +"redeem.email.placeholder" = "E-mailadres"; +"redeem.submit" = "VERSTUREN"; +"redeem.error.title" = "Inwisselen"; +"redeem.error.code" = "Code moet uit %lu getallen bestaan."; +"redeem.error.allfields" = "Voer uw e-mailadres en pincode in."; +"redeem.accessibility.back" = "Terug"; +"redeem.giftcard.placeholder" = "Pincode cadeaubon"; + +"plan.monthly.title" = "Maandelijks"; +"plan.yearly.title" = "Jaarlijks"; +"plan.yearly.detail_format" = "%@%@ per jaar"; +"plan.price_format" = "%@/ma"; +"plan.best_value" = "Beste waarde"; +"plan.accessibility.per_month" = "per maand"; + +"restore.title" = "Aankoop herstellen"; +"restore.subtitle" = "Als u een abonnement via deze app heeft aangeschaft en u heeft uw gegevens niet ontvangen, dan kunt u ze hier opnieuw verzenden. Er worden geen kosten in rekening gebracht tijdens dit proces."; +"restore.email.placeholder" = "E-mailadres"; +"restore.submit" = "BEVESTIGEN"; + +"iap.error.message.unavailable" = "De Apple-servers zijn momenteel niet beschikbaar. Probeer het later opnieuw."; +"iap.error.title" = "Fout"; + +"agreement.trials.title" = "Algemene voorwaarden gratis proefabonnementen"; +"agreement.trials.message" = "De betaling wordt bij de bevestiging van uw aankoop via uw Apple ID-account in rekening gebracht. Het abonnement wordt automatisch verlengd, tenzij het ten minste 24 uur voor het einde van de huidige periode wordt geannuleerd. De verlenging van uw account wordt binnen 24 uur voor het einde van de huidige periode in rekening gebracht. U kunt uw abonnementen beheren en annuleren door na de aankoop naar uw accountinstellingen in de App Store te gaan.\n\nBepaalde betaalde abonnementen kunnen een gratis proefabonnement aanbieden voordat er kosten in rekening worden gebracht. Als u zich wilt afmelden voor een betaald abonnement voordat we kosten in rekening brengen, moet u het abonnement ten minste 24 uur voor het einde van de gratis proefperiode annuleren.\n\nGratis proefabonnementen zijn alleen beschikbaar voor nieuwe gebruikers. Als u zich aanmeldt voor een aanvullend gratis proefabonnement worden onmiddellijk de standaard abonnementskosten in rekening gebracht.\n\nWij behouden ons het recht voor om uw gratis proefabonnement te allen tijde in te trekken.\n\nEen eventueel ongebruikt deel van uw gratis proefperiode vervalt bij aankoop van een abonnement.\n\nAls u zich aanmeldt, gaat u akkoord met deze algemene voorwaarden."; +"agreement.message" = "Na de gratis proefperiode van 7 dagen wordt het abonnement automatisch verlengd voor %@, tenzij u het ten minste 24 uur voor het einde van de proefperiode annuleert. De kosten voor de verlenging worden binnen 24 uur voor het einde van de proefperiode via uw Apple ID-account in rekening gebracht. U kunt uw abonnementen beheren en annuleren door na de aankoop naar uw accountinstellingen in de App Store te gaan. De proefperiode van 7 dagen is beperkt tot één proefperiode van 7 dagen per gebruiker. Een eventueel ongebruikt deel van uw gratis proefperiode vervalt bij aankoop van een abonnement. Alle prijzen zijn inclusief lokale omzetbelasting, indien van toepassing.\n\nAls u zich aanmeldt, gaat u akkoord met de $1 en het $2."; +"agreement.trials.yearly.plan" = "jaar"; +"agreement.trials.monthly.plan" = "maand"; + +"agreement.message.tos" = "Servicevoorwaarden"; +"agreement.message.privacy" = "Privacybeleid"; + +"getstarted.buttons.buyaccount" = "Account kopen"; + +"gdpr.collect.data.title" = "Persoonlijke informatie die we verzamelen"; +"gdpr.collect.data.description" = "E-mailadres voor accountbeheer en bescherming tegen misbruik."; +"gdpr.usage.data.title" = "Gebruik van de persoonlijke informatie die we verzamelen"; +"gdpr.usage.data.description" = "E-mailadres wordt alleen gebruikt voor het verzenden van abonnementsinformatie, betalingsbevestigingen, correspondentie met klanten en promotionele aanbiedingen van Private Internet Access."; +"gdpr.accept.button.title" = "Akkoord en doorgaan"; + +"update.account.email.error" = "Kan accountmail niet aanpassen"; diff --git a/Resources/Resources/iOS/pl.lproj/Signup.strings b/Resources/Resources/iOS/pl.lproj/Signup.strings new file mode 100644 index 000000000..9922f65c3 --- /dev/null +++ b/Resources/Resources/iOS/pl.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "Potwierdź rejestrację"; +"in_progress.message" = "Potwierdzamy zakup w naszym systemie. Poczekaj spokojnie, bo to może trochę potrwać."; +"in_progress.redeem.message" = "Potwierdzamy Twój PIN karty w naszym systemie, TO może chwilę potrwać, więc prosimy o cierpliwość."; + +"success.title" = "Zakup zakończony"; +"success.message_format" = "Dziękujemy za rejestrację. Przesłaliśmy Twoją nazwę użytkownika i hasło na Twój adres e-mail: %@"; +"success.redeem.title" = "Karta została wymieniona"; +"success.redeem.message" = "Wkrótce otrzymasz e-mail z nazwą użytkownika i hasłem.\n\nTwoje dane do logowania"; +"success.username.caption" = "Nazwa użytkownika"; +"success.password.caption" = "Hasło"; +"success.submit" = "Rozpocznij"; + +"failure.vc_title" = "Rejestracja nie powiodła się"; +"failure.title" = "Błąd tworzenia konta"; +"failure.message" = "Obecnie nie możemy utworzyć konta. Spróbuj ponownie później."; +"failure.purchase.sandbox.message" = "Wybrana subskrypcja na piaskownicę (środowisko testowe) nie jest dostępna w produkcji."; +"failure.redeem.invalid.title" = "Nieprawidłowy PIN karty"; +"failure.redeem.invalid.message" = "Wygląda na to, ze wpisałeś nieprawidłowy PIN karty. Spróbuj ponownie."; +"failure.redeem.claimed.title" = "Karta już użyta"; +"failure.redeem.claimed.message" = "Wygląda na to, że ta karta została użyta na innym koncie. Możesz spróbować wpisać inny PIN."; +"failure.submit" = "WSTECZ"; + +"unreachable.vc_title" = "Błąd"; +"unreachable.title" = "Ups!"; +"unreachable.message" = "Nie znaleziono połączenia z internetem. Potwierdź, że masz połączenie z internetem i naciśnij „Spróbuj ponownie” poniżej..\n\nMożesz wrócić do aplikacji później, aby dokończyć proces."; +"unreachable.submit" = "SPRÓBUJ PONOWNIE"; + +"purchase.uncredited.alert.message" = "Masz nieuznane transakcje. Chcesz odzyskać dane swojego konta?"; +"purchase.uncredited.alert.button.cancel" = "Anuluj"; +"purchase.uncredited.alert.button.recover" = "Odzyskaj konto"; + +"purchase.trials.intro" = "Zacznij 7-dniowy bezpłatny okres próbny"; +"purchase.trials.price.after" = "Potem %@"; +"purchase.trials.money.back" = "30-dniowa gwarancja zwrotu pieniędzy"; +"purchase.trials.1year.protection" = "1 rok ochrony prywatności i tożsamości"; +"purchase.trials.anonymous" = "Przeglądaj sieć anonimowa i ukryj swoje IP"; +"purchase.trials.devices" = "Obsługa 10 urządzeń jednocześnie"; +"purchase.trials.devices.description" = "Ochrona na maksymalnie 10 urządzeniach jednocześnie."; +"purchase.trials.region" = "Łatwo połączysz się z dowolnym regionem"; +"purchase.trials.servers" = "Ponad 3300 serwerów w 32 krajach"; +"purchase.trials.start" = "Rozpocznij subskrypcję"; +"purchase.trials.all.plans" = "Sprawdź wszystkie dostępne plany"; + +"purchase.subscribe.now" = "Subskrybuj teraz"; + +// WALKTHROUGH + +"walkthrough.action.next" = "DALEJ"; +"walkthrough.action.done" = "GOTOWE"; +"walkthrough.action.skip" = "POMIŃ"; + +"walkthrough.page.1.title" = "Obsługa 10 urządzeń jednocześnie"; +"walkthrough.page.1.description" = "Ochrona na maksymalnie 10 urządzeniach jednocześnie."; +"walkthrough.page.2.title" = "Łatwo połączysz się z dowolnym regionem"; +"walkthrough.page.2.description" = "Dzięki serwerom na całym świecie zawsze jesteś pod ochroną."; +"walkthrough.page.3.title" = "Unikaj reklam"; +"walkthrough.page.3.description" = "Włączenie naszej Blokady zawartości chroni Cię przed wyświetlaniem reklam w Safari."; + +"share.data.buttons.accept" = "Akceptuj"; +"share.data.buttons.noThanks" = "Nie, dziękuję"; +"share.data.buttons.readMore" = "Dowiedz się więcej"; +"share.data.text.title" = "Prosimy o pomoc w ulepszeniu naszych usług"; +"share.data.text.description" = "Aby pomóc nam zapewnić najlepszą wydajność naszych usług, możesz anonimowo udostępniać nam swoje statystyki połączeń. Raporty te nie zawierają żadnych informacji umożliwiających identyfikację osób."; +"share.data.text.footer" = "Zawsze możesz to kontrolować w swoich ustawieniach"; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/pl.lproj/Welcome.strings b/Resources/Resources/iOS/pl.lproj/Welcome.strings new file mode 100644 index 000000000..1af0054e2 --- /dev/null +++ b/Resources/Resources/iOS/pl.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "Zaloguj się na konto"; +"login.username.placeholder" = "Nazwa użytkownika (p1234567)"; +"login.password.placeholder" = "Hasło"; +"login.submit" = "ZALOGUJ"; +"login.restore.button" = "Nie dostałeś(-aś) Danych swojego konta?"; +"login.error.title" = "Zaloguj"; +"login.error.validation" = "Musisz podać nazwę użytkownika i hasło."; +"login.error.unauthorized" = "Twoja nazwa użytkownika i hasło są nieprawidłowe."; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "Zaloguj się, używając pokwitowania zakupu"; +"login.magic.link.title" = "Zaloguj się, używając sekretnego linku z e-maila"; +"login.magic.link.response" = "Link do logowania wysłaliśmy e-mailem,"; +"login.magic.link.send" = "Wyślij link"; +"login.magic.link.invalid.email" = "Nieprawidłowy e-mail. Spróbuj ponownie"; + +"purchase.title" = "Wybierz plan VPN"; +"purchase.subtitle" = "30-dniowej gwarancji zwrotu pieniędzy"; +"purchase.email.placeholder" = "Adres e-mail"; +"purchase.continue" = "Kontynuuj"; +"purchase.login.footer" = "Masz już konto?"; +"purchase.login.button" = "Zaloguj"; +"purchase.error.title" = "Zakup"; +"purchase.error.validation" = "Musisz podać adres e-mail."; +"purchase.error.connectivity.title" = "Błąd połączenia"; +"purchase.error.connectivity.description" = "Nie można połączyć z Private Internet Access. Może to być spowodowane słabym połączeniem z internetem lub nasza usługa może być zablokowana w Twoim kraju."; +"purchase.confirm.form.email" = "Podaj swój adres e-mail"; +"purchase.confirm.plan" = "Kupujesz abonament %@"; +"purchase.email.why" = "Twój e-mail jest nam potrzebny do przesłania Ci nazwy użytkownika i hasła,"; +"purchase.submit" = "Wyślij"; +"purchase.or" = "lub"; + +"upgrade.header" = "Witaj ponownie!"; +"upgrade.title" = "Aby korzystać z Private Internet Access, musisz odnowić swoją subskrypcję."; +"upgrade.renew.now" = "Odnów teraz"; + + + +"redeem.title" = "Wykorzystaj kartę podarunkową"; +"redeem.subtitle" = "Wpisz swój adres e-mail oraz %lu-cyfrowy PIN z karty podarunkowej lub karty próbnej poniżej."; +"redeem.email.placeholder" = "Adres e-mail"; +"redeem.submit" = "PRZEŚLIJ"; +"redeem.error.title" = "Wykorzystaj"; +"redeem.error.code" = "Kod musi składać się z %lu znaków numerycznych."; +"redeem.error.allfields" = "Podaj swój adres r-mail i PIN karty"; +"redeem.accessibility.back" = "Wstecz"; +"redeem.giftcard.placeholder" = "PIN karty upominkowej"; + +"plan.monthly.title" = "Miesięcznie"; +"plan.yearly.title" = "Rocznie"; +"plan.yearly.detail_format" = "%@%@ rocznie"; +"plan.price_format" = "%@/msc."; +"plan.best_value" = "Najlepsza wartość"; +"plan.accessibility.per_month" = "miesięcznie"; + +"restore.title" = "Przywróć pominięty zakup"; +"restore.subtitle" = "Jeśli kupił(a)ś abonament, korzystając z tej aplikacji, ale nie dostałe(a)ś danych logowania, możesz wysłać je ponownie stąd.\nOpłata nie zostanie za to pobrana za tę czynność"; +"restore.email.placeholder" = "Adres e-mail"; +"restore.submit" = "POTWIERDŹ"; + +"iap.error.message.unavailable" = "Serwery Apple'a są w tej chwili niedostępne. Spróbuj ponownie później."; +"iap.error.title" = "Błąd"; + +"agreement.trials.title" = "Regulamin korzystania z bezpłatnej wersji próbnej"; +"agreement.trials.message" = "Płatność zostanie pobrana z Twojego konta Apple ID z chwilą potwierdzenia zakupu. Subskrypcja odnawia się automatycznie, chyba że zostanie anulowana co najmniej 24 godziny przed końcem bieżącego okresu rozliczeniowego. Opłata za odnowienie subskrypcji zostanie pobrana w ciągu 24 godzin przed końcem bieżącego okresu. Możesz zarządzać subskrypcjami i je anulować, przechodząc do ustawień konta w App Store po dokonaniu zakupu.\n\nCzęść płatnych Subskrypcji może obejmować bezpłatną wersję próbną, z której możesz skorzystać przed naliczeniem opłaty. Jeśli postanowisz zrezygnować z płatnej subskrypcji przed rozpoczęciem naliczania opłat zgodnie z wybraną metodą płatności, anuluj subskrypcję co najmniej 24 godziny przed zakończeniem bezpłatnej wersji próbnej.\n\nBezpłatne wersje próbne są dostępne tylko dla nowych użytkowników i są przyznawane wyłącznie według naszego uznania, a w przypadku próby zarejestrowania w celu uzyskania dodatkowej bezpłatnej wersji próbnej, zostaniesz natychmiast obciążony standardową opłatą subskrypcyjną.\n\nRejestracja oznacza akceptację niniejszego regulaminu."; +"agreement.message" = "Po 7 dniach bezpłatnego okresu próbnego ta subskrypcja odnowi się automatycznie na %@, jeżeli nie zostanie anulowana co najmniej 24 godziny przed końcem okresu próbnego. Twoje konto Apple ID zostanie obciążone opłatą za odnowienie w ciągu 24 godzin przed końcem okresu próbnego. Możesz zarządzać swoimi subskrypcjami i je anulować, wchodząc po zakupie w ustawienia swojego konta w App Store Oferta 7-dniowego okresu próbnego jest ograniczona – każdemu użytkownikowi przysługuje jeden 7-dniowy okres próbny. Każda niewykorzystana część bezpłatnego okresu próbnego, jeśli zostanie zaoferowany, przepada wraz zakupem subskrypcji przez użytkownika. Wszystkie ceny zawierają obowiązujące lokalne podatki obrotowe.\n\nRejestracja oznacza akceptację artykułów. $1 i $2."; +"agreement.trials.yearly.plan" = "rok"; +"agreement.trials.monthly.plan" = "miesiąc"; + +"agreement.message.tos" = "Warunki użytkowania"; +"agreement.message.privacy" = "Zasady ochrony prywatności"; + +"getstarted.buttons.buyaccount" = "Zakup konto"; + +"gdpr.collect.data.title" = "Zbierane dane osobowe"; +"gdpr.collect.data.description" = "Adres e-mail do zarządzania kontem i ochrony przed nadużyciami."; +"gdpr.usage.data.title" = "Sposoby wykorzystania zbieranych przez nas danych osobowych"; +"gdpr.usage.data.description" = "Adres e-mail; używany go do wysyłania informacji o subskrypcji, potwierdzeń płatności, korespondencji z klientami i wysyłania ofert promocyjnych wyłącznie Private Internet Access"; +"gdpr.accept.button.title" = "Zaakceptuj i kontynuuj"; + +"update.account.email.error" = "Nie udało się zmienić adresu e-mail konta"; diff --git a/Resources/Resources/iOS/pt-BR.lproj/Signup.strings b/Resources/Resources/iOS/pt-BR.lproj/Signup.strings new file mode 100644 index 000000000..5bcf7cb9a --- /dev/null +++ b/Resources/Resources/iOS/pt-BR.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "Confirme o registro"; +"in_progress.message" = "Confirmamos sua compra com o nosso sistema. Isso pode demorar um pouco. Por favor, aguarde."; +"in_progress.redeem.message" = "Estamos confirmando o PIN do seu cartão com o nosso sistema. Isso pode demorar um pouco. Por favor, aguarde."; + +"success.title" = "Compra Concluída"; +"success.message_format" = "Obrigado por se registrar conosco. Nós enviamos o nome de usuário e a senha da sua conta para o seu endereço de e-mail em %@"; +"success.redeem.title" = "Cartão resgatado com sucesso"; +"success.redeem.message" = "Você receberá um e-mail com seu nome de usuário e senha em breve.\n\nSeus dados de login"; +"success.username.caption" = "Nome de usuário"; +"success.password.caption" = "Senha"; +"success.submit" = "Comece Agora"; + +"failure.vc_title" = "Falha ao registrar"; +"failure.title" = "Falha ao Criar Conta"; +"failure.message" = "Não podemos criar uma conta neste momento. Tente novamente mais tarde.\n\nApós a reabertua do aplicativo, uma nova tentativa de criação de conta será realizada."; +"failure.purchase.sandbox.message" = "A assinatura da área restrita selecionada não está disponível em produção."; +"failure.redeem.invalid.title" = "PIN de cartão inválido"; +"failure.redeem.invalid.message" = "Parece que você inseriu um PIN de cartão inválido. Por favor, tente novamente."; +"failure.redeem.claimed.title" = "O cartão já foi resgatado"; +"failure.redeem.claimed.message" = "Parece que este cartão já foi resgatado por outra conta. Você pode tentar usar um PIN diferente."; +"failure.submit" = "VOLTAR"; + +"unreachable.vc_title" = "Erro"; +"unreachable.title" = "Ops!"; +"unreachable.message" = "Nenhuma conexão com a Internet encontrada. Confirme que você possui uma conexão com a Internet e pressione Tentar Novamente abaixo.\n\nVocê pode retornar ao aplicativo mais tarde para finalizar o processo."; +"unreachable.submit" = "TENTAR NOVAMENTE"; + +"purchase.uncredited.alert.message" = "Você tem transações não creditadas. Deseja recuperar os detalhes da sua conta?"; +"purchase.uncredited.alert.button.cancel" = "Cancelar"; +"purchase.uncredited.alert.button.recover" = "Recuperar conta"; + +"purchase.trials.intro" = "Comece sua avaliação gratuita de 7 dias"; +"purchase.trials.price.after" = "Em seguida, %@"; +"purchase.trials.money.back" = "Garantia do dinheiro de volta em até 30 dias"; +"purchase.trials.1year.protection" = "1 ano de proteção de privacidade e identidade"; +"purchase.trials.anonymous" = "Navegue anonimamente e oculte seu IP."; +"purchase.trials.devices" = "Suporte para 10 dispositivos ao mesmo tempo"; +"purchase.trials.devices.description" = "Proteja-se em até 10 dispositivos ao mesmo tempo."; +"purchase.trials.region" = "Conecte-se a qualquer região facilmente"; +"purchase.trials.servers" = "Mais de 3.300 servidores em 32 países"; +"purchase.trials.start" = "Iniciar assinatura"; +"purchase.trials.all.plans" = "Veja todos os planos disponíveis"; + +"purchase.subscribe.now" = "Assine agora"; + +// WALKTHROUGH + +"walkthrough.action.next" = "AVANÇAR"; +"walkthrough.action.done" = "CONCLUÍDO"; +"walkthrough.action.skip" = "PULAR"; + +"walkthrough.page.1.title" = "Suporte para 10 dispositivos ao mesmo tempo"; +"walkthrough.page.1.description" = "Proteja-se em até 10 dispositivos ao mesmo tempo."; +"walkthrough.page.2.title" = "Conecte-se a qualquer região facilmente"; +"walkthrough.page.2.description" = "Com servidores ao redor do mundo, você está sempre protegido."; +"walkthrough.page.3.title" = "Proteja-se contra propagandas"; +"walkthrough.page.3.description" = "A ativação do nosso Bloqueador de Conteúdo impede que anúncios sejam exibidos no Safari."; + +"share.data.buttons.accept" = "Aceitar"; +"share.data.buttons.noThanks" = "Não, obrigado"; +"share.data.buttons.readMore" = "Leia mais"; +"share.data.text.title" = "Ajude-nos a melhorar nosso serviço"; +"share.data.text.description" = "Para nos ajudar a garantir o desempenho da conexão de nosso serviço, você pode compartilhar anonimamente as estatísticas da sua conexão conosco. Esses relatórios não incluem nenhuma informação de identificação pessoal."; +"share.data.text.footer" = "Você sempre poderá controlar isso em suas configurações"; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/pt-BR.lproj/Welcome.strings b/Resources/Resources/iOS/pt-BR.lproj/Welcome.strings new file mode 100644 index 000000000..7ed6d1e85 --- /dev/null +++ b/Resources/Resources/iOS/pt-BR.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "Entre na sua conta"; +"login.username.placeholder" = "Nome de usuário (p1234567)"; +"login.password.placeholder" = "Senha"; +"login.submit" = "ENTRAR"; +"login.restore.button" = "Não recebeu detalhes da conta?"; +"login.error.title" = "Entrar"; +"login.error.validation" = "Você deve inserir um nome de usuário e senha."; +"login.error.unauthorized" = "Seu nome de usuário ou senha está incorreto."; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "Faça login usando o recibo de compra"; +"login.magic.link.title" = "Faça login usando o link mágico enviado por e-mail"; +"login.magic.link.response" = "Veja se você recebeu por e-mail um link para fazer login."; +"login.magic.link.send" = "Enviar link"; +"login.magic.link.invalid.email" = "E-mail inválido. Tente novamente."; + +"purchase.title" = "Selecione um plano de VPN"; +"purchase.subtitle" = "Garantia do dinheiro de volta em até 30 dias"; +"purchase.email.placeholder" = "Endereço de e-mail"; +"purchase.continue" = "Continuar"; +"purchase.login.footer" = "Já tem uma conta?"; +"purchase.login.button" = "Faça login"; +"purchase.error.title" = "Comprar"; +"purchase.error.validation" = "Você precisa inserir um endereço de e-mail."; +"purchase.error.connectivity.title" = "Falha na conexão"; +"purchase.error.connectivity.description" = "Não conseguimos acessar o Private Internet Access. Isso pode ser devido a uma conexão de internet fraca ou o nosso serviço está bloqueado em seu país."; +"purchase.confirm.form.email" = "Insira seu endereço de e-mail"; +"purchase.confirm.plan" = "Você está adquirindo o plano %@"; +"purchase.email.why" = "Precisamos do seu e-mail para enviarmos seu nome de usuário e senha."; +"purchase.submit" = "Enviar"; +"purchase.or" = "ou"; + +"upgrade.header" = "Seja bem-vindo(a)!"; +"upgrade.title" = "Para usar a Private Internet Access, você precisará renovar sua assinatura."; +"upgrade.renew.now" = "Renovar agora"; + + + +"redeem.title" = "Resgatar cartão-presente"; +"redeem.subtitle" = "Digite o seu endereço de e-mail e o PIN de %lu dígitos do seu cartão-presente ou cartão de teste abaixo."; +"redeem.email.placeholder" = "Endereço de e-mail"; +"redeem.submit" = "ENVIAR"; +"redeem.error.title" = "Resgatar"; +"redeem.error.code" = "O código precisa ter %lu dígitos numéricos."; +"redeem.error.allfields" = "Digite o seu e-mail e PIN do cartão."; +"redeem.accessibility.back" = "Voltar"; +"redeem.giftcard.placeholder" = "PIN do cartão-presente"; + +"plan.monthly.title" = "Mensal"; +"plan.yearly.title" = "Anual"; +"plan.yearly.detail_format" = "%@%@ por ano"; +"plan.price_format" = "%@/mês"; +"plan.best_value" = "Melhor valor"; +"plan.accessibility.per_month" = "por mês"; + +"restore.title" = "Restaurar compra não creditada"; +"restore.subtitle" = "Se você adquiriu um plano por este aplicativo e não recebeu as suas credenciais, você pode enviá-las novamente por aqui. Você não será cobrado durante esse processo."; +"restore.email.placeholder" = "Endereço de e-mail"; +"restore.submit" = "CONFIRMAR"; + +"iap.error.message.unavailable" = "Servidores da Apple indisponíveis no momento. Tente novamente mais tarde."; +"iap.error.title" = "Erro"; + +"agreement.trials.title" = "Termos e condições das avaliações gratuitas"; +"agreement.trials.message" = "O pagamento será cobrado na sua conta do ID Apple no ato da confirmação da compra. A assinatura será renovada automaticamente, a não ser que ela seja cancelada, pelo menos, 24 horas antes do fim do período atual. O valor da renovação será debitado na sua conta em até 24 horas antes do término do período da atual. Você pode gerenciar e cancelar suas assinaturas nos ajustes da conta na App Store após a compra.\n\nCertas assinaturas pagas podem oferecer uma avaliação gratuita antes da cobrança do pagamento. Se você decidir cancelar uma Assinatura Paga antes de começarmos a cobrar por ela, cancele-a, pelo menos, 24 horas antes do término da avaliação gratuita.\n\nAs avaliações gratuitas estão disponíveis apenas para novos usuários, e são a nosso critério. Se tentar se registrar para uma avaliação gratuita adicional, você será cobrado imediatamente com a Taxa de Assinatura padrão.\n\nReservamo-nos o direito de revogar sua avaliação gratuita a qualquer momento.\n\nQualquer parte não utilizada do período da sua avaliação gratuita será perdida após a compra de uma assinatura.\n\nO registro constitui da aceitação dos termos e condições."; +"agreement.message" = "Após os 7 dias da avaliação gratuita, a assinatura será renovada automaticamente por %@, a não ser que ela seja cancelada, pelo menos, 24 horas antes do término do período de avaliação. O valor da renovação será debitado da conta do seu ID Apple em até 24 horas antes do término do período de avaliação. Você pode gerenciar e cancelar suas assinaturas nos ajustes da conta da App Store após a compra. A oferta de avaliação de 7 dias é limitada a uma oferta de avaliação de 7 dias por usuário. Qualquer parte não utilizada do período de avaliação gratuita, se oferecida, será perdida após a aquisição de uma assinatura. Todos os preços incluem impostos de vendas locais aplicáveis.\n\nO registro constitui da aceitação dos $1 e da $2."; +"agreement.trials.yearly.plan" = "ano"; +"agreement.trials.monthly.plan" = "mês"; + +"agreement.message.tos" = "Termos de Serviço"; +"agreement.message.privacy" = "Política de Privacidade"; + +"getstarted.buttons.buyaccount" = "Comprar conta"; + +"gdpr.collect.data.title" = "Informações pessoais que coletamos"; +"gdpr.collect.data.description" = "Endereço de e-mail para gerenciamento de conta e proteção contra abuso."; +"gdpr.usage.data.title" = "Uso de informações pessoais coletadas por nós"; +"gdpr.usage.data.description" = "O endereço de e-mail é utilizado apenas para enviar informação de assinatura, confirmações de pagamento, correspondência com clientes e ofertas promocionais do Private Internet Access."; +"gdpr.accept.button.title" = "Concordar e continuar"; + +"update.account.email.error" = "Falha ao modificar o e-mail da conta"; diff --git a/Resources/Resources/iOS/ru.lproj/Signup.strings b/Resources/Resources/iOS/ru.lproj/Signup.strings new file mode 100644 index 000000000..9f032f794 --- /dev/null +++ b/Resources/Resources/iOS/ru.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "Подтверждение регистрации"; +"in_progress.message" = "Мы проверяем вашу покупку в системе. На это может понадобиться время, так что оставайтесь с нами."; +"in_progress.redeem.message" = "Мы подтверждаем ваш PIN-код в системе. Это займет какое-то время, подождите немного."; + +"success.title" = "Покупка завершена"; +"success.message_format" = "Спасибо за регистрацию. Мы отправили имя пользователя и пароль на адрес электронной почты %@"; +"success.redeem.title" = "Карта успешно использована"; +"success.redeem.message" = "Также вы получите эл. письмо с именем пользователя и паролем.\n\nВаши учетные данные"; +"success.username.caption" = "Имя пользователя"; +"success.password.caption" = "Пароль"; +"success.submit" = "Начать"; + +"failure.vc_title" = "Ошибка регистрации"; +"failure.title" = "Сбой создания учетной записи"; +"failure.message" = "Нам не удалось создать учетную запись. Повторите попытку позже.\nПри повторном открытии приложения создание учетной записи будет возобновлено."; +"failure.purchase.sandbox.message" = "Выбранная подписка на «песочницу» недоступна в производственной среде."; +"failure.redeem.invalid.title" = "Неверный PIN-код карты"; +"failure.redeem.invalid.message" = "Похоже, вы ввели неверный PIN-код карты. Попробуйте еще раз."; +"failure.redeem.claimed.title" = "Карта уже использована"; +"failure.redeem.claimed.message" = "Похоже, эту карту уже использовали с другой учетной записи. Попробуйте ввести другой PIN."; +"failure.submit" = "ВОЗВРАТ"; + +"unreachable.vc_title" = "Ошибка"; +"unreachable.title" = "Ой!"; +"unreachable.message" = "Интернет-соединение не найдено. Пожалуйста, убедитесь, что у вас есть подключение к Интернету, и повторите попытку позже.\n\nМожно вернуться в приложение позже и завершить процесс."; +"unreachable.submit" = "ПОВТОРИТЬ"; + +"purchase.uncredited.alert.message" = "У вас есть непроведенные транзакции. Хотите восстановить данные своей учетной записи?"; +"purchase.uncredited.alert.button.cancel" = "Отмена"; +"purchase.uncredited.alert.button.recover" = "Восстановить учетную запись"; + +"purchase.trials.intro" = "Начните 7-дневную бесплатную пробу"; +"purchase.trials.price.after" = "После этого %@"; +"purchase.trials.money.back" = "30-дневная гарантия возврата денег"; +"purchase.trials.1year.protection" = "1 год защиты конфиденциальности и личных данных"; +"purchase.trials.anonymous" = "Пользуйтесь Интернетом анонимно и скрывайте свой IP-адрес."; +"purchase.trials.devices" = "Поддержка сразу 10 устройств"; +"purchase.trials.devices.description" = "Защищайте себя на нескольких устройствах одновременно (до 10)."; +"purchase.trials.region" = "Простое подключение к любому региону"; +"purchase.trials.servers" = "Более 3300 серверов в 32 странах"; +"purchase.trials.start" = "Запустить подписку."; +"purchase.trials.all.plans" = "Смотреть все доступные планы"; + +"purchase.subscribe.now" = "Подписаться"; + +// WALKTHROUGH + +"walkthrough.action.next" = "ДАЛЕЕ"; +"walkthrough.action.done" = "ГОТОВО"; +"walkthrough.action.skip" = "ПРОПУСК"; + +"walkthrough.page.1.title" = "Поддержка 10 устройств одновременно"; +"walkthrough.page.1.description" = "Защищайте себя на нескольких устройствах одновременно (до 10)."; +"walkthrough.page.2.title" = "Простое подключение к любому региону"; +"walkthrough.page.2.description" = "У нас есть серверы по всему миру, так что вы всегда будете под защитой."; +"walkthrough.page.3.title" = "Защита от рекламы"; +"walkthrough.page.3.description" = "Активация нашего правила блокирования контента препятствует отображению рекламы в Safari."; + +"share.data.buttons.accept" = "Принять"; +"share.data.buttons.noThanks" = "Спасибо, не надо"; +"share.data.buttons.readMore" = "Подробнее"; +"share.data.text.title" = "Помогите нам сделать нашу службу еще лучше"; +"share.data.text.description" = "Вы можете анонимно делиться с нами своей статистикой соединения, чтобы помочь нам обеспечить производительность подключения нашей службы. Эти отчеты не включают какую-либо личную информацию."; +"share.data.text.footer" = "Вы всегда можете управлять этим параметром через настройки"; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/ru.lproj/Welcome.strings b/Resources/Resources/iOS/ru.lproj/Welcome.strings new file mode 100644 index 000000000..cd0684978 --- /dev/null +++ b/Resources/Resources/iOS/ru.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "Войдите в свою учетную запсь"; +"login.username.placeholder" = "Имя пользователя (p1234567)"; +"login.password.placeholder" = "Пароль"; +"login.submit" = "ВХОД"; +"login.restore.button" = "Не получили данные учетной записи?"; +"login.error.title" = "Войти"; +"login.error.validation" = "Нужно ввести имя пользователя и пароль."; +"login.error.unauthorized" = "Неправильное имя пользователя или пароль."; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "Выполните вход по квитанции о покупке"; +"login.magic.link.title" = "Войти через волшебную ссылку из письма"; +"login.magic.link.response" = "Поищите ссылку для входа в письме."; +"login.magic.link.send" = "Отправить ссылку"; +"login.magic.link.invalid.email" = "Недействительный эл. адрес. Повторите попытку."; + +"purchase.title" = "Выберите план VPN"; +"purchase.subtitle" = "30-дневная гарантия возврата денег"; +"purchase.email.placeholder" = "Адрес эл. почты"; +"purchase.continue" = "Продолжить"; +"purchase.login.footer" = "Уже есть учетная запись?"; +"purchase.login.button" = "Вход"; +"purchase.error.title" = "Купить"; +"purchase.error.validation" = "Укажите адрес эл. почты."; +"purchase.error.connectivity.title" = "Ошибка подключения"; +"purchase.error.connectivity.description" = "Не удалось подключиться к Private Internet Access. Это может быть из-за плохого качества соединения с Интернетом, или наша служба заблокирована в вашей стране."; +"purchase.confirm.form.email" = "Введите свой адрес эл. почты"; +"purchase.confirm.plan" = "Вы приобретаете план «%@»"; +"purchase.email.why" = "Нам нужен ваш электронный адрес, чтобы мы могли прислать вам имя пользователя и пароль."; +"purchase.submit" = "Отправить"; +"purchase.or" = "или"; + +"upgrade.header" = "С возвращением!"; +"upgrade.title" = "Чтобы пользоваться Private Internet Access, вам необходимо продлить подписку."; +"upgrade.renew.now" = "Продлить"; + + + +"redeem.title" = "Исп-ть подарочную карту"; +"redeem.subtitle" = "Введите свой адрес эл. почты и %lu-зн. PIN-код с подарочной или триальной карты."; +"redeem.email.placeholder" = "Адрес эл. почты"; +"redeem.submit" = "ОТПРАВИТЬ"; +"redeem.error.title" = "Использовать"; +"redeem.error.code" = "В коде должно быть %lu цифр(ы)."; +"redeem.error.allfields" = "Введите свой эл. адрес и PIN-код карты"; +"redeem.accessibility.back" = "Назад"; +"redeem.giftcard.placeholder" = "PIN-код подарочной карты"; + +"plan.monthly.title" = "Ежемесячно"; +"plan.yearly.title" = "Ежегодно"; +"plan.yearly.detail_format" = "%@%@ в год"; +"plan.price_format" = "%@/мес."; +"plan.best_value" = "Максимальная выгода"; +"plan.accessibility.per_month" = "в месяц"; + +"restore.title" = "Восстановить непроведенную покупку"; +"restore.subtitle" = "Если вы купили план через это приложение и не получили учетных данных, вы можете повторно отправить их отсюда. За эту процедуру не взимается плата."; +"restore.email.placeholder" = "Адрес электронной почты"; +"restore.submit" = "ПОДТВЕРДИТЬ"; + +"iap.error.message.unavailable" = "Серверы Apple временно недоступны. Повторите попытку позже."; +"iap.error.title" = "Ошибка"; + +"agreement.trials.title" = "Положения и условия бесплатного пробного пользования"; +"agreement.trials.message" = "Платеж списывается со счета вашего Apple ID при подтверждении покупки. Подписка продлевается автоматически, если не отменить ее по меньшей мере за 24 часа до окончания текущего периода. Плата за продление подписки будет списана со счета вашей учетной записи в течение 24 часов до окончания текущего периода. Для управления подписками, в том числе их отмены, после покупки перейдите в настройки учетной записи в App Store.\n\nНекоторые платные подписки могут предлагать бесплатное пробное пользование перед тем, как списывать средства с использованием указанного вами способа оплаты. Если вы решите отменить платную подписку до того, как мы начнем списывать средства с использованием указанного вами способа оплаты, сделайте это по меньшей мере за 24 часа до окончания бесплатного пробного периода.\n\nБесплатное пробное пользование доступно только для новых пользователей и предлагается по нашему единоличному усмотрению, и если вы попытаетесь зарегистрироваться для участия в дополнительном бесплатном пробном пользовании, с вас будет незамедлительно списана стандартная стоимость подписки.\n\nМы оставляем за собой право в любое время прервать ваше бесплатное пробное пользование.\n\nНеиспользованная часть бесплатного пробного периода сгорает после приобретения подписки.\n\nЗарегистрировавшись, вы тем самым принимаете настоящие положения и условия."; +"agreement.message" = "После завершения 7-дневного бесплатного пробного периода подписка автоматически продлевается на %@, если не отменить ее по меньшей мере за 24 часа до окончания текущего периода. Плата за продление подписки будет списана со счета вашей учетной записи в течение 24 часов до окончания текущего периода. Для управления подписками, в том числе их отмены, после покупки перейдите в настройки учетной записи в App Store. 7-дневное пробное пользование предлагается каждому пользователю только 1 раз. Неиспользованная часть бесплатного пробного периода сгорает после приобретения подписки. Цена указывается с учетом местных налогов на продажу.\n\nРегистрируясь, вы тем самым принимаете списание с вашего счета сумм в $1 и $2 доллара США."; +"agreement.trials.yearly.plan" = "год"; +"agreement.trials.monthly.plan" = "месяц"; + +"agreement.message.tos" = "Условия использования"; +"agreement.message.privacy" = "Политика конфиденциальности"; + +"getstarted.buttons.buyaccount" = "Приобрести учетную запись"; + +"gdpr.collect.data.title" = "Личная информация, которую мы собираем"; +"gdpr.collect.data.description" = "Адрес эл. почты используется в целях управления учетной записью и защиты от злоупотреблений."; +"gdpr.usage.data.title" = "Использование собираемой нами личной информации"; +"gdpr.usage.data.description" = "Адрес эл. почты используется исключительно для отправки информации о подписке, подтверждений оплаты, переписки с пользователем и отправки акционных предложений Private Internet Access."; +"gdpr.accept.button.title" = "Принять и продолжить"; + +"update.account.email.error" = "Не удалось изменить эл. адрес учетной записи"; diff --git a/Resources/Resources/iOS/th.lproj/Signup.strings b/Resources/Resources/iOS/th.lproj/Signup.strings new file mode 100644 index 000000000..520a4ea39 --- /dev/null +++ b/Resources/Resources/iOS/th.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "ยืนยันการสมัคร"; +"in_progress.message" = "เรากำลังยืนยันการซื้อของคุณกับระบบของเรา กรุณารอสักครู่"; +"in_progress.redeem.message" = "เรากำลังยืนยัน PIN การ์ดของคุณกับระบบของเรา กรุณารอสักครู่"; + +"success.title" = "ซื้อสำเร็จ"; +"success.message_format" = "ขอบคุณที่สมัครสมาชิกกับเรา เราได้ส่งชื่อผู้ใช้และรหัสผ่านไปยังอีเมลของคุณแล้วที่ %@"; +"success.redeem.title" = "คืนบัตรเสร็จสมบูรณ์!"; +"success.redeem.message" = "คุณจะได้รับอีเมลพร้อมชื่อผู้ใช้และรหัสผ่านในไม่ช้า\n\nข้อมูลการล็อกอิน"; +"success.username.caption" = "ชื่อผู้ใช้"; +"success.password.caption" = "รหัสผ่าน"; +"success.submit" = "เริ่มใช้งาน"; + +"failure.vc_title" = "การสมัครล้มเหลว"; +"failure.title" = "สร้างบัญชีผู้ใช้ไม่สำเร็จ"; +"failure.message" = "เราไม่สามารถสร้างบัญชีได้ในขณะนี้ กรุณาลองใหม่อีกครั้งภายหลัง การปิดเปิดแอปใหม่จะเป็นการพยายามสร้างบัญชีใหม่"; +"failure.purchase.sandbox.message" = "ไม่มีระบบสมาชิก Sandbox ที่คุณเลือกอยู่ในการผลิต"; +"failure.redeem.invalid.title" = "PIN การ์ดไม่ถูกต้อง"; +"failure.redeem.invalid.message" = "ดูเหมือนว่าคุณใส่ PIN การ์ดไม่ถูกต้อง กรุณาลองใหม่"; +"failure.redeem.claimed.title" = "บัตรมีการใช้สิทธิ์แล้ว"; +"failure.redeem.claimed.message" = "ดูเหมือนว่าบัตรนี้มีการใช้สิทธิ์แล้วโดยบัญชีอื่น กรุณาลองใส่ PIN อื่น"; +"failure.submit" = "ย้อนกลับ"; + +"unreachable.vc_title" = "ข้อผิดพลาด"; +"unreachable.title" = "อุ๊ปส์!"; +"unreachable.message" = "ไม่พบการเชื่อมต่ออินเทอร์เน็ต กรุณายืนยันว่าคุณมีการเชื่อมต่ออินเทอร์เน็ตแล้วกดลองอีกครั้งด้านล่างนี้\n\nคุณสามารถกลับมาใหม่ในภายหลังเพื่อดำเนินการต่อให้เสร็จ"; +"unreachable.submit" = "ลองใหม่"; + +"purchase.uncredited.alert.message" = "คุณได้ยกเลิกการทำธุรกรรมแล้ว คุณต้องการกู้คืนรายละเอียดบัญชีหรือไม่"; +"purchase.uncredited.alert.button.cancel" = "ยกเลิก"; +"purchase.uncredited.alert.button.recover" = "กู้คืนบัญชี"; + +"purchase.trials.intro" = "เริ่มทดลองใช้ฟรี 7 วัน"; +"purchase.trials.price.after" = "จากนั้น %@"; +"purchase.trials.money.back" = "รับประกันการคืนเงินภายใน 30 วัน"; +"purchase.trials.1year.protection" = "การป้องกันข้อมูลประจำตัวและความเป็นส่วนตัว 1 ปี"; +"purchase.trials.anonymous" = "เรียกดูโดยไม่ระบุชื่อและซ่อน ip ของคุณ"; +"purchase.trials.devices" = "รับรองอุปกรณ์ 10 เครื่องในเวลาเดียวกัน"; +"purchase.trials.devices.description" = "ปกป้องตัวคุณบนอุปกรณ์ถึง 10 เครื่องในเวลาเดียวกัน"; +"purchase.trials.region" = "เชื่อมต่อไปยังภูมิภาคใดก็ตามได้อย่างง่ายดาย"; +"purchase.trials.servers" = "กว่า 3300 เซิร์ฟเวอร์ใน 32 ประเทศ"; +"purchase.trials.start" = "เริ่มการเป็นสมาชิก"; +"purchase.trials.all.plans" = "ดูแผนที่มีทั้งหมด"; + +"purchase.subscribe.now" = "สมัครสมาชิกตอนนี้"; + +// WALKTHROUGH + +"walkthrough.action.next" = "ถัดไป"; +"walkthrough.action.done" = "เสร็จสิ้น"; +"walkthrough.action.skip" = "ข้าม"; + +"walkthrough.page.1.title" = "รับรองอุปกรณ์ 10 เครื่องในคราวเดียว"; +"walkthrough.page.1.description" = "ปกป้องตัวคุณบนอุปกรณ์ถึง 10 เครื่องในเวลาเดียวกัน"; +"walkthrough.page.2.title" = "เชื่อมต่อไปยังภูมิภาคใดก็ตามได้อย่างง่ายดาย"; +"walkthrough.page.2.description" = "ด้วยเซิร์ฟเวอร์ที่มีอยู่ทั่วโลก คุณจะได้รับความคุ้มครองตลอดเวลา"; +"walkthrough.page.3.title" = "ปกป้องตัวคุณจากโฆษณา"; +"walkthrough.page.3.description" = "การเปิดใช้งานตัวปิดกั้นเนื้อหาของเราจะเป็นการป้องกันไม่ให้แสดงโฆษณาใน Safari"; + +"share.data.buttons.accept" = "ยอมรับ"; +"share.data.buttons.noThanks" = "ไม่ล่ะ ขอบคุณ"; +"share.data.buttons.readMore" = "อ่านเพิ่มเติม"; +"share.data.text.title" = "โปรดช่วยเราปรับปรุงบริการของเรา"; +"share.data.text.description" = "เพื่อช่วยให้เรามั่นใจในประสิทธิภาพการเชื่อมต่อของบริการของเรา คุณสามารถแชร์สถิติการเชื่อมต่อของคุณกับเราโดยไม่ระบุชื่อ รายงานเหล่านี้ไม่รวมข้อมูลส่วนบุคคลที่สามารถระบุตัวตนได้"; +"share.data.text.footer" = "คุณสามารถควบคุมสิ่งนี้ได้จากการตั้งค่าของคุณ"; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/th.lproj/Welcome.strings b/Resources/Resources/iOS/th.lproj/Welcome.strings new file mode 100644 index 000000000..637de14e0 --- /dev/null +++ b/Resources/Resources/iOS/th.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "ลงชื่อเข้าใช้บัญชีของคุณ"; +"login.username.placeholder" = "ชื่อผู้ใช้ (p1234567)"; +"login.password.placeholder" = "รหัสผ่าน"; +"login.submit" = "เข้าสู่ระบบ"; +"login.restore.button" = "ยังไม่ได้รับรายละเอียดบัญชีหรือ"; +"login.error.title" = "เข้าสู่ระบบ"; +"login.error.validation" = "คุณต้องกรอกชื่อผู้ใช้และรหัสผ่าน"; +"login.error.unauthorized" = "ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง"; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "เข้าสู่ระบบโดยใช้ใบเสร็จรับเงิน"; +"login.magic.link.title" = "เข้าสู่ระบบโดยใช้ลิงก์อีเมลวิเศษ"; +"login.magic.link.response" = "โปรดตรวจสอบอีเมลของคุณเพื่อดูลิงค์สำหรับเข้าสู่ระบบ"; +"login.magic.link.send" = "ส่งลิงก์"; +"login.magic.link.invalid.email" = "อีเมลไม่ถูกต้อง กรุณาลองอีกครั้ง"; + +"purchase.title" = "เลือกแผน VPN"; +"purchase.subtitle" = "รับประกันการคืนเงินภายใน 30 วัน"; +"purchase.email.placeholder" = "อีเมลแอดเดรส"; +"purchase.continue" = "ดำเนินการต่อ"; +"purchase.login.footer" = "มีบัญชีแล้วหรือยัง"; +"purchase.login.button" = "ลงชื่อเข้าใช้"; +"purchase.error.title" = "ซื้อ"; +"purchase.error.validation" = "คุณต้องใส่ที่อยู่อีเมล"; +"purchase.error.connectivity.title" = "ความล้มเหลวในการเชื่อมต่อ"; +"purchase.error.connectivity.description" = "เราไม่สามารถเข้าถึง Private Internet Access ซึ่งอาจเป็นเพราะอินเทอร์เน็ตไม่เสถียรหรือบริการของเราถูกบล็อกในประเทศของคุณ"; +"purchase.confirm.form.email" = "กรุณากรอกอีเมลของคุณ"; +"purchase.confirm.plan" = "คุณกำลังซื้อแผน %@"; +"purchase.email.why" = "โปรดแจ้งอีเมลสำหรับส่งชื่อผู้ใช้และรหัสผ่านของคุณ"; +"purchase.submit" = "ส่ง"; +"purchase.or" = "หรือ"; + +"upgrade.header" = "ยินดีต้อนรับกลับมา!"; +"upgrade.title" = "หากต้องการใช้ Private Internet Access คุณจะต้องต่ออายุสมาชิกของคุณ"; +"upgrade.renew.now" = "ต่ออายุตอนนี้"; + + + +"redeem.title" = "ใช้สิทธิ์บัตรของขวัญ"; +"redeem.subtitle" = "พิมพ์ที่อยู่อีเมลของคุณและ PIN %lu หลักจากบัตรของขวัญหรือบัตรทดลองด้านล่าง"; +"redeem.email.placeholder" = "ที่อยู่อีเมล"; +"redeem.submit" = "ส่ง"; +"redeem.error.title" = "ใช้สิทธิ์"; +"redeem.error.code" = "รหัสต้องประกอบด้วยตัวเลข %lu หลัก"; +"redeem.error.allfields" = "กรุณากรอกอีเมลและ PIN"; +"redeem.accessibility.back" = "กลับ"; +"redeem.giftcard.placeholder" = "PIN บัตรของขวัญ"; + +"plan.monthly.title" = "รายเดือน"; +"plan.yearly.title" = "รายปี"; +"plan.yearly.detail_format" = "%@%@ ต่อปี"; +"plan.price_format" = "%@/เดือน"; +"plan.best_value" = "คุ้มค่าที่สุด"; +"plan.accessibility.per_month" = "ต่อเดือน"; + +"restore.title" = "คืนค่าการซื้อยังไม่ได้คิดเครดิต"; +"restore.subtitle" = "หากคุณทำการซื้อแผนผ่านแอพนี้และยังไม่ได้รับข้อมูลประจำตัว คุณสามารถส่งอีกครั้งได้จากที่นี่ คุณจะไม่ถูกเรียกเก็บเงินซ้ำอีกครั้งในกระบวนการนี้"; +"restore.email.placeholder" = "อีเมลแอดเดรส"; +"restore.submit" = "ยืนยัน"; + +"iap.error.message.unavailable" = "ไม่พบเซิร์ฟเวอร์แอปเปิ้ลในขณะนี้ โปรดลองใหม่ภายหลัง"; +"iap.error.title" = "ข้อผิดพลาด"; + +"agreement.trials.title" = "ข้อกำหนดและเงื่อนไขการทดลองใช้ฟรี"; +"agreement.trials.message" = "ยอดชำระเงินจะถูกหักจากบัญชี Apple ID ของคุณเมื่อมีการยืนยันคำสั่งซื้อ ระบบจะต่ออายุสมาชิกโดยอัตโนมัติเว้นแต่จะมีการยกเลิกล่วงหน้าอย่างน้อย 24 ชั่วโมงก่อนที่จะสิ้นสุดรอบใช้งานปัจจุบัน บัญชีของคุณจะถูกเรียกเก็บเงินค่าต่ออายุสมาชิกภายใน 24 ชั่วโมงก่อนสิ้นสุดรอบใช้งานปัจจุบัน คุณสามารถจัดการและยกเลิกการเป็นสมาชิกได้โดยไปที่การตั้งค่าบัญชีใน App Store หลังจากซื้อแล้ว\n\nบางระบบสมาชิกแบบชำระเงินอาจเสนอให้คุณทดลองใช้ฟรีก่อนที่จะเรียกเก็บเงินผ่านช่องทางการชำระเงินที่คุณเลือกไว้ หากคุณตัดสินใจที่จะยกเลิกการสมัครสมาชิกแบบชำระเงินก่อนที่เราจะเริ่มเรียกเก็บเงินผ่านช่องทางการชำระเงินที่เลือกไว้ ให้ทำการยกเลิกสมาชิกล่วงหน้าอย่างน้อย 24 ชั่วโมงก่อนที่ช่วงทดลองใช้ฟรีจะสิ้นสุดลง\n\nสามารถทดลองใช้ฟรีเฉพาะผู้ใช้ใหม่เท่านั้นและขึ้นอยู่กับดุลยพินิจของเราแต่เพียงผู้เดียว และหากคุณพยายามที่จะสมัครเพื่อทดลองใช้ฟรีซ้ำอีก คุณจะถูกเรียกเก็บค่าธรรมเนียมการสมัครสมาชิกตามมาตรฐานทันที\n\nเราขอสงวนสิทธิ์ในการเพิกถอนสิทธิ์ทดลองใช้ฟรีของคุณได้ตลอดเวลา\n\nส่วนที่ไม่ได้ใช้งานของช่วงทดลองใช้ฟรีจะถูกหักทิ้งเมื่อมีการสมัครสมาชิก\n\nการสมัครสมาชิกถือว่าเป็นการยอมรับข้อกำหนดและเงื่อนไขนี้"; +"agreement.message" = "หลังจากทดลองใช้ฟรี 7 วัน ระบบจะต่ออายุสมาชิกนี้โดยอัตโนมัติในราคา %@ เว้นแต่จะมีการยกเลิกล่วงหน้าอย่างน้อย 24 ชั่วโมงก่อนที่จะสิ้นสุดรอบใช้งานปัจจุบัน บัญชี Apple ID ของคุณจะถูกเรียกเก็บเงินค่าต่ออายุสมาชิกภายใน 24 ชั่วโมงก่อนสิ้นสุดรอบใช้งานปัจจุบัน คุณสามารถจัดการและยกเลิกการเป็นสมาชิกได้โดยไปที่การตั้งค่าบัญชีใน App Store หลังจากซื้อแล้ว จำกัดสิทธิ์การทดลองใช้ฟรี 7 วัน เพียง 1 สิทธิ์ต่อผู้ใช้หนึ่งราย ส่วนที่ไม่ได้ใช้งานของช่วงทดลองใช้ฟรีหากมีให้จะถูกริบเมื่อผู้ใช้ซื้อการสมัครสมาชิก ราคาทั้งหมดรวมภาษีการขายในท้องที่\n\nการลงทะเบียนจะถือว่าคุณยินยอมตาม $1 และ $2"; +"agreement.trials.yearly.plan" = "ปี"; +"agreement.trials.monthly.plan" = "เดือน"; + +"agreement.message.tos" = "ข้อตกลงการใช้บริการ"; +"agreement.message.privacy" = "นโยบายความเป็นส่วนตัว"; + +"getstarted.buttons.buyaccount" = "ซื้อบัญชี"; + +"gdpr.collect.data.title" = "ข้อมูลส่วนบุคคลที่เรารวบรวม"; +"gdpr.collect.data.description" = "อีเมลเพื่อจุดประสงค์ในการจัดการและการป้องกันบัญชีจากการใช้งานในทางที่ผิด"; +"gdpr.usage.data.title" = "การใช้ข้อมูลส่วนบุคคลที่เรารวบรวม"; +"gdpr.usage.data.description" = "ที่อยู่อีเมลใช้ในการส่งข้อมูลการสมัครสมาชิก การยืนยันการชำระเงิน การติดต่อกับลูกค้า และข้อเสนอส่งเสริมการขาย Private Internet Access เท่านั้น"; +"gdpr.accept.button.title" = "ยอมรับและดำเนินการต่อ"; + +"update.account.email.error" = "ไม่สามารถแก้ไขอีเมลบัญชีได้"; diff --git a/Resources/Resources/iOS/tr.lproj/Signup.strings b/Resources/Resources/iOS/tr.lproj/Signup.strings new file mode 100644 index 000000000..36d6ad53a --- /dev/null +++ b/Resources/Resources/iOS/tr.lproj/Signup.strings @@ -0,0 +1,166 @@ +/* (No Comment) */ +"failure.message" = "Şu anda hesap oluşturamıyoruz.\nLütfen daha sonra tekrar deneyin.\n\nUygulama yeniden açıldığından hesap oluşturma tekrar denenecektir."; + +/* (No Comment) */ +"failure.purchase.sandbox.message" = "Seçilen Sandbox aboneliği üretim ortamında mevcut değil."; + +/* (No Comment) */ +"failure.redeem.claimed.message" = "Bu kart önceden başka bir hesap tarafından talep edilmiş. Farklı bir PIN girmeyi deneyebilirsiniz."; + +/* (No Comment) */ +"failure.redeem.claimed.title" = "Kart önceden talep edilmiş"; + +/* (No Comment) */ +"failure.redeem.invalid.message" = "Görünüşe bakılırsa geçersiz bir kart PIN'i girdiniz. Lütfen tekrar deneyin."; + +/* (No Comment) */ +"failure.redeem.invalid.title" = "Geçersiz kart PIN'i"; + +/* (No Comment) */ +"failure.submit" = "GERİ DÖN"; + +/* (No Comment) */ +"failure.title" = "Hesap Oluşturma Hatası"; + +/* (No Comment) */ +"failure.vc_title" = "Kaydolma başarısız oldu"; + +/* (No Comment) */ +"in_progress.message" = "Sistemimizle satın alım işleminizi onaylıyoruz. Biraz zaman alabilir; lütfen bekleyin."; + +/* (No Comment) */ +"in_progress.redeem.message" = "Kart PIN'inizi sistemimizde onaylıyoruz. Bu biraz zaman alabilir, lütfen bekleyin."; + +/* Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. */ +"in_progress.title" = "Kaydolmayı onayla"; + +/* (No Comment) */ +"purchase.subscribe.now" = "Hemen abone ol"; + +/* (No Comment) */ +"purchase.trials.1year.protection" = "1 yıllık gizlilik ve kimlik koruması"; + +/* (No Comment) */ +"purchase.trials.all.plans" = "Tüm mevcut planlara bakın"; + +/* (No Comment) */ +"purchase.trials.anonymous" = "IP'nizi gizleyerek İnternet'te isimsiz gezinin."; + +/* (No Comment) */ +"purchase.trials.devices" = "Aynı anda 10 cihaz desteği"; + +/* (No Comment) */ +"purchase.trials.devices.description" = "Aynı anda 10 cihazda kendinizi koruyun."; + +/* (No Comment) */ +"purchase.trials.intro" = "7 günlük ücretsiz deneme sürenizi başlatın"; + +/* (No Comment) */ +"purchase.trials.money.back" = "30 günlük para iade garantisi"; + +/* (No Comment) */ +"purchase.trials.price.after" = "Ardından %@"; + +/* (No Comment) */ +"purchase.trials.region" = "Tüm bölgelere kolayca bağlanın"; + +/* (No Comment) */ +"purchase.trials.servers" = "32 ülkede en az 3.300 sunucu"; + +/* (No Comment) */ +"purchase.trials.start" = "Aboneliği başlat"; + +/* (No Comment) */ +"purchase.uncredited.alert.button.cancel" = "İptal"; + +/* (No Comment) */ +"purchase.uncredited.alert.button.recover" = "Hesabı kurtar"; + +/* (No Comment) */ +"purchase.uncredited.alert.message" = "Hesaba yansıtılmamış olan işlemleriniz var. Hesap bilgilerinizi kurtarmak ister misiniz?"; + +/* (No Comment) */ +"share.data.buttons.accept" = "Kabul Et"; + +/* (No Comment) */ +"share.data.buttons.noThanks" = "Hayır, teşekkürler"; + +/* (No Comment) */ +"share.data.buttons.readMore" = "Devamını oku"; + +/* (No Comment) */ +"share.data.readMore.text.description" = "Bu asgari bilgi, potansiyel bağlantı sorunlarını tanımlayıp düzeltmemize yardımcı olur. Bu bilgiyi paylaşmak için onay verilmesi ve varsayılan olarak kapalı olduğu için elle etkinleştirilmesi gerektiğini dikkate alın.\n\nŞu olaylarla ilgili bilgi toplayacağız:\n\n - Bağlantı Denemesi\n - Bağlantı İptal Edildi\n - Bağlantı Kuruldu\n\nTüm bu olaylar için şu bilgileri toplayacağız:\n - Platform\n - Uygulama sürümü\n - Uygulama türü (ön sürüm olup olmadığı)\n - Kullanılan protokol\n - Bağlantı kaynağı (manuel ya da otomasyon ile)\n\nTüm olaylarda rastgele oluşturulan eşsiz bir kimlik yer alacak. Bu kimlik kullanıcı hesabınızla bağlantılı değildir. Bu eşsiz kimlik, gizliliğin korunması açısından günlük olarak üretilir.\n\nKontrol daima sizde olacak. Ayarlardan hangi verileri topladığımızı görebilir ve bunu istediğiniz zaman kapatabilirsiniz."; + +/* (No Comment) */ +"share.data.text.description" = "Hizmetimizin bağlantı performansını korumamıza yardımcı olmak için bağlantı istatistiklerinizi isimsiz olarak bizimle paylaşabilirsiniz. Bu raporlarda kişiyi tanımlayabilecek herhangi bir bilgi yer almaz."; + +/* (No Comment) */ +"share.data.text.footer" = "Bunu istediğiniz zaman ayarlarınızdan kontrol edebilirsiniz"; + +/* (No Comment) */ +"share.data.text.title" = "Lütfen hizmetimizi geliştirmemize yardım edin"; + +/* (No Comment) */ +"success.message_format" = "Bize kayıt olduğunuz için teşekkürler. Hesap kullanıcı adı ve parolanızı %@ e-posta adresinize gönderdik"; + +/* (No Comment) */ +"success.password.caption" = "Şifre"; + +/* (No Comment) */ +"success.redeem.message" = "Kısa süre içinde kullanıcı adınızı ve şifrenizi içeren bir e-posta alacaksınız.\n\nGiriş bilgileriniz"; + +/* (No Comment) */ +"success.redeem.title" = "Kart başarıyla kullanıldı"; + +/* (No Comment) */ +"success.submit" = "Başlarken"; + +/* (No Comment) */ +"success.title" = "Satın Alma Tamamlandı"; + +/* (No Comment) */ +"success.username.caption" = "Kullanıcı Adı"; + +/* (No Comment) */ +"unreachable.message" = "İnternet bağlantısı yok. Lütfen İnternet bağlantınız olup olmadığını kontrol edin ve tekrar aşağıya tıklamayı deneyin.\n\nİşlemi bitirmek için uygulamaya daha sonra tekrar gelebilirsiniz."; + +/* (No Comment) */ +"unreachable.submit" = "TEKRAR DENE"; + +/* (No Comment) */ +"unreachable.title" = "Heeey!"; + +/* (No Comment) */ +"unreachable.vc_title" = "Hata"; + +/* (No Comment) */ +"walkthrough.action.done" = "BİTTİ"; + +/* WALKTHROUGH */ +"walkthrough.action.next" = "İLERİ"; + +/* (No Comment) */ +"walkthrough.action.skip" = "ATLA"; + +/* (No Comment) */ +"walkthrough.page.1.description" = "Aynı anda 10 cihazda koruma sağlayın."; + +/* (No Comment) */ +"walkthrough.page.1.title" = "Aynı anda 10 cihazı destekler"; + +/* (No Comment) */ +"walkthrough.page.2.description" = "Dünya çapındaki sunucularla daima koruma altındasınız."; + +/* (No Comment) */ +"walkthrough.page.2.title" = "Herhangi bir bölgeye kolayca bağlanın"; + +/* (No Comment) */ +"walkthrough.page.3.description" = "İçerik Engelleyicimizi etkinleştirdiğinizde Safari'de reklamların gösterilmesi engellenir."; + +/* (No Comment) */ +"walkthrough.page.3.title" = "Kendinizi reklamlardan koruyun"; + diff --git a/Resources/Resources/iOS/tr.lproj/Welcome.strings b/Resources/Resources/iOS/tr.lproj/Welcome.strings new file mode 100644 index 000000000..49d7ab5ca --- /dev/null +++ b/Resources/Resources/iOS/tr.lproj/Welcome.strings @@ -0,0 +1,205 @@ +/* (No Comment) */ +"agreement.message" = "Bu abonelik, deneme süresinin sonuna en az 24 saat kalana dek iptal edilmezse, otomatik olarak %@ karşılığında yenilenir. Deneme süresinin sonuna 24 saat kaldıktan sonra, Apple Kimliğinizin hesabından yenileme ücreti alınır. Satın alım işleminden sonra App Store hesap ayarlarına giderek abonelik ayarlarınızı değiştirebilir ve aboneliklerinizi iptal edebilirsiniz. 7 günlük deneme süresi, her kullanıcı için bir tane 7 günlük deneme süresi hakkı ile sınırlıdır. Kullanıcı bir abonelik satın aldığında, teklif edilmişse, ücretsiz deneme süresinin kullanılmayan herhangi bir kısmı geçerliliğini yitirir. Tüm fiyatlara uygulanabilir yerel satış vergileri dahildir.\n\nKaydolduğunuzda, $1 ile $2 unsurlarını kabul etmiş olursunuz."; + +/* (No Comment) */ +"agreement.message.privacy" = "Gizlilik Politikası"; + +/* (No Comment) */ +"agreement.message.tos" = "Hizmet Koşulları"; + +/* (No Comment) */ +"agreement.trials.message" = "Satın alım onaylandıktan sonra, ödeme ücreti, Apple Kimliği hesabınızdan alınır. Mevcut dönemin sonuna en az 24 saat kalana dek iptal edilmezse, abonelik otomatik olarak yenilenir. Mevcut dönemin sonuna 24 saat kaldıktan sonra hesabınızdan yenileme ücreti alınır. Satın alım işleminden sonra App Store'daki hesap ayarlarınıza giderek abonelik ayarlarınızı değiştirebilir ve aboneliğinizi iptal edebilirsiniz.\n\nBelirli Ücretli Abonelik planlarında, seçtiğiniz ödeme yöntemiyle ücret alınmadan önce, bir ücretsiz kullanım süresi sunulabilir. Seçtiğiniz ödeme yöntemiyle sizden ücret almaya başlamamızdan önce Ücretli Aboneliğinizi iptal etmek isterseniz, ücretsiz kullanım süresinin sonuna en az 24 saat kalana dek aboneliğinizi iptal etmelisiniz.\n\nÜcretsiz deneme süresinden sadece yeni kullanıcılar faydalanabilir ve bu tamamen bizim takdirimizdedir. Tekrar kaydolarak bir ücretsiz deneme süresi daha almaya çalışırsanız, standart Abonelik Ücreti anında hesabınızdan düşülür.\n\nÜcretsiz deneme sürenizi istediğimiz zaman iptal etme hakkına sahibiz.\n\nBir abonelik satın aldığınızda, ücretsiz kullanım sürenizin kullanılmayan kısmını yitirirsiniz.\n\nKaydolduğunuzda bu şart ve koşulları kabul etmiş olursunuz."; + +/* (No Comment) */ +"agreement.trials.monthly.plan" = "ay"; + +/* (No Comment) */ +"agreement.trials.title" = "Ücretsiz deneme süresi şart ve koşulları"; + +/* (No Comment) */ +"agreement.trials.yearly.plan" = "yıl"; + +/* (No Comment) */ +"gdpr.accept.button.title" = "Kabul edip devam et"; + +/* (No Comment) */ +"gdpr.collect.data.description" = "Hesap yönetimi ve ihlalden koruma için E-posta Adresi."; + +/* (No Comment) */ +"gdpr.collect.data.title" = "Topladığımız kişisel bilgiler"; + +/* (No Comment) */ +"gdpr.usage.data.description" = "E-posta adresi sadece abonelik bilgilerinin, ödeme onaylarının, müşteri iletişim unsurlarının ve Private Internet Access'in promosyonel tekliflerinin gönderilmesi için kullanılır."; + +/* (No Comment) */ +"gdpr.usage.data.title" = "Tarafımızca toplanan kişisel bilgilerin kullanım şekilleri"; + +/* (No Comment) */ +"getstarted.buttons.buyaccount" = "Hesap satın al"; + +/* (No Comment) */ +"iap.error.message.unavailable" = "Apple sunucuları şu anda kullanılamıyor. Lütfen daha sonra tekrar deneyin."; + +/* (No Comment) */ +"iap.error.title" = "Hata"; + +/* (No Comment) */ +"login.error.throttled" = "Bu kullanıcı adı ile çok fazla kez yanlış giriş yapıldı. Lütfen daha sonra tekrar deneyin."; + +/* (No Comment) */ +"login.error.title" = "Giriş yap"; + +/* (No Comment) */ +"login.error.unauthorized" = "Kullanıcı adınız ya da şifreniz hatalı."; + +/* (No Comment) */ +"login.error.validation" = "Bir kullanıcı adı ve şifre girmelisiniz."; + +/* (No Comment) */ +"login.magic.link.invalid.email" = "E-posta adresi geçersiz. Lütfen tekrar deneyin."; + +/* (No Comment) */ +"login.magic.link.response" = "Giriş linkini bulmak için lütfen e-postalarınıza bakın."; + +/* (No Comment) */ +"login.magic.link.send" = "Link Gönder"; + +/* (No Comment) */ +"login.magic.link.title" = "Sihirli e-posta linki ile giriş yap"; + +/* (No Comment) */ +"login.password.placeholder" = "Şifre"; + +/* (No Comment) */ +"login.receipt.button" = "Satın alım faturasıyla giriş yapın"; + +/* (No Comment) */ +"login.restore.button" = "Hesap bilgilerinizi almadınız mı?"; + +/* (No Comment) */ +"login.submit" = "GİRİŞ YAP"; + +/* Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. */ +"login.title" = "Hesabınıza giriş yapın"; + +/* (No Comment) */ +"login.username.placeholder" = "Kullanıcı Adı (p1234567)"; + +/* (No Comment) */ +"plan.accessibility.per_month" = "aylık"; + +/* (No Comment) */ +"plan.best_value" = "En iyi fiyat"; + +/* (No Comment) */ +"plan.monthly.title" = "Aylık"; + +/* (No Comment) */ +"plan.price_format" = "%@/ay"; + +/* (No Comment) */ +"plan.yearly.detail_format" = "Yılda %@%@"; + +/* (No Comment) */ +"plan.yearly.title" = "Yıllık"; + +/* (No Comment) */ +"purchase.confirm.form.email" = "E-posta adresinizi girin"; + +/* (No Comment) */ +"purchase.confirm.plan" = "%@ planını satın alıyorsunuz"; + +/* (No Comment) */ +"purchase.continue" = "Devam et"; + +/* (No Comment) */ +"purchase.email.placeholder" = "E-posta adresi"; + +/* (No Comment) */ +"purchase.email.why" = "Kullanıcı adınızla şifrenizi gönderebilmemiz için e-posta adresinize ihtiyacımız var."; + +/* (No Comment) */ +"purchase.error.connectivity.description" = "Özel İnternet Erişimi'ne ulaşamıyoruz. Bu zayıf internet yüzünden olabilir veya hizmetimiz ülkenizde engelleniyor."; + +/* (No Comment) */ +"purchase.error.connectivity.title" = "Bağlantı Hatası"; + +/* (No Comment) */ +"purchase.error.title" = "Satın Al"; + +/* (No Comment) */ +"purchase.error.validation" = "Bir e-posta adresi girmeniz gerekiyor."; + +/* (No Comment) */ +"purchase.login.button" = "Giriş yapın"; + +/* (No Comment) */ +"purchase.login.footer" = "Hesabınız var mı?"; + +/* (No Comment) */ +"purchase.or" = "veya"; + +/* (No Comment) */ +"purchase.submit" = "Gönder"; + +/* (No Comment) */ +"purchase.subtitle" = "30 günlük para iade garantisi"; + +/* (No Comment) */ +"purchase.title" = "Bir VPN planı seçin"; + +/* (No Comment) */ +"redeem.accessibility.back" = "Geri"; + +/* (No Comment) */ +"redeem.email.placeholder" = "E-posta adresi"; + +/* (No Comment) */ +"redeem.error.allfields" = "Lütfen e-posta adresinizi ve kart PIN numaranızı girin."; + +/* (No Comment) */ +"redeem.error.code" = "Kod, %lu sayısal haneden oluşmalıdır."; + +/* (No Comment) */ +"redeem.error.title" = "Kullan"; + +/* (No Comment) */ +"redeem.giftcard.placeholder" = "Hediye kartı ve PIN"; + +/* (No Comment) */ +"redeem.submit" = "Gönder"; + +/* (No Comment) */ +"redeem.subtitle" = "E-posta adresinizi ve hediye kartınızdaki ya da deneme kartınızdaki %lu haneli PIN'i aşağıya girin."; + +/* (No Comment) */ +"redeem.title" = "Hediye kartını kullan"; + +/* (No Comment) */ +"restore.email.placeholder" = "E-posta adresi"; + +/* (No Comment) */ +"restore.submit" = "ONAYLA"; + +/* (No Comment) */ +"restore.subtitle" = "Bu uygulamadan bir plan satın aldıktan sonra hesap bilgilerinizi alamadıysanız, onları buradan tekrar gönderebilirsiniz. Bu işlem esnasında sizden ücret alınmayacak."; + +/* (No Comment) */ +"restore.title" = "Bilgileri gönderilmeyen satın alma işlemini tekrarlayın"; + +/* (No Comment) */ +"update.account.email.error" = "Hesap e-posta adresi değiştirilemedi"; + +/* (No Comment) */ +"upgrade.header" = "Tekrar Hoş Geldiniz!"; + +/* (No Comment) */ +"upgrade.renew.now" = "Hemen yenileyin"; + +/* (No Comment) */ +"upgrade.title" = "Private Internet Access kullanabilmek için aboneliğinizi yenilemeniz gerekiyor."; + diff --git a/Resources/Resources/iOS/zh-Hans.lproj/Signup.strings b/Resources/Resources/iOS/zh-Hans.lproj/Signup.strings new file mode 100644 index 000000000..2f4afbb78 --- /dev/null +++ b/Resources/Resources/iOS/zh-Hans.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "确认注册"; +"in_progress.message" = "我们正在确认您在我们系统中的购买。这可能需要一点时间,请耐心等待。"; +"in_progress.redeem.message" = "我们正在通过系统确认您的卡片PIN。这可能需要一些时间,请耐心等待。"; + +"success.title" = "购买完成"; +"success.message_format" = "感谢注册。我们已将您的用户名和密码发送至您的电子邮箱:%@"; +"success.redeem.title" = "卡片兑换成功"; +"success.redeem.message" = "您将很快收到一封电子邮件,记有您的用户名和密码。\n\n您的登录详情"; +"success.username.caption" = "用户名"; +"success.password.caption" = "密码"; +"success.submit" = "开始体验"; + +"failure.vc_title" = "注册失败"; +"failure.title" = "账户创建失败"; +"failure.message" = "我们现在无法创建账号。\n请稍后再试。\n\n重新打开本应用将会再次尝试创建账号。"; +"failure.purchase.sandbox.message" = "所选的沙盒订阅不适用于生产"; +"failure.redeem.invalid.title" = "卡片PIN无效"; +"failure.redeem.invalid.message" = "您输入的卡片PIN似乎无效。请重试。"; +"failure.redeem.claimed.title" = "卡片已使用"; +"failure.redeem.claimed.message" = "此卡片似乎已被另一个账号使用。您可尝试输入另一个PIN。"; +"failure.submit" = "返回"; + +"unreachable.vc_title" = "错误"; +"unreachable.title" = "糟糕!"; +"unreachable.message" = "未找到网络连接。请确认您已接入网络,然后点击下面的重试。\n\n您可在稍后再回到本应用完成该操作。"; +"unreachable.submit" = "重试"; + +"purchase.uncredited.alert.message" = "你有未入账的的交易。要恢复您的账户详情吗?"; +"purchase.uncredited.alert.button.cancel" = "取消"; +"purchase.uncredited.alert.button.recover" = "恢复账户"; + +"purchase.trials.intro" = "开始 7 天免费试用"; +"purchase.trials.price.after" = "然后 %@"; +"purchase.trials.money.back" = "7 天退款保证"; +"purchase.trials.1year.protection" = "1 年隐私和身份保护"; +"purchase.trials.anonymous" = "匿名浏览并隐藏您的 IP。"; +"purchase.trials.devices" = "一次支持 10 台设备"; +"purchase.trials.devices.description" = "一次在多达 10 台设备上保护自己。"; +"purchase.trials.region" = "轻松连接到任何地区"; +"purchase.trials.servers" = "在 32 个国家h地区拥有超过 3300 台服务器"; +"purchase.trials.start" = "开始订阅"; +"purchase.trials.all.plans" = "查看所有可用套餐"; + +"purchase.subscribe.now" = "立即订阅"; + +// WALKTHROUGH + +"walkthrough.action.next" = "下一个"; +"walkthrough.action.done" = "完成"; +"walkthrough.action.skip" = "跳过"; + +"walkthrough.page.1.title" = "一次支持 10 台设备"; +"walkthrough.page.1.description" = "一次在多达 10 台设备上保护自己。"; +"walkthrough.page.2.title" = "轻松连接到任何地区"; +"walkthrough.page.2.description" = "服务器遍布世界各地,您始终受到保护。"; +"walkthrough.page.3.title" = "让自己远离广告"; +"walkthrough.page.3.description" = "启用内容拦截器以防止 Safari 中出现广告。"; + +"share.data.buttons.accept" = "接受"; +"share.data.buttons.noThanks" = "不用,谢谢"; +"share.data.buttons.readMore" = "阅读更多"; +"share.data.text.title" = "请帮助我们改进服务"; +"share.data.text.description" = "为了帮助我们保持服务的连接性能,您可以匿名与我们共享您的连接统计数据。这些报告不包括任何可识别个人身份的信息。"; +"share.data.text.footer" = "您始终可以从设置中进行控制"; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/zh-Hans.lproj/Welcome.strings b/Resources/Resources/iOS/zh-Hans.lproj/Welcome.strings new file mode 100644 index 000000000..8d5c7e113 --- /dev/null +++ b/Resources/Resources/iOS/zh-Hans.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "登录到您的帐户"; +"login.username.placeholder" = "用户名 (p1234567)"; +"login.password.placeholder" = "密码"; +"login.submit" = "登录"; +"login.restore.button" = "没有收到账户详情?"; +"login.error.title" = "登录"; +"login.error.validation" = "您必须输入用户名和密码。"; +"login.error.unauthorized" = "您的用户名或密码不正确。"; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "使用购买收据登录"; +"login.magic.link.title" = "使用魔法电子邮件链接登录"; +"login.magic.link.response" = "请检查您的电子邮件,以查找登录链接。"; +"login.magic.link.send" = "发送链接"; +"login.magic.link.invalid.email" = "电子邮箱乎无效。请重试。"; + +"purchase.title" = "选择 VPN 套餐"; +"purchase.subtitle" = "7 天退款保证"; +"purchase.email.placeholder" = "电子邮件地址"; +"purchase.continue" = "继续"; +"purchase.login.footer" = "已有帐号?"; +"purchase.login.button" = "登录"; +"purchase.error.title" = "购买"; +"purchase.error.validation" = "您必须输入一个电子邮箱地址。"; +"purchase.error.connectivity.title" = "连接失败"; +"purchase.error.connectivity.description" = "我们无法接入Private Internet Access。原因可能是互联网连接不良或者我们的服务在您的国家遭到屏蔽。"; +"purchase.confirm.form.email" = "请输入您的电子邮件地址"; +"purchase.confirm.plan" = "您正在购买 %@ 套餐"; +"purchase.email.why" = "我们需要您的电子邮件以发送您的用户名和密码。"; +"purchase.submit" = "提交"; +"purchase.or" = "或"; + +"upgrade.header" = "欢迎回来!"; +"upgrade.title" = "您需要续订才能使用 Private Internet Access。"; +"upgrade.renew.now" = "立即续订"; + + + +"redeem.title" = "兑换礼品卡"; +"redeem.subtitle" = "请输入您的电子邮箱地址以及礼品卡或试用卡上的%lu位数字PIN。"; +"redeem.email.placeholder" = "邮箱地址"; +"redeem.submit" = "提交"; +"redeem.error.title" = "兑换"; +"redeem.error.code" = "PIN码必须包含%lu个数字。"; +"redeem.error.allfields" = "请输入您的电子邮件和卡片 PIN 码。"; +"redeem.accessibility.back" = "返回"; +"redeem.giftcard.placeholder" = "礼品卡 PIN"; + +"plan.monthly.title" = "每月"; +"plan.yearly.title" = "每年"; +"plan.yearly.detail_format" = "%@%@/每年"; +"plan.price_format" = "%@/每月"; +"plan.best_value" = "最超值"; +"plan.accessibility.per_month" = "每月"; + +"restore.title" = "恢复未入账的购买"; +"restore.subtitle" = "如果您通过此 app 购买了套餐,但没有收到凭据,可以从这里重新发送。在此过程中您不会被收费。"; +"restore.email.placeholder" = "电子邮件地址"; +"restore.submit" = "确认"; + +"iap.error.message.unavailable" = "Apple 服务器目前不可用。请稍后重试。"; +"iap.error.title" = "错误"; + +"agreement.trials.title" = "免费试用条款和条件"; +"agreement.trials.message" = "确认购买后,将从您的 Apple ID 账户中收取款项。除非在当前使用期结束前至少提前 24 小时取消,否则会自动续订。您的账户将在当前使用期结束前 24 小时内收取续订费用。您可以在购买后前往 App Store 上的账户设置来管理和取消订阅。\n\n某些“付费订阅”可能会在向您收取款项之前先提供免费试用。如果您在我们开始收取款项之前决定取消订阅“付费订阅”,请在免费试用结束前至少提前 24 小时取消订阅。\n\n免费试用仅适用于新用户,并由我们自行决定,如果您尝试注册额外的免费试用,您将被立即收取标准订阅费。\n\n我们有权随时撤销您的免费试用。\n\n免费试用期的任何未使用部分将在购买订阅时取消。\n\n注册即表示接受上述条款和条件。"; +"agreement.message" = "免费试用 7 天后,除非在试用期结束前至少提前 24 小时取消,否则此订阅将自动续订 %@。您的 Apple ID 账户将在试用期结束前 24 小时内收取续订费用。您可以在购买后前往 App Store 上的账户设置来管理和取消订阅。每个用户仅可享受一次 7 天试用优惠。免费试用期的任何未使用部分(如果提供)将在用户购买订阅时失效。所有价格均包含适用的当地营业税。\n\n注册即表示接受$1和$2。"; +"agreement.trials.yearly.plan" = "年"; +"agreement.trials.monthly.plan" = "月"; + +"agreement.message.tos" = "服务条款"; +"agreement.message.privacy" = "隐私政策"; + +"getstarted.buttons.buyaccount" = "购买账户"; + +"gdpr.collect.data.title" = "我们收集的个人信息"; +"gdpr.collect.data.description" = "用于账户管理和防止滥用的电子邮件地址。"; +"gdpr.usage.data.title" = "我们收集的个人信息的使用方式"; +"gdpr.usage.data.description" = "电子邮件地址仅用于发送订阅信息、付款确认、客户通信以及 Private Internet Access 促销优惠。"; +"gdpr.accept.button.title" = "同意并继续"; + +"update.account.email.error" = "无法修改账户电子邮件"; diff --git a/Resources/Resources/iOS/zh-Hant.lproj/Signup.strings b/Resources/Resources/iOS/zh-Hant.lproj/Signup.strings new file mode 100644 index 000000000..ed78c07e6 --- /dev/null +++ b/Resources/Resources/iOS/zh-Hant.lproj/Signup.strings @@ -0,0 +1,92 @@ +/* + Signup.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"in_progress.title" = "壓認註冊"; +"in_progress.message" = "我們的系統正確認您的購買交易,可能需要停留在此畫面幾分鐘時間。"; +"in_progress.redeem.message" = "我們的系統正在確認您的卡 PIN 碼。這可能需要一點時間,請稍候。"; + +"success.title" = "購買完成"; +"success.message_format" = "感謝您註冊我們的服務!我們已將您的帳戶使用者名稱和密碼寄送到您的電子郵件地址:%@"; +"success.redeem.title" = "已成功兌換禮品卡"; +"success.redeem.message" = "您很快就會收到一封電子郵件,內有使用者名稱及密碼。\n\n您的登入資料"; +"success.username.caption" = "使用者名稱"; +"success.password.caption" = "密碼"; +"success.submit" = "開始使用"; + +"failure.vc_title" = "註冊失敗"; +"failure.title" = "帳戶建立失敗"; +"failure.message" = "我們目前未能建立帳戶,請稍後再試。\n\n應用程式將在重新啟動時再次嘗試建立帳戶。"; +"failure.purchase.sandbox.message" = "所選的沙盒訂閱已不再提供。"; +"failure.redeem.invalid.title" = "無效的卡 PIN 碼"; +"failure.redeem.invalid.message" = "您似乎輸入了一個無效的卡 PIN 碼,請再試一次。"; +"failure.redeem.claimed.title" = "此卡已被使用"; +"failure.redeem.claimed.message" = "這張卡似乎已經被另一個帳戶使用。您可以嘗試輸入不同的 PIN 碼。"; +"failure.submit" = "返回"; + +"unreachable.vc_title" = "錯誤"; +"unreachable.title" = "噢!"; +"unreachable.message" = "找不到網路連線。請確定您已連線上網,然後點選下方按鈕重試。\n\n您可以稍後再回來完成程序。"; +"unreachable.submit" = "重試"; + +"purchase.uncredited.alert.message" = "您有未貸記的交易,要回復帳戶資料嗎?"; +"purchase.uncredited.alert.button.cancel" = "取消"; +"purchase.uncredited.alert.button.recover" = "回復帳戶"; + +"purchase.trials.intro" = "開始 7 天免費試用"; +"purchase.trials.price.after" = "然後 %@"; +"purchase.trials.money.back" = "30 天退款保證"; +"purchase.trials.1year.protection" = "1 年隱私權和身分保護"; +"purchase.trials.anonymous" = "匿名瀏覽並隱藏 IP。"; +"purchase.trials.devices" = "同時支援 10 台裝置"; +"purchase.trials.devices.description" = "同時為最多 10 台裝置提供保護。"; +"purchase.trials.region" = "可輕鬆連線到任何地區"; +"purchase.trials.servers" = "32 個國家超過 3300 台伺服器"; +"purchase.trials.start" = "開始訂閱"; +"purchase.trials.all.plans" = "檢視所有可用方案"; + +"purchase.subscribe.now" = "馬上訂閱"; + +// WALKTHROUGH + +"walkthrough.action.next" = "下一步"; +"walkthrough.action.done" = "完成"; +"walkthrough.action.skip" = "跳過"; + +"walkthrough.page.1.title" = "同時支援 10 台裝置"; +"walkthrough.page.1.description" = "同時為多達 10 台裝置提供保護"; +"walkthrough.page.2.title" = "輕易就能連線到任何地區"; +"walkthrough.page.2.description" = "我們的伺服器遍佈全球,能為您提供全面保護。"; +"walkthrough.page.3.title" = "讓自己免受廣告滋擾"; +"walkthrough.page.3.description" = "只要啟用我們的內容阻擋器,使用 Safari 瀏覽器時就再也不會看到廣告。"; + +"share.data.buttons.accept" = "接受"; +"share.data.buttons.noThanks" = "不了,謝謝"; +"share.data.buttons.readMore" = "閱讀更多內容"; +"share.data.text.title" = "請協助我們改善服務"; +"share.data.text.description" = "為了確保服務的連線品質,您可以匿名與我們分享您的連線統計資料。這些報告不會包含任何個人識別資訊。"; +"share.data.text.footer" = "您隨時都可以在設定中控制。"; + +"share.data.readMore.text.description" = "This minimal information assists us in identifying and fixing potential connection issues. Note that sharing this information requires consent and manual activation as it is turned off by default. + +We will collect information about the following events: + + - Connection Attempt + - Connection Canceled + - Connection Established + +For all of these events, we will collect the following information: + - Platform + - App version + - App type (pre-release or not) + - Protocol used + - Connection source (manual or using automation) + - Time To Connect (time between connecting and connected state) + +All events will contain a unique ID, which is randomly generated. This ID is not associated with your user account. This unique ID is re-generated daily for privacy purposes. + +You will always be in control. You can see what data we’ve collected from Settings, and you can turn it off at any time."; diff --git a/Resources/Resources/iOS/zh-Hant.lproj/Welcome.strings b/Resources/Resources/iOS/zh-Hant.lproj/Welcome.strings new file mode 100644 index 000000000..b4c846e04 --- /dev/null +++ b/Resources/Resources/iOS/zh-Hant.lproj/Welcome.strings @@ -0,0 +1,88 @@ +/* + Welcome.strings + PIALibrary + + Created by Davide De Rosa on 12/7/17. + Copyright © 2017 London Trust Media. All rights reserved. +*/ + +"login.title" = "登入帳戶"; +"login.username.placeholder" = "使用者名稱(p1234567)"; +"login.password.placeholder" = "密碼"; +"login.submit" = "登入"; +"login.restore.button" = "收不到帳戶資料?"; +"login.error.title" = "登入"; +"login.error.validation" = "您必須輸入使用者名稱及密碼。"; +"login.error.unauthorized" = "您的使用者名稱或密碼不正確。"; +"login.error.throttled" = "Too many failed login attempts with this username. Please try again after %@ second(s)."; +"login.receipt.button" = "使用購買收據登入"; +"login.magic.link.title" = "使用神奇的電子郵件連結登入"; +"login.magic.link.response" = "請確認電子信箱是否已收到登入連結。"; +"login.magic.link.send" = "傳送連結"; +"login.magic.link.invalid.email" = "無效的電子郵件。請重試。"; + +"purchase.title" = "選擇 VPN 方案"; +"purchase.subtitle" = "30 天退款保證"; +"purchase.email.placeholder" = "電子郵件地址"; +"purchase.continue" = "繼續"; +"purchase.login.footer" = "已有帳號?"; +"purchase.login.button" = "登入"; +"purchase.error.title" = "購買"; +"purchase.error.validation" = "必須輸入電郵地址。"; +"purchase.error.connectivity.title" = "連線失敗"; +"purchase.error.connectivity.description" = "我們無法連線至 Private Internet Access。這可能是因為您的網路連線狀態不佳,或我們的服務在您的國家遭到封鎖。"; +"purchase.confirm.form.email" = "請輸入您的電子郵件地址"; +"purchase.confirm.plan" = "您正在購買 %@ 方案"; +"purchase.email.why" = "請提供電子郵件以便我們傳送使用者名稱及密碼。"; +"purchase.submit" = "提交"; +"purchase.or" = "或"; + +"upgrade.header" = "歡迎回來!"; +"upgrade.title" = "如果要使用 Private Internet Access,您必須續訂。"; +"upgrade.renew.now" = "立即續訂"; + + + +"redeem.title" = "兌換禮品卡"; +"redeem.subtitle" = "於下方輸入您的電子郵件地址及禮品卡或試用卡並的 %lu 位數 PIN 碼。"; +"redeem.email.placeholder" = "電子郵件地址"; +"redeem.submit" = "提交"; +"redeem.error.title" = "兌換"; +"redeem.error.code" = "代碼必須包含 %lu 個數字。"; +"redeem.error.allfields" = "請輸入您的電子郵件地址及禮品卡 PIN 碼。"; +"redeem.accessibility.back" = "返回"; +"redeem.giftcard.placeholder" = "禮品卡 PIN 碼"; + +"plan.monthly.title" = "月繳"; +"plan.yearly.title" = "年繳"; +"plan.yearly.detail_format" = "每年 %@%@"; +"plan.price_format" = "每月 %@"; +"plan.best_value" = "最佳值"; +"plan.accessibility.per_month" = "/ 月"; + +"restore.title" = "回復未貸記購買項目"; +"restore.subtitle" = "如果您透過此應用程式購買方案後收不到您的憑證,可以透過這裡要求再次傳送。此操作不會收取任何費用。"; +"restore.email.placeholder" = "電子郵件地址"; +"restore.submit" = "確定"; + +"iap.error.message.unavailable" = "Apple 伺服器目前無法使用,請稍後再試。"; +"iap.error.title" = "錯誤"; + +"agreement.trials.title" = "免費試用條款與條件"; +"agreement.trials.message" = "確認購買後,系統將從您的 Apple ID 帳號收取費用。除非您在目前訂閱期間結束前至少 24 小時取消訂閱,否則訂閱將自動續訂。在目前期間結束前 24 小時內,將從您的帳號收取續訂費用。您可在購買後,前往 App Store 的帳號設定管理和取消您的訂閱。\n\n「特定付費訂閱」可能在以您的付費方式收費前,提供免費試用。若您在我們開始以您的付費方式收費前,決定取消「付費訂閱」,請在免費試用結束前至少 24 小時取消訂閱。\n\n只有新使用者才享有免費試用的資格,且我們擁有唯一決定權。若您試圖再次註冊免費試用,我們會立即以「標準訂閱費用」向您收費。\n\n我們保留隨時解除您免費試用的權利。\n\n若您的免費試用有任何未使用的期間,將會在購買訂閱時收回。\n\n若註冊即代表您接受此條款與條件。"; +"agreement.message" = "免費試用 7 天後,除非您在試用期結束前至少 24 小時取消訂閱,否則系統將自動續訂 %@,並在試用期結束前的 24 小時內,從您的 Apple ID 帳號收取續訂費用。購買後,您可以前往 App Store 的帳號設定管理或取消您的訂閱方案。每位用戶只有一次 7 天試用的機會。若您的免費試用有任何未使用的期間,將會在購買訂閱時收回。所有價格已包括適用於當地的營業稅。\n\n若註冊,即代表您已接受 $1 與 $2。"; +"agreement.trials.yearly.plan" = "年"; +"agreement.trials.monthly.plan" = "月"; + +"agreement.message.tos" = "服務條款"; +"agreement.message.privacy" = "隱私權政策"; + +"getstarted.buttons.buyaccount" = "購買帳戶"; + +"gdpr.collect.data.title" = "我們收集的個人資料"; +"gdpr.collect.data.description" = "電子郵件地址用於管理帳戶及防止濫用。"; +"gdpr.usage.data.title" = "我們收集個人資料的用途"; +"gdpr.usage.data.description" = "電子郵件地址僅用於傳送訂閱資訊、付款確認函、客戶通訊及 Private Internet Access 的促銷優惠。"; +"gdpr.accept.button.title" = "同意並繼續"; + +"update.account.email.error" = "無法修改帳戶電子郵件"; diff --git a/Resources/UI/Signup.storyboard b/Resources/UI/Signup.storyboard new file mode 100644 index 000000000..4b96ebcb0 --- /dev/null +++ b/Resources/UI/Signup.storyboard @@ -0,0 +1,1121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Resources/UI/Welcome.storyboard b/Resources/UI/Welcome.storyboard new file mode 100644 index 000000000..82799cc1f --- /dev/null +++ b/Resources/UI/Welcome.storyboard @@ -0,0 +1,1321 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swiftgen.yml b/swiftgen.yml index 1d30d5171..42f6feb72 100644 --- a/swiftgen.yml +++ b/swiftgen.yml @@ -1,22 +1,26 @@ strings: - paths: + inputs: - PIA VPN/en.lproj/Localizable.strings + - Resources/Resources/Shared/en.lproj/UI.strings + - Resources/Resources/iOS/en.lproj/Signup.strings + - Resources/Resources/iOS/en.lproj/Welcome.strings templateName: structured-swift4 output: PIA VPN/SwiftGen+Strings.swift ib: - paths: + inputs: - Resources/UI/en.lproj/Main.storyboard + - Resources/UI/Welcome.storyboard + - Resources/UI/Signup.storyboard outputs: templateName: scenes-swift4 output: PIA VPN/SwiftGen+ScenesStoryboards.swift - templateName: segues-swift4 - output: PIA VPN/SwiftGen+SeguesStoryboards.swift - params: + outputs.params: module: PIA_VPN xcassets: - paths: + inputs: - PIA VPN/Images.xcassets + - Resources/Resources/iOS/UI.xcassets templatePath: Resources/SwiftGen/xcassets/swift4.stencil output: PIA VPN/SwiftGen+Assets.swift