diff --git a/.github/actions/asana-extract-task-assignee/action.yml b/.github/actions/asana-extract-task-assignee/action.yml deleted file mode 100644 index e50e105889..0000000000 --- a/.github/actions/asana-extract-task-assignee/action.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Extract Asana Task Assignee -description: Returns the Asana task assignee, if task is assigned -inputs: - access-token: - description: "Asana access token" - required: true - type: string - task-id: - description: "Asana task ID" - required: true - type: string -outputs: - assignee-id: - description: "Assignee ID" - value: ${{ steps.extract-assignee-id.outputs.assignee-id }} -runs: - using: "composite" - steps: - - id: extract-assignee-id - run: | - assignee_id=$(curl -fLSs "https://app.asana.com/api/1.0/tasks/${{ inputs.task-id }}?opt_fields=assignee" \ - -H "Authorization: Bearer ${{ inputs.access-token }}" \ - | jq -r '.data.assignee.gid') - echo "assignee-id=${assignee_id}" >> $GITHUB_OUTPUT - shell: bash diff --git a/.github/actions/asana-get-release-automation-subtask-id/action.yml b/.github/actions/asana-get-release-automation-subtask-id/action.yml index 750a960984..0b50aa37bc 100644 --- a/.github/actions/asana-get-release-automation-subtask-id/action.yml +++ b/.github/actions/asana-get-release-automation-subtask-id/action.yml @@ -15,7 +15,7 @@ outputs: value: ${{ steps.extract-automation-task-id.outputs.automation-task-id }} assignee-id: description: "Release task assignee ID" - value: ${{ steps.extract-assignee-id.outputs.assignee-id }} + value: ${{ steps.extract-assignee-id.outputs.asana_assignee_id }} runs: using: "composite" steps: @@ -24,10 +24,10 @@ runs: run: bundle exec fastlane run asana_extract_task_id task_url:"${{ inputs.task-url }}" - id: extract-assignee-id - uses: ./.github/actions/asana-extract-task-assignee - with: - task-id: ${{ steps.extract-task-id.outputs.asana_task_id }} - access-token: ${{ inputs.access-token }} + shell: bash + env: + ASANA_ACCESS_TOKEN: ${{ inputs.access-token }} + run: bundle exec fastlane run asana_extract_task_assignee task_id:"${{ steps.extract-task-id.outputs.asana_task_id }}" - id: extract-automation-task-id run: | diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index e606637993..f23069962d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -297,6 +297,7 @@ jobs: with: check_name: "Test Report: ${{ matrix.flavor }}" report_paths: '${{ matrix.flavor }}*.xml' + check_retries: true - name: Update Asana with failed unit tests if: always() # always run even if the previous step fails diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index b71e79c75e..527880d7b3 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 250 +CURRENT_PROJECT_VERSION = 251 diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 074739e6fe..1c924956d6 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -185,6 +185,8 @@ 1EA7B8D52B7E078C000330A4 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = 1EA7B8D42B7E078C000330A4 /* Subscription */; }; 1ED910D52B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED910D42B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift */; }; 1ED910D62B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED910D42B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift */; }; + 1EFA1A072C7C7F0E0099F508 /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; + 1EFA1A082C7C7F0F0099F508 /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; 310E79BF294A19A8007C49E8 /* FireproofingReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310E79BE294A19A8007C49E8 /* FireproofingReferenceTests.swift */; }; 311B262728E73E0A00FD181A /* TabShadowConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311B262628E73E0A00FD181A /* TabShadowConfig.swift */; }; 31267C692B640C4200FEF811 /* DataBrokerProtectionFeatureGatekeeper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31C5FFB82AF64D120008A79F /* DataBrokerProtectionFeatureGatekeeper.swift */; }; @@ -266,7 +268,7 @@ 3706FA83293F65D500E42796 /* LazyLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37534C9F28113101002621E7 /* LazyLoadable.swift */; }; 3706FA85293F65D500E42796 /* KeyedCodingExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0230C0A2272080090018F728 /* KeyedCodingExtension.swift */; }; 3706FA87293F65D500E42796 /* DownloadListStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C0B22F26E61D630031CB7F /* DownloadListStore.swift */; }; - 3706FA88293F65D500E42796 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85799C1725DEBB3F0007EC87 /* Logging.swift */; }; + 3706FA88293F65D500E42796 /* Logger+Multiple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85799C1725DEBB3F0007EC87 /* Logger+Multiple.swift */; }; 3706FA89293F65D500E42796 /* CrashReportPromptPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC30A2D268F1EE300D2D9CD /* CrashReportPromptPresenter.swift */; }; 3706FA8B293F65D500E42796 /* PreferencesRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AFCE8627DA334800471A10 /* PreferencesRootView.swift */; }; 3706FA8C293F65D500E42796 /* AppStateChangedPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B684590725C9027900DC17B6 /* AppStateChangedPublisher.swift */; }; @@ -1117,9 +1119,7 @@ 4B0511CA262CAA5A00F6079C /* FireproofDomainsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0511B4262CAA5A00F6079C /* FireproofDomainsViewController.swift */; }; 4B0511E1262CAA8600F6079C /* NSOpenPanelExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0511DF262CAA8600F6079C /* NSOpenPanelExtensions.swift */; }; 4B0511E2262CAA8600F6079C /* NSViewControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0511E0262CAA8600F6079C /* NSViewControllerExtension.swift */; }; - 4B05265E2B1AE5C70054955A /* VPNMetadataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B05265D2B1AE5C70054955A /* VPNMetadataCollector.swift */; }; - 4B0526612B1D55320054955A /* VPNFeedbackSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0526602B1D55320054955A /* VPNFeedbackSender.swift */; }; - 4B0526642B1D55D80054955A /* VPNFeedbackCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0526632B1D55D80054955A /* VPNFeedbackCategory.swift */; }; + 4B0526612B1D55320054955A /* UnifiedFeedbackSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0526602B1D55320054955A /* UnifiedFeedbackSender.swift */; }; 4B0A63E8289DB58E00378EF7 /* FirefoxFaviconsReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0A63E7289DB58E00378EF7 /* FirefoxFaviconsReader.swift */; }; 4B0AACAC28BC63ED001038AC /* ChromiumFaviconsReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0AACAB28BC63ED001038AC /* ChromiumFaviconsReader.swift */; }; 4B0AACAE28BC6FD0001038AC /* SafariFaviconsReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0AACAD28BC6FD0001038AC /* SafariFaviconsReader.swift */; }; @@ -1149,10 +1149,8 @@ 4B2D06292A11C0C900DE1F49 /* Bundle+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* Bundle+VPN.swift */; }; 4B2D062A2A11C0C900DE1F49 /* NetworkProtectionOptionKeyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605F2A0B29FA00BCD287 /* NetworkProtectionOptionKeyExtension.swift */; }; 4B2D062C2A11C0E100DE1F49 /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2D062B2A11C0E100DE1F49 /* Networking */; }; - 4B2D065B2A11D1FF00DE1F49 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4BEC322A11B509001D9AC5 /* Logging.swift */; }; 4B2D065E2A11D2D700DE1F49 /* DuckDuckGo Notifications.app in Embed Login Items */ = {isa = PBXBuildFile; fileRef = 4B4BEC202A11B4E2001D9AC5 /* DuckDuckGo Notifications.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 4B2D065F2A11D2D700DE1F49 /* DuckDuckGo VPN.app in Embed Login Items */ = {isa = PBXBuildFile; fileRef = 4B2D06392A11CFBB00DE1F49 /* DuckDuckGo VPN.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 4B2D067C2A13340900DE1F49 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4BEC322A11B509001D9AC5 /* Logging.swift */; }; 4B2D067F2A1334D700DE1F49 /* NetworkProtectionUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2D067E2A1334D700DE1F49 /* NetworkProtectionUI */; }; 4B2E7D6326FF9D6500D2DB17 /* PrintingUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E7D6226FF9D6500D2DB17 /* PrintingUserScript.swift */; }; 4B2F565C2B38F93E001214C0 /* NetworkProtectionSubscriptionEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2F565B2B38F93E001214C0 /* NetworkProtectionSubscriptionEventHandler.swift */; }; @@ -1174,18 +1172,15 @@ 4B41EDA72B1543C9001EEDF4 /* PreferencesVPNView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41EDA62B1543C9001EEDF4 /* PreferencesVPNView.swift */; }; 4B41EDA82B1543C9001EEDF4 /* PreferencesVPNView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41EDA62B1543C9001EEDF4 /* PreferencesVPNView.swift */; }; 4B41EDAB2B1544B2001EEDF4 /* LoginItems in Frameworks */ = {isa = PBXBuildFile; productRef = 4B41EDAA2B1544B2001EEDF4 /* LoginItems */; }; - 4B41EDAE2B168AFF001EEDF4 /* VPNFeedbackFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41EDAD2B168AFF001EEDF4 /* VPNFeedbackFormViewController.swift */; }; - 4B41EDAF2B168AFF001EEDF4 /* VPNFeedbackFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41EDAD2B168AFF001EEDF4 /* VPNFeedbackFormViewController.swift */; }; - 4B41EDB12B168B1E001EEDF4 /* VPNFeedbackFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41EDB02B168B1E001EEDF4 /* VPNFeedbackFormView.swift */; }; - 4B41EDB22B168B1E001EEDF4 /* VPNFeedbackFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41EDB02B168B1E001EEDF4 /* VPNFeedbackFormView.swift */; }; - 4B41EDB42B168C55001EEDF4 /* VPNFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41EDB32B168C55001EEDF4 /* VPNFeedbackFormViewModel.swift */; }; - 4B41EDB52B168C55001EEDF4 /* VPNFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41EDB32B168C55001EEDF4 /* VPNFeedbackFormViewModel.swift */; }; + 4B41EDAE2B168AFF001EEDF4 /* UnifiedFeedbackFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41EDAD2B168AFF001EEDF4 /* UnifiedFeedbackFormViewController.swift */; }; + 4B41EDAF2B168AFF001EEDF4 /* UnifiedFeedbackFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41EDAD2B168AFF001EEDF4 /* UnifiedFeedbackFormViewController.swift */; }; + 4B41EDB42B168C55001EEDF4 /* UnifiedFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41EDB32B168C55001EEDF4 /* UnifiedFeedbackFormViewModel.swift */; }; + 4B41EDB52B168C55001EEDF4 /* UnifiedFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41EDB32B168C55001EEDF4 /* UnifiedFeedbackFormViewModel.swift */; }; 4B434690285ED7A100177407 /* BookmarksBarViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B43468F285ED7A100177407 /* BookmarksBarViewModelTests.swift */; }; 4B43469528655D1400177407 /* FirefoxDataImporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B43469428655D1400177407 /* FirefoxDataImporterTests.swift */; }; 4B44FEF32B1FEF5A000619D8 /* FocusableTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B44FEF22B1FEF5A000619D8 /* FocusableTextEditor.swift */; }; 4B44FEF42B1FEF5A000619D8 /* FocusableTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B44FEF22B1FEF5A000619D8 /* FocusableTextEditor.swift */; }; 4B4BEC3D2A11B56B001D9AC5 /* DuckDuckGoNotificationsAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4BEC382A11B509001D9AC5 /* DuckDuckGoNotificationsAppDelegate.swift */; }; - 4B4BEC3E2A11B56E001D9AC5 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4BEC322A11B509001D9AC5 /* Logging.swift */; }; 4B4BEC412A11B5BD001D9AC5 /* NetworkProtectionUNNotificationsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60762A0B29FA00BCD287 /* NetworkProtectionUNNotificationsPresenter.swift */; }; 4B4BEC422A11B5C7001D9AC5 /* NetworkProtectionOptionKeyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605F2A0B29FA00BCD287 /* NetworkProtectionOptionKeyExtension.swift */; }; 4B4BEC432A11B5C7001D9AC5 /* Bundle+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* Bundle+VPN.swift */; }; @@ -1214,6 +1209,8 @@ 4B4D60E22A0C883A00BCD287 /* AppMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60E12A0C883A00BCD287 /* AppMain.swift */; }; 4B4D60E32A0C883A00BCD287 /* AppMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60E12A0C883A00BCD287 /* AppMain.swift */; }; 4B4F72EC266B2ED300814C60 /* CollectionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4F72EB266B2ED300814C60 /* CollectionExtension.swift */; }; + 4B5235452C7BB14D00AFAF64 /* WireGuard in Frameworks */ = {isa = PBXBuildFile; productRef = 4B5235442C7BB14D00AFAF64 /* WireGuard */; }; + 4B5235472C7BB15700AFAF64 /* WireGuard in Frameworks */ = {isa = PBXBuildFile; productRef = 4B5235462C7BB15700AFAF64 /* WireGuard */; }; 4B59023E26B35F3600489384 /* ChromiumLoginReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B59023926B35F3600489384 /* ChromiumLoginReader.swift */; }; 4B59024026B35F3600489384 /* ChromiumDataImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B59023B26B35F3600489384 /* ChromiumDataImporter.swift */; }; 4B59024826B3673600489384 /* ThirdPartyBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B59024726B3673600489384 /* ThirdPartyBrowser.swift */; }; @@ -1381,8 +1378,8 @@ 4BD18F01283F0BC500058124 /* BookmarksBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD18EFF283F0BC500058124 /* BookmarksBarViewController.swift */; }; 4BD18F05283F151F00058124 /* BookmarksBar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4BD18F04283F151F00058124 /* BookmarksBar.storyboard */; }; 4BDFA4AE27BF19E500648192 /* ToggleableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDFA4AD27BF19E500648192 /* ToggleableScrollView.swift */; }; - 4BE344EE2B2376DF003FC223 /* VPNFeedbackFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE344ED2B2376DF003FC223 /* VPNFeedbackFormViewModelTests.swift */; }; - 4BE344EF2B23786F003FC223 /* VPNFeedbackFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE344ED2B2376DF003FC223 /* VPNFeedbackFormViewModelTests.swift */; }; + 4BE344EE2B2376DF003FC223 /* UnifiedFeedbackFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE344ED2B2376DF003FC223 /* UnifiedFeedbackFormViewModelTests.swift */; }; + 4BE344EF2B23786F003FC223 /* UnifiedFeedbackFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE344ED2B2376DF003FC223 /* UnifiedFeedbackFormViewModelTests.swift */; }; 4BE3A6C12C16BEB1003FC378 /* VPNRedditSessionWorkaround.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE3A6C02C16BEB1003FC378 /* VPNRedditSessionWorkaround.swift */; }; 4BE3A6C22C16BEB1003FC378 /* VPNRedditSessionWorkaround.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE3A6C02C16BEB1003FC378 /* VPNRedditSessionWorkaround.swift */; }; 4BE4005327CF3DC3007D3161 /* SavePaymentMethodPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE4005227CF3DC3007D3161 /* SavePaymentMethodPopover.swift */; }; @@ -1424,9 +1421,7 @@ 4BF97AD72B43C53D00EB4240 /* NetworkProtectionIPCTunnelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC4D2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift */; }; 4BF97AD82B43C5B300EB4240 /* NetworkProtectionAppEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2DDCF72A93A8BB0039D884 /* NetworkProtectionAppEvents.swift */; }; 4BF97AD92B43C5C000EB4240 /* Bundle+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* Bundle+VPN.swift */; }; - 4BF97ADA2B43C5DC00EB4240 /* VPNFeedbackCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0526632B1D55D80054955A /* VPNFeedbackCategory.swift */; }; - 4BF97ADB2B43C5E000EB4240 /* VPNMetadataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B05265D2B1AE5C70054955A /* VPNMetadataCollector.swift */; }; - 4BF97ADC2B43C5E200EB4240 /* VPNFeedbackSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0526602B1D55320054955A /* VPNFeedbackSender.swift */; }; + 4BF97ADC2B43C5E200EB4240 /* UnifiedFeedbackSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0526602B1D55320054955A /* UnifiedFeedbackSender.swift */; }; 4BF97ADD2B43C5FC00EB4240 /* KeychainType+ClientDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */; }; 5601FECD29B7973D00068905 /* TabBarViewItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5601FECC29B7973D00068905 /* TabBarViewItemTests.swift */; }; 5603D90629B7B746007F9F01 /* MockTabViewItemDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5603D90529B7B746007F9F01 /* MockTabViewItemDelegate.swift */; }; @@ -1435,6 +1430,12 @@ 560C3FFD2BC9911000F589CE /* PermanentSurveyManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560C3FFB2BC9911000F589CE /* PermanentSurveyManagerTests.swift */; }; 560C3FFF2BCD5A1E00F589CE /* PermanentSurveyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560C3FFE2BCD5A1E00F589CE /* PermanentSurveyManager.swift */; }; 560C40002BCD5A1E00F589CE /* PermanentSurveyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560C3FFE2BCD5A1E00F589CE /* PermanentSurveyManager.swift */; }; + 560EB9322C78946F0080DBC8 /* ContextualOnboardingDialogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560EB9312C78946F0080DBC8 /* ContextualOnboardingDialogs.swift */; }; + 560EB9332C78946F0080DBC8 /* ContextualOnboardingDialogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560EB9312C78946F0080DBC8 /* ContextualOnboardingDialogs.swift */; }; + 560EB9352C7897370080DBC8 /* Onboarding in Frameworks */ = {isa = PBXBuildFile; productRef = 560EB9342C7897370080DBC8 /* Onboarding */; }; + 560EB9372C78974C0080DBC8 /* Onboarding in Frameworks */ = {isa = PBXBuildFile; productRef = 560EB9362C78974C0080DBC8 /* Onboarding */; }; + 560EB9392C789A450080DBC8 /* OnboardingSuggestedSearchesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560EB9382C789A450080DBC8 /* OnboardingSuggestedSearchesProvider.swift */; }; + 560EB93A2C789A450080DBC8 /* OnboardingSuggestedSearchesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560EB9382C789A450080DBC8 /* OnboardingSuggestedSearchesProvider.swift */; }; 5614B3A12BBD639D009B5031 /* ZoomPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5614B3A02BBD639D009B5031 /* ZoomPopover.swift */; }; 5614B3A22BBD639D009B5031 /* ZoomPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5614B3A02BBD639D009B5031 /* ZoomPopover.swift */; }; 561D29C22BDA745A007B91D0 /* MockSyncPausedStateManaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 561D29C02BDA7430007B91D0 /* MockSyncPausedStateManaging.swift */; }; @@ -1462,6 +1463,13 @@ 566B736A2BECC02D00FF1959 /* SyncAlertsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B73682BECBF8400FF1959 /* SyncAlertsPresenter.swift */; }; 566B736C2BECC3C600FF1959 /* SyncPausedStateManaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B736B2BECC3C600FF1959 /* SyncPausedStateManaging.swift */; }; 566B736D2BECC3C600FF1959 /* SyncPausedStateManaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B736B2BECC3C600FF1959 /* SyncPausedStateManaging.swift */; }; + 567A23BE2C7F539C0010F66C /* SpecialErrorPageUserScriptExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567A23BD2C7F539C0010F66C /* SpecialErrorPageUserScriptExtension.swift */; }; + 567A23BF2C7F539C0010F66C /* SpecialErrorPageUserScriptExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567A23BD2C7F539C0010F66C /* SpecialErrorPageUserScriptExtension.swift */; }; + 567A23C12C7F71570010F66C /* SpecialErrorPages in Frameworks */ = {isa = PBXBuildFile; productRef = 567A23C02C7F71570010F66C /* SpecialErrorPages */; }; + 567A23C52C7F75BB0010F66C /* SpecialErrorPages in Frameworks */ = {isa = PBXBuildFile; productRef = 567A23C42C7F75BB0010F66C /* SpecialErrorPages */; }; + 567A23CD2C80CE060010F66C /* SpecialErrorPageUserScriptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567A23CC2C80CE060010F66C /* SpecialErrorPageUserScriptTests.swift */; }; + 567A23CE2C80CF3D0010F66C /* SpecialErrorPageUserScriptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567A23CC2C80CE060010F66C /* SpecialErrorPageUserScriptTests.swift */; }; + 567A23CF2C80CF4B0010F66C /* ErrorPageTabExtensionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E7C2BAB290E001CF69F /* ErrorPageTabExtensionTest.swift */; }; 567DA93F29E8045D008AC5EE /* MockEmailStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567DA93E29E8045D008AC5EE /* MockEmailStorage.swift */; }; 567DA94029E8045D008AC5EE /* MockEmailStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567DA93E29E8045D008AC5EE /* MockEmailStorage.swift */; }; 567DA94529E95C3F008AC5EE /* YoutubeOverlayUserScriptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567DA94429E95C3F008AC5EE /* YoutubeOverlayUserScriptTests.swift */; }; @@ -1514,16 +1522,13 @@ 56AC09C82C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */; }; 56B234BF2A84EFD200F2A1CC /* NavigationBarUrlExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B234BE2A84EFD200F2A1CC /* NavigationBarUrlExtensionsTests.swift */; }; 56B234C02A84EFD800F2A1CC /* NavigationBarUrlExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B234BE2A84EFD200F2A1CC /* NavigationBarUrlExtensionsTests.swift */; }; - 56BA1E752BAAF70F001CF69F /* SSLErrorPageTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E742BAAF70F001CF69F /* SSLErrorPageTabExtension.swift */; }; - 56BA1E762BAAF70F001CF69F /* SSLErrorPageTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E742BAAF70F001CF69F /* SSLErrorPageTabExtension.swift */; }; - 56BA1E7F2BAB2D29001CF69F /* ErrorPageTabExtensionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E7C2BAB290E001CF69F /* ErrorPageTabExtensionTest.swift */; }; + 56BA1E752BAAF70F001CF69F /* SpecialErrorPageTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E742BAAF70F001CF69F /* SpecialErrorPageTabExtension.swift */; }; + 56BA1E762BAAF70F001CF69F /* SpecialErrorPageTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E742BAAF70F001CF69F /* SpecialErrorPageTabExtension.swift */; }; 56BA1E802BAB2E43001CF69F /* ErrorPageTabExtensionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E7C2BAB290E001CF69F /* ErrorPageTabExtensionTest.swift */; }; - 56BA1E822BAC506F001CF69F /* SSLErrorPageUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E812BAC506F001CF69F /* SSLErrorPageUserScript.swift */; }; - 56BA1E832BAC506F001CF69F /* SSLErrorPageUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E812BAC506F001CF69F /* SSLErrorPageUserScript.swift */; }; - 56BA1E872BAC8239001CF69F /* SSLErrorPageUserScriptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E862BAC8239001CF69F /* SSLErrorPageUserScriptTests.swift */; }; - 56BA1E882BAC8239001CF69F /* SSLErrorPageUserScriptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E862BAC8239001CF69F /* SSLErrorPageUserScriptTests.swift */; }; 56BA1E8A2BB1CB5B001CF69F /* CertificateTrustEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E892BB1CB5B001CF69F /* CertificateTrustEvaluator.swift */; }; 56BA1E8B2BB1CB5B001CF69F /* CertificateTrustEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA1E892BB1CB5B001CF69F /* CertificateTrustEvaluator.swift */; }; + 56CE77612C7DFCF800AC1ED2 /* OnboardingSuggestedSearchesProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56CE77602C7DFCF800AC1ED2 /* OnboardingSuggestedSearchesProviderTests.swift */; }; + 56CE77622C7DFCF800AC1ED2 /* OnboardingSuggestedSearchesProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56CE77602C7DFCF800AC1ED2 /* OnboardingSuggestedSearchesProviderTests.swift */; }; 56CEE90E2B7A725B00CF10AA /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 56CEE90D2B7A6DE100CF10AA /* InfoPlist.xcstrings */; }; 56CEE90F2B7A725C00CF10AA /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 56CEE90D2B7A6DE100CF10AA /* InfoPlist.xcstrings */; }; 56D145E829E6BB6300E3488A /* CapturingDataImportProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D145E729E6BB6300E3488A /* CapturingDataImportProvider.swift */; }; @@ -1539,8 +1544,6 @@ 7B00997D2B6508B700FE7C31 /* NetworkProtectionProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 7B00997C2B6508B700FE7C31 /* NetworkProtectionProxy */; }; 7B00997F2B6508C200FE7C31 /* NetworkProtectionProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 7B00997E2B6508C200FE7C31 /* NetworkProtectionProxy */; }; 7B0099822B65C6B300FE7C31 /* MacTransparentProxyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0099802B65C6B300FE7C31 /* MacTransparentProxyProvider.swift */; }; - 7B01AC6E2C36BB7E004FADC7 /* VPNLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B01AC6D2C36BB7E004FADC7 /* VPNLogger.swift */; }; - 7B01AC6F2C36BB7E004FADC7 /* VPNLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B01AC6D2C36BB7E004FADC7 /* VPNLogger.swift */; }; 7B0694982B6E980F00FA4DBA /* VPNProxyLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0694972B6E980F00FA4DBA /* VPNProxyLauncher.swift */; }; 7B09CBA92BA4BE8100CF245B /* NetworkProtectionPixelEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B09CBA72BA4BE7000CF245B /* NetworkProtectionPixelEventTests.swift */; }; 7B09CBAA2BA4BE8200CF245B /* NetworkProtectionPixelEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B09CBA72BA4BE7000CF245B /* NetworkProtectionPixelEventTests.swift */; }; @@ -1702,7 +1705,7 @@ 85774B002A713D3B00DE0561 /* BookmarksBarMenuFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85774AFE2A713D3B00DE0561 /* BookmarksBarMenuFactory.swift */; }; 85774B032A71CDD000DE0561 /* BlockMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85774B022A71CDD000DE0561 /* BlockMenuItem.swift */; }; 85774B042A71CDD000DE0561 /* BlockMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85774B022A71CDD000DE0561 /* BlockMenuItem.swift */; }; - 85799C1825DEBB3F0007EC87 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85799C1725DEBB3F0007EC87 /* Logging.swift */; }; + 85799C1825DEBB3F0007EC87 /* Logger+Multiple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85799C1725DEBB3F0007EC87 /* Logger+Multiple.swift */; }; 857E44642A9F70F200ED77A7 /* CampaignVariantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857E44612A9F6F3500ED77A7 /* CampaignVariantTests.swift */; }; 857E44652A9F70F300ED77A7 /* CampaignVariantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857E44612A9F6F3500ED77A7 /* CampaignVariantTests.swift */; }; 857E5AF52A79045800FC0FB4 /* PixelExperiment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857E5AF42A79045800FC0FB4 /* PixelExperiment.swift */; }; @@ -2555,6 +2558,14 @@ BD384ACA2BBC821A00EF3735 /* vpn-light-mode.json in Resources */ = {isa = PBXBuildFile; fileRef = BD384AC72BBC821100EF3735 /* vpn-light-mode.json */; }; BD384ACB2BBC821B00EF3735 /* vpn-dark-mode.json in Resources */ = {isa = PBXBuildFile; fileRef = BD384AC82BBC821100EF3735 /* vpn-dark-mode.json */; }; BD384ACC2BBC821B00EF3735 /* vpn-light-mode.json in Resources */ = {isa = PBXBuildFile; fileRef = BD384AC72BBC821100EF3735 /* vpn-light-mode.json */; }; + BD7090CF2C5182FB009EED82 /* UnifiedFeedbackFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7090CE2C5182FB009EED82 /* UnifiedFeedbackFormView.swift */; }; + BD7090D02C5182FB009EED82 /* UnifiedFeedbackFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7090CE2C5182FB009EED82 /* UnifiedFeedbackFormView.swift */; }; + BD7090D22C52ECFE009EED82 /* UnifiedMetadataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7090D12C52ECFE009EED82 /* UnifiedMetadataCollector.swift */; }; + BD7090D32C52ECFE009EED82 /* UnifiedMetadataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7090D12C52ECFE009EED82 /* UnifiedMetadataCollector.swift */; }; + BD7090D62C540D5D009EED82 /* EmptyMetadataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7090D52C540D5D009EED82 /* EmptyMetadataCollector.swift */; }; + BD7090D72C540D5D009EED82 /* EmptyMetadataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7090D52C540D5D009EED82 /* EmptyMetadataCollector.swift */; }; + BD88A83E2C4F3E4300460A26 /* FeedbackCategoryProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD88A83D2C4F3E4300460A26 /* FeedbackCategoryProviding.swift */; }; + BD88A83F2C4F3E4300460A26 /* FeedbackCategoryProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD88A83D2C4F3E4300460A26 /* FeedbackCategoryProviding.swift */; }; BDA7647C2BC497BE00D0400C /* DefaultVPNLocationFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA7647B2BC497BE00D0400C /* DefaultVPNLocationFormatter.swift */; }; BDA7647D2BC497BE00D0400C /* DefaultVPNLocationFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA7647B2BC497BE00D0400C /* DefaultVPNLocationFormatter.swift */; }; BDA7647F2BC4998900D0400C /* DefaultVPNLocationFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA7647B2BC497BE00D0400C /* DefaultVPNLocationFormatter.swift */; }; @@ -2569,6 +2580,20 @@ BDADBDCB2BD2BC2800421B9B /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = BDADBDCA2BD2BC2800421B9B /* Lottie */; }; BDADBDCC2BD2BC4D00421B9B /* vpn-dark-mode.json in Resources */ = {isa = PBXBuildFile; fileRef = BD384AC82BBC821100EF3735 /* vpn-dark-mode.json */; }; BDADBDCD2BD2BC5700421B9B /* vpn-light-mode.json in Resources */ = {isa = PBXBuildFile; fileRef = BD384AC72BBC821100EF3735 /* vpn-light-mode.json */; }; + BDBA85902C5D252A00BC54F5 /* VPNFeedbackSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBA858F2C5D252A00BC54F5 /* VPNFeedbackSender.swift */; }; + BDBA85912C5D252A00BC54F5 /* VPNFeedbackSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBA858F2C5D252A00BC54F5 /* VPNFeedbackSender.swift */; }; + BDBA85932C5D255200BC54F5 /* VPNFeedbackCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBA85922C5D255200BC54F5 /* VPNFeedbackCategory.swift */; }; + BDBA85942C5D255200BC54F5 /* VPNFeedbackCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBA85922C5D255200BC54F5 /* VPNFeedbackCategory.swift */; }; + BDBA85962C5D256C00BC54F5 /* VPNFeedbackFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBA85952C5D256C00BC54F5 /* VPNFeedbackFormView.swift */; }; + BDBA85972C5D256C00BC54F5 /* VPNFeedbackFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBA85952C5D256C00BC54F5 /* VPNFeedbackFormView.swift */; }; + BDBA85992C5D258100BC54F5 /* VPNFeedbackFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBA85982C5D258100BC54F5 /* VPNFeedbackFormViewController.swift */; }; + BDBA859A2C5D258100BC54F5 /* VPNFeedbackFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBA85982C5D258100BC54F5 /* VPNFeedbackFormViewController.swift */; }; + BDBA859C2C5D25A300BC54F5 /* VPNFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBA859B2C5D25A300BC54F5 /* VPNFeedbackFormViewModel.swift */; }; + BDBA859D2C5D25A300BC54F5 /* VPNFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBA859B2C5D25A300BC54F5 /* VPNFeedbackFormViewModel.swift */; }; + BDBA859F2C5D25B700BC54F5 /* VPNMetadataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBA859E2C5D25B700BC54F5 /* VPNMetadataCollector.swift */; }; + BDBA85A02C5D25B700BC54F5 /* VPNMetadataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBA859E2C5D25B700BC54F5 /* VPNMetadataCollector.swift */; }; + BDCB66D82C7CE1A600E8ABC9 /* VPNFeedbackFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCB66D72C7CE1A600E8ABC9 /* VPNFeedbackFormViewModelTests.swift */; }; + BDCB66D92C7CE1A700E8ABC9 /* VPNFeedbackFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCB66D72C7CE1A600E8ABC9 /* VPNFeedbackFormViewModelTests.swift */; }; BDE981D92BBD10D600645880 /* vpn-dark-mode.json in Resources */ = {isa = PBXBuildFile; fileRef = BD384AC82BBC821100EF3735 /* vpn-dark-mode.json */; }; BDE981DA2BBD10D600645880 /* vpn-light-mode.json in Resources */ = {isa = PBXBuildFile; fileRef = BD384AC72BBC821100EF3735 /* vpn-light-mode.json */; }; C1372EF42BBC5BAD003F8793 /* SecureTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1372EF32BBC5BAD003F8793 /* SecureTextField.swift */; }; @@ -2679,6 +2704,7 @@ EEE50C2A2C38249C003DD7FF /* OptionalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE50C282C38249C003DD7FF /* OptionalExtension.swift */; }; EEF12E6F2A2111880023E6BF /* MacPacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */; }; EEF53E182950CED5002D78F4 /* JSAlertViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */; }; + F10C99422C7E20A1005568B4 /* Logger+DBPBackgroundAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17E7DDB2C7C7F8100907A84 /* Logger+DBPBackgroundAgent.swift */; }; F116A7C32BD1924B00F3FCF7 /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F116A7C22BD1924B00F3FCF7 /* PixelKitTestingUtilities */; }; F116A7C72BD1925500F3FCF7 /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F116A7C62BD1925500F3FCF7 /* PixelKitTestingUtilities */; }; F116A7C92BD1929000F3FCF7 /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = F116A7C82BD1929000F3FCF7 /* PixelKitTestingUtilities */; }; @@ -2688,6 +2714,13 @@ F118EA862BEACC7000F77634 /* NonStandardPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA842BEACC7000F77634 /* NonStandardPixel.swift */; }; F1476FC02C1359FB00EAE46A /* SubscriptionUIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */; }; F1476FC12C1359FB00EAE46A /* SubscriptionUIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */; }; + F17114822C7C98FB009836C1 /* Logger+Favicons.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17114812C7C98FB009836C1 /* Logger+Favicons.swift */; }; + F17114832C7C98FB009836C1 /* Logger+Favicons.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17114812C7C98FB009836C1 /* Logger+Favicons.swift */; }; + F17114852C7C9D28009836C1 /* Logger+Fire.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17114842C7C9D28009836C1 /* Logger+Fire.swift */; }; + F17114862C7C9D28009836C1 /* Logger+Fire.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17114842C7C9D28009836C1 /* Logger+Fire.swift */; }; + F17E7DDC2C7C7F8100907A84 /* Logger+DBPBackgroundAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17E7DDB2C7C7F8100907A84 /* Logger+DBPBackgroundAgent.swift */; }; + F17E7DDE2C7C83E500907A84 /* Logger+FileDownload.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17E7DDD2C7C83E500907A84 /* Logger+FileDownload.swift */; }; + F17E7DDF2C7C83E500907A84 /* Logger+FileDownload.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17E7DDD2C7C83E500907A84 /* Logger+FileDownload.swift */; }; F188267C2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; F188267D2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; }; F18826802BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; }; @@ -2732,6 +2765,9 @@ F1C70D812BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */; }; F1C70D822BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */; }; F1C70D832BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */; }; + F1CA67062C7DCA2300264E6A /* Logger+BitWarden.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1CA67052C7DCA2300264E6A /* Logger+BitWarden.swift */; }; + F1CA67072C7DCA2D00264E6A /* Logger+BitWarden.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1CA67052C7DCA2300264E6A /* Logger+BitWarden.swift */; }; + F1CA67082C7DE92200264E6A /* Logger+FileDownload.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17E7DDD2C7C83E500907A84 /* Logger+FileDownload.swift */; }; F1D0428E2BFB9F9C00A31506 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = F1D0428D2BFB9F9C00A31506 /* Subscription */; }; F1D042902BFB9FA300A31506 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = F1D0428F2BFB9FA300A31506 /* Subscription */; }; F1D042942BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */; }; @@ -3229,9 +3265,7 @@ 4B0511B4262CAA5A00F6079C /* FireproofDomainsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FireproofDomainsViewController.swift; sourceTree = ""; }; 4B0511DF262CAA8600F6079C /* NSOpenPanelExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSOpenPanelExtensions.swift; sourceTree = ""; }; 4B0511E0262CAA8600F6079C /* NSViewControllerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSViewControllerExtension.swift; sourceTree = ""; }; - 4B05265D2B1AE5C70054955A /* VPNMetadataCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNMetadataCollector.swift; sourceTree = ""; }; - 4B0526602B1D55320054955A /* VPNFeedbackSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackSender.swift; sourceTree = ""; }; - 4B0526632B1D55D80054955A /* VPNFeedbackCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackCategory.swift; sourceTree = ""; }; + 4B0526602B1D55320054955A /* UnifiedFeedbackSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedFeedbackSender.swift; sourceTree = ""; }; 4B0A63E7289DB58E00378EF7 /* FirefoxFaviconsReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirefoxFaviconsReader.swift; sourceTree = ""; }; 4B0AACAB28BC63ED001038AC /* ChromiumFaviconsReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromiumFaviconsReader.swift; sourceTree = ""; }; 4B0AACAD28BC6FD0001038AC /* SafariFaviconsReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariFaviconsReader.swift; sourceTree = ""; }; @@ -3271,15 +3305,13 @@ 4B41ED9F2B15437A001EEDF4 /* NetworkProtectionNotificationsPresenterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionNotificationsPresenterFactory.swift; sourceTree = ""; }; 4B41EDA22B1543B9001EEDF4 /* VPNPreferencesModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNPreferencesModel.swift; sourceTree = ""; }; 4B41EDA62B1543C9001EEDF4 /* PreferencesVPNView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesVPNView.swift; sourceTree = ""; }; - 4B41EDAD2B168AFF001EEDF4 /* VPNFeedbackFormViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormViewController.swift; sourceTree = ""; }; - 4B41EDB02B168B1E001EEDF4 /* VPNFeedbackFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormView.swift; sourceTree = ""; }; - 4B41EDB32B168C55001EEDF4 /* VPNFeedbackFormViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormViewModel.swift; sourceTree = ""; }; + 4B41EDAD2B168AFF001EEDF4 /* UnifiedFeedbackFormViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedFeedbackFormViewController.swift; sourceTree = ""; }; + 4B41EDB32B168C55001EEDF4 /* UnifiedFeedbackFormViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedFeedbackFormViewModel.swift; sourceTree = ""; }; 4B43468F285ED7A100177407 /* BookmarksBarViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksBarViewModelTests.swift; sourceTree = ""; }; 4B43469428655D1400177407 /* FirefoxDataImporterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirefoxDataImporterTests.swift; sourceTree = ""; }; 4B44FEF22B1FEF5A000619D8 /* FocusableTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusableTextEditor.swift; sourceTree = ""; }; 4B4BEC182A11B3EA001D9AC5 /* DuckDuckGoNotifications.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DuckDuckGoNotifications.xcconfig; sourceTree = ""; }; 4B4BEC202A11B4E2001D9AC5 /* DuckDuckGo Notifications.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DuckDuckGo Notifications.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 4B4BEC322A11B509001D9AC5 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; 4B4BEC332A11B509001D9AC5 /* DuckDuckGoNotifications.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DuckDuckGoNotifications.entitlements; sourceTree = ""; }; 4B4BEC342A11B509001D9AC5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 4B4BEC382A11B509001D9AC5 /* DuckDuckGoNotificationsAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckDuckGoNotificationsAppDelegate.swift; sourceTree = ""; }; @@ -3441,7 +3473,7 @@ 4BD18EFF283F0BC500058124 /* BookmarksBarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksBarViewController.swift; sourceTree = ""; }; 4BD18F04283F151F00058124 /* BookmarksBar.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = BookmarksBar.storyboard; sourceTree = ""; }; 4BDFA4AD27BF19E500648192 /* ToggleableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleableScrollView.swift; sourceTree = ""; }; - 4BE344ED2B2376DF003FC223 /* VPNFeedbackFormViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormViewModelTests.swift; sourceTree = ""; }; + 4BE344ED2B2376DF003FC223 /* UnifiedFeedbackFormViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedFeedbackFormViewModelTests.swift; sourceTree = ""; }; 4BE3A6C02C16BEB1003FC378 /* VPNRedditSessionWorkaround.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNRedditSessionWorkaround.swift; sourceTree = ""; }; 4BE4005227CF3DC3007D3161 /* SavePaymentMethodPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavePaymentMethodPopover.swift; sourceTree = ""; }; 4BE4005427CF3F19007D3161 /* SavePaymentMethodViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavePaymentMethodViewController.swift; sourceTree = ""; }; @@ -3471,6 +3503,8 @@ 5603D90529B7B746007F9F01 /* MockTabViewItemDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTabViewItemDelegate.swift; sourceTree = ""; }; 560C3FFB2BC9911000F589CE /* PermanentSurveyManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermanentSurveyManagerTests.swift; sourceTree = ""; }; 560C3FFE2BCD5A1E00F589CE /* PermanentSurveyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermanentSurveyManager.swift; sourceTree = ""; }; + 560EB9312C78946F0080DBC8 /* ContextualOnboardingDialogs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualOnboardingDialogs.swift; sourceTree = ""; }; + 560EB9382C789A450080DBC8 /* OnboardingSuggestedSearchesProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingSuggestedSearchesProvider.swift; sourceTree = ""; }; 5614B3A02BBD639D009B5031 /* ZoomPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomPopover.swift; sourceTree = ""; }; 561D29C02BDA7430007B91D0 /* MockSyncPausedStateManaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSyncPausedStateManaging.swift; sourceTree = ""; }; 561D29C42BDA749A007B91D0 /* MockDDGSyncing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDDGSyncing.swift; sourceTree = ""; }; @@ -3486,6 +3520,8 @@ 566B196029CDB7C9007E38F4 /* CapturingOptionsButtonMenuDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapturingOptionsButtonMenuDelegate.swift; sourceTree = ""; }; 566B73682BECBF8400FF1959 /* SyncAlertsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncAlertsPresenter.swift; sourceTree = ""; }; 566B736B2BECC3C600FF1959 /* SyncPausedStateManaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPausedStateManaging.swift; sourceTree = ""; }; + 567A23BD2C7F539C0010F66C /* SpecialErrorPageUserScriptExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialErrorPageUserScriptExtension.swift; sourceTree = ""; }; + 567A23CC2C80CE060010F66C /* SpecialErrorPageUserScriptTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialErrorPageUserScriptTests.swift; sourceTree = ""; }; 567DA93E29E8045D008AC5EE /* MockEmailStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockEmailStorage.swift; sourceTree = ""; }; 567DA94429E95C3F008AC5EE /* YoutubeOverlayUserScriptTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YoutubeOverlayUserScriptTests.swift; sourceTree = ""; }; 5681ED3F2BDB955100F59729 /* SyncCredentialsAdapterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncCredentialsAdapterTests.swift; sourceTree = ""; }; @@ -3512,11 +3548,10 @@ 56A054522C2592CE007D8FAB /* OnboardingUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingUITests.swift; sourceTree = ""; }; 56AC09C62C2D7DD6002D70E0 /* BookmarksBarViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksBarViewControllerTests.swift; sourceTree = ""; }; 56B234BE2A84EFD200F2A1CC /* NavigationBarUrlExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarUrlExtensionsTests.swift; sourceTree = ""; }; - 56BA1E742BAAF70F001CF69F /* SSLErrorPageTabExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSLErrorPageTabExtension.swift; sourceTree = ""; }; + 56BA1E742BAAF70F001CF69F /* SpecialErrorPageTabExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialErrorPageTabExtension.swift; sourceTree = ""; }; 56BA1E7C2BAB290E001CF69F /* ErrorPageTabExtensionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPageTabExtensionTest.swift; sourceTree = ""; }; - 56BA1E812BAC506F001CF69F /* SSLErrorPageUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSLErrorPageUserScript.swift; sourceTree = ""; }; - 56BA1E862BAC8239001CF69F /* SSLErrorPageUserScriptTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSLErrorPageUserScriptTests.swift; sourceTree = ""; }; 56BA1E892BB1CB5B001CF69F /* CertificateTrustEvaluator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateTrustEvaluator.swift; sourceTree = ""; }; + 56CE77602C7DFCF800AC1ED2 /* OnboardingSuggestedSearchesProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingSuggestedSearchesProviderTests.swift; sourceTree = ""; }; 56CEE9092B7A66C500CF10AA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56CEE90D2B7A6DE100CF10AA /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; 56D145E729E6BB6300E3488A /* CapturingDataImportProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapturingDataImportProvider.swift; sourceTree = ""; }; @@ -3525,7 +3560,6 @@ 56D145F029E6F06D00E3488A /* MockBookmarkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBookmarkManager.swift; sourceTree = ""; }; 56D6A3D529DB2BAB0055215A /* ContinueSetUpView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContinueSetUpView.swift; sourceTree = ""; }; 7B0099802B65C6B300FE7C31 /* MacTransparentProxyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacTransparentProxyProvider.swift; sourceTree = ""; }; - 7B01AC6D2C36BB7E004FADC7 /* VPNLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNLogger.swift; sourceTree = ""; }; 7B05829D2A812AC000AC3F7C /* NetworkProtectionOnboardingMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionOnboardingMenu.swift; sourceTree = ""; }; 7B0694972B6E980F00FA4DBA /* VPNProxyLauncher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNProxyLauncher.swift; sourceTree = ""; }; 7B09CBA72BA4BE7000CF245B /* NetworkProtectionPixelEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionPixelEventTests.swift; sourceTree = ""; }; @@ -3631,7 +3665,7 @@ 85707F30276A7DCA00DC0649 /* OnboardingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModel.swift; sourceTree = ""; }; 85774AFE2A713D3B00DE0561 /* BookmarksBarMenuFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksBarMenuFactory.swift; sourceTree = ""; }; 85774B022A71CDD000DE0561 /* BlockMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockMenuItem.swift; sourceTree = ""; }; - 85799C1725DEBB3F0007EC87 /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; + 85799C1725DEBB3F0007EC87 /* Logger+Multiple.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Logger+Multiple.swift"; sourceTree = ""; }; 857E44612A9F6F3500ED77A7 /* CampaignVariantTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CampaignVariantTests.swift; sourceTree = ""; }; 857E5AF42A79045800FC0FB4 /* PixelExperiment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelExperiment.swift; sourceTree = ""; }; 857E5AF82A79618100FC0FB4 /* PixelExperimentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelExperimentTests.swift; sourceTree = ""; }; @@ -4244,9 +4278,20 @@ BBFF355C2C4AF26200DA3289 /* BookmarksSortModeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksSortModeTests.swift; sourceTree = ""; }; BD384AC72BBC821100EF3735 /* vpn-light-mode.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "vpn-light-mode.json"; sourceTree = ""; }; BD384AC82BBC821100EF3735 /* vpn-dark-mode.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "vpn-dark-mode.json"; sourceTree = ""; }; + BD7090CE2C5182FB009EED82 /* UnifiedFeedbackFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedFeedbackFormView.swift; sourceTree = ""; }; + BD7090D12C52ECFE009EED82 /* UnifiedMetadataCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedMetadataCollector.swift; sourceTree = ""; }; + BD7090D52C540D5D009EED82 /* EmptyMetadataCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyMetadataCollector.swift; sourceTree = ""; }; + BD88A83D2C4F3E4300460A26 /* FeedbackCategoryProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackCategoryProviding.swift; sourceTree = ""; }; BDA7647B2BC497BE00D0400C /* DefaultVPNLocationFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultVPNLocationFormatter.swift; sourceTree = ""; }; BDA7648C2BC4E4EF00D0400C /* DefaultVPNLocationFormatterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultVPNLocationFormatterTests.swift; sourceTree = ""; }; BDA764902BC4E57200D0400C /* MockVPNLocationFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockVPNLocationFormatter.swift; sourceTree = ""; }; + BDBA858F2C5D252A00BC54F5 /* VPNFeedbackSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackSender.swift; sourceTree = ""; }; + BDBA85922C5D255200BC54F5 /* VPNFeedbackCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackCategory.swift; sourceTree = ""; }; + BDBA85952C5D256C00BC54F5 /* VPNFeedbackFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormView.swift; sourceTree = ""; }; + BDBA85982C5D258100BC54F5 /* VPNFeedbackFormViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormViewController.swift; sourceTree = ""; }; + BDBA859B2C5D25A300BC54F5 /* VPNFeedbackFormViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormViewModel.swift; sourceTree = ""; }; + BDBA859E2C5D25B700BC54F5 /* VPNMetadataCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNMetadataCollector.swift; sourceTree = ""; }; + BDCB66D72C7CE1A600E8ABC9 /* VPNFeedbackFormViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormViewModelTests.swift; sourceTree = ""; }; C1372EF32BBC5BAD003F8793 /* SecureTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureTextField.swift; sourceTree = ""; }; C13909EE2B85FD4E001626ED /* AutofillActionExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillActionExecutor.swift; sourceTree = ""; }; C13909F32B85FD79001626ED /* AutofillDeleteAllPasswordsExecutorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillDeleteAllPasswordsExecutorTests.swift; sourceTree = ""; }; @@ -4303,6 +4348,10 @@ F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift"; sourceTree = ""; }; F118EA842BEACC7000F77634 /* NonStandardPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonStandardPixel.swift; sourceTree = ""; }; F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandler.swift; sourceTree = ""; }; + F17114812C7C98FB009836C1 /* Logger+Favicons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Favicons.swift"; sourceTree = ""; }; + F17114842C7C9D28009836C1 /* Logger+Fire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Fire.swift"; sourceTree = ""; }; + F17E7DDB2C7C7F8100907A84 /* Logger+DBPBackgroundAgent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+DBPBackgroundAgent.swift"; sourceTree = ""; }; + F17E7DDD2C7C83E500907A84 /* Logger+FileDownload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+FileDownload.swift"; sourceTree = ""; }; F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPixel.swift; sourceTree = ""; }; F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProPixel.swift; sourceTree = ""; }; F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PixelKit+Assertion.swift"; sourceTree = ""; }; @@ -4314,6 +4363,7 @@ F1C5763D2BFF972900C78647 /* SubscriptionUIHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandling.swift; sourceTree = ""; }; F1C70D782BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionLoginItemInterface.swift; sourceTree = ""; }; F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SubscriptionEnvironment+Default.swift"; sourceTree = ""; }; + F1CA67052C7DCA2300264E6A /* Logger+BitWarden.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+BitWarden.swift"; sourceTree = ""; }; F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataBrokerProtectionSettings+Environment.swift"; sourceTree = ""; }; F1D042982BFBABA100A31506 /* SubscriptionManager+StandardConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SubscriptionManager+StandardConfiguration.swift"; sourceTree = ""; }; F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainMenuActions+VanillaBrowser.swift"; sourceTree = ""; }; @@ -4361,10 +4411,12 @@ 371209252C232E6C003ADF3D /* RemoteMessaging in Frameworks */, 4BCBE45A2BA7E17800FC75A1 /* Subscription in Frameworks */, B6EC37FF29B8D915001ACE79 /* Configuration in Frameworks */, + 567A23C52C7F75BB0010F66C /* SpecialErrorPages in Frameworks */, 372217822B33380700B8E9C2 /* TestUtils in Frameworks */, 3706FCAA293F65D500E42796 /* UserScript in Frameworks */, 85E2BBD02B8F534A00DBEC7A /* History in Frameworks */, 4BF97AD52B43C43F00EB4240 /* NetworkProtection in Frameworks */, + 560EB9372C78974C0080DBC8 /* Onboarding in Frameworks */, 3739326529AE4B39009346AE /* DDGSync in Frameworks */, D6BC8AC82C5A95B10025375B /* DuckPlayer in Frameworks */, 37DF000729F9C061002B7D3E /* SyncDataProviders in Frameworks */, @@ -4417,6 +4469,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4B5235452C7BB14D00AFAF64 /* WireGuard in Frameworks */, 37269F012B332FC8005E8E46 /* Common in Frameworks */, 9D9DE57B2C63AA1F00D20B15 /* AppKitExtensions in Frameworks */, EE7295E92A545BC4008C0991 /* NetworkProtection in Frameworks */, @@ -4481,6 +4534,7 @@ buildActionMask = 2147483647; files = ( F1DF95E52BD1807C0045E591 /* Subscription in Frameworks */, + 4B5235472C7BB15700AFAF64 /* WireGuard in Frameworks */, 37269EFF2B332FBB005E8E46 /* Common in Frameworks */, EE7295E72A545BBB008C0991 /* NetworkProtection in Frameworks */, F198C7162BD18A44000BF24D /* PixelKit in Frameworks */, @@ -4549,8 +4603,10 @@ files = ( C18BF9CC2C73678500ED6B8A /* Freemium in Frameworks */, F1DF95E32BD1807C0045E591 /* Crashes in Frameworks */, + 560EB9352C7897370080DBC8 /* Onboarding in Frameworks */, 85E2BBCE2B8F534000DBEC7A /* History in Frameworks */, 1EA7B8D32B7E078C000330A4 /* SubscriptionUI in Frameworks */, + 567A23C12C7F71570010F66C /* SpecialErrorPages in Frameworks */, B6F7128129F681EB00594A45 /* QuickLookUI.framework in Frameworks */, EE7295E32A545B9A008C0991 /* NetworkProtection in Frameworks */, 9807F645278CA16F00E1547B /* BrowserServicesKit in Frameworks */, @@ -4724,14 +4780,6 @@ path = Services; sourceTree = ""; }; - 1D43EAFF291D7D280065E5D6 /* Logging */ = { - isa = PBXGroup; - children = ( - 85799C1725DEBB3F0007EC87 /* Logging.swift */, - ); - path = Logging; - sourceTree = ""; - }; 1D72D5902BFF361700AEDE36 /* Updates */ = { isa = PBXGroup; children = ( @@ -5325,17 +5373,17 @@ path = Surveys; sourceTree = ""; }; - 4B41EDAC2B168A66001EEDF4 /* VPNFeedbackForm */ = { + 4B41EDAC2B168A66001EEDF4 /* UnifiedFeedbackForm */ = { isa = PBXGroup; children = ( - 4B41EDAD2B168AFF001EEDF4 /* VPNFeedbackFormViewController.swift */, - 4B41EDB02B168B1E001EEDF4 /* VPNFeedbackFormView.swift */, - 4B41EDB32B168C55001EEDF4 /* VPNFeedbackFormViewModel.swift */, - 4B0526632B1D55D80054955A /* VPNFeedbackCategory.swift */, - 4B05265D2B1AE5C70054955A /* VPNMetadataCollector.swift */, - 4B0526602B1D55320054955A /* VPNFeedbackSender.swift */, + BD88A83D2C4F3E4300460A26 /* FeedbackCategoryProviding.swift */, + 4B41EDAD2B168AFF001EEDF4 /* UnifiedFeedbackFormViewController.swift */, + 4B41EDB32B168C55001EEDF4 /* UnifiedFeedbackFormViewModel.swift */, + BD7090D42C540C0D009EED82 /* MetadataCollectors */, + 4B0526602B1D55320054955A /* UnifiedFeedbackSender.swift */, + BD7090CE2C5182FB009EED82 /* UnifiedFeedbackFormView.swift */, ); - path = VPNFeedbackForm; + path = UnifiedFeedbackForm; sourceTree = ""; }; 4B43468D285ED6BD00177407 /* BookmarksBar */ = { @@ -5361,7 +5409,6 @@ children = ( 7B5291882A1697680022E406 /* Info.plist */, 4B4BEC382A11B509001D9AC5 /* DuckDuckGoNotificationsAppDelegate.swift */, - 4B4BEC322A11B509001D9AC5 /* Logging.swift */, 4B4BEC342A11B509001D9AC5 /* Assets.xcassets */, 4B4BEC332A11B509001D9AC5 /* DuckDuckGoNotifications.entitlements */, ); @@ -5500,7 +5547,6 @@ EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */, 7B0099802B65C6B300FE7C31 /* MacTransparentProxyProvider.swift */, EE66418B2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift */, - 7B01AC6D2C36BB7E004FADC7 /* VPNLogger.swift */, ); path = NetworkExtensionTargets; sourceTree = ""; @@ -5985,6 +6031,7 @@ 7B09CBA72BA4BE7000CF245B /* NetworkProtectionPixelEventTests.swift */, BDA7648C2BC4E4EF00D0400C /* DefaultVPNLocationFormatterTests.swift */, 7B4C5CF42BE51D640007A164 /* VPNUninstallerTests.swift */, + BDCB66D72C7CE1A600E8ABC9 /* VPNFeedbackFormViewModelTests.swift */, ); path = NetworkProtection; sourceTree = ""; @@ -6004,12 +6051,12 @@ path = View; sourceTree = ""; }; - 4BE344EC2B2376AE003FC223 /* VPNFeedbackForm */ = { + 4BE344EC2B2376AE003FC223 /* UnifiedFeedbackForm */ = { isa = PBXGroup; children = ( - 4BE344ED2B2376DF003FC223 /* VPNFeedbackFormViewModelTests.swift */, + 4BE344ED2B2376DF003FC223 /* UnifiedFeedbackFormViewModelTests.swift */, ); - path = VPNFeedbackForm; + path = UnifiedFeedbackForm; sourceTree = ""; }; 4BF6961B28BE90E800D402D4 /* HomePage */ = { @@ -6051,6 +6098,15 @@ path = View; sourceTree = ""; }; + 560EB9302C78943E0080DBC8 /* ContextualOnboarding */ = { + isa = PBXGroup; + children = ( + 560EB9312C78946F0080DBC8 /* ContextualOnboardingDialogs.swift */, + 560EB9382C789A450080DBC8 /* OnboardingSuggestedSearchesProvider.swift */, + ); + path = ContextualOnboarding; + sourceTree = ""; + }; 561D29BF2BDA7419007B91D0 /* Mocks */ = { isa = PBXGroup; children = ( @@ -6129,14 +6185,6 @@ path = DuckSchemeHandler; sourceTree = ""; }; - 56BA1E852BAC820D001CF69F /* UserScripts */ = { - isa = PBXGroup; - children = ( - 56BA1E862BAC8239001CF69F /* SSLErrorPageUserScriptTests.swift */, - ); - path = UserScripts; - sourceTree = ""; - }; 7B1E819A27C8874900FF0E60 /* Autofill */ = { isa = PBXGroup; children = ( @@ -6282,6 +6330,7 @@ 8556A60C256C15C60092FA9D /* FileDownload */ = { isa = PBXGroup; children = ( + F17E7DDD2C7C83E500907A84 /* Logger+FileDownload.swift */, B6B1E87C26D5DA020062C350 /* View */, B61EF3EA266F91D700B4D78F /* Extensions */, 8556A615256C15E10092FA9D /* Model */, @@ -6467,6 +6516,7 @@ 85B7184727677A7D00B4277F /* Onboarding */ = { isa = PBXGroup; children = ( + 560EB9302C78943E0080DBC8 /* ContextualOnboarding */, 85707F2F276A7DB000DC0649 /* ViewModel */, 85B7184827677A9200B4277F /* View */, 56A053FB2C19E8F7007D8FAB /* OnboardingActionsManager.swift */, @@ -6579,6 +6629,7 @@ 56A0542F2C2043C8007D8FAB /* OnboardingTabExtensionTests.swift */, 56A054402C22438C007D8FAB /* OnboardingNavigatingTests.swift */, 56A054432C2252CE007D8FAB /* OnboardingUserScriptTests.swift */, + 56CE77602C7DFCF800AC1ED2 /* OnboardingSuggestedSearchesProviderTests.swift */, ); path = Onboarding; sourceTree = ""; @@ -6633,6 +6684,7 @@ isa = PBXGroup; children = ( 9D9AE9152AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift */, + F17E7DDB2C7C7F8100907A84 /* Logger+DBPBackgroundAgent.swift */, 9D9AE9172AAA3B450026E7DC /* UserText.swift */, 31ECDA102BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift */, 9D9AE9162AAA3B450026E7DC /* Assets.xcassets */, @@ -6960,7 +7012,8 @@ B6040859274B8C5200680351 /* UnprotectedDomains */, 1D72D5902BFF361700AEDE36 /* Updates */, AACF6FD426BC35C200CF09F9 /* UserAgent */, - 4B41EDAC2B168A66001EEDF4 /* VPNFeedbackForm */, + BDBA858E2C5D24EF00BC54F5 /* VPNFeedbackForm */, + 4B41EDAC2B168A66001EEDF4 /* UnifiedFeedbackForm */, 4B9DB0062A983B23000927DB /* Waitlist */, AA6EF9AE25066F99004754E6 /* Windows */, 31F28C4B28C8EE9000119F70 /* YoutubePlayer */, @@ -6972,7 +7025,6 @@ isa = PBXGroup; children = ( 56A054392C20876F007D8FAB /* DuckSchemeHandler */, - 56BA1E852BAC820D001CF69F /* UserScripts */, C13909F22B85FD60001626ED /* Autofill */, 5629846D2AC460DF00AC20EB /* Sync */, B6A5A28C25B962CB00AA7ADA /* App */, @@ -7022,7 +7074,7 @@ 4B9DB04D2A983B55000927DB /* Waitlist */, 3776582B27F7163B009A6B35 /* WebsiteBreakageReport */, 376718FE28E58504003A2A15 /* YoutubePlayer */, - 4BE344EC2B2376AE003FC223 /* VPNFeedbackForm */, + 4BE344EC2B2376AE003FC223 /* UnifiedFeedbackForm */, AA585D96248FD31400E9A3E2 /* Info.plist */, ); path = UnitTests; @@ -7047,6 +7099,7 @@ AA5FA698275F90CD00DCE9C9 /* Model */, AA5FA69B275F944500DCE9C9 /* Services */, 4BBD3BFF285ACE090047A89D /* NSNotificationName+Favicons.swift */, + F17114812C7C98FB009836C1 /* Logger+Favicons.swift */, ); path = Favicons; sourceTree = ""; @@ -7143,6 +7196,7 @@ AA6820E825503A21005ED0D5 /* Fire */ = { isa = PBXGroup; children = ( + F17114842C7C9D28009836C1 /* Logger+Fire.swift */, AAFCB38325E546FF00859DD4 /* View */, AA6820EF25503D93005ED0D5 /* ViewModel */, AA6820E925503A49005ED0D5 /* Model */, @@ -7193,6 +7247,7 @@ 4BBDEE8D28FC14760092FAA6 /* View */, 4BBDEE8B28FC14760092FAA6 /* Model */, 1D43EAFE291D29E40065E5D6 /* Services */, + F1CA67052C7DCA2300264E6A /* Logger+BitWarden.swift */, ); path = Bitwarden; sourceTree = ""; @@ -7284,7 +7339,6 @@ AADC60E92493B305008F8EF7 /* Extensions */, 4BA1A691258B06F600F6F690 /* FileSystem */, AA80EC52256BE33A007083E7 /* Localizables */, - 1D43EAFF291D7D280065E5D6 /* Logging */, 85AC3B3325DA828900C7D2AA /* Network */, 1D8057C62A83CAD500F4FED6 /* OsVersion */, 4BB88B4E25B7BA20006F6B06 /* Utilities */, @@ -7292,6 +7346,7 @@ AA86491424D831C4001BABEE /* View */, 4B37EE5B2B4CFC3C00A89A61 /* Surveys */, B65E5DAE2B74DE6D00480415 /* TrackerNetwork.swift */, + 85799C1725DEBB3F0007EC87 /* Logger+Multiple.swift */, ); path = Common; sourceTree = ""; @@ -8088,10 +8143,11 @@ B66260DC29AC5D4300E9E3EE /* NavigationProtectionTabExtension.swift */, B6F1B0292BCE675C005E863C /* NetworkProtectionControllerTabExtension.swift */, B6BF5D842946FFDA006742B1 /* PrivacyDashboardTabExtension.swift */, - 56BA1E742BAAF70F001CF69F /* SSLErrorPageTabExtension.swift */, + 56BA1E742BAAF70F001CF69F /* SpecialErrorPageTabExtension.swift */, B6BDDA002942389000F68088 /* TabExtensions.swift */, 1D9A4E592B43213B00F449E2 /* TabSnapshotExtension.swift */, B626A76C29928B1600053070 /* TestsClosureNavigationResponder.swift */, + 567A23BD2C7F539C0010F66C /* SpecialErrorPageUserScriptExtension.swift */, ); path = TabExtensions; sourceTree = ""; @@ -8169,7 +8225,6 @@ isa = PBXGroup; children = ( B684121B2B6A1D880092F66A /* ErrorPageHTMLTemplate.swift */, - 56BA1E812BAC506F001CF69F /* SSLErrorPageUserScript.swift */, ); path = ErrorPage; sourceTree = ""; @@ -8352,6 +8407,7 @@ 1D1C36E529FB019C001FA40C /* HistoryTabExtensionTests.swift */, 1D8C2FE42B70F4C4005E4BBD /* TabSnapshotExtensionTests.swift */, 56BA1E7C2BAB290E001CF69F /* ErrorPageTabExtensionTest.swift */, + 567A23CC2C80CE060010F66C /* SpecialErrorPageUserScriptTests.swift */, ); path = TabExtensionsTests; sourceTree = ""; @@ -8437,6 +8493,15 @@ path = View; sourceTree = ""; }; + BD7090D42C540C0D009EED82 /* MetadataCollectors */ = { + isa = PBXGroup; + children = ( + BD7090D12C52ECFE009EED82 /* UnifiedMetadataCollector.swift */, + BD7090D52C540D5D009EED82 /* EmptyMetadataCollector.swift */, + ); + path = MetadataCollectors; + sourceTree = ""; + }; BDA7648F2BC4E56200D0400C /* Mocks */ = { isa = PBXGroup; children = ( @@ -8445,6 +8510,19 @@ path = Mocks; sourceTree = ""; }; + BDBA858E2C5D24EF00BC54F5 /* VPNFeedbackForm */ = { + isa = PBXGroup; + children = ( + BDBA858F2C5D252A00BC54F5 /* VPNFeedbackSender.swift */, + BDBA85922C5D255200BC54F5 /* VPNFeedbackCategory.swift */, + BDBA85952C5D256C00BC54F5 /* VPNFeedbackFormView.swift */, + BDBA85982C5D258100BC54F5 /* VPNFeedbackFormViewController.swift */, + BDBA859B2C5D25A300BC54F5 /* VPNFeedbackFormViewModel.swift */, + BDBA859E2C5D25B700BC54F5 /* VPNMetadataCollector.swift */, + ); + path = VPNFeedbackForm; + sourceTree = ""; + }; BDE981DB2BBD110800645880 /* Assets */ = { isa = PBXGroup; children = ( @@ -8689,7 +8767,9 @@ 371209242C232E6C003ADF3D /* RemoteMessaging */, D6BC8AC72C5A95B10025375B /* DuckPlayer */, 9D9DE5742C63AA0C00D20B15 /* AppKitExtensions */, + 560EB9362C78974C0080DBC8 /* Onboarding */, C18BF9CD2C73678C00ED6B8A /* Freemium */, + 567A23C42C7F75BB0010F66C /* SpecialErrorPages */, ); productName = DuckDuckGo; productReference = 3706FD05293F65D500E42796 /* DuckDuckGo App Store.app */; @@ -8809,6 +8889,7 @@ 7B37C7A42BAA32A50062546A /* Subscription */, F198C7172BD18A4C000BF24D /* PixelKit */, 9D9DE57A2C63AA1F00D20B15 /* AppKitExtensions */, + 4B5235442C7BB14D00AFAF64 /* WireGuard */, ); productName = NetworkProtectionSystemExtension; productReference = 4B25375A2A11BE7300610219 /* com.duckduckgo.macos.vpn.network-extension.debug.systemextension */; @@ -8930,6 +9011,7 @@ F198C7152BD18A44000BF24D /* PixelKit */, 7B2366872C09FADA002D393F /* VPNAppLauncher */, 9D9DE5762C63AA1600D20B15 /* AppKitExtensions */, + 4B5235462C7BB15700AFAF64 /* WireGuard */, ); productName = NetworkProtectionAppExtension; productReference = 4B4D603D2A0B290200BCD287 /* NetworkProtectionAppExtension.appex */; @@ -9104,7 +9186,9 @@ 371209222C232E66003ADF3D /* RemoteMessaging */, D6BC8AC52C5A95AA0025375B /* DuckPlayer */, 9D9DE5722C63AA0700D20B15 /* AppKitExtensions */, + 560EB9342C7897370080DBC8 /* Onboarding */, C18BF9CB2C73678500ED6B8A /* Freemium */, + 567A23C02C7F71570010F66C /* SpecialErrorPages */, ); productName = DuckDuckGo; productReference = AA585D7E248FD31100E9A3E2 /* DuckDuckGo.app */; @@ -9273,6 +9357,7 @@ B6F997B92B8F352500476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */, F1D43AF12B98E47800BAB743 /* XCRemoteSwiftPackageReference "BareBonesBrowser" */, 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */, + 4B5235432C7BB14D00AFAF64 /* XCRemoteSwiftPackageReference "wireguard-apple" */, ); productRefGroup = AA585D7F248FD31100E9A3E2 /* Products */; projectDirPath = ""; @@ -9921,7 +10006,7 @@ 3706FA85293F65D500E42796 /* KeyedCodingExtension.swift in Sources */, 3706FA87293F65D500E42796 /* DownloadListStore.swift in Sources */, 37197EAB2942443D00394917 /* WebViewContainerView.swift in Sources */, - 3706FA88293F65D500E42796 /* Logging.swift in Sources */, + 3706FA88293F65D500E42796 /* Logger+Multiple.swift in Sources */, 3706FA89293F65D500E42796 /* CrashReportPromptPresenter.swift in Sources */, 3706FA8B293F65D500E42796 /* PreferencesRootView.swift in Sources */, 3706FA8C293F65D500E42796 /* AppStateChangedPublisher.swift in Sources */, @@ -9956,6 +10041,7 @@ 7BFF35742C10D75000F89673 /* IPCServiceLauncher.swift in Sources */, F1D042A22BFBB4DD00A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */, 3706FAA3293F65D500E42796 /* CrashReportPromptViewController.swift in Sources */, + BD88A83F2C4F3E4300460A26 /* FeedbackCategoryProviding.swift in Sources */, 3706FAA4293F65D500E42796 /* ContextMenuManager.swift in Sources */, 31AA6B982B960BA50025014E /* DataBrokerProtectionLoginItemPixels.swift in Sources */, 3706FAA5293F65D500E42796 /* GradientView.swift in Sources */, @@ -9983,6 +10069,7 @@ 3706FAB9293F65D500E42796 /* TabBarViewController.swift in Sources */, 56A054202C1CA1F5007D8FAB /* OnboardingTabExtension.swift in Sources */, 3706FABA293F65D500E42796 /* BookmarkOutlineViewDataSource.swift in Sources */, + 560EB9332C78946F0080DBC8 /* ContextualOnboardingDialogs.swift in Sources */, 3706FABB293F65D500E42796 /* PasswordManagementBitwardenItemView.swift in Sources */, 1D220BF92B86192200F8BBC6 /* PreferencesEmailProtectionView.swift in Sources */, 9FA173E42B7A12B600EE4E6E /* BookmarkDialogFolderManagementView.swift in Sources */, @@ -10096,7 +10183,6 @@ 3706FB05293F65D500E42796 /* Favicons.xcdatamodeld in Sources */, 3706FB07293F65D500E42796 /* Publisher.asVoid.swift in Sources */, 3706FB08293F65D500E42796 /* NavigationButtonMenuDelegate.swift in Sources */, - 4BF97ADB2B43C5E000EB4240 /* VPNMetadataCollector.swift in Sources */, 1DDC84F82B83558F00670238 /* PreferencesPrivateSearchView.swift in Sources */, 3706FB09293F65D500E42796 /* CrashReport.swift in Sources */, 1E0C72072ABC63BD00802009 /* SubscriptionPagesUserScript.swift in Sources */, @@ -10160,7 +10246,8 @@ 3706FB35293F65D500E42796 /* FlatButton.swift in Sources */, 3706FB36293F65D500E42796 /* PinnedTabView.swift in Sources */, 3706FB37293F65D500E42796 /* DataEncryption.swift in Sources */, - 56BA1E762BAAF70F001CF69F /* SSLErrorPageTabExtension.swift in Sources */, + 56BA1E762BAAF70F001CF69F /* SpecialErrorPageTabExtension.swift in Sources */, + BD7090D32C52ECFE009EED82 /* UnifiedMetadataCollector.swift in Sources */, 4B9DB0362A983B24000927DB /* WaitlistTermsAndConditionsView.swift in Sources */, 37197EA82942443D00394917 /* BrowserTabViewController.swift in Sources */, 3706FB39293F65D500E42796 /* PrivacyDashboardPopover.swift in Sources */, @@ -10213,6 +10300,7 @@ 3706FB5D293F65D500E42796 /* VisitMenuItem.swift in Sources */, 3706FB5E293F65D500E42796 /* EncryptionKeyStore.swift in Sources */, F1DA51872BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift in Sources */, + BDBA85A02C5D25B700BC54F5 /* VPNMetadataCollector.swift in Sources */, 3706FB60293F65D500E42796 /* PasswordManagementIdentityItemView.swift in Sources */, 3706FB61293F65D500E42796 /* ProgressExtension.swift in Sources */, 3706FB62293F65D500E42796 /* CSVParser.swift in Sources */, @@ -10247,6 +10335,7 @@ 37CBCA9B2A8966E60050218F /* SyncSettingsAdapter.swift in Sources */, 3707C72A294B5D2900682A9F /* URLExtension.swift in Sources */, 3706FB76293F65D500E42796 /* ASN1Parser.swift in Sources */, + F17E7DDF2C7C83E500907A84 /* Logger+FileDownload.swift in Sources */, 9F56CFAA2B82DC4300BB7F11 /* AddEditBookmarkFolderView.swift in Sources */, 37FD78122A29EBD100B36DB1 /* SyncErrorHandler.swift in Sources */, 987799F42999993C005D8EB6 /* LegacyBookmarksStoreMigration.swift in Sources */, @@ -10271,6 +10360,7 @@ 3706FB82293F65D500E42796 /* PasswordManagementNoteItemView.swift in Sources */, 4BF97AD82B43C5B300EB4240 /* NetworkProtectionAppEvents.swift in Sources */, 3706FEC5293F6F0600E42796 /* BWInstallationService.swift in Sources */, + BDBA85972C5D256C00BC54F5 /* VPNFeedbackFormView.swift in Sources */, 3775912E29AAC72700E26367 /* SyncPreferences.swift in Sources */, 3706FB83293F65D500E42796 /* NSApplicationExtension.swift in Sources */, 37197EA42942441D00394917 /* NewWindowPolicy.swift in Sources */, @@ -10310,6 +10400,7 @@ B690152D2ACBF4DA00AD0BAB /* MenuPreview.swift in Sources */, 1D36F4252A3B85C50052B527 /* TabCleanupPreparer.swift in Sources */, 3706FB97293F65D500E42796 /* ActionSpeech.swift in Sources */, + BD7090D72C540D5D009EED82 /* EmptyMetadataCollector.swift in Sources */, B6AFE6BD29A5D621002FF962 /* HTTPSUpgradeTabExtension.swift in Sources */, 3706FB9A293F65D500E42796 /* FireproofDomainsStore.swift in Sources */, 3706FB9B293F65D500E42796 /* PrivacyDashboardPermissionHandler.swift in Sources */, @@ -10335,7 +10426,7 @@ 3706FBA4293F65D500E42796 /* ContentOverlayPopover.swift in Sources */, 3706FBA5293F65D500E42796 /* TabShadowView.swift in Sources */, 3706FBA7293F65D500E42796 /* EncryptedValueTransformer.swift in Sources */, - 4B41EDAF2B168AFF001EEDF4 /* VPNFeedbackFormViewController.swift in Sources */, + 4B41EDAF2B168AFF001EEDF4 /* UnifiedFeedbackFormViewController.swift in Sources */, 31EF1E802B63FFA800E6DB17 /* DBPHomeViewController.swift in Sources */, 3706FBA8293F65D500E42796 /* PasteboardBookmark.swift in Sources */, 3706FBA9293F65D500E42796 /* PinnedTabsManager.swift in Sources */, @@ -10345,6 +10436,8 @@ 9FBD84532BB3AACB00220859 /* AttributionOriginFileProvider.swift in Sources */, 4BF97AD92B43C5C000EB4240 /* Bundle+VPN.swift in Sources */, 3706FBAE293F65D500E42796 /* DataImport.swift in Sources */, + BDBA859A2C5D258100BC54F5 /* VPNFeedbackFormViewController.swift in Sources */, + BDBA859D2C5D25A300BC54F5 /* VPNFeedbackFormViewModel.swift in Sources */, 3706FBAF293F65D500E42796 /* FireproofDomains.xcdatamodeld in Sources */, B626A7552991413000053070 /* SerpHeadersNavigationResponder.swift in Sources */, B68D21C42ACBC917002DA3C2 /* ContentBlockingMock.swift in Sources */, @@ -10380,7 +10473,7 @@ 3706FBC8293F65D500E42796 /* FirePopoverCollectionViewItem.swift in Sources */, 3706FBC9293F65D500E42796 /* ArrayExtension.swift in Sources */, 3706FBCB293F65D500E42796 /* BookmarkHTMLImporter.swift in Sources */, - 4BF97ADC2B43C5E200EB4240 /* VPNFeedbackSender.swift in Sources */, + 4BF97ADC2B43C5E200EB4240 /* UnifiedFeedbackSender.swift in Sources */, 987799F72999996B005D8EB6 /* BookmarkDatabase.swift in Sources */, F1C5763F2BFF972900C78647 /* SubscriptionUIHandling.swift in Sources */, BBE013EB2C5BFD660025F2C6 /* BookmarksEmptyStateContent.swift in Sources */, @@ -10411,13 +10504,14 @@ 3706FBDB293F65D500E42796 /* DefaultBrowserPreferences.swift in Sources */, 9FEE98662B846870002E44E8 /* AddEditBookmarkView.swift in Sources */, 3706FBDC293F65D500E42796 /* Permissions.xcdatamodeld in Sources */, - 4B41EDB52B168C55001EEDF4 /* VPNFeedbackFormViewModel.swift in Sources */, + 4B41EDB52B168C55001EEDF4 /* UnifiedFeedbackFormViewModel.swift in Sources */, 371209302C233D66003ADF3D /* RemoteMessagingDatabase.swift in Sources */, 3706FBDD293F65D500E42796 /* PaddedImageButton.swift in Sources */, 37CEFCAA2A6737A2001EF741 /* CredentialsCleanupErrorHandling.swift in Sources */, 3706FBDE293F65D500E42796 /* EncryptionKeyStoring.swift in Sources */, EEE50C2A2C38249C003DD7FF /* OptionalExtension.swift in Sources */, 4B4D60E32A0C883A00BCD287 /* AppMain.swift in Sources */, + 567A23BF2C7F539C0010F66C /* SpecialErrorPageUserScriptExtension.swift in Sources */, 37197EA12942441700394917 /* Tab+UIDelegate.swift in Sources */, 56D6A3D729DB2BAB0055215A /* ContinueSetUpView.swift in Sources */, 4BF97ADD2B43C5FC00EB4240 /* KeychainType+ClientDefault.swift in Sources */, @@ -10437,6 +10531,7 @@ 3706FBE8293F65D500E42796 /* SuggestionTableCellView.swift in Sources */, 3706FBE9293F65D500E42796 /* FireViewModel.swift in Sources */, B68D21D02ACBC9FD002DA3C2 /* ContentBlockerRulesManagerMock.swift in Sources */, + BD7090D02C5182FB009EED82 /* UnifiedFeedbackFormView.swift in Sources */, 3706FEC6293F6F0600E42796 /* BWKeyStorage.swift in Sources */, 4B6785482AA8DE69008A5004 /* VPNUninstaller.swift in Sources */, 3706FBEC293F65D500E42796 /* EditableTextView.swift in Sources */, @@ -10460,6 +10555,7 @@ 379E877729E98729001C8BB0 /* BookmarksCleanupErrorHandling.swift in Sources */, 3706FBFB293F65D500E42796 /* MoreOrLessView.swift in Sources */, 987799FA29999973005D8EB6 /* LocalBookmarkStore.swift in Sources */, + BDBA85942C5D255200BC54F5 /* VPNFeedbackCategory.swift in Sources */, B602E7D02A93A5FF00F12201 /* WKBackForwardListExtension.swift in Sources */, 3706FBFE293F65D500E42796 /* History.xcdatamodeld in Sources */, B68D21C92ACBC96E002DA3C2 /* MockPrivacyConfiguration.swift in Sources */, @@ -10547,7 +10643,6 @@ 3706FC30293F65D500E42796 /* FireInfoViewController.swift in Sources */, B6F1B02F2BCE6B47005E863C /* TunnelControllerProvider.swift in Sources */, 31A83FB62BE28D7D00F74E67 /* UserText+DBP.swift in Sources */, - 56BA1E832BAC506F001CF69F /* SSLErrorPageUserScript.swift in Sources */, 3706FC31293F65D500E42796 /* PermissionButton.swift in Sources */, 9F6434622BEC82B700D2D8A0 /* AttributionPixelHandler.swift in Sources */, 3706FC32293F65D500E42796 /* MoreOptionsMenu.swift in Sources */, @@ -10581,11 +10676,13 @@ 3706FC47293F65D500E42796 /* FavoritesView.swift in Sources */, 3706FC48293F65D500E42796 /* HomePage.swift in Sources */, 56A0543F2C215FB3007D8FAB /* OnboardingUserScript.swift in Sources */, + 560EB93A2C789A450080DBC8 /* OnboardingSuggestedSearchesProvider.swift in Sources */, 3706FC49293F65D500E42796 /* RoundedSelectionRowView.swift in Sources */, 4B9DB01E2A983B24000927DB /* Waitlist.swift in Sources */, 3706FC4A293F65D500E42796 /* LocalStatisticsStore.swift in Sources */, 3706FC4B293F65D500E42796 /* BackForwardListItem.swift in Sources */, 4B4D60DD2A0C875E00BCD287 /* NetworkProtectionOptionKeyExtension.swift in Sources */, + BDBA85912C5D252A00BC54F5 /* VPNFeedbackSender.swift in Sources */, 31267C6A2B640C4B00FEF811 /* DataBrokerProtectionFeatureDisabler.swift in Sources */, 3707C723294B5D2900682A9F /* URLSessionExtension.swift in Sources */, 3706FC4E293F65D500E42796 /* AtbAndVariantCleanup.swift in Sources */, @@ -10614,11 +10711,13 @@ 7BEC20432B0F505F00243D3E /* AddBookmarkPopoverView.swift in Sources */, 3706FC5B293F65D500E42796 /* NSOutlineViewExtensions.swift in Sources */, 3706FC5C293F65D500E42796 /* AppDelegate.swift in Sources */, + F1CA67072C7DCA2D00264E6A /* Logger+BitWarden.swift in Sources */, 31EF1E842B63FFD100E6DB17 /* DataBrokerProtectionDebugMenu.swift in Sources */, 3706FC5D293F65D500E42796 /* ContentOverlayViewController.swift in Sources */, 3706FC5E293F65D500E42796 /* OnboardingViewController.swift in Sources */, 7BE146082A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift in Sources */, 3706FC5F293F65D500E42796 /* DeviceAuthenticator.swift in Sources */, + F17114832C7C98FB009836C1 /* Logger+Favicons.swift in Sources */, B6DE57F72B05EA9000CD54B9 /* SheetHostingWindow.swift in Sources */, 3706FEB9293F6EFF00E42796 /* BWVault.swift in Sources */, B6B5F5852B03580A008DB58A /* RequestFilePermissionView.swift in Sources */, @@ -10641,7 +10740,6 @@ 3706FC6A293F65D500E42796 /* NSWorkspaceExtension.swift in Sources */, B6C0BB6829AEFF8100AE8E3C /* BookmarkExtension.swift in Sources */, B69A14FB2B4D705D00B9417D /* BookmarkFolderPicker.swift in Sources */, - 4B41EDB22B168B1E001EEDF4 /* VPNFeedbackFormView.swift in Sources */, 3706FC6C293F65D500E42796 /* BookmarkViewModel.swift in Sources */, 3706FC6D293F65D500E42796 /* DaxSpeech.swift in Sources */, 3706FC6E293F65D500E42796 /* DuckURLSchemeHandler.swift in Sources */, @@ -10663,6 +10761,7 @@ 3706FC7D293F65D500E42796 /* NSMenuExtension.swift in Sources */, 3701C9CF29BD040C00305B15 /* FirefoxBerkeleyDatabaseReader.swift in Sources */, B65E5DB02B74E6A900480415 /* TrackerNetwork.swift in Sources */, + F17114862C7C9D28009836C1 /* Logger+Fire.swift in Sources */, 3706FC7E293F65D500E42796 /* MainWindowController.swift in Sources */, 3706FC7F293F65D500E42796 /* Tab.swift in Sources */, 3706FC81293F65D500E42796 /* DispatchQueueExtensions.swift in Sources */, @@ -10673,7 +10772,6 @@ 371209312C233D69003ADF3D /* RemoteMessagingStoreErrorHandling.swift in Sources */, 3706FC83293F65D500E42796 /* PopoverMessageViewController.swift in Sources */, 7B5A23702C46A116007213AC /* ExcludedDomainsModel.swift in Sources */, - 4BF97ADA2B43C5DC00EB4240 /* VPNFeedbackCategory.swift in Sources */, 9D9AE86E2AA76D1F0026E7DC /* LoginItem+NetworkProtection.swift in Sources */, 3707C721294B5D2900682A9F /* WKMenuItemIdentifier.swift in Sources */, 3706FEBE293F6EFF00E42796 /* BWMessageIdGenerator.swift in Sources */, @@ -10764,6 +10862,7 @@ 1D1C36E729FB019C001FA40C /* HistoryTabExtensionTests.swift in Sources */, 1D9FDEBB2B9B5E090040B78C /* WebTrackingProtectionPreferencesTests.swift in Sources */, 3706FDF3293F661700E42796 /* ChromiumLoginReaderTests.swift in Sources */, + BDCB66D92C7CE1A700E8ABC9 /* VPNFeedbackFormViewModelTests.swift in Sources */, 3706FDF4293F661700E42796 /* TabCollectionTests.swift in Sources */, 3706FDF5293F661700E42796 /* StartupPreferencesTests.swift in Sources */, 3706FDF6293F661700E42796 /* DuckPlayerTests.swift in Sources */, @@ -10793,9 +10892,9 @@ 3706FE03293F661700E42796 /* CoreDataStoreTests.swift in Sources */, 1D9FDEC42B9B63C90040B78C /* DataClearingPreferencesTests.swift in Sources */, 3706FE04293F661700E42796 /* TreeControllerTests.swift in Sources */, + 56CE77622C7DFCF800AC1ED2 /* OnboardingSuggestedSearchesProviderTests.swift in Sources */, 3706FE05293F661700E42796 /* DownloadsWebViewMock.m in Sources */, 3706FE06293F661700E42796 /* CoreDataEncryptionTesting.xcdatamodeld in Sources */, - 56BA1E7F2BAB2D29001CF69F /* ErrorPageTabExtensionTest.swift in Sources */, 3706FE07293F661700E42796 /* PasswordManagementItemListModelTests.swift in Sources */, 3706FE08293F661700E42796 /* WKWebsiteDataStoreExtensionTests.swift in Sources */, 3706FE09293F661700E42796 /* VariantManagerTests.swift in Sources */, @@ -10813,7 +10912,7 @@ 1D9FDEC12B9B5FEA0040B78C /* AccessibilityPreferencesTests.swift in Sources */, 3706FE11293F661700E42796 /* URLSuggestedFilenameTests.swift in Sources */, 5681ED472BDBAF6E00F59729 /* SyncErrorHandlerTests.swift in Sources */, - 4BE344EF2B23786F003FC223 /* VPNFeedbackFormViewModelTests.swift in Sources */, + 4BE344EF2B23786F003FC223 /* UnifiedFeedbackFormViewModelTests.swift in Sources */, B6F56569299A414300A04298 /* WKWebViewMockingExtension.swift in Sources */, 3706FE13293F661700E42796 /* ConfigurationStorageTests.swift in Sources */, 3706FE15293F661700E42796 /* PrivacyIconViewModelTests.swift in Sources */, @@ -10834,6 +10933,7 @@ 857E44642A9F70F200ED77A7 /* CampaignVariantTests.swift in Sources */, 561D29C72BDA74F4007B91D0 /* MockDDGSyncing.swift in Sources */, 3706FE1E293F661700E42796 /* GeolocationProviderTests.swift in Sources */, + 567A23CE2C80CF3D0010F66C /* SpecialErrorPageUserScriptTests.swift in Sources */, 9F39106A2B68D87B00CB5112 /* ProgressExtensionTests.swift in Sources */, 9FAD623B2BCFDB32007F3A65 /* WebsiteInfoHelpers.swift in Sources */, 56A054312C2043C8007D8FAB /* OnboardingTabExtensionTests.swift in Sources */, @@ -10864,6 +10964,7 @@ 562532A12BC069190034D316 /* ZoomPopoverViewModelTests.swift in Sources */, 3706FE28293F661700E42796 /* BookmarkTests.swift in Sources */, 3706FE29293F661700E42796 /* SuggestionContainerViewModelTests.swift in Sources */, + 567A23CF2C80CF4B0010F66C /* ErrorPageTabExtensionTest.swift in Sources */, 37DB56F02C3B31CD0093D4DC /* MockRemoteMessagingAvailabilityProvider.swift in Sources */, 1D8C2FEB2B70F5A7005E4BBD /* MockWebViewSnapshotRenderer.swift in Sources */, 56A0540E2C1C375E007D8FAB /* MockWindow.swift in Sources */, @@ -11006,7 +11107,6 @@ 3706FE79293F661700E42796 /* AppearancePreferencesTests.swift in Sources */, 3706FE7A293F661700E42796 /* FirePopoverViewModelTests.swift in Sources */, 7B09CBAA2BA4BE8200CF245B /* NetworkProtectionPixelEventTests.swift in Sources */, - 56BA1E882BAC8239001CF69F /* SSLErrorPageUserScriptTests.swift in Sources */, 3706FE7B293F661700E42796 /* HistoryStoringMock.swift in Sources */, 562984702AC4610100AC20EB /* SyncPreferencesTests.swift in Sources */, 3706FE7C293F661700E42796 /* LocalBookmarkStoreTests.swift in Sources */, @@ -11134,7 +11234,6 @@ B602E8192A1E2570006D261F /* URL+NetworkProtection.swift in Sources */, F1FDC93B2BF51F41006B1435 /* VPNSettings+Environment.swift in Sources */, 4BF0E50B2AD2552200FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, - 7B01AC6F2C36BB7E004FADC7 /* VPNLogger.swift in Sources */, 4B41EDA12B15437A001EEDF4 /* NetworkProtectionNotificationsPresenterFactory.swift in Sources */, 7B0099822B65C6B300FE7C31 /* MacTransparentProxyProvider.swift in Sources */, B65DA5F32A77D3C700CBEE8D /* UserDefaultsWrapper.swift in Sources */, @@ -11152,7 +11251,6 @@ buildActionMask = 2147483647; files = ( B6F92BA22A691580002ABA6B /* UserDefaultsWrapper.swift in Sources */, - 4B2D065B2A11D1FF00DE1F49 /* Logging.swift in Sources */, 7BA7CC3A2AD11E2D0042E5CE /* DuckDuckGoVPNAppDelegate.swift in Sources */, 7BAF9E4C2A8A3CCA002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */, 7BA7CC592AD1203B0042E5CE /* UserText+NetworkProtection.swift in Sources */, @@ -11195,7 +11293,6 @@ 7B2DDCFB2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */, B6F92BA32A691583002ABA6B /* UserDefaultsWrapper.swift in Sources */, 4BA7C4DB2B3F63AE00AFE511 /* NetworkExtensionController.swift in Sources */, - 4B2D067C2A13340900DE1F49 /* Logging.swift in Sources */, 7B1459552B7D438F00047F2C /* VPNProxyLauncher.swift in Sources */, EEDE50122BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift in Sources */, 7BD7B0042C19D3830039D20A /* VPNIPCResources.swift in Sources */, @@ -11234,7 +11331,6 @@ buildActionMask = 2147483647; files = ( 4B4BEC3D2A11B56B001D9AC5 /* DuckDuckGoNotificationsAppDelegate.swift in Sources */, - 4B4BEC3E2A11B56E001D9AC5 /* Logging.swift in Sources */, 4B4BEC412A11B5BD001D9AC5 /* NetworkProtectionUNNotificationsPresenter.swift in Sources */, 4B4BEC432A11B5C7001D9AC5 /* Bundle+VPN.swift in Sources */, 4B4BEC452A11B5EE001D9AC5 /* UserText+NetworkProtectionExtensions.swift in Sources */, @@ -11261,7 +11357,6 @@ F1C70D7E2BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, 4B4D60A02A0B2D5B00BCD287 /* Bundle+VPN.swift in Sources */, F1DA51922BF6081C00CF29FA /* AttributionPixelHandler.swift in Sources */, - 7B01AC6E2C36BB7E004FADC7 /* VPNLogger.swift in Sources */, 4B4D60A52A0B2EC000BCD287 /* UserText+NetworkProtectionExtensions.swift in Sources */, 4BF0E50C2AD2552300FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, B65DA5F22A77D3C600CBEE8D /* UserDefaultsWrapper.swift in Sources */, @@ -11315,9 +11410,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F17E7DDC2C7C7F8100907A84 /* Logger+DBPBackgroundAgent.swift in Sources */, 31A83FB72BE28D8A00F74E67 /* UserText+DBP.swift in Sources */, F1D042942BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */, F1C70D822BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, + 1EFA1A072C7C7F0E0099F508 /* PrivacyProPixel.swift in Sources */, 9D9AE91D2AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift in Sources */, 9D9AE9212AAA3B450026E7DC /* UserText.swift in Sources */, 31ECDA132BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, @@ -11330,9 +11427,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F10C99422C7E20A1005568B4 /* Logger+DBPBackgroundAgent.swift in Sources */, 31A83FB82BE28D8A00F74E67 /* UserText+DBP.swift in Sources */, F1D042952BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift in Sources */, F1C70D832BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */, + 1EFA1A082C7C7F0F0099F508 /* PrivacyProPixel.swift in Sources */, 9D9AE91E2AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift in Sources */, 9D9AE9222AAA3B450026E7DC /* UserText.swift in Sources */, 31ECDA142BED339600AE679F /* DataBrokerAuthenticationManagerBuilder.swift in Sources */, @@ -11366,7 +11465,7 @@ B6E3E55B2BC0041900A41922 /* DownloadListStoreMock.swift in Sources */, 9FA5A0A52BC8F34900153786 /* UserDefaultsBookmarkFoldersStore.swift in Sources */, B6C0B23026E61D630031CB7F /* DownloadListStore.swift in Sources */, - 85799C1825DEBB3F0007EC87 /* Logging.swift in Sources */, + 85799C1825DEBB3F0007EC87 /* Logger+Multiple.swift in Sources */, AAC30A2E268F1EE300D2D9CD /* CrashReportPromptPresenter.swift in Sources */, 1D2DC00629016798008083A1 /* BWCredential.swift in Sources */, EEA3EEB12B24EBD000E8333A /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, @@ -11407,7 +11506,7 @@ B693955126F04BEB0015B914 /* GradientView.swift in Sources */, 37AFCE8527DA2D3900471A10 /* PreferencesSidebar.swift in Sources */, B6C00ED5292FB21E009C73A6 /* HoveredLinkTabExtension.swift in Sources */, - 4B0526612B1D55320054955A /* VPNFeedbackSender.swift in Sources */, + 4B0526612B1D55320054955A /* UnifiedFeedbackSender.swift in Sources */, AA5C8F5E2590EEE800748EB7 /* NSPointExtension.swift in Sources */, 4BF0E5122AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, AA6EF9AD25066F42004754E6 /* WindowsManager.swift in Sources */, @@ -11435,8 +11534,9 @@ 4BB88B5025B7BA2B006F6B06 /* TabInstrumentation.swift in Sources */, 85D33F1225C82EB3002B91A6 /* ConfigurationManager.swift in Sources */, 31F28C4F28C8EEC500119F70 /* YoutubePlayerUserScript.swift in Sources */, - 4B41EDAE2B168AFF001EEDF4 /* VPNFeedbackFormViewController.swift in Sources */, + 4B41EDAE2B168AFF001EEDF4 /* UnifiedFeedbackFormViewController.swift in Sources */, 7BFF35732C10D75000F89673 /* IPCServiceLauncher.swift in Sources */, + 567A23BE2C7F539C0010F66C /* SpecialErrorPageUserScriptExtension.swift in Sources */, AA5FA697275F90C400DCE9C9 /* FaviconImageCache.swift in Sources */, 1430DFF524D0580F00B8978C /* TabBarViewController.swift in Sources */, B62B483E2ADE48DE000DECE5 /* ArrayBuilder.swift in Sources */, @@ -11477,6 +11577,7 @@ 7BEC20452B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift in Sources */, B6C0BB6A29AF1C7000AE8E3C /* BrowserTabView.swift in Sources */, B6B1E88026D5DA9B0062C350 /* DownloadsViewController.swift in Sources */, + BD7090D62C540D5D009EED82 /* EmptyMetadataCollector.swift in Sources */, 85AC3AF725D5DBFD00C7D2AA /* DataExtension.swift in Sources */, 85480FCF25D1AA22009424E3 /* ConfigurationStore.swift in Sources */, AA3D531B27A2F57E00074EC1 /* Feedback.swift in Sources */, @@ -11494,6 +11595,7 @@ EEC4A6712B2C90AB00F7C0AA /* VPNLocationPreferenceItem.swift in Sources */, 85589E8727BBB8F20038AD11 /* HomePageFavoritesModel.swift in Sources */, 4BB88B4A25B7B690006F6B06 /* SequenceExtensions.swift in Sources */, + BDBA85932C5D255200BC54F5 /* VPNFeedbackCategory.swift in Sources */, B602E7CF2A93A5FF00F12201 /* WKBackForwardListExtension.swift in Sources */, 4B59024026B35F3600489384 /* ChromiumDataImporter.swift in Sources */, B62B48562ADE730D000DECE5 /* FileImportView.swift in Sources */, @@ -11598,6 +11700,7 @@ B6AAAC2D260330580029438D /* PublishedAfter.swift in Sources */, 3701C9CE29BD040C00305B15 /* FirefoxBerkeleyDatabaseReader.swift in Sources */, 37054FCE2876472D00033B6F /* WebViewSnapshotView.swift in Sources */, + 560EB9392C789A450080DBC8 /* OnboardingSuggestedSearchesProvider.swift in Sources */, 4BBC16A027C4859400E00A38 /* DeviceAuthenticationService.swift in Sources */, CB24F70C29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift in Sources */, 1DEF3BAD2BD145A9004A2FBA /* AutoClearHandler.swift in Sources */, @@ -11619,6 +11722,7 @@ 4BB99CFE26FE191E001E4761 /* FirefoxBookmarksReader.swift in Sources */, F1DA518C2BF607D200CF29FA /* SubscriptionRedirectManager.swift in Sources */, 4BBC16A227C485BC00E00A38 /* DeviceIdleStateDetector.swift in Sources */, + BDBA859C2C5D25A300BC54F5 /* VPNFeedbackFormViewModel.swift in Sources */, 4B379C2427BDE1B0008A968E /* FlatButton.swift in Sources */, 37054FC92873301700033B6F /* PinnedTabView.swift in Sources */, 4BA1A6A0258B079600F6F690 /* DataEncryption.swift in Sources */, @@ -11695,8 +11799,8 @@ 4B9DB0352A983B24000927DB /* WaitlistTermsAndConditionsView.swift in Sources */, AAB8203C26B2DE0D00788AC3 /* SuggestionListCharacteristics.swift in Sources */, 4B6785472AA8DE68008A5004 /* VPNUninstaller.swift in Sources */, - 4B0526642B1D55D80054955A /* VPNFeedbackCategory.swift in Sources */, 4B9292D42667123700AD2C21 /* BookmarkListViewController.swift in Sources */, + BD88A83E2C4F3E4300460A26 /* FeedbackCategoryProviding.swift in Sources */, 9F56CFB12B843F6C00BB7F11 /* BookmarksDialogViewFactory.swift in Sources */, F1FD5B672C2B0AAA0040FA0D /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */, 4B723E0D26B0006100E14D75 /* SecureVaultLoginImporter.swift in Sources */, @@ -11704,6 +11808,7 @@ 9D9AE86B2AA76CF90026E7DC /* LoginItemsManager.swift in Sources */, 857E5AF52A79045800FC0FB4 /* PixelExperiment.swift in Sources */, B6C416A7294A4AE500C4F2E7 /* DuckPlayerTabExtension.swift in Sources */, + BDBA859F2C5D25B700BC54F5 /* VPNMetadataCollector.swift in Sources */, AA5C1DD5285C780C0089850C /* RecentlyClosedCoordinator.swift in Sources */, AA88D14B252A557100980B4E /* URLRequestExtension.swift in Sources */, AA6197C6276B3168008396F0 /* FaviconHostReference.swift in Sources */, @@ -11800,7 +11905,7 @@ 4B723E1226B0006E00E14D75 /* DataImport.swift in Sources */, 7BE146072A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift in Sources */, B6085D092743AAB600A9C456 /* FireproofDomains.xcdatamodeld in Sources */, - 56BA1E752BAAF70F001CF69F /* SSLErrorPageTabExtension.swift in Sources */, + 56BA1E752BAAF70F001CF69F /* SpecialErrorPageTabExtension.swift in Sources */, 85589E8227BBB8630038AD11 /* HomePageView.swift in Sources */, B6BF5D932947199A006742B1 /* SerpHeadersNavigationResponder.swift in Sources */, 569277C129DDCBB500B633EF /* HomePageContinueSetUpModel.swift in Sources */, @@ -11812,6 +11917,8 @@ 9F872D982B8DA9F800138637 /* Bookmarks+Tab.swift in Sources */, 85D885B326A5A9DE0077C374 /* NSAlert+PasswordManager.swift in Sources */, 983DFB2528B67036006B7E34 /* UserContentUpdating.swift in Sources */, + BD7090D22C52ECFE009EED82 /* UnifiedMetadataCollector.swift in Sources */, + BD7090CF2C5182FB009EED82 /* UnifiedFeedbackFormView.swift in Sources */, 1D9A4E5A2B43213B00F449E2 /* TabSnapshotExtension.swift in Sources */, 316913262BD2B76F0051B46D /* DataBrokerPrerequisitesStatusVerifier.swift in Sources */, 4B7A57CF279A4EF300B1C70E /* ChromiumPreferences.swift in Sources */, @@ -11823,12 +11930,12 @@ 1D69C553291302F200B75945 /* BWVault.swift in Sources */, AA6FFB4424DC33320028F4D0 /* NSViewExtension.swift in Sources */, B6C0B23E26E8BF1F0031CB7F /* DownloadListViewModel.swift in Sources */, + F17114822C7C98FB009836C1 /* Logger+Favicons.swift in Sources */, 1D9A37672BD8EA8800EBC58D /* DockPositionProvider.swift in Sources */, 4B9292D52667123700AD2C21 /* BookmarkManagementDetailViewController.swift in Sources */, F188267C2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */, 4B723E1026B0006700E14D75 /* CSVImporter.swift in Sources */, 37A4CEBA282E992F00D75B89 /* StartupPreferences.swift in Sources */, - 4B41EDB12B168B1E001EEDF4 /* VPNFeedbackFormView.swift in Sources */, 1D72D59C2BFF61B200AEDE36 /* UpdateNotificationPresenter.swift in Sources */, AA4BBA3B25C58FA200C4FB0F /* MainMenu.swift in Sources */, AA585D84248FD31100E9A3E2 /* BrowserTabViewController.swift in Sources */, @@ -11854,6 +11961,7 @@ C13909EF2B85FD4E001626ED /* AutofillActionExecutor.swift in Sources */, 4BB88B5B25B7BA50006F6B06 /* Instruments.swift in Sources */, 3768D8442C2CC884004120AE /* RemoteMessagingConfigMatcherProvider.swift in Sources */, + 560EB9322C78946F0080DBC8 /* ContextualOnboardingDialogs.swift in Sources */, 9812D895276CEDA5004B6181 /* ContentBlockerRulesLists.swift in Sources */, 4B0511E2262CAA8600F6079C /* NSViewControllerExtension.swift in Sources */, C16127EE2BDFB46400966BB9 /* DataImportShortcutsView.swift in Sources */, @@ -11880,7 +11988,6 @@ B66260E629ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift in Sources */, EA0BA3A9272217E6002A0B6C /* ClickToLoadUserScript.swift in Sources */, AAA892EA250A4CEF005B37B2 /* WindowControllersManager.swift in Sources */, - 56BA1E822BAC506F001CF69F /* SSLErrorPageUserScript.swift in Sources */, 85C5991B27D10CF000E605B2 /* FireAnimationView.swift in Sources */, B6B4D1CA2B0C8C9200C26286 /* FirefoxCompatibilityPreferences.swift in Sources */, AA6197C4276B314D008396F0 /* FaviconUrlReference.swift in Sources */, @@ -11905,7 +12012,6 @@ 9F9C49FD2BC7E9830099738D /* BookmarkAllTabsDialogViewModel.swift in Sources */, 4B9292A226670D2A00AD2C21 /* PseudoFolder.swift in Sources */, 1DDD3EC42B84F96B004CBF2B /* CookiePopupProtectionPreferences.swift in Sources */, - 4B05265E2B1AE5C70054955A /* VPNMetadataCollector.swift in Sources */, 4B9DB0292A983B24000927DB /* WaitlistStorage.swift in Sources */, AA2CB1352587C29500AA6FBE /* TabBarFooter.swift in Sources */, EEC111E6294D06290086524F /* JSAlertViewModel.swift in Sources */, @@ -11941,7 +12047,7 @@ 85C48CCC278D808F00D3263E /* NSAttributedStringExtension.swift in Sources */, 7B5A236F2C46A116007213AC /* ExcludedDomainsModel.swift in Sources */, 1D710F4B2C48F1F200C3975F /* UpdateDialogHelper.swift in Sources */, - 4B41EDB42B168C55001EEDF4 /* VPNFeedbackFormViewModel.swift in Sources */, + 4B41EDB42B168C55001EEDF4 /* UnifiedFeedbackFormViewModel.swift in Sources */, AA7EB6E527E7D6DC00036718 /* AnimationView.swift in Sources */, 8562599A269CA0A600EE44BC /* NSRectExtension.swift in Sources */, 4B37EE5F2B4CFC3C00A89A61 /* HomePageRemoteMessagingStorage.swift in Sources */, @@ -11978,8 +12084,10 @@ 3158B14A2B0BF74300AF130C /* DataBrokerProtectionDebugMenu.swift in Sources */, 4BBDEE9128FC14760092FAA6 /* BWInstallationService.swift in Sources */, 7B4D8A212BDA857300852966 /* VPNOperationErrorRecorder.swift in Sources */, + F17E7DDE2C7C83E500907A84 /* Logger+FileDownload.swift in Sources */, 859F30642A72A7BB00C20372 /* BookmarksBarPromptPopover.swift in Sources */, 7B6545ED2C0778BB00115BEA /* VPNControllerUDSClient+ConvenienceInitializers.swift in Sources */, + BDBA85902C5D252A00BC54F5 /* VPNFeedbackSender.swift in Sources */, B693955426F04BEC0015B914 /* ColorView.swift in Sources */, AA5C1DD3285A217F0089850C /* RecentlyClosedCacheItem.swift in Sources */, B6BBF17427475B15004F850E /* PopupBlockedPopover.swift in Sources */, @@ -11996,6 +12104,7 @@ B68458B825C7E8B200DC17B6 /* Tab+NSSecureCoding.swift in Sources */, 85378DA0274E6F42007C5CBF /* NSNotificationName+EmailManager.swift in Sources */, 1DDC84FF2B835BC000670238 /* SearchPreferences.swift in Sources */, + BDBA85992C5D258100BC54F5 /* VPNFeedbackFormViewController.swift in Sources */, B693955726F04BEC0015B914 /* MouseOverButton.swift in Sources */, AA61C0D02722159B00E6B681 /* FireInfoViewController.swift in Sources */, 9D9AE8692AA76CDC0026E7DC /* LoginItem+NetworkProtection.swift in Sources */, @@ -12077,6 +12186,7 @@ AABEE69C24A902BB0043105B /* SuggestionContainer.swift in Sources */, B6C00ECD292F89D9009C73A6 /* FindInPageTabExtension.swift in Sources */, 85589E8327BBB8630038AD11 /* HomePageViewController.swift in Sources */, + F17114852C7C9D28009836C1 /* Logger+Fire.swift in Sources */, 5614B3A12BBD639D009B5031 /* ZoomPopover.swift in Sources */, B6A9E46B2614618A0067D1B9 /* OperatingSystemVersionExtension.swift in Sources */, 4BDFA4AE27BF19E500648192 /* ToggleableScrollView.swift in Sources */, @@ -12151,6 +12261,7 @@ 4B92928B26670D1700AD2C21 /* BookmarksOutlineView.swift in Sources */, 4BF01C00272AE74C00884A61 /* CountryList.swift in Sources */, 37CD54CC27F2FDD100F1F7B9 /* PreferencesSection.swift in Sources */, + F1CA67062C7DCA2300264E6A /* Logger+BitWarden.swift in Sources */, 4B4D60B62A0C847D00BCD287 /* NetworkProtectionNavBarButtonModel.swift in Sources */, FD23FD2D2886A81D007F6985 /* AutoconsentManagement.swift in Sources */, 4B4D60D32A0C84F700BCD287 /* UserText+NetworkProtection.swift in Sources */, @@ -12164,6 +12275,7 @@ 9F514F912B7D88AD001832A9 /* AddEditBookmarkFolderDialogView.swift in Sources */, 9FA173E32B7A12B600EE4E6E /* BookmarkDialogFolderManagementView.swift in Sources */, 37534CA8281198CD002621E7 /* AdjacentItemEnumerator.swift in Sources */, + BDBA85962C5D256C00BC54F5 /* VPNFeedbackFormView.swift in Sources */, 987799F62999996B005D8EB6 /* BookmarkDatabase.swift in Sources */, 7BB4BC6A2C5CD96200E06FC8 /* ActiveDomainPublisher.swift in Sources */, 4BE53374286E39F10019DBFD /* ChromiumKeychainPrompt.swift in Sources */, @@ -12184,7 +12296,6 @@ B67C6C472654C643006C872E /* FileManagerExtensionTests.swift in Sources */, B69B50482726C5C200758A2B /* StatisticsLoaderTests.swift in Sources */, 9F6434702BECBA2800D2D8A0 /* SubscriptionRedirectManagerTests.swift in Sources */, - 56BA1E872BAC8239001CF69F /* SSLErrorPageUserScriptTests.swift in Sources */, 142879DA24CE1179005419BB /* SuggestionViewModelTests.swift in Sources */, 4B9292BC2667103100AD2C21 /* BookmarkSidebarTreeControllerTests.swift in Sources */, 4B9DB05A2A983B55000927DB /* MockWaitlistRequest.swift in Sources */, @@ -12216,6 +12327,7 @@ 4B9292BB2667103100AD2C21 /* BookmarkNodeTests.swift in Sources */, 4B0219A825E0646500ED7DEA /* WebsiteDataStoreTests.swift in Sources */, AAC9C01E24CB6BEB00AD1325 /* TabCollectionViewModelTests.swift in Sources */, + 56CE77612C7DFCF800AC1ED2 /* OnboardingSuggestedSearchesProviderTests.swift in Sources */, B662D3DE275613BB0035D4D6 /* EncryptionKeyStoreMock.swift in Sources */, 1D3B1ABF29369FC8006F4388 /* BWEncryptionTests.swift in Sources */, B6F56567299A414300A04298 /* WKWebViewMockingExtension.swift in Sources */, @@ -12301,7 +12413,7 @@ B6A5A2A025B96E8300AA7ADA /* AppStateChangePublisherTests.swift in Sources */, B63ED0E326B3E7FA00A9DAD1 /* CLLocationManagerMock.swift in Sources */, 37CD54BB27F25A4000F1F7B9 /* DownloadsPreferencesTests.swift in Sources */, - 4BE344EE2B2376DF003FC223 /* VPNFeedbackFormViewModelTests.swift in Sources */, + 4BE344EE2B2376DF003FC223 /* UnifiedFeedbackFormViewModelTests.swift in Sources */, 9F872D9D2B9058D000138637 /* Bookmarks+TabTests.swift in Sources */, BBFF355D2C4AF26200DA3289 /* BookmarksSortModeTests.swift in Sources */, 9F3910622B68C35600CB5112 /* DownloadsTabExtensionTests.swift in Sources */, @@ -12331,6 +12443,7 @@ 4B98D27A28D95F1A003C2B6F /* ChromiumFaviconsReaderTests.swift in Sources */, 9F0660732BECC71200B8EEF1 /* SubscriptionAttributionPixelHandlerTests.swift in Sources */, 56A054302C2043C8007D8FAB /* OnboardingTabExtensionTests.swift in Sources */, + BDCB66D82C7CE1A600E8ABC9 /* VPNFeedbackFormViewModelTests.swift in Sources */, 986189E62A7CFB3E001B4519 /* LocalBookmarkStoreSavingTests.swift in Sources */, AA652CD325DDA6E9009059CC /* LocalBookmarkManagerTests.swift in Sources */, 1D232E992C7870D90043840D /* BinaryOwnershipCheckerTests.swift in Sources */, @@ -12409,6 +12522,7 @@ AABAF59C260A7D130085060C /* FaviconManagerMock.swift in Sources */, 1DFAB5222A8983DE00A0F7F6 /* SetExtensionTests.swift in Sources */, 4BF6962028BEEE8B00D402D4 /* LocalPinningManagerTests.swift in Sources */, + 567A23CD2C80CE060010F66C /* SpecialErrorPageUserScriptTests.swift in Sources */, 9F0FFFB82BCCAE9C007C87DD /* AddEditBookmarkDialogViewModelMock.swift in Sources */, AAEC74B82642E43800C2EFBC /* HistoryStoreTests.swift in Sources */, 9FAD623D2BD09DE5007F3A65 /* WebsiteInfoTests.swift in Sources */, @@ -12495,6 +12609,7 @@ B6E6BA142BA2CDD6008AA7E1 /* SandboxTestToolNotifications.swift in Sources */, B6E6BA042BA1FE05008AA7E1 /* FilePresenter.swift in Sources */, B6E6B9F62BA1FD90008AA7E1 /* SandboxTestTool.swift in Sources */, + F1CA67082C7DE92200264E6A /* Logger+FileDownload.swift in Sources */, B6E6BA252BA2EDDE008AA7E1 /* FileReadResult.swift in Sources */, B6EECB322BC40A1400B3CB77 /* FileManagerExtension.swift in Sources */, B6EECB302BC3FA5A00B3CB77 /* SecurityScopedFileURLController.swift in Sources */, @@ -13468,12 +13583,20 @@ revision = c06709ba8a586f6a40190bacaaaaa96b2d55e540; }; }; + 4B5235432C7BB14D00AFAF64 /* XCRemoteSwiftPackageReference "wireguard-apple" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/duckduckgo/wireguard-apple"; + requirement = { + kind = exactVersion; + version = 1.1.3; + }; + }; 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 186.0.0; + version = 190.0.0; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { @@ -13750,6 +13873,16 @@ isa = XCSwiftPackageProductDependency; productName = NetworkProtectionUI; }; + 4B5235442C7BB14D00AFAF64 /* WireGuard */ = { + isa = XCSwiftPackageProductDependency; + package = 4B5235432C7BB14D00AFAF64 /* XCRemoteSwiftPackageReference "wireguard-apple" */; + productName = WireGuard; + }; + 4B5235462C7BB15700AFAF64 /* WireGuard */ = { + isa = XCSwiftPackageProductDependency; + package = 4B5235432C7BB14D00AFAF64 /* XCRemoteSwiftPackageReference "wireguard-apple" */; + productName = WireGuard; + }; 4B5F14FB2A15291D0060320F /* InputFilesChecker */ = { isa = XCSwiftPackageProductDependency; productName = "plugin:InputFilesChecker"; @@ -13794,6 +13927,26 @@ package = 4311906792B7676CE9535D76 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Crashes; }; + 560EB9342C7897370080DBC8 /* Onboarding */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Onboarding; + }; + 560EB9362C78974C0080DBC8 /* Onboarding */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Onboarding; + }; + 567A23C02C7F71570010F66C /* SpecialErrorPages */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = SpecialErrorPages; + }; + 567A23C42C7F75BB0010F66C /* SpecialErrorPages */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = SpecialErrorPages; + }; 7B00997C2B6508B700FE7C31 /* NetworkProtectionProxy */ = { isa = XCSwiftPackageProductDependency; productName = NetworkProtectionProxy; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 151e8e8b96..6c396add94 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "4a55217003ad7b2d44a1ac616d47596c0bda69dc", - "version" : "186.0.0" + "revision" : "ac53011582abcca4aefd66f15308332273eecb49", + "version" : "190.0.0" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "f97053d24c21ea301d4067adbbe0899ff940526a", - "version" : "6.7.0" + "revision" : "5876a5d2e2e7f5a2e11f6419c6c3fafb7cafdfca", + "version" : "6.12.0" } }, { @@ -104,8 +104,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "36dc07cba4bc1e7e0c1d1fb679c3cd077694a072", - "version" : "5.0.0" + "revision" : "665b23dc656c9f787494620494f8e56098a900b2", + "version" : "5.1.1" } }, { diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 567082b8b1..3699ba82d2 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -39,6 +39,7 @@ import Subscription import NetworkProtectionIPC import DataBrokerProtection import RemoteMessaging +import os.log final class AppDelegate: NSObject, NSApplicationDelegate { @@ -65,7 +66,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { let fileStore: FileStore #if APPSTORE - private let crashCollection = CrashCollection(platform: .macOSAppStore, log: .default) + private let crashCollection = CrashCollection(platform: .macOSAppStore) #else private let crashReporter = CrashReporter() #endif @@ -164,7 +165,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { let encryptionKey = NSApplication.runType.requiresEnvironment ? try keyStore.readKey() : nil fileStore = EncryptedFileStore(encryptionKey: encryptionKey) } catch { - os_log("App Encryption Key could not be read: %s", "\(error)") + Logger.general.error("App Encryption Key could not be read: \(error.localizedDescription)") fileStore = EncryptedFileStore() } @@ -545,7 +546,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate { dataProvidersSource: syncDataProviders, errorEvents: SyncErrorHandler(), privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, - log: OSLog.sync, environment: environment ) syncService.initializeIfNeeded() @@ -620,10 +620,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate { return } if isLocked { - os_log(.debug, log: .sync, "Screen is locked") + Logger.sync.debug("Screen is locked") syncService.scheduler.cancelSyncAndSuspendSyncQueue() } else { - os_log(.debug, log: .sync, "Screen is unlocked") + Logger.sync.debug("Screen is unlocked") syncService.scheduler.resumeSyncQueue() } } diff --git a/DuckDuckGo/Application/DockCustomizer.swift b/DuckDuckGo/Application/DockCustomizer.swift index 61391fc34c..f2baf5d894 100644 --- a/DuckDuckGo/Application/DockCustomizer.swift +++ b/DuckDuckGo/Application/DockCustomizer.swift @@ -18,6 +18,7 @@ import Foundation import Common +import os.log protocol DockCustomization { var isAddedToDock: Bool { get } @@ -104,7 +105,7 @@ final class DockCustomizer: DockCustomization { } return true } catch { - os_log(.error, "Error writing to Dock plist: %{public}@", error.localizedDescription) + Logger.general.error("Error writing to Dock plist: \(error.localizedDescription, privacy: .public)") return false } } diff --git a/DuckDuckGo/Application/URLEventHandler.swift b/DuckDuckGo/Application/URLEventHandler.swift index d78142b001..3883e3bee4 100644 --- a/DuckDuckGo/Application/URLEventHandler.swift +++ b/DuckDuckGo/Application/URLEventHandler.swift @@ -24,6 +24,8 @@ import Subscription import NetworkProtectionUI import VPNAppLauncher import DataBrokerProtection +import os.log +import BrowserServicesKit // @MainActor final class URLEventHandler { @@ -64,14 +66,14 @@ final class URLEventHandler { @objc func handleUrlEvent(event: NSAppleEventDescriptor, reply: NSAppleEventDescriptor) { guard let stringValue = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue else { - os_log("UrlEventListener: unable to determine path", type: .error) + Logger.general.error("UrlEventListener: unable to determine path") let error = NSError(domain: "CouldNotGetPath", code: -1, userInfo: nil) PixelKit.fire(DebugEvent(GeneralPixel.appOpenURLFailed, error: error)) return } guard let url = URL.makeURL(from: stringValue) else { - os_log("UrlEventListener: failed to construct URL from path %s", type: .error, stringValue) + Logger.general.debug("UrlEventListener: failed to construct URL from path \(stringValue)") let error = NSError(domain: "CouldNotConstructURL", code: -1, userInfo: nil) PixelKit.fire(DebugEvent(GeneralPixel.appOpenURLFailed, error: error)) return diff --git a/DuckDuckGo/Assets.xcassets/Images/PPro-Feedback.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/PPro-Feedback.imageset/Contents.json new file mode 100644 index 0000000000..b0631f4c9b --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/PPro-Feedback.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "Privacy-Pro-16D.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/PPro-Feedback.imageset/Privacy-Pro-16D.svg b/DuckDuckGo/Assets.xcassets/Images/PPro-Feedback.imageset/Privacy-Pro-16D.svg new file mode 100644 index 0000000000..c64970b038 --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/PPro-Feedback.imageset/Privacy-Pro-16D.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/DuckDuckGo/Autoconsent/AutoconsentExperiment.swift b/DuckDuckGo/Autoconsent/AutoconsentExperiment.swift index bdd1c01746..e90e350fad 100644 --- a/DuckDuckGo/Autoconsent/AutoconsentExperiment.swift +++ b/DuckDuckGo/Autoconsent/AutoconsentExperiment.swift @@ -18,11 +18,12 @@ import Foundation import Common +import os.log enum AutoconsentFilterlistExperiment: String, CaseIterable { static var logic = AutoconsentExperimentLogic() static var cohort: AutoconsentFilterlistExperiment? { - os_log("🚧 requesting CPM cohort", log: .autoconsent, type: .debug) + Logger.autoconsent.debug("🚧 requesting CPM cohort") return logic.experimentCohort } @@ -36,11 +37,11 @@ final internal class AutoconsentExperimentLogic { // if the stored cohort doesn't match, allocate a new one let cohort = AutoconsentFilterlistExperiment(rawValue: allocatedExperimentCohort) { - os_log("🚧 existing CPM cohort: %s", log: .autoconsent, type: .debug, String(describing: cohort.rawValue)) + Logger.autoconsent.debug("🚧 existing CPM cohort: \(String(describing: cohort.rawValue))") return cohort } let cohort = AutoconsentFilterlistExperiment.allCases.randomElement()! - os_log("🚧 new CPM cohort: %s", log: .autoconsent, type: .debug, String(describing: cohort.rawValue)) + Logger.autoconsent.debug("🚧 new CPM cohort: \(String(describing: cohort.rawValue))") allocatedExperimentCohort = cohort.rawValue return cohort } diff --git a/DuckDuckGo/Autoconsent/AutoconsentUserScript.swift b/DuckDuckGo/Autoconsent/AutoconsentUserScript.swift index 61a9624319..38a15b5b97 100644 --- a/DuckDuckGo/Autoconsent/AutoconsentUserScript.swift +++ b/DuckDuckGo/Autoconsent/AutoconsentUserScript.swift @@ -21,6 +21,7 @@ import BrowserServicesKit import Common import UserScript import PrivacyDashboard +import os.log protocol AutoconsentUserScriptDelegate: AnyObject { func autoconsentUserScript(consentStatus: CookieConsentInfo) @@ -50,7 +51,7 @@ final class AutoconsentUserScript: NSObject, WKScriptMessageHandlerWithReply, Us weak var delegate: AutoconsentUserScriptDelegate? init(scriptSource: ScriptSourceProviding, config: PrivacyConfiguration) { - os_log("Initialising autoconsent userscript", log: .autoconsent, type: .debug) + Logger.autoconsent.debug("Initialising autoconsent userscript") source = Self.loadJS("autoconsent-bundle", from: .main, withReplacements: [:]) self.config = config } @@ -65,7 +66,7 @@ final class AutoconsentUserScript: NSObject, WKScriptMessageHandlerWithReply, Us let consentStatus = CookieConsentInfo( consentManaged: consentManaged, cosmetic: cosmetic, optoutFailed: optoutFailed, selftestFailed: selftestFailed ) - os_log("Refreshing dashboard state: %s", log: .autoconsent, type: .debug, String(describing: consentStatus)) + Logger.autoconsent.debug("Refreshing dashboard state: \(String(describing: consentStatus))") self.delegate?.autoconsentUserScript(consentStatus: consentStatus) } @@ -73,7 +74,7 @@ final class AutoconsentUserScript: NSObject, WKScriptMessageHandlerWithReply, Us func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) { - os_log("Message received: %s", log: .autoconsent, type: .debug, String(describing: message.body)) + Logger.autoconsent.debug("Message received: \(String(describing: message.body))") return handleMessage(replyHandler: replyHandler, message: message) } } @@ -149,7 +150,7 @@ extension AutoconsentUserScript { let json = try JSONSerialization.data(withJSONObject: message) return try JSONDecoder().decode(Target.self, from: json) } catch { - os_log(.error, "Error decoding message body: %{public}@", error.localizedDescription) + Logger.autoconsent.error("Error decoding message body: \(error.localizedDescription, privacy: .public)") return nil } } @@ -170,13 +171,13 @@ extension AutoconsentUserScript { case MessageName.eval: handleEval(message: message, replyHandler: replyHandler) case MessageName.popupFound: - os_log("Autoconsent popup found", log: .autoconsent) + Logger.autoconsent.debug("Autoconsent popup found") replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection case MessageName.optOutResult: handleOptOutResult(message: message, replyHandler: replyHandler) case MessageName.optInResult: // this is not supported in browser - os_log("ignoring optInResult: %s", log: .autoconsent, type: .debug, String(describing: message.body)) + Logger.autoconsent.debug("ignoring optInResult: \(String(describing: message.body))") replyHandler(nil, "opt-in is not supported") case MessageName.cmpDetected: // no need to do anything here @@ -186,7 +187,7 @@ extension AutoconsentUserScript { case MessageName.autoconsentDone: handleAutoconsentDone(message: message, replyHandler: replyHandler) case MessageName.autoconsentError: - os_log("Autoconsent error: %s", log: .autoconsent, String(describing: message.body)) + Logger.autoconsent.debug("Autoconsent error: \(String(describing: message.body))") replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection } } @@ -204,7 +205,7 @@ extension AutoconsentUserScript { guard url.navigationalScheme?.isHypertextScheme == true else { // ignore special schemes - os_log("Ignoring special URL scheme: %s", log: .autoconsent, type: .debug, messageData.url) + Logger.autoconsent.debug("Ignoring special URL scheme: \(messageData.url)") replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection return } @@ -217,7 +218,7 @@ extension AutoconsentUserScript { let topURLDomain = message.webView?.url?.host guard config.isFeature(.autoconsent, enabledForDomain: topURLDomain) else { - os_log("disabled for site: %s", log: .autoconsent, type: .info, String(describing: url.absoluteString)) + Logger.autoconsent.info("disabled for site: \(String(describing: url.absoluteString))") replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection return } @@ -297,7 +298,7 @@ extension AutoconsentUserScript { replyHandler(nil, "cannot decode message") return } - os_log("opt-out result: %s", log: .autoconsent, type: .debug, String(describing: messageData)) + Logger.autoconsent.debug("opt-out result: \(String(describing: messageData))") if !messageData.result { refreshDashboardState(consentManaged: true, cosmetic: nil, optoutFailed: true, selftestFailed: nil) @@ -317,7 +318,7 @@ extension AutoconsentUserScript { replyHandler(nil, "cannot decode message") return } - os_log("opt-out successful: %s", log: .autoconsent, type: .debug, String(describing: messageData)) + Logger.autoconsent.debug("opt-out successful: \(String(describing: messageData))") guard let url = URL(string: messageData.url), let host = url.host else { @@ -329,7 +330,7 @@ extension AutoconsentUserScript { // trigger popup once per domain if !management.sitesNotifiedCache.contains(host) { - os_log("bragging that we closed a popup", log: .autoconsent, type: .debug) + Logger.autoconsent.debug("bragging that we closed a popup") management.sitesNotifiedCache.insert(host) // post popover notification on main thread DispatchQueue.main.async { @@ -344,7 +345,7 @@ extension AutoconsentUserScript { if let selfTestWebView = selfTestWebView, let selfTestFrameInfo = selfTestFrameInfo { - os_log("requesting self-test in: %s", log: .autoconsent, type: .debug, messageData.url) + Logger.autoconsent.debug("requesting self-test in: \(messageData.url)") selfTestWebView.evaluateJavaScript( "window.autoconsentMessageCallback({ type: 'selfTest' })", in: selfTestFrameInfo, @@ -352,14 +353,14 @@ extension AutoconsentUserScript { completionHandler: { (result) in switch result { case.failure(let error): - os_log("Error running self-test: %s", log: .autoconsent, type: .debug, String(describing: error)) + Logger.autoconsent.error("Error running self-test: \(error.localizedDescription, privacy: .public)") case.success: - os_log("self-test requested", log: .autoconsent, type: .debug) + Logger.autoconsent.debug("self-test requested") } } ) } else { - os_log("no self-test scheduled in this tab", log: .autoconsent, type: .debug) + Logger.autoconsent.debug("no self-test scheduled in this tab") } selfTestWebView = nil selfTestFrameInfo = nil @@ -372,7 +373,7 @@ extension AutoconsentUserScript { return } // store self-test result - os_log("self-test result: %s", log: .autoconsent, type: .debug, String(describing: messageData)) + Logger.autoconsent.debug("self-test result: \(String(describing: messageData))") refreshDashboardState(consentManaged: true, cosmetic: nil, optoutFailed: false, selftestFailed: messageData.result) replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection } diff --git a/DuckDuckGo/Bookmarks/Model/BookmarkList.swift b/DuckDuckGo/Bookmarks/Model/BookmarkList.swift index dcae056327..61edd92a17 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarkList.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarkList.swift @@ -20,6 +20,7 @@ import Foundation import BrowserServicesKit import Common import Suggestions +import os.log struct BookmarkList { @@ -79,7 +80,7 @@ struct BookmarkList { mutating func insert(_ bookmark: Bookmark) { guard itemsDict[bookmark.url] == nil else { - os_log("BookmarkList: Adding failed, the item already is in the bookmark list", type: .error) + Logger.bookmarks.error("BookmarkList: Adding failed, the item already is in the bookmark list") return } @@ -108,7 +109,7 @@ struct BookmarkList { guard !newBookmark.isFolder else { return } guard itemsDict[newBookmark.url] != nil else { - os_log("BookmarkList: Update failed, no such item in bookmark list") + Logger.bookmarks.debug("BookmarkList: Update failed, no such item in bookmark list") return } @@ -136,7 +137,7 @@ struct BookmarkList { guard !bookmark.isFolder else { return nil } guard itemsDict[newURL] == nil else { - os_log("BookmarkList: Update failed, new url already in bookmark list") + Logger.bookmarks.debug("BookmarkList: Update failed, new url already in bookmark list") return nil } @@ -154,7 +155,7 @@ private extension BookmarkList { mutating private func updateBookmarkList(newBookmark: Bookmark, oldBookmark bookmark: Bookmark) -> Bookmark? { guard itemsDict[bookmark.url] != nil, let index = allBookmarkURLsOrdered.firstIndex(of: IdentifiableBookmark(from: bookmark)) else { - os_log("BookmarkList: Update failed, no such item in bookmark list") + Logger.bookmarks.debug("BookmarkList: Update failed, no such item in bookmark list") return nil } diff --git a/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift b/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift index 67d2ef132a..349d01526c 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift @@ -20,6 +20,7 @@ import Bookmarks import Cocoa import Combine import Common +import os.log protocol BookmarkManager: AnyObject { @@ -116,19 +117,19 @@ final class LocalBookmarkManager: BookmarkManager { func loadBookmarks() { bookmarkStore.loadAll(type: .topLevelEntities) { [weak self] (topLevelEntities, error) in guard error == nil, let topLevelEntities = topLevelEntities else { - os_log("LocalBookmarkManager: Failed to fetch entities.", type: .error) + Logger.bookmarks.error("LocalBookmarkManager: Failed to fetch entities.") return } self?.bookmarkStore.loadAll(type: .bookmarks) { [weak self] (bookmarks, error) in guard error == nil, let bookmarks = bookmarks else { - os_log("LocalBookmarkManager: Failed to fetch bookmarks.", type: .error) + Logger.bookmarks.error("LocalBookmarkManager: Failed to fetch bookmarks.") return } self?.bookmarkStore.loadAll(type: .favorites) { [weak self] (favorites, error) in guard error == nil, let favorites = favorites else { - os_log("LocalBookmarkManager: Failed to fetch favorites.", type: .error) + Logger.bookmarks.error("LocalBookmarkManager: Failed to fetch favorites.") return } @@ -170,7 +171,7 @@ final class LocalBookmarkManager: BookmarkManager { guard list != nil else { return nil } guard !isUrlBookmarked(url: url) else { - os_log("LocalBookmarkManager: Url is already bookmarked", type: .error) + Logger.bookmarks.error("LocalBookmarkManager: Url is already bookmarked") return nil } @@ -200,7 +201,7 @@ final class LocalBookmarkManager: BookmarkManager { func remove(bookmark: Bookmark) { guard list != nil else { return } guard let latestBookmark = getBookmark(forUrl: bookmark.url) else { - os_log("LocalBookmarkManager: Attempt to remove already removed bookmark", type: .error) + Logger.bookmarks.error("LocalBookmarkManager: Attempt to remove already removed bookmark") return } @@ -232,7 +233,7 @@ final class LocalBookmarkManager: BookmarkManager { func update(bookmark: Bookmark) { guard list != nil else { return } guard getBookmark(forUrl: bookmark.url) != nil else { - os_log("LocalBookmarkManager: Failed to update bookmark - not in the list.", type: .error) + Logger.bookmarks.error("LocalBookmarkManager: Failed to update bookmark - not in the list.") return } @@ -246,12 +247,12 @@ final class LocalBookmarkManager: BookmarkManager { func update(bookmark: Bookmark, withURL url: URL, title: String, isFavorite: Bool) { guard list != nil else { return } guard getBookmark(forUrl: bookmark.url) != nil else { - os_log("LocalBookmarkManager: Failed to update bookmark url - not in the list.", type: .error) + Logger.bookmarks.error("LocalBookmarkManager: Failed to update bookmark url - not in the list.") return } guard let newBookmark = list?.update(bookmark: bookmark, newURL: url.absoluteString, newTitle: title, newIsFavorite: isFavorite) else { - os_log("LocalBookmarkManager: Failed to update URL of bookmark.", type: .error) + Logger.bookmarks.error("LocalBookmarkManager: Failed to update URL of bookmark.") return } @@ -275,12 +276,12 @@ final class LocalBookmarkManager: BookmarkManager { func updateUrl(of bookmark: Bookmark, to newUrl: URL) -> Bookmark? { guard list != nil else { return nil } guard getBookmark(forUrl: bookmark.url) != nil else { - os_log("LocalBookmarkManager: Failed to update bookmark url - not in the list.", type: .error) + Logger.bookmarks.error("LocalBookmarkManager: Failed to update bookmark url - not in the list.") return nil } guard let newBookmark = list?.updateUrl(of: bookmark, to: newUrl.absoluteString) else { - os_log("LocalBookmarkManager: Failed to update URL of bookmark.", type: .error) + Logger.bookmarks.error("LocalBookmarkManager: Failed to update URL of bookmark.") return nil } @@ -389,7 +390,7 @@ final class LocalBookmarkManager: BookmarkManager { guard let syncService = NSApp.delegateTyped.syncService else { return } - os_log(.debug, log: OSLog.sync, "Requesting sync if enabled") + Logger.bookmarks.debug("Requesting sync if enabled") syncService.scheduler.notifyDataChanged() } } diff --git a/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift b/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift index 09fa347374..7cf8b69414 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift @@ -19,6 +19,7 @@ import AppKit import Common import Foundation +import os.log final class BookmarkOutlineViewDataSource: NSObject, NSOutlineViewDataSource, NSOutlineViewDelegate { @@ -305,13 +306,13 @@ final class BookmarkOutlineViewDataSource: NSObject, NSOutlineViewDataSource, NS bookmark?.isFavorite = true }, completion: { error in if let error = error { - os_log("Failed to update entities during drop via outline view: %s", error.localizedDescription) + Logger.bookmarks.error("Failed to update entities during drop via outline view: \(error.localizedDescription, privacy: .public)") } }) } else if pseudoFolder == .bookmarks { bookmarkManager.add(objectsWithUUIDs: draggedObjectIdentifiers, to: nil) { error in if let error = error { - os_log("Failed to accept nil parent drop via outline view: %s", error.localizedDescription) + Logger.bookmarks.error("Failed to accept nil parent drop via outline view: \(error.localizedDescription, privacy: .public)") } } } @@ -340,7 +341,7 @@ final class BookmarkOutlineViewDataSource: NSObject, NSOutlineViewDataSource, NS let parent: ParentFolderType = (representedObject as? BookmarkFolder).map { .parent(uuid: $0.id) } ?? .root bookmarkManager.move(objectUUIDs: draggedObjectIdentifiers, toIndex: index, withinParentFolder: parent) { error in if let error = error { - os_log("Failed to accept existing parent drop via outline view: %s", error.localizedDescription) + Logger.bookmarks.error("Failed to accept existing parent drop via outline view: \(error.localizedDescription, privacy: .public)") } } diff --git a/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift b/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift index b6b67dcacc..970ed26503 100644 --- a/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift +++ b/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift @@ -25,6 +25,7 @@ import Bookmarks import Cocoa import Persistence import PixelKit +import os.log final class LocalBookmarkStore: BookmarkStore { @@ -430,9 +431,9 @@ final class LocalBookmarkStore: BookmarkStore { bookmarkFolderToReturn = bookmarkFolder } catch BookmarkStoreError.badModelMapping { - os_log("Failed to map BookmarkEntity to BookmarkFolder, with error: %s", log: .bookmarks, type: .error) + Logger.bookmarks.error("Failed to map BookmarkEntity to BookmarkFolder, with error: BookmarkStoreError.badModelMapping") } catch { - os_log("Failed to fetch last saved folder for bookmarks all tabs, with error: %s", log: .bookmarks, type: .error, error.localizedDescription) + Logger.bookmarks.error("Failed to fetch last saved folder for bookmarks all tabs, with error: \(error.localizedDescription, privacy: .public)") } } @@ -796,7 +797,7 @@ final class LocalBookmarkStore: BookmarkStore { } } catch { - os_log("Failed to import bookmarks, with error: %s", log: .dataImportExport, type: .error, error.localizedDescription) + Logger.dataImportExport.error("Failed to import bookmarks, with error: \(error.localizedDescription, privacy: .public)") let error = error as NSError let processedErrors = CoreDataErrorsParser.parse(error: error) @@ -943,7 +944,7 @@ final class LocalBookmarkStore: BookmarkStore { } onError: { [weak self] error in self?.commonOnSaveErrorHandler(error) - os_log("Failed to remove invalid bookmark entities", type: .error) + Logger.bookmarks.error("Failed to remove invalid bookmark entities") } onDidSave: {} } diff --git a/DuckDuckGo/BookmarksBar/View/BookmarksBarViewController.swift b/DuckDuckGo/BookmarksBar/View/BookmarksBarViewController.swift index 82d098a6e8..dd7c6ccb12 100644 --- a/DuckDuckGo/BookmarksBar/View/BookmarksBarViewController.swift +++ b/DuckDuckGo/BookmarksBar/View/BookmarksBarViewController.swift @@ -20,6 +20,7 @@ import AppKit import Combine import Common import Foundation +import os.log final class BookmarksBarViewController: NSViewController { @@ -217,7 +218,7 @@ final class BookmarksBarViewController: NSViewController { self.bookmarksBarCollectionView.indexPathForItem(at: point) }), let item = bookmarksBarCollectionView.item(at: indexPath.item) as? BookmarksBarCollectionViewItem else { - os_log("Item at mouseUp point not found.", type: .error) + Logger.bookmarks.error("Item at mouseUp point not found.") return } diff --git a/DuckDuckGo/Common/Extensions/FileManagerExtension.swift b/DuckDuckGo/Common/Extensions/FileManagerExtension.swift index b7e767fece..ba6c31e203 100644 --- a/DuckDuckGo/Common/Extensions/FileManagerExtension.swift +++ b/DuckDuckGo/Common/Extensions/FileManagerExtension.swift @@ -18,6 +18,7 @@ import Common import Foundation +import os.log extension FileManager { @@ -89,7 +90,7 @@ extension FileManager { index += 1 } while index <= limit // If it gets beyond the limit then chances are something else is wrong - os_log("Failed to move file to %s, attempt: %d", type: .error, desiredURL.deletingLastPathComponent().path, index) + Logger.general.error("Failed to move file to \(desiredURL.deletingLastPathComponent().path), attempt: \(index)") throw CocoaError(.fileWriteFileExists) } diff --git a/DuckDuckGo/Common/Extensions/NSTextFieldExtension.swift b/DuckDuckGo/Common/Extensions/NSTextFieldExtension.swift index 9b37797f7b..c2f41dd0ff 100644 --- a/DuckDuckGo/Common/Extensions/NSTextFieldExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSTextFieldExtension.swift @@ -18,6 +18,7 @@ import Cocoa import Common +import os.log extension NSTextField { @@ -54,7 +55,7 @@ extension NSTextField { func gradient(width: CGFloat, trailingPadding: CGFloat) { guard let layer = layer else { - os_log("NSTextField: Making of gradient failed - Text field has no layer.", type: .error) + Logger.general.error("NSTextField: Making of gradient failed - Text field has no layer.") return } @@ -65,7 +66,7 @@ extension NSTextField { } guard let mask = layer.mask as? CAGradientLayer else { - os_log("NSTextField: Making of gradient failed - Mask has no gradient layer.", type: .error) + Logger.general.error("NSTextField: Making of gradient failed - Mask has no gradient layer.") return } diff --git a/DuckDuckGo/Common/Extensions/NSViewExtension.swift b/DuckDuckGo/Common/Extensions/NSViewExtension.swift index 333dce57cf..13abfebb0a 100644 --- a/DuckDuckGo/Common/Extensions/NSViewExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSViewExtension.swift @@ -19,6 +19,7 @@ import Cocoa import Combine import Common +import os.log extension NSView { @@ -86,7 +87,7 @@ extension NSView { func makeMeFirstResponder() { guard let window = window else { - os_log("%s: Window not available", type: .error, className) + Logger.general.error("\(self.className): Window not available") return } // prevent all text selection on repeated Address Bar activation diff --git a/DuckDuckGo/Common/Extensions/URLExtension.swift b/DuckDuckGo/Common/Extensions/URLExtension.swift index ca459c0173..a937bc373b 100644 --- a/DuckDuckGo/Common/Extensions/URLExtension.swift +++ b/DuckDuckGo/Common/Extensions/URLExtension.swift @@ -21,6 +21,7 @@ import BrowserServicesKit import Common import Foundation import AppKitExtensions +import os.log extension URL.NavigationalScheme { @@ -117,7 +118,7 @@ extension URL { return searchUrl } - os_log("URL extension: Making URL from %s failed", type: .error, addressBarString) + Logger.general.error("URL extension: Making URL from \(addressBarString) failed") return nil } diff --git a/DuckDuckGo/Common/Extensions/WKWebViewConfigurationExtensions.swift b/DuckDuckGo/Common/Extensions/WKWebViewConfigurationExtensions.swift index eed0d7c745..0019081b39 100644 --- a/DuckDuckGo/Common/Extensions/WKWebViewConfigurationExtensions.swift +++ b/DuckDuckGo/Common/Extensions/WKWebViewConfigurationExtensions.swift @@ -21,6 +21,7 @@ import Combine import Common import WebKit import UserScript +import os.log extension WKWebViewConfiguration { @@ -120,7 +121,7 @@ extension NSPopover { observer?.cancel() } - os_log(.error, "trying to present \(self) from \(positioningView) not in view hierarchy") + Logger.general.error("trying to present \(self) from \(positioningView) not in view hierarchy") return } self.swizzled_show(relativeTo: positioningRect, of: positioningView, preferredEdge: preferredEdge) diff --git a/DuckDuckGo/Common/Extensions/WKWebViewExtension.swift b/DuckDuckGo/Common/Extensions/WKWebViewExtension.swift index b3b56fd610..573dfaf38d 100644 --- a/DuckDuckGo/Common/Extensions/WKWebViewExtension.swift +++ b/DuckDuckGo/Common/Extensions/WKWebViewExtension.swift @@ -19,6 +19,7 @@ import Common import Navigation import WebKit +import os.log extension WKWebView { @@ -274,7 +275,7 @@ extension WKWebView { func loadAlternateHTML(_ html: String, baseURL: URL, forUnreachableURL failingURL: URL) { guard responds(to: Selector.loadAlternateHTMLString) else { if #available(macOS 12.0, *) { - os_log(.error, log: .navigation, "WKWebView._loadAlternateHTMLString not available") + Logger.navigation.error("WKWebView._loadAlternateHTMLString not available") loadSimulatedRequest(URLRequest(url: failingURL), responseHTML: html) } return diff --git a/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift b/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift index 26fabdff43..bec5c7538e 100644 --- a/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift +++ b/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift @@ -42,8 +42,8 @@ extension UserText { static let networkProtectionInviteSuccessMessage = "DuckDuckGo's VPN secures all of your device's Internet traffic anytime, anywhere." // MARK: - Navigation Bar Status View - // "network.protection.navbar.status.view.share.feedback" - Menu item for 'Share VPN Feedback' in the VPN status view that's shown in the navigation bar - static let networkProtectionNavBarStatusViewShareFeedback = "Share VPN Feedback…" + // "network.protection.navbar.status.view.send.feedback" - Menu item for 'Send Feedback' in the VPN status view that's shown in the navigation bar + static let networkProtectionNavBarStatusViewSendFeedback = "Send Feedback…" // "network.protection.status.menu.vpn.settings" - The status menu 'VPN Settings' menu item static let networkProtectionNavBarStatusMenuVPNSettings = "VPN Settings…" // "network.protection.status.menu.faq" - The status menu 'FAQ' menu item @@ -73,6 +73,24 @@ extension UserText { extension UserText { // MARK: - Feedback Form + // "feedback-form.title" - Title for each screen of the feedback form + static let feedbackFormTitle = "Help Improve Privacy Pro" + // "general.feedback-form.category.select-feature" - Title for the feature selection state of the general feedback form + static let generalFeedbackFormCategorySelect = "Select a category" + // "general.feedback-form.category.ppro" - Description for the feedback form when the issue is related to subscription and payments + static let generalFeedbackFormCategoryPPro = "Subscription and Payments" + // "general.feedback-form.category.vpn" - Description for the feedback form when the issue is related to VPN + static let generalFeedbackFormCategoryVPN = "VPN" + // "general.feedback-form.category.pir" - Description for the feedback form when the issue is related to Personal Info Removal (PIR) + static let generalFeedbackFormCategoryPIR = "Personal Info Removal" + // "general.feedback-form.category.itr" - Description for the feedback form when the issue is related to Identity Theft Restoration (ITR) + static let generalFeedbackFormCategoryITR = "Identity Theft Restoration" + // "ppro.feedback-form.category.select-category" - Title for the category selection state of the feedback form + static let pproFeedbackFormCategorySelect = "Select a category" + // "ppro.feedback-form.category.otp" - Description for the feedback form when there is an issue with the one-time password + static let pproFeedbackFormCategoryOTP = "Issue with one-time password" + // "ppro.feedback-form.category.something-else" - Description for the feedback form when the user has an issue not categorized in other options + static let pproFeedbackFormCategoryOther = "Something else" // "vpn.feedback-form.title" - Title for each screen of the VPN feedback form static let vpnFeedbackFormTitle = "Help Improve the DuckDuckGo VPN" // "vpn.feedback-form.category.select-category" - Title for the category selection state of the VPN feedback form @@ -93,7 +111,63 @@ extension UserText { static let vpnFeedbackFormCategoryFeatureRequest = "VPN feature request" // "vpn.feedback-form.category.other" - Title for the 'other VPN feedback' category of the VPN feedback form static let vpnFeedbackFormCategoryOther = "Other VPN feedback" + // "pir.feedback-form.category.select-category" - Title for the category selection state of the PIR feedback form + static let pirFeedbackFormCategorySelect = "Select a category" + // "pir.feedback-form.category.no-info-on-specific-site" - Description for the feedback form when the scan didn't find user's info on a specific site + static let pirFeedbackFormCategoryNothingOnSpecificSite = "The scan didn't find my info on a specific site" + // "pir.feedback-form.category.not-me" - Description for the feedback form when the scan found records that don’t belong to the user + static let pirFeedbackFormCategoryNotMe = "The scan found records which aren't me" + // "pir.feedback-form.category.scan-stuck" - Description for the feedback form when the scan process is stuck + static let pirFeedbackFormCategoryScanStuck = "The scan for records is stuck" + // "pir.feedback-form.category.removal-stuck" - Description for the feedback form when the removal process is stuck + static let pirFeedbackFormCategoryRemovalStuck = "The removal process is stuck" + // "itr.feedback-form.category.select-category" - Title for the category selection state of the ITR feedback form + static let itrFeedbackFormCategorySelect = "Select a category" + // "itr.feedback-form.category.access-code" - Description for the feedback form when there is an issue with the access code + static let itrFeedbackFormCategoryAccessCode = "Issue with access code" + // "itr.feedback-form.category.contact-advisor" - Description for the feedback form when the user is unable to contact an advisor + static let itrFeedbackFormCategoryCantContactAdvisor = "Unable to contact advisor" + // "itr.feedback-form.category.unhelpful" - Description for the feedback form when the call to an advisor was unhelpful + static let itrFeedbackFormCategoryUnhelpful = "Call to Advisor was unhelpful" + // "itr.feedback-form.category.something-else" - Description for the feedback form when the user has an issue not categorized in other options + static let itrFeedbackFormCategorySomethingElse = "Something else" + // "ppro.feedback-form.text-1" - Text for the body of the PPro feedback form + static let pproFeedbackFormText1 = "Found an issue not cover in our [help center](duck://)? We definitely want to know about it.\n\nTell us what's going on:" + // "ppro.feedback-form.text-2" - Text for the body of the PPro feedback form + static let pproFeedbackFormText2 = "In addition to the details entered above, we send some anonymized info with your feedback:" + // "ppro.feedback-form.text-3" - Bullet text for the body of the PPro feedback form + static let pproFeedbackFormText3 = "• Whether specific browser features are active" + // "ppro.feedback-form.text-4" - Bullet text for the body of the PPro feedback form + static let pproFeedbackFormText4 = "• Aggregate app diagnostics (e.g., error codes)" + // "ppro.feedback-form.text-5" - Text for the body of the PPro feedback form + static let pproFeedbackFormText5 = "By clicking \"Submit\" you agree that DuckDuckGo may use information submitted to improve the app." + // "ppro.feedback-form.disclaimer" - Text for the disclaimer of the PPro feedback form + static let pproFeedbackFormDisclaimer = "Reports are anonymous and sent to DuckDuckGo to help improve our service" + + // "ppro.feedback-form.sending-confirmation.title" - Title for the feedback sent view title of the feedback form + static let pproFeedbackFormSendingConfirmationTitle = "Thank you!" + // "ppro.feedback-form.sending-confirmation.description" - Title for the feedback sent view description of the feedback form + static let pproFeedbackFormSendingConfirmationDescription = "Your Feedback will help us improve Privacy Pro." + // "ppro.feedback-form.sending-confirmation.error" - Title for the feedback sending error text of the feedback form + static let pproFeedbackFormSendingConfirmationError = "We couldn't send your feedback right now, please try again." + + // "ppro.feedback-form.button.done" - Title for the Done button of the PPro feedback form + static let pproFeedbackFormButtonDone = "Done" + // "ppro.feedback-form.button.cancel" - Title for the Cancel button of the PPro feedback form + static let pproFeedbackFormButtonCancel = "Cancel" + // "ppro.feedback-form.button.submit" - Title for the Submit button of the PPro feedback form + static let pproFeedbackFormButtonSubmit = "Submit" + // "ppro.feedback-form.button.submitting" - Title for the Submitting state of the PPro feedback form + static let pproFeedbackFormButtonSubmitting = "Submitting…" + + // "ppro.feedback-form.general-feedback.placeholder" - Placeholder for the General Feedback step in the Privacy Pro feedback form + static let pproFeedbackFormGeneralFeedbackPlaceholder = "Please give us your feedback:" + // "ppro.feedback-form.request-feature.placeholder" - Placeholder for the Feature Request step in the Privacy Pro feedback form + static let pproFeedbackFormRequestFeaturePlaceholder = "What feature would you like to see?" + + // "pir.feedback-form.category.other" - Description for the feedback form when the user has an issue not categorized in other options + static let pirFeedbackFormCategoryOther = "Something else" // "vpn.feedback-form.text-1" - Text for the body of the VPN feedback form static let vpnFeedbackFormText1 = "Please describe what's happening, what you expected to happen, and the steps that led to the issue:" // "vpn.feedback-form.text-2" - Text for the body of the VPN feedback form diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index a922574c2f..21443b6a1a 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -229,40 +229,7 @@ struct UserText { static let errorPageHeader = NSLocalizedString("page.error.header", value: "DuckDuckGo can’t load this page.", comment: "Error page heading text") static let webProcessCrashPageHeader = NSLocalizedString("page.crash.header", value: "This webpage has crashed.", comment: "Error page heading text shown when a Web Page process had crashed") static let webProcessCrashPageMessage = NSLocalizedString("page.crash.message", value: "Try reloading the page or come back later.", comment: "Error page message text shown when a Web Page process had crashed") - static let sslErrorPageHeader = NSLocalizedString("ssl.error.page.header", value: "Warning: This site may be insecure", comment: "Title shown in an error page that warn users of security risks on a website due to SSL issues") static let sslErrorPageTabTitle = NSLocalizedString("ssl.error.page.tab.title", value: "Warning: Site May Be Insecure", comment: "Title shown in an error page tab that warn users of security risks on a website due to SSL issues") - static func sslErrorPageBody(_ domain: String) -> String { - let localized = NSLocalizedString("ssl.error.page.body", - value: "The certificate for this site is invalid. You might be connecting to a server that is pretending to be %1$@ which could put your confidential information at risk.", - comment: "Error description shown in an error page that warns users of security risks on a website due to SSL issues. %1$@ represent the site domain.") - return String(format: localized, domain) - } - static let sslErrorPageAdvancedButton = NSLocalizedString("ssl.error.page.advanced.button", value: "Advanced…", comment: "Button shown in an error page that warns users of security risks on a website due to SSL issues. The buttons allows the user to see advanced options on click.") - static let sslErrorPageLeaveSiteButton = NSLocalizedString("ssl.error.page.leave.site.button", value: "Leave This Site", comment: "Button shown in an error page that warns users of security risks on a website due to SSL issues. The buttons allows the user to leave the website and navigate to previous page.") - static let sslErrorPageVisitSiteButton = NSLocalizedString("ssl.error.page.visit.site.button", value: "Accept Risk and Visit Site", comment: "Button shown in an error page that warns users of security risks on a website due to SSL issues. The buttons allows the user to visit the website anyway despite the risks.") - static let sslErrorAdvancedInfoTitle = NSLocalizedString("ssl.error.page.advanced.info.title", value: "DuckDuckGo warns you when a website has an invalid certificate.", comment: "Title of the Advanced info section shown in an error page that warns users of security risks on a website due to SSL issues.") - static let sslErrorAdvancedInfoBodyWrongHost = NSLocalizedString("ssl.error.page.advanced.info.body.wrong.host", value: "It’s possible that the website is misconfigured or that an attacker has compromised your connection.", comment: "Body of the text of the Advanced info shown in an error page that warns users of security risks on a website due to SSL issues.") - static let sslErrorAdvancedInfoBodyExpired = NSLocalizedString("ssl.error.page.advanced.info.body.expired", value: "It’s possible that the website is misconfigured, that an attacker has compromised your connection, or that your system clock is incorrect.", comment: "Body of the text of the Advanced info shown in an error page that warns users of security risks on a website due to SSL issues.") - static func sslErrorCertificateExpiredMessage(_ domain: String) -> String { - let localized = NSLocalizedString("ssl.error.certificate.expired.message", - value: "The security certificate for %1$@ is expired.", - comment: "Describes an SSL error where a website's security certificate is expired. '%1$@' is a placeholder for the website's domain.") - return String(format: localized, domain) - } - static func sslErrorCertificateWrongHostMessage(_ domain: String, eTldPlus1: String) -> String { - let localized = NSLocalizedString("ssl.error.wrong.host.message", - value: "The security certificate for %1$@ does not match *.%2$@.", - comment: "Explains an SSL error when a site's certificate doesn't match its domain. '%1$@' is the site's domain.") - return String(format: localized, domain, eTldPlus1) - } - static func sslErrorCertificateSelfSignedMessage(_ domain: String) -> String { - let localized = NSLocalizedString("ssl.error.self.signed.message", - value: "The security certificate for %1$@ is not trusted by your device's operating system.", - comment: "Warns the user that the site's security certificate is self-signed and not trusted. '%1$@' is the site's domain.") - return String(format: localized, domain) - } - - static let openSystemPreferences = NSLocalizedString("open.preferences", value: "Open System Preferences", comment: "Open System Preferences (to re-enable permission for the App) (up to and including macOS 12") static let openSystemSettings = NSLocalizedString("open.settings", value: "Open System Settings…", comment: "This string represents a prompt or button label prompting the user to open system settings") @@ -492,6 +459,7 @@ struct UserText { static let bookmarkImportedFromFolder = NSLocalizedString("bookmarks.imported.from.folder", value: "Imported from", comment: "Name of the folder the imported bookmarks are saved into") // MARK: Feedback + static let sendPProFeedback = NSLocalizedString("send.ppro.feedback", value: "Send Privacy Pro Feedback", comment: "Menu with feedback commands") static let reportBrokenSite = NSLocalizedString("report.broken.site", value: "Report Broken Site", comment: "Menu with feedback commands") static let browserFeedback = NSLocalizedString("send.browser.feedback", value: "Send Browser Feedback", comment: "Menu with feedback commands") static let browserFeedbackTitle = NSLocalizedString("send.browser.feedback.title", value: "Help Improve the DuckDuckGo Browser", comment: "Title of the interface to send feedback on the browser") @@ -1245,6 +1213,30 @@ struct UserText { } } } + + // MARK: - Onboarding + enum ContextualOnboarding { + static let onboardingTryASearchTitle = NSLocalizedString("contextual.onboarding.try-a-search.title", value: "Try a search!", comment: "Title of a popover on the browser that invites the user to try a search") + static let onboardingTryASearchMessage = NSLocalizedString("contextual.onboarding.try-a-search.message", value: "Your DuckDuckGo searches are always anonymous.", comment: "Message of a popover on the browser that invites the user to try a search explaining that their searches are anonymous") + static let onboardingTryASiteTitle = NSLocalizedString("contextual.onboarding.try-a-site.title", value: "Next, try visiting a site!", comment: "Title of a popover on the browser that invites the user to try a visiting a website") + static let onboardingTryASiteNTPTitle = NSLocalizedString("contextual.onboarding.ntp.try-a-site.title", value: "Try visiting a site!", comment: "Title of a popover on the new tab page browser that invites the user to try a visiting a website") + static let onboardingTryASiteMessage = NSLocalizedString("contextual.onboarding.try-a-site.message", value: "I’ll block trackers so they can’t spy on you.", comment: "Message of a popover on the browser that invites the user to try visiting a website to explain that we block trackers") + static let onboardingTryFireButtonMessage = NSLocalizedString("contextual.onboarding.try-fire-button.message", value: "Instantly clear your browsing activity with the Fire Button.\n\nGive it a try! 🔥", comment: "Message of a popover on the browser that invites the user to try visiting the browser Fire Button. Please leave the line break") + static let onboardingGotItButton = NSLocalizedString("contextual.onboarding.got-it.button", value: "Got it", comment: "During onboarding steps this button is shown and takes either to the next steps or closes the onboarding.") + static let onboardingFirstSearchDoneTitle = NSLocalizedString("contextual.onboarding.first-search-done.title", value: "That’s DuckDuckGo Search.", comment: "After the user performs their first search using the browser, this dialog explains the advantages of using DuckDuckGo") + static let onboardingFirstSearchDoneMessage = NSLocalizedString("contextual.onboarding.first-search-done.message", value: "Private. Fast. Fewer ads.", comment: "After the user performs their first search using the browser, this dialog explains the advantages of using DuckDuckGo") + static let onboardingFinalScreenTitle = NSLocalizedString("contextual.onboarding.final-screen.title", value: "You’ve got this!", comment: "Title of the last screen of the onboarding to the browser app") + static let onboardingFinalScreenMessage = NSLocalizedString("contextual.onboarding.final-screen.message", value: "Remember: every time you browse with me a creepy ad loses its wings. 👌", comment: "Message of the last screen of the onboarding to the browser app.") + static let onboardingFinalScreenButton = NSLocalizedString("contextual.onboarding.final-screen.button", value: "High five!", comment: "Button on the last screen of the onboarding, it will dismiss the onboarding screen.") + static let tryASearchOption1English = NSLocalizedString("contextual.onboarding.try-search.option1-English", value: "how to say “duck” in spanish", comment: "Browser Search query for how to say duck in english") + static let tryASearchOption1International = NSLocalizedString("contextual.onboarding.try-search.option1international", value: "how to say “duck” in english", comment: "Browser Search query for how to say duck in english") + static let tryASearchOption2English = NSLocalizedString("contextual.onboarding.try-search.option2-english", value: "mighty ducks cast", comment: "Search query for the cast of Mighty Ducks") + static let tryASearchOption2International = NSLocalizedString("contextual.onboarding.try-search.option2-international", value: "cast of avatar", comment: "Search query for the cast of Avatar") + static let tryASearchOption3 = NSLocalizedString("contextual.onboarding.try-search.option3", value: "local weather", comment: "Browser Search query for local weather") + static let tryASearchOptionSurpriseMeTitle = NSLocalizedString("contextual.onboarding.try-search.surprise-me-title", value: "Surprise me!", comment: "Title for a button that triggers an unknown search query for the user.") + static let tryASearchOptionSurpriseMeEnglish = NSLocalizedString("contextual.onboarding.try-search.surprise-me-english", value: "chocolate chip cookie recipes", comment: "Browser Search query for chocolate chip cookie recipes") + static let tryASearchOptionSurpriseMeInternational = NSLocalizedString("contextual.onboarding.try-search.surprise-me-international", value: "dinner recipes", comment: "Browser Search query for dinner recipes") + } // Key: "subscription.menu.item" // Comment: "Title for Subscription item in the options menu" diff --git a/DuckDuckGo/Common/Logger+Multiple.swift b/DuckDuckGo/Common/Logger+Multiple.swift new file mode 100644 index 0000000000..f29c3e8b7e --- /dev/null +++ b/DuckDuckGo/Common/Logger+Multiple.swift @@ -0,0 +1,31 @@ +// +// Logger+Multiple.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import os.log + +extension Logger { + static var dataImportExport = { Logger(subsystem: "Data Import/Export", category: "") }() + static var config = { Logger(subsystem: "Configuration Downloading", category: "") }() + static var autoLock = { Logger(subsystem: "Auto-Lock", category: "") }() + static var httpsUpgrade = { Logger(subsystem: "HTTPS Upgrade", category: "") }() + static var atb = { Logger(subsystem: "ATB", category: "") }() + static var tabSnapshots = { Logger(subsystem: "Tab Snapshots", category: "") }() + static var tabLazyLoading = { Logger(subsystem: "Lazy Loading", category: "") }() + static var updates = { Logger(subsystem: "Updates", category: "") }() +} diff --git a/DuckDuckGo/Common/Logging/Logging.swift b/DuckDuckGo/Common/Logging/Logging.swift deleted file mode 100644 index 7b3af9d9ea..0000000000 --- a/DuckDuckGo/Common/Logging/Logging.swift +++ /dev/null @@ -1,181 +0,0 @@ -// -// Logging.swift -// -// Copyright © 2021 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import os.log - -extension OSLog { - - enum AppCategories: String, CaseIterable { - case atb = "ATB" - case config = "Configuration Downloading" - case downloads = "Downloads" - case fire = "Fire" - case dataImportExport = "Data Import/Export" - case pixel = "Pixel" - case httpsUpgrade = "HTTPS Upgrade" - case favicons = "Favicons" - case autoLock = "Auto-Lock" - case tabLazyLoading = "Lazy Loading" - case autoconsent = "Autoconsent" - case bookmarks = "Bookmarks" - case attribution = "Ad Attribution" - case bitwarden = "Bitwarden" - case navigation = "Navigation" - case duckPlayer = "Duck Player" - case tabSnapshots = "Tab Snapshots" - case updates = "Updates" - case sync = "Sync" - case dbp = "dbp" - } - - enum AllCategories { - static var allCases: [String] { - AppCategories.allCases.map(\.rawValue) - } - } - - @OSLogWrapper(AppCategories.atb) static var atb - @OSLogWrapper(AppCategories.config) static var config - @OSLogWrapper(AppCategories.downloads) static var downloads - @OSLogWrapper(AppCategories.fire) static var fire - @OSLogWrapper(AppCategories.dataImportExport) static var dataImportExport - @OSLogWrapper(AppCategories.pixel) static var pixel - @OSLogWrapper(AppCategories.httpsUpgrade) static var httpsUpgrade - @OSLogWrapper(AppCategories.favicons) static var favicons - @OSLogWrapper(AppCategories.autoLock) static var autoLock - @OSLogWrapper(AppCategories.tabLazyLoading) static var tabLazyLoading - @OSLogWrapper(AppCategories.autoconsent) static var autoconsent - @OSLogWrapper(AppCategories.bookmarks) static var bookmarks - @OSLogWrapper(AppCategories.attribution) static var attribution - @OSLogWrapper(AppCategories.bitwarden) static var bitwarden - @OSLogWrapper(AppCategories.navigation) static var navigation - @OSLogWrapper(AppCategories.duckPlayer) static var duckPlayer - @OSLogWrapper(AppCategories.tabSnapshots) static var tabSnapshots - @OSLogWrapper(AppCategories.updates) static var updates - @OSLogWrapper(AppCategories.sync) static var sync - @OSLogWrapper(AppCategories.dbp) static var dbp - - // Debug->Logging categories will only be enabled for one day - @UserDefaultsWrapper(key: .loggingEnabledDate, defaultValue: .distantPast) - private static var loggingEnabledDate: Date - private static var isLoggingEnabledToday: Bool { - NSCalendar.current.isDate(Date(), inSameDayAs: loggingEnabledDate) - } - - @UserDefaultsWrapper(key: .loggingCategories, defaultValue: []) - private static var loggingCategoriesSetting: [String] { - didSet { - loggingEnabledDate = Date() - } - } - static var loggingCategories: Set { - get { - guard isLoggingEnabledToday else { return [] } - return Set(loggingCategoriesSetting) - } - set { - loggingCategoriesSetting = Array(newValue) - enabledLoggingCategories = loggingCategories - } - } - - static let isRunningInDebugEnvironment: Bool = { - ProcessInfo().environment[ProcessInfo.Constants.osActivityMode] == ProcessInfo.Constants.debug - || ProcessInfo().environment[ProcessInfo.Constants.osActivityDtMode] == ProcessInfo.Constants.yes - }() - - static let subsystem = Bundle.main.bundleIdentifier! - -} - -extension ProcessInfo { - enum Constants { - static let osActivityMode = "OS_ACTIVITY_MODE" - static let osActivityDtMode = "OS_ACTIVITY_DT_MODE" - static let debug = "debug" - static let yes = "YES" - } -} - -extension OSLog.OSLogWrapper { - - private static let enableLoggingCategoriesOnce: Void = { -#if CI - OSLog.enabledLoggingCategories = Set(OSLog.AllCategories.allCases) -#else - OSLog.enabledLoggingCategories = OSLog.loggingCategories -#endif - }() - - init(_ category: OSLog.AppCategories) { - _=Self.enableLoggingCategoriesOnce - self.init(rawValue: category.rawValue) - } - -} - -func logOrAssertionFailure(_ message: String) { -#if DEBUG && !CI - assertionFailure(message) -#else - os_log("%{public}s", type: .error, message) -#endif -} - -#if DEBUG - -func breakByRaisingSigInt(_ description: String, file: StaticString = #file, line: Int = #line) { - let fileLine = "\(("\(file)" as NSString).lastPathComponent):\(line)" - os_log(""" - - - ------------------------------------------------------------------------------------------------------ - BREAK at %s: - ------------------------------------------------------------------------------------------------------ - - %s - - Hit Continue (^⌘Y) to continue program execution - ------------------------------------------------------------------------------------------------------ - - """, type: .debug, fileLine, description.components(separatedBy: "\n").map { " " + $0.trimmingWhitespace() }.joined(separator: "\n")) - raise(SIGINT) -} - -// get symbol from stack trace for a caller of a calling method -func callingSymbol() -> String { - let stackTrace = Thread.callStackSymbols - // find `callingSymbol` itself or dispatch_once_callout - var callingSymbolIdx = stackTrace.firstIndex(where: { $0.contains("_dispatch_once_callout") }) - ?? stackTrace.firstIndex(where: { $0.contains("callingSymbol") })! - // procedure calling `callingSymbol` - callingSymbolIdx += 1 - - var symbolName: String - repeat { - // caller for the procedure - callingSymbolIdx += 1 - let line = stackTrace[callingSymbolIdx].replacingOccurrences(of: Bundle.main.executableURL!.lastPathComponent, with: "DDG") - symbolName = String(line.split(separator: " ", maxSplits: 3)[3]).components(separatedBy: " + ")[0] - } while stackTrace[callingSymbolIdx - 1].contains(symbolName.dropping(suffix: "To")) // skip objc wrappers - - return symbolName -} - -#endif diff --git a/DuckDuckGo/Common/Utilities/TabInstrumentation.swift b/DuckDuckGo/Common/Utilities/TabInstrumentation.swift index 9e1d674db2..2fa526eaba 100644 --- a/DuckDuckGo/Common/Utilities/TabInstrumentation.swift +++ b/DuckDuckGo/Common/Utilities/TabInstrumentation.swift @@ -91,9 +91,7 @@ final class TabInstrumentation: TabInstrumentationProtocol { // 0 is treated as 1ms let timeInNS: UInt64 = timeInMs.asNanos - os_log(.debug, - log: type(of: self).tabsLog, - "[%@] Request: %@ - %@ - %@ (%@) in %llu", currentURL, url, requestType, status, reason, timeInNS) + Logger.general.debug("[\(currentURL)] Request: \(url) - \(requestType) - \(status) (\(reason)) in \(timeInNS)") } func jsEvent(name: String, executedIn timeInMs: Double) { @@ -102,9 +100,7 @@ final class TabInstrumentation: TabInstrumentationProtocol { // 0 is treated as 1ms let timeInNS: UInt64 = timeInMs.asNanos - os_log(.debug, - log: type(of: self).tabsLog, - "[%@] JSEvent: %@ executedIn: %llu", currentURL, name, timeInNS) + Logger.general.debug("[\(currentURL)] JSEvent: \(name) executedIn: \(timeInNS)") } } diff --git a/DuckDuckGo/Configuration/ConfigurationManager.swift b/DuckDuckGo/Configuration/ConfigurationManager.swift index f7bd81720b..4f912030f5 100644 --- a/DuckDuckGo/Configuration/ConfigurationManager.swift +++ b/DuckDuckGo/Configuration/ConfigurationManager.swift @@ -23,6 +23,7 @@ import Configuration import Common import Networking import PixelKit +import os.log final class ConfigurationManager { @@ -81,7 +82,7 @@ final class ConfigurationManager { } func start() { - os_log("Starting configuration refresh timer", log: .config, type: .debug) + Logger.config.debug("Starting configuration refresh timer") timerCancellable = Timer.publish(every: Constants.refreshCheckIntervalSeconds, on: .main, in: .default) .autoconnect() .receive(on: Self.queue) @@ -94,11 +95,6 @@ final class ConfigurationManager { } } - func log() { - os_log("last update %{public}s", log: .config, type: .default, String(describing: lastUpdateTime)) - os_log("last refresh check %{public}s", log: .config, type: .default, String(describing: lastRefreshCheckTime)) - } - private func refreshNow(isDebug: Bool = false) async { let updateTrackerBlockingDependenciesTask = Task { let didFetchAnyTrackerBlockingDependencies = await fetchTrackerBlockingDependencies(isDebug: isDebug) @@ -133,7 +129,9 @@ final class ConfigurationManager { await updateBloomFilterExclusionsTask.value ConfigurationStore.shared.log() - log() + + Logger.config.info("last update \(String(describing: self.lastUpdateTime), privacy: .public)") + Logger.config.info("last refresh check \(String(describing: self.lastRefreshCheckTime), privacy: .public)") } private func fetchTrackerBlockingDependencies(isDebug: Bool) async -> Bool { @@ -149,11 +147,7 @@ final class ConfigurationManager { try await task.value didFetchAnyTrackerBlockingDependencies = true } catch { - os_log("Failed to complete configuration update to %@: %@", - log: .config, - type: .error, - configuration.rawValue, - error.localizedDescription) + Logger.config.error("Failed to complete configuration update to \(configuration.rawValue): \(error.localizedDescription, privacy: .public)") tryAgainSoon() } } @@ -168,7 +162,7 @@ final class ConfigurationManager { return } - os_log("Failed to complete configuration update %@", log: .config, type: .error, error.localizedDescription) + Logger.config.error("Failed to complete configuration update \(error.localizedDescription, privacy: .public)") PixelKit.fire(DebugEvent(GeneralPixel.configurationFetchError(error: error))) tryAgainSoon() } @@ -176,7 +170,7 @@ final class ConfigurationManager { @discardableResult public func refreshIfNeeded() -> Task? { guard isReadyToRefresh else { - os_log("Configuration refresh is not needed at this time", log: .config, type: .debug) + Logger.config.debug("Configuration refresh is not needed at this time") return nil } return Task { diff --git a/DuckDuckGo/Configuration/ConfigurationStore.swift b/DuckDuckGo/Configuration/ConfigurationStore.swift index 3a754dccb3..5d21979c29 100644 --- a/DuckDuckGo/Configuration/ConfigurationStore.swift +++ b/DuckDuckGo/Configuration/ConfigurationStore.swift @@ -20,6 +20,7 @@ import Common import Foundation import Configuration import PixelKit +import os.log final class ConfigurationStore: ConfigurationStoring { @@ -119,14 +120,14 @@ final class ConfigurationStore: ConfigurationStoring { } func log() { - os_log("bloomFilterBinaryEtag %{public}s", log: .config, type: .default, bloomFilterBinaryEtag ?? "") - os_log("bloomFilterSpecEtag %{public}s", log: .config, type: .default, bloomFilterSpecEtag ?? "") - os_log("bloomFilterExcludedDomainsEtag %{public}s", log: .config, type: .default, bloomFilterExcludedDomainsEtag ?? "") - os_log("surrogatesEtag %{public}s", log: .config, type: .default, surrogatesEtag ?? "") - os_log("trackerRadarEtag %{public}s", log: .config, type: .default, trackerRadarEtag ?? "") - os_log("privacyConfigurationEtag %{public}s", log: .config, type: .default, privacyConfigurationEtag ?? "") - os_log("FBConfigEtag %{public}s", log: .config, type: .default, FBConfigEtag ?? "") - os_log("remoteMessagingConfig %{public}s", log: .config, type: .default, remoteMessagingConfigEtag ?? "") + Logger.config.info("bloomFilterBinaryEtag \(self.bloomFilterBinaryEtag ?? "", privacy: .public)") + Logger.config.info("bloomFilterSpecEtag \(self.bloomFilterSpecEtag ?? "", privacy: .public)") + Logger.config.info("bloomFilterExcludedDomainsEtag \(self.self.bloomFilterExcludedDomainsEtag ?? "", privacy: .public)") + Logger.config.info("surrogatesEtag \(self.surrogatesEtag ?? "", privacy: .public)") + Logger.config.info("trackerRadarEtag \(self.trackerRadarEtag ?? "", privacy: .public)") + Logger.config.info("privacyConfigurationEtag \(self.privacyConfigurationEtag ?? "", privacy: .public)") + Logger.config.info("FBConfigEtag \(self.FBConfigEtag ?? "", privacy: .public)") + Logger.config.info("remoteMessagingConfig \(self.remoteMessagingConfigEtag ?? "", privacy: .public)") } func fileUrl(for config: Configuration) -> URL { diff --git a/DuckDuckGo/ContentBlocker/ContentBlocking.swift b/DuckDuckGo/ContentBlocker/ContentBlocking.swift index 28825f7c5a..137ef93f2c 100644 --- a/DuckDuckGo/ContentBlocker/ContentBlocking.swift +++ b/DuckDuckGo/ContentBlocker/ContentBlocking.swift @@ -92,8 +92,7 @@ final class AppContentBlocking { compiledRulesSource: contentBlockingManager, exceptionsSource: exceptionsSource, errorReporting: attributionDebugEvents, - compilationErrorReporting: Self.debugEvents, - log: .attribution) + compilationErrorReporting: Self.debugEvents) } private static let debugEvents = EventMapping { event, error, parameters, onComplete in diff --git a/DuckDuckGo/CrashReports/Model/CrashReporter.swift b/DuckDuckGo/CrashReports/Model/CrashReporter.swift index f8e73f87e6..b3456a3adb 100644 --- a/DuckDuckGo/CrashReports/Model/CrashReporter.swift +++ b/DuckDuckGo/CrashReports/Model/CrashReporter.swift @@ -24,7 +24,7 @@ import PixelKit final class CrashReporter { private let reader = CrashReportReader() - private lazy var sender = CrashReportSender(platform: .macOS, log: .default) + private lazy var sender = CrashReportSender(platform: .macOS) private lazy var promptPresenter = CrashReportPromptPresenter() @UserDefaultsWrapper(key: .lastCrashReportCheckDate, defaultValue: nil) diff --git a/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift b/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift index 4eeb776e44..f0b173a42a 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift @@ -77,7 +77,7 @@ struct DataBrokerProtectionAppEvents { private func restartBackgroundAgent(loginItemsManager: LoginItemsManager) { DataBrokerProtectionLoginItemPixels.fire(pixel: GeneralPixel.dataBrokerResetLoginItemDaily, frequency: .daily) loginItemsManager.disableLoginItems([LoginItem.dbpBackgroundAgent]) - loginItemsManager.enableLoginItems([LoginItem.dbpBackgroundAgent], log: .dbp) + loginItemsManager.enableLoginItems([LoginItem.dbpBackgroundAgent]) // restartLoginItems doesn't work when we change the agent name } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift b/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift index 660cf055c5..0a0651957e 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift @@ -22,6 +22,7 @@ import AppKit import Common import LoginItems import NetworkProtectionProxy +import os.log @MainActor final class DataBrokerProtectionDebugMenu: NSMenu { @@ -175,28 +176,28 @@ final class DataBrokerProtectionDebugMenu: NSMenu { } @objc private func startScheduledOperations(_ sender: NSMenuItem) { - os_log("Running queued operations...", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Running queued operations...") let showWebView = sender.representedObject as? Bool ?? false DataBrokerProtectionManager.shared.loginItemInterface.startScheduledOperations(showWebView: showWebView) } @objc private func runScanOperations(_ sender: NSMenuItem) { - os_log("Running scan operations...", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Running scan operations...") let showWebView = sender.representedObject as? Bool ?? false DataBrokerProtectionManager.shared.loginItemInterface.startImmediateOperations(showWebView: showWebView) } @objc private func runOptoutOperations(_ sender: NSMenuItem) { - os_log("Running Optout operations...", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Running Optout operations...") let showWebView = sender.representedObject as? Bool ?? false DataBrokerProtectionManager.shared.loginItemInterface.runAllOptOuts(showWebView: showWebView) } @objc private func backgroundAgentRestart() { - LoginItemsManager().restartLoginItems([LoginItem.dbpBackgroundAgent], log: .dbp) + LoginItemsManager().restartLoginItems([LoginItem.dbpBackgroundAgent]) } @objc private func backgroundAgentDisable() { @@ -204,7 +205,7 @@ final class DataBrokerProtectionDebugMenu: NSMenu { } @objc private func backgroundAgentEnable() { - LoginItemsManager().enableLoginItems([LoginItem.dbpBackgroundAgent], log: .dbp) + LoginItemsManager().enableLoginItems([LoginItem.dbpBackgroundAgent]) } @objc private func deleteAllDataAndStopAgent() { diff --git a/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift b/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift index 66df557611..96b3f94c95 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift @@ -19,6 +19,7 @@ import Foundation import DataBrokerProtection import Common +import os.log public extension Notification.Name { static let dbpWasDisabled = Notification.Name("com.duckduckgo.DBP.DBPWasDisabled") @@ -43,7 +44,7 @@ struct DataBrokerProtectionFeatureDisabler: DataBrokerProtectionFeatureDisabling try dataManager.removeAllData() // the dataManagers delegate handles login item disabling } catch { - os_log("DataBrokerProtectionFeatureDisabler error: disableAndDelete, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("DataBrokerProtectionFeatureDisabler error: disableAndDelete, error: \(error.localizedDescription, privacy: .public)") } DataBrokerProtectionLoginItemPixels.fire(pixel: GeneralPixel.dataBrokerDisableAndDeleteDaily, frequency: .daily) diff --git a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift index 491ca77a52..0affd1fb34 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionFeatureGatekeeper.swift @@ -21,6 +21,7 @@ import BrowserServicesKit import Common import DataBrokerProtection import Subscription +import os.log protocol DataBrokerProtectionFeatureGatekeeper { func isFeatureVisible() -> Bool @@ -76,7 +77,7 @@ struct DefaultDataBrokerProtectionFeatureGatekeeper: DataBrokerProtectionFeature func disableAndDeleteForAllUsers() { featureDisabler.disableAndDelete() - os_log("Disabling and removing DBP for all users", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Disabling and removing DBP for all users") } /// If we want to prevent new users from joining the waitlist while still allowing waitlist users to continue using it, @@ -118,12 +119,12 @@ private extension DefaultDataBrokerProtectionFeatureGatekeeper { func firePrerequisitePixelsAndLogIfNecessary(hasEntitlements: Bool, isAuthenticatedResult: Bool) { if !hasEntitlements { pixelHandler.fire(.gatekeeperEntitlementsInvalid) - os_log("🔴 DBP feature Gatekeeper: Entitlement check failed", log: .dataBrokerProtection) + Logger.dataBrokerProtection.error("DBP feature Gatekeeper: Entitlement check failed") } if !isAuthenticatedResult { pixelHandler.fire(.gatekeeperNotAuthenticated) - os_log("🔴 DBP feature Gatekeeper: Authentication check failed", log: .dataBrokerProtection) + Logger.dataBrokerProtection.error("DBP feature Gatekeeper: Authentication check failed") } } } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionLoginItemInterface.swift b/DuckDuckGo/DBP/DataBrokerProtectionLoginItemInterface.swift index 881312dd17..b619092aa3 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionLoginItemInterface.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionLoginItemInterface.swift @@ -51,7 +51,7 @@ extension DefaultDataBrokerProtectionLoginItemInterface: DataBrokerProtectionLog private func enableLoginItem() { DataBrokerProtectionLoginItemPixels.fire(pixel: GeneralPixel.dataBrokerEnableLoginItemDaily, frequency: .daily) - loginItemsManager.enableLoginItems([.dbpBackgroundAgent], log: .dbp) + loginItemsManager.enableLoginItems([.dbpBackgroundAgent]) } // MARK: - DataBrokerProtectionLoginItemInterface diff --git a/DuckDuckGo/DBP/LoginItem+DataBrokerProtection.swift b/DuckDuckGo/DBP/LoginItem+DataBrokerProtection.swift index 3411cf1dc6..540dc8e1f6 100644 --- a/DuckDuckGo/DBP/LoginItem+DataBrokerProtection.swift +++ b/DuckDuckGo/DBP/LoginItem+DataBrokerProtection.swift @@ -19,11 +19,11 @@ import Foundation import LoginItems import DataBrokerProtection +import os.log extension LoginItem { - static let dbpBackgroundAgent = LoginItem(bundleId: Bundle.main.dbpBackgroundAgentBundleId, defaults: .dbp, log: .dbp) - + static let dbpBackgroundAgent = LoginItem(bundleId: Bundle.main.dbpBackgroundAgentBundleId, defaults: .dbp, logger: Logger.dataBrokerProtection) } extension LoginItem: DBPLoginItemStatusChecker { diff --git a/DuckDuckGo/DataImport/Bookmarks/Safari/SafariBookmarksReader.swift b/DuckDuckGo/DataImport/Bookmarks/Safari/SafariBookmarksReader.swift index 51b2ceb122..4e0d75a040 100644 --- a/DuckDuckGo/DataImport/Bookmarks/Safari/SafariBookmarksReader.swift +++ b/DuckDuckGo/DataImport/Bookmarks/Safari/SafariBookmarksReader.swift @@ -18,6 +18,7 @@ import Common import Foundation +import os.log final class SafariBookmarksReader { @@ -71,7 +72,7 @@ final class SafariBookmarksReader { let bookmarks = try reallyReadBookmarks() return .success(bookmarks) } catch { - os_log("Failed to read Safari Bookmarks Plist at \"%s\": %s", log: .dataImportExport, type: .error, safariBookmarksFileURL.path, error.localizedDescription) + Logger.dataImportExport.error("Failed to read Safari Bookmarks Plist at \(self.safariBookmarksFileURL.path): \(error.localizedDescription)") switch error { case let error as ImportError: return .failure(error) diff --git a/DuckDuckGo/DataImport/Bookmarks/Safari/SafariFaviconsReader.swift b/DuckDuckGo/DataImport/Bookmarks/Safari/SafariFaviconsReader.swift index d871378c67..2a4013defa 100644 --- a/DuckDuckGo/DataImport/Bookmarks/Safari/SafariFaviconsReader.swift +++ b/DuckDuckGo/DataImport/Bookmarks/Safari/SafariFaviconsReader.swift @@ -21,6 +21,7 @@ import Foundation import Common import CryptoKit import GRDB +import os.log final class SafariFaviconsReader { @@ -119,7 +120,7 @@ final class SafariFaviconsReader { do { imageData = try readImageData(with: record.host) } catch { - os_log(.error, log: .dataImportExport, "could not read image data for \(record.host)") + Logger.dataImportExport.error("could not read image data for \(record.host): \(error.localizedDescription)") return nil } guard let formattedHost = record.formattedHost else { return nil } diff --git a/DuckDuckGo/DataImport/Model/DataImportViewModel.swift b/DuckDuckGo/DataImport/Model/DataImportViewModel.swift index 489e857c6e..fb28f85350 100644 --- a/DuckDuckGo/DataImport/Model/DataImportViewModel.swift +++ b/DuckDuckGo/DataImport/Model/DataImportViewModel.swift @@ -20,6 +20,7 @@ import AppKit import Common import UniformTypeIdentifiers import PixelKit +import os.log struct DataImportViewModel { @@ -54,15 +55,6 @@ struct DataImportViewModel { /// Factory for a DataImporter for importSource private let reportSenderFactory: ReportSenderFactory - private func log(_ message: @autoclosure () -> String) { - if NSApp.runType == .unitTests { - } else if OSLog.dataImportExport != .disabled { - os_log(log: .dataImportExport, message()) - } else if NSApp.runType == .xcPreviews { - print(message()) - } - } - enum Screen: Hashable { case profileAndDataTypesPicker case moreInfo @@ -188,7 +180,7 @@ struct DataImportViewModel { ?? selectedDataTypes.subtracting(self.summary.filter { $0.result.isSuccess }.map(\.dataType)) let importer = dataImporterFactory(importSource, dataType, url, primaryPassword) - log("import \(dataTypes) at \"\(url.path)\" using \(type(of: importer))") + Logger.dataImportExport.debug("import \(dataTypes) at \"\(url.path)\" using \(type(of: importer))") // validate file access/encryption password requirement before starting import if let errors = importer.validateAccess(for: dataTypes), @@ -226,7 +218,7 @@ struct DataImportViewModel { private mutating func mergeImportSummary(with summary: DataImportSummary) { self.importTask = nil - log("merging summary \(summary)") + Logger.dataImportExport.debug("merging summary \(summary)") // append successful import results first keeping the original DataType sorting order self.summary.append(contentsOf: DataType.allCases.compactMap { dataType in @@ -261,23 +253,23 @@ struct DataImportViewModel { } if let nextScreen { - log("mergeImportSummary: next screen: \(nextScreen)") + Logger.dataImportExport.debug("mergeImportSummary: next screen: \(String(describing: nextScreen))") self.screen = nextScreen } else if screenForNextDataTypeRemainingToImport(after: DataType.allCases.last(where: summary.keys.contains)) == nil, // no next data type manual import screen // and there should be failed data types (and non-recovered) selectedDataTypes.contains(where: { dataType in self.summary.last(where: { $0.dataType == dataType })?.result.error != nil }) { - log("mergeImportSummary: feedback") + Logger.dataImportExport.debug("mergeImportSummary: feedback") // after last failed datatype show feedback self.screen = .feedback } else if self.screen.isFileImport, let dataType = self.screen.fileImportDataType { - log("mergeImportSummary: file import summary(\(dataType))") + Logger.dataImportExport.debug("mergeImportSummary: file import summary(\(dataType))") self.screen = .summary([dataType], isFileImport: true) } else if screenForNextDataTypeRemainingToImport(after: DataType.allCases.last(where: summary.keys.contains)) == nil { // no next data type manual import screen let allKeys = self.summary.reduce(into: Set()) { $0.insert($1.dataType) } - log("mergeImportSummary: final summary(\(Set(allKeys)))") + Logger.dataImportExport.debug("mergeImportSummary: final summary(\(Set(allKeys)))") self.screen = .summary(allKeys) } else { - log("mergeImportSummary: intermediary summary(\(Set(summary.keys)))") + Logger.dataImportExport.debug("mergeImportSummary: intermediary summary(\(Set(summary.keys)))") self.screen = .summary(Set(summary.keys)) } @@ -301,7 +293,7 @@ struct DataImportViewModel { // firefox passwords db is main-password protected: request password case let error as FirefoxLoginReader.ImportError where error.type == .requiresPrimaryPassword: - log("primary password required") + Logger.dataImportExport.debug("primary password required") // stay on the same screen but request password synchronously if let password = self.requestPrimaryPasswordCallback(importSource) { self.initiateImport(primaryPassword: password) @@ -315,7 +307,7 @@ struct DataImportViewModel { assertionFailure("No url") break } - log("file read no permission for \(url.path)") + Logger.dataImportExport.debug("file read no permission for \(url.path)") if url != selectedProfile?.profileURL.appendingPathComponent(SafariDataImporter.bookmarksFileName) { PixelKit.fire(GeneralPixel.dataImportFailed(source: importSource, sourceVersion: importSource.installedAppsMajorVersionDescription(selectedProfile: selectedProfile), error: importError)) @@ -566,7 +558,7 @@ extension DataImportViewModel { for await event in importTask.progress { switch event { case .progress(let update): - log("progress: \(update)") + Logger.dataImportExport.debug("progress: \(String(describing: update))") return .progress(update) // on completion returns new DataImportViewModel with merged import summary case .completed(.success(let summary)): @@ -721,7 +713,7 @@ extension DataImportViewModel { } } - log("dismiss") + Logger.dataImportExport.debug("dismiss") dismiss() if case .xcPreviews = NSApp.runType { self.update(with: importSource) // reset diff --git a/DuckDuckGo/DataImport/View/FileImportView.swift b/DuckDuckGo/DataImport/View/FileImportView.swift index a644363c63..8e704b2293 100644 --- a/DuckDuckGo/DataImport/View/FileImportView.swift +++ b/DuckDuckGo/DataImport/View/FileImportView.swift @@ -19,6 +19,7 @@ import Common import SwiftUI import UniformTypeIdentifiers +import os.log @InstructionsView.InstructionsBuilder func fileImportInstructionsBuilder(source: DataImport.Source, dataType: DataImport.DataType, button: @escaping (String) -> AnyView) -> [InstructionsView.InstructionsItem] { @@ -532,13 +533,13 @@ struct FileImportView: View { let provider = providers.first(where: { $0.hasItemConformingToTypeIdentifier(typeIdentifier) }) else { - os_log(.error, log: .dataImportExport, "invalid type identifiers: \(allowedTypeIdentifiers)") + Logger.dataImportExport.error("invalid type identifiers: \(allowedTypeIdentifiers)") return false } provider.loadItem(forTypeIdentifier: typeIdentifier) { data, error in guard let data else { - os_log(.error, log: .dataImportExport, "error loading \(typeIdentifier): \(error?.localizedDescription ?? "?")") + Logger.dataImportExport.error("error loading \(typeIdentifier): \(error?.localizedDescription ?? "?")") return } let url: URL @@ -547,12 +548,12 @@ struct FileImportView: View { url = value case let data as Data: guard let value = URL(dataRepresentation: data, relativeTo: nil) else { - os_log(.error, log: .dataImportExport, "could not decode data: \(data.debugDescription)") + Logger.dataImportExport.error("could not decode data: \(data.debugDescription)") return } url = value default: - os_log(.error, log: .dataImportExport, "unsupported data: \(data)") + Logger.dataImportExport.error("unsupported data: \(String(describing: data))") return } diff --git a/DuckDuckGo/DeviceAuthentication/DeviceAuthenticator.swift b/DuckDuckGo/DeviceAuthentication/DeviceAuthenticator.swift index 45219d5e5d..aad803bf55 100644 --- a/DuckDuckGo/DeviceAuthentication/DeviceAuthenticator.swift +++ b/DuckDuckGo/DeviceAuthentication/DeviceAuthenticator.swift @@ -19,6 +19,7 @@ import Foundation import LocalAuthentication import Common +import os.log extension NSNotification.Name { @@ -117,7 +118,7 @@ final class DeviceAuthenticator: UserAuthenticating { self._deviceIsLocked = newState } - os_log("Device lock state changed: %s", log: .autoLock, deviceIsLocked ? "locked" : "unlocked") + Logger.autoLock.debug("Device lock state changed: \(self.deviceIsLocked ? "locked" : "unlocked", privacy: .public)") if newState { NotificationCenter.default.post(name: .deviceBecameLocked, object: nil) @@ -167,12 +168,12 @@ final class DeviceAuthenticator: UserAuthenticating { return } - os_log("Began authenticating", log: .autoLock) + Logger.autoLock.debug("Began authenticating") isAuthenticating = true authenticationService.authenticateDevice(reason: reason.localizedDescription) { authenticationResult in - os_log("Completed authenticating, with result: %{bool}d", log: .autoLock, authenticationResult.authenticated) + Logger.autoLock.debug("Completed authenticating, with result: \(authenticationResult.authenticated, privacy: .public)") self.isAuthenticating = false self.deviceIsLocked = !authenticationResult.authenticated @@ -199,7 +200,7 @@ final class DeviceAuthenticator: UserAuthenticating { // MARK: - Idle Timer Monitoring private func beginIdleCheckTimer() { - os_log("Beginning idle check timer", log: .autoLock) + Logger.autoLock.debug("Beginning idle check timer") self.timer?.invalidate() self.timer = nil @@ -217,7 +218,7 @@ final class DeviceAuthenticator: UserAuthenticating { } private func cancelIdleCheckTimer() { - os_log("Cancelling idle check timer", log: .autoLock) + Logger.autoLock.debug("Cancelling idle check timer") self.timer?.invalidate() self.timer = nil } @@ -235,12 +236,12 @@ final class DeviceAuthenticator: UserAuthenticating { func beginCheckingIdleTimer() { guard !deviceIsLocked else { - os_log("Tried to start idle timer while device was already locked", log: .autoLock) + Logger.autoLock.debug("Tried to start idle timer while device was already locked") return } guard autofillPreferences.isAutoLockEnabled else { - os_log("Tried to start idle timer but device should not auto-lock", log: .autoLock) + Logger.autoLock.debug("Tried to start idle timer but device should not auto-lock") return } @@ -256,7 +257,7 @@ final class DeviceAuthenticator: UserAuthenticating { // MARK: - Credit Card Autofill Timer private func beginCreditCardAutofillTimer() { - os_log("Beginning credit card autofill timer", log: .autoLock) + Logger.autoLock.debug("Beginning credit card autofill timer") self.timerCreditCard?.invalidate() self.timerCreditCard = nil @@ -273,7 +274,7 @@ final class DeviceAuthenticator: UserAuthenticating { } private func cancelCreditCardAutofillTimer() { - os_log("Cancelling credit card autofill timer", log: .autoLock) + Logger.autoLock.debug("Cancelling credit card autofill timer") self.timerCreditCard?.invalidate() self.timerCreditCard = nil } @@ -288,7 +289,7 @@ final class DeviceAuthenticator: UserAuthenticating { // MARK: - Sync Timer private func beginSyncSettingsTimer() { - os_log("Beginning Sync Settings timer", log: .autoLock) + Logger.autoLock.debug("Beginning Sync Settings timer") self.timerSyncSettings?.invalidate() self.timerSyncSettings = nil @@ -305,7 +306,7 @@ final class DeviceAuthenticator: UserAuthenticating { } private func cancelSyncSettingsTimer() { - os_log("Cancelling Sync Settings timer", log: .autoLock) + Logger.autoLock.debug("Cancelling Sync Settings timer") self.timerSyncSettings?.invalidate() self.timerSyncSettings = nil } diff --git a/DuckDuckGo/DeviceAuthentication/QuartzIdleStateProvider.swift b/DuckDuckGo/DeviceAuthentication/QuartzIdleStateProvider.swift index 1e2fb0fd1f..2d8ebe26ea 100644 --- a/DuckDuckGo/DeviceAuthentication/QuartzIdleStateProvider.swift +++ b/DuckDuckGo/DeviceAuthentication/QuartzIdleStateProvider.swift @@ -19,6 +19,7 @@ import Foundation import Common import CoreGraphics +import os.log final class QuartzIdleStateProvider: DeviceIdleStateProvider { @@ -26,7 +27,7 @@ final class QuartzIdleStateProvider: DeviceIdleStateProvider { let anyInputEventType = CGEventType(rawValue: ~0)! let seconds = CGEventSource.secondsSinceLastEventType(.hidSystemState, eventType: anyInputEventType) - os_log("Idle duration since last user input event: %f", log: .autoLock, seconds) + Logger.autoLock.debug("Idle duration since last user input event: \(seconds)") return seconds } diff --git a/DuckDuckGo/ErrorPage/ErrorPageHTMLTemplate.swift b/DuckDuckGo/ErrorPage/ErrorPageHTMLTemplate.swift index a288139546..3a51ebb3b3 100644 --- a/DuckDuckGo/ErrorPage/ErrorPageHTMLTemplate.swift +++ b/DuckDuckGo/ErrorPage/ErrorPageHTMLTemplate.swift @@ -44,130 +44,3 @@ struct ErrorPageHTMLTemplate { } } - -struct SSLErrorPageHTMLTemplate { - let domain: String - let errorCode: Int - let tld = TLD() - - static var htmlTemplatePath: String { - guard let file = ContentScopeScripts.Bundle.path(forResource: "index", ofType: "html", inDirectory: "pages/sslerrorpage") else { - assertionFailure("HTML template not found") - return "" - } - return file - } - - func makeHTMLFromTemplate() -> String { - let sslError = SSLErrorType.forErrorCode(errorCode) - guard let html = try? String(contentsOfFile: Self.htmlTemplatePath) else { - assertionFailure("Should be able to load template") - return "" - } - let eTldPlus1 = tld.eTLDplus1(domain) ?? domain - let loadTimeData = createJSONString(header: sslError.header, body: sslError.body(for: domain), advancedButton: sslError.advancedButton, leaveSiteButton: sslError.leaveSiteButton, advancedInfoHeader: sslError.advancedInfoTitle, specificMessage: sslError.specificMessage(for: domain, eTldPlus1: eTldPlus1), advancedInfoBody: sslError.advancedInfoBody, visitSiteButton: sslError.visitSiteButton) - return html.replacingOccurrences(of: "$LOAD_TIME_DATA$", with: loadTimeData, options: .literal) - } - - private func createJSONString(header: String, body: String, advancedButton: String, leaveSiteButton: String, advancedInfoHeader: String, specificMessage: String, advancedInfoBody: String, visitSiteButton: String) -> String { - let innerDictionary: [String: Any] = [ - "header": header.escapedUnicodeHtmlString(), - "body": body.escapedUnicodeHtmlString(), - "advancedButton": advancedButton.escapedUnicodeHtmlString(), - "leaveSiteButton": leaveSiteButton.escapedUnicodeHtmlString(), - "advancedInfoHeader": advancedInfoHeader.escapedUnicodeHtmlString(), - "specificMessage": specificMessage.escapedUnicodeHtmlString(), - "advancedInfoBody": advancedInfoBody.escapedUnicodeHtmlString(), - "visitSiteButton": visitSiteButton.escapedUnicodeHtmlString() - ] - - let outerDictionary: [String: Any] = [ - "strings": innerDictionary - ] - - do { - let jsonData = try JSONSerialization.data(withJSONObject: outerDictionary, options: .prettyPrinted) - if let jsonString = String(data: jsonData, encoding: .utf8) { - return jsonString - } else { - return "Error: Could not encode jsonData to String." - } - } catch { - return "Error: \(error.localizedDescription)" - } - } - -} - -public enum SSLErrorType { - case expired - case wrongHost - case selfSigned - case invalid - - var header: String { - return UserText.sslErrorPageHeader - } - - func body(for domain: String) -> String { - let boldDomain = "\(domain)" - return UserText.sslErrorPageBody(boldDomain) - } - - var advancedButton: String { - return UserText.sslErrorPageAdvancedButton - } - - var leaveSiteButton: String { - return UserText.sslErrorPageLeaveSiteButton - } - - var visitSiteButton: String { - return UserText.sslErrorPageVisitSiteButton - } - - var advancedInfoTitle: String { - return UserText.sslErrorAdvancedInfoTitle - } - - var advancedInfoBody: String { - switch self { - case .expired: - return UserText.sslErrorAdvancedInfoBodyExpired - case .wrongHost: - return UserText.sslErrorAdvancedInfoBodyWrongHost - case .selfSigned: - return UserText.sslErrorAdvancedInfoBodyWrongHost - case .invalid: - return UserText.sslErrorAdvancedInfoBodyWrongHost - } - } - - func specificMessage(for domain: String, eTldPlus1: String) -> String { - let boldDomain = "\(domain)" - let boldETldPlus1 = "\(eTldPlus1)" - switch self { - case .expired: - return UserText.sslErrorCertificateExpiredMessage(boldDomain) - case .wrongHost: - return UserText.sslErrorCertificateWrongHostMessage(boldDomain, eTldPlus1: boldETldPlus1) - case .selfSigned: - return UserText.sslErrorCertificateSelfSignedMessage(boldDomain) - case .invalid: - return UserText.sslErrorCertificateSelfSignedMessage(boldDomain) - } - } - - static func forErrorCode(_ errorCode: Int) -> Self { - switch Int32(errorCode) { - case errSSLCertExpired: - return .expired - case errSSLHostNameMismatch: - return .wrongHost - case errSSLXCertChainInvalid: - return .selfSigned - default: - return .invalid - } - } -} diff --git a/DuckDuckGo/ErrorPage/SSLErrorPageUserScript.swift b/DuckDuckGo/ErrorPage/SSLErrorPageUserScript.swift deleted file mode 100644 index 1f3487834e..0000000000 --- a/DuckDuckGo/ErrorPage/SSLErrorPageUserScript.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// SSLErrorPageUserScript.swift -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import UserScript - -final class SSLErrorPageUserScript: NSObject, Subfeature { - enum MessageName: String, CaseIterable { - case leaveSite - case visitSite - } - - public let messageOriginPolicy: MessageOriginPolicy = .all - public let featureName: String = "sslErrorPage" - - var isEnabled: Bool = false - var failingURL: URL? - - weak var broker: UserScriptMessageBroker? - weak var delegate: SSLErrorPageUserScriptDelegate? - - func with(broker: UserScriptMessageBroker) { - self.broker = broker - } - - @MainActor - func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { - guard isEnabled else { return nil } - switch MessageName(rawValue: methodName) { - case .leaveSite: - return handleLeaveSiteAction - case .visitSite: - return handleVisitSiteAction - default: - assertionFailure("SSLErrorPageUserScript: Failed to parse User Script message: \(methodName)") - return nil - } - } - - @MainActor - func handleLeaveSiteAction(params: Any, message: UserScriptMessage) -> Encodable? { - delegate?.leaveSite() - return nil - } - - @MainActor - func handleVisitSiteAction(params: Any, message: UserScriptMessage) -> Encodable? { - delegate?.visitSite() - return nil - } - - // MARK: - UserValuesNotification - - struct UserValuesNotification: Encodable { - let userValuesNotification: UserValues - } -} - -protocol SSLErrorPageUserScriptDelegate: AnyObject { - func leaveSite() - func visitSite() -} diff --git a/DuckDuckGo/Favicons/Logger+Favicons.swift b/DuckDuckGo/Favicons/Logger+Favicons.swift new file mode 100644 index 0000000000..d245415510 --- /dev/null +++ b/DuckDuckGo/Favicons/Logger+Favicons.swift @@ -0,0 +1,24 @@ +// +// Logger+Favicons.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import os.log + +public extension Logger { + static var favicons = { Logger(subsystem: "Favicons", category: "") }() +} diff --git a/DuckDuckGo/Favicons/Model/FaviconImageCache.swift b/DuckDuckGo/Favicons/Model/FaviconImageCache.swift index 133712ccee..9b022a3d22 100644 --- a/DuckDuckGo/Favicons/Model/FaviconImageCache.swift +++ b/DuckDuckGo/Favicons/Model/FaviconImageCache.swift @@ -20,6 +20,7 @@ import Foundation import Combine import Common import BrowserServicesKit +import os.log @MainActor final class FaviconImageCache { @@ -49,9 +50,9 @@ final class FaviconImageCache { let favicons: [Favicon] do { favicons = try await storing.loadFavicons() - os_log("Favicons loaded successfully", log: .favicons) + Logger.favicons.debug("Favicons loaded successfully") } catch { - os_log("Loading of favicons failed: %s", log: .favicons, type: .error, error.localizedDescription) + Logger.favicons.error("Loading of favicons failed: \(error.localizedDescription)") throw error } @@ -76,12 +77,12 @@ final class FaviconImageCache { do { await removeFaviconsFromStore(oldFavicons) try await storing.save(favicons) - os_log("Favicon saved successfully. URL: %s", log: .favicons, favicons.map(\.url.absoluteString).description) + Logger.favicons.debug("Favicon saved successfully. URL: \(favicons.map(\.url.absoluteString).description)") await MainActor.run { NotificationCenter.default.post(name: .faviconCacheUpdated, object: nil) } } catch { - os_log("Saving of favicon failed: %s", log: .favicons, type: .error, error.localizedDescription) + Logger.favicons.error("Saving of favicon failed: \(error.localizedDescription)") } } } @@ -171,9 +172,9 @@ final class FaviconImageCache { do { try await storing.removeFavicons(favicons) - os_log("Favicons removed successfully.", log: .favicons) + Logger.favicons.debug("Favicons removed successfully.") } catch { - os_log("Removing of favicons failed: %s", log: .favicons, type: .error, error.localizedDescription) + Logger.favicons.error("Removing of favicons failed: \(error.localizedDescription)") } } diff --git a/DuckDuckGo/Favicons/Model/FaviconReferenceCache.swift b/DuckDuckGo/Favicons/Model/FaviconReferenceCache.swift index bb8be9fb8b..709d03f686 100644 --- a/DuckDuckGo/Favicons/Model/FaviconReferenceCache.swift +++ b/DuckDuckGo/Favicons/Model/FaviconReferenceCache.swift @@ -20,6 +20,7 @@ import Foundation import Combine import Common import BrowserServicesKit +import os.log @MainActor final class FaviconReferenceCache { @@ -62,12 +63,12 @@ final class FaviconReferenceCache { } loaded = true - os_log("References loaded successfully", log: .favicons) + Logger.favicons.debug("References loaded successfully") NotificationCenter.default.post(name: .faviconCacheUpdated, object: nil) }.value } catch { - os_log("Loading of references failed: %s", log: .favicons, type: .error, error.localizedDescription) + Logger.favicons.error("Loading of references failed: \(error.localizedDescription)") throw error } } @@ -268,9 +269,9 @@ final class FaviconReferenceCache { Task { do { try await self.storing.save(hostReference: hostReference) - os_log("Host reference saved successfully. host: %s", log: .favicons, hostReference.host) + Logger.favicons.debug("Host reference saved successfully. host: \(hostReference.host)") } catch { - os_log("Saving of host reference failed: %s", log: .favicons, type: .error, error.localizedDescription) + Logger.favicons.error("Saving of host reference failed: \(error.localizedDescription)") } } } @@ -295,9 +296,9 @@ final class FaviconReferenceCache { Task.detached { do { try await self.storing.save(urlReference: urlReference) - os_log("URL reference saved successfully. document URL: %s", log: .favicons, urlReference.documentUrl.absoluteString) + Logger.favicons.debug("URL reference saved successfully. document URL: \(urlReference.documentUrl.absoluteString)") } catch { - os_log("Saving of URL reference failed: %s", log: .favicons, type: .error, error.localizedDescription) + Logger.favicons.error("Saving of URL reference failed: \(error.localizedDescription)") } } } @@ -322,9 +323,9 @@ final class FaviconReferenceCache { do { try await storing.remove(hostReferences: hostReferences) - os_log("Host references removed successfully.", log: .favicons) + Logger.favicons.debug("Host references removed successfully.") } catch { - os_log("Removing of host references failed: %s", log: .favicons, type: .error, error.localizedDescription) + Logger.favicons.error("Removing of host references failed: \(error.localizedDescription)") } } @@ -342,9 +343,9 @@ final class FaviconReferenceCache { do { try await storing.remove(urlReferences: urlReferences) - os_log("URL references removed successfully.", log: .favicons) + Logger.favicons.debug("URL references removed successfully.") } catch { - os_log("Removing of URL references failed: %s", log: .favicons, type: .error, error.localizedDescription) + Logger.favicons.error("Removing of URL references failed: \(error.localizedDescription)") } } diff --git a/DuckDuckGo/Favicons/Services/FaviconStore.swift b/DuckDuckGo/Favicons/Services/FaviconStore.swift index 9ef7d9ed7d..049895add1 100644 --- a/DuckDuckGo/Favicons/Services/FaviconStore.swift +++ b/DuckDuckGo/Favicons/Services/FaviconStore.swift @@ -21,6 +21,7 @@ import CoreData import Combine import Common import PixelKit +import os.log protocol FaviconStoring { @@ -61,7 +62,7 @@ final class FaviconStore: FaviconStoring { fetchRequest.returnsObjectsAsFaults = false do { let faviconMOs = try context.fetch(fetchRequest) - os_log("%d favicons loaded ", log: .favicons, faviconMOs.count) + Logger.favicons.debug("\(faviconMOs.count) favicons loaded") let favicons = faviconMOs.compactMap { Favicon(faviconMO: $0) } continuation.resume(returning: favicons) @@ -112,7 +113,7 @@ final class FaviconStore: FaviconStoring { let faviconHostReferences: [FaviconHostReference] do { let faviconHostReferenceMOs = try context.fetch(hostFetchRequest) - os_log("%d favicon host references loaded ", log: .favicons, faviconHostReferenceMOs.count) + Logger.favicons.debug("\(faviconHostReferenceMOs.count) favicon host references loaded") faviconHostReferences = faviconHostReferenceMOs.compactMap { FaviconHostReference(faviconHostReferenceMO: $0) } } catch { continuation.resume(throwing: error) @@ -124,7 +125,7 @@ final class FaviconStore: FaviconStoring { urlFetchRequest.returnsObjectsAsFaults = false do { let faviconUrlReferenceMOs = try context.fetch(urlFetchRequest) - os_log("%d favicon url references loaded ", log: .favicons, faviconUrlReferenceMOs.count) + Logger.favicons.debug("\(faviconUrlReferenceMOs.count) favicon url references loaded") let faviconUrlReferences = faviconUrlReferenceMOs.compactMap { FaviconUrlReference(faviconUrlReferenceMO: $0) } continuation.resume(returning: (faviconHostReferences, faviconUrlReferences)) } catch { @@ -207,7 +208,7 @@ final class FaviconStore: FaviconStoring { let deletedObjects = result?.result as? [NSManagedObjectID] ?? [] let changes: [AnyHashable: Any] = [NSDeletedObjectsKey: deletedObjects] NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [context]) - os_log("%d entries of %s removed", log: .favicons, deletedObjects.count, entityName) + Logger.favicons.debug("\(deletedObjects.count) entries of \(entityName) removed") continuation.resume(returning: ()) } catch { diff --git a/DuckDuckGo/Feedback/Model/FeedbackSender.swift b/DuckDuckGo/Feedback/Model/FeedbackSender.swift index 09be2ef5a7..d261de729d 100644 --- a/DuckDuckGo/Feedback/Model/FeedbackSender.swift +++ b/DuckDuckGo/Feedback/Model/FeedbackSender.swift @@ -20,6 +20,7 @@ import Common import Foundation import Networking import PixelKit +import os.log final class FeedbackSender { @@ -43,7 +44,7 @@ final class FeedbackSender { let request = APIRequest(configuration: configuration, urlSession: URLSession.session()) request.fetch { _, error in if let error = error { - os_log("FeedbackSender: Failed to submit feedback %s", type: .error, error.localizedDescription) + Logger.general.error("FeedbackSender: Failed to submit feedback \(error.localizedDescription)") PixelKit.fire(DebugEvent(GeneralPixel.feedbackReportingFailed, error: error)) } } diff --git a/DuckDuckGo/FileDownload/Logger+FileDownload.swift b/DuckDuckGo/FileDownload/Logger+FileDownload.swift new file mode 100644 index 0000000000..304e4dc437 --- /dev/null +++ b/DuckDuckGo/FileDownload/Logger+FileDownload.swift @@ -0,0 +1,24 @@ +// +// Logger+FileDownload.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import os.log + +extension Logger { + static var fileDownload = { Logger(subsystem: "File Download", category: "") }() +} diff --git a/DuckDuckGo/FileDownload/Model/DownloadListViewModel.swift b/DuckDuckGo/FileDownload/Model/DownloadListViewModel.swift index 8cf63751e3..64a62c902d 100644 --- a/DuckDuckGo/FileDownload/Model/DownloadListViewModel.swift +++ b/DuckDuckGo/FileDownload/Model/DownloadListViewModel.swift @@ -19,6 +19,7 @@ import Combine import Common import Foundation +import os.log @MainActor final class DownloadListViewModel { @@ -41,7 +42,7 @@ final class DownloadListViewModel { } private func handleDownloadsUpdate(of kind: DownloadListCoordinator.UpdateKind, item: DownloadListItem) { - os_log(.debug, log: .downloads, "DownloadListViewModel: .\(kind) \(item.identifier)") + Logger.fileDownload.debug("DownloadListViewModel: .\(String(describing: kind)) \(item.identifier)") dispatchPrecondition(condition: .onQueue(.main)) switch kind { diff --git a/DuckDuckGo/FileDownload/Model/DownloadViewModel.swift b/DuckDuckGo/FileDownload/Model/DownloadViewModel.swift index d22e8a9e6e..ecffbaedfb 100644 --- a/DuckDuckGo/FileDownload/Model/DownloadViewModel.swift +++ b/DuckDuckGo/FileDownload/Model/DownloadViewModel.swift @@ -20,6 +20,7 @@ import Combine import Common import Foundation import UniformTypeIdentifiers +import os.log final class DownloadViewModel { @@ -80,7 +81,7 @@ final class DownloadViewModel { let oldState = self.state let newState = State(item: item, shouldAnimateOnAppear: state.shouldAnimateOnAppear ?? true) if oldState != newState { - os_log(.debug, log: .downloads, "DownloadViewModel: \(item.identifier): \(oldState) ➡️ \(newState)") + Logger.fileDownload.debug("DownloadViewModel: \(item.identifier.uuidString): \(oldState.debugDescription) ➡️ \(newState.debugDescription)") self.state = newState } } diff --git a/DuckDuckGo/FileDownload/Model/FileDownloadManager.swift b/DuckDuckGo/FileDownload/Model/FileDownloadManager.swift index 8a02089430..fe9bb4f873 100644 --- a/DuckDuckGo/FileDownload/Model/FileDownloadManager.swift +++ b/DuckDuckGo/FileDownload/Model/FileDownloadManager.swift @@ -21,6 +21,7 @@ import Combine import Common import Navigation import UniformTypeIdentifiers +import os.log protocol FileDownloadManagerProtocol: AnyObject { var downloads: Set { get } @@ -47,14 +48,9 @@ final class FileDownloadManager: FileDownloadManagerProtocol { static let shared = FileDownloadManager() private let preferences: DownloadsPreferences - private let getLogger: (() -> OSLog) - private var log: OSLog { - getLogger() - } - init(preferences: DownloadsPreferences = .shared, log: @autoclosure @escaping (() -> OSLog) = .downloads) { + init(preferences: DownloadsPreferences = .shared) { self.preferences = preferences - self.getLogger = log } private(set) var downloads = Set() @@ -76,16 +72,16 @@ final class FileDownloadManager: FileDownloadManagerProtocol { destination = .prompt } let task = WebKitDownloadTask(download: download, destination: destination, isBurner: fromBurnerWindow) - os_log(log: log, "add \(download): \(download.originalRequest?.url?.absoluteString ?? "") -> \(destination): \(task)") + Logger.fileDownload.debug("add \(String(describing: download)): \(download.originalRequest?.url?.absoluteString ?? "") -> \(destination.debugDescription): \(task)") let shouldCancelDownloadIfDelegateIsGone = delegate != nil - self.downloadTaskDelegates[task] = { [weak delegate, log] in + self.downloadTaskDelegates[task] = { [weak delegate] in if let delegate { return delegate } // if the delegate was originally provided but deallocated since then – the download task should be cancelled if shouldCancelDownloadIfDelegateIsGone { - os_log(log: log, "🦀 \(download) delegate is gone: cancelling") + Logger.fileDownload.debug("🦀 \( String(describing: download) ) delegate is gone: cancelling") return CancelledDownloadTaskDelegate() } return nil @@ -101,7 +97,7 @@ final class FileDownloadManager: FileDownloadManagerProtocol { func cancelAll(waitUntilDone: Bool) { dispatchPrecondition(condition: .onQueue(.main)) - os_log(log: log, "FileDownloadManager: cancel all: [\(downloads.map(\.debugDescription).joined(separator: ", "))]") + Logger.fileDownload.debug("FileDownloadManager: cancel all: [\(self.downloads.map(\.debugDescription).joined(separator: ", "))]") let dispatchGroup: DispatchGroup? = waitUntilDone ? DispatchGroup() : nil var cancellables = Set() for task in downloads { @@ -131,10 +127,10 @@ extension FileDownloadManager: WebKitDownloadTaskDelegate { @MainActor func fileDownloadTaskNeedsDestinationURL(_ task: WebKitDownloadTask, suggestedFilename: String, suggestedFileType fileType: UTType?) async -> (URL?, UTType?) { guard case (.some(let url), let fileType) = await chooseDestination(for: task, suggestedFilename: suggestedFilename, suggestedFileType: fileType) else { - os_log(log: log, "choose destination cancelled: \(task)") + Logger.fileDownload.debug("choose destination cancelled: \(task)") return (nil, nil) } - os_log(log: log, "destination chosen: \(task): \"\(url.path)\" (\(fileType?.description ?? "nil"))") + Logger.fileDownload.debug("destination chosen: \(task): \"\(url.path)\" (\(fileType?.description ?? "nil"))") if let originalRect = self.downloadTaskDelegates[task]?()?.fileIconFlyAnimationOriginalRect(for: task) { let utType = UTType(filenameExtension: url.pathExtension) ?? fileType ?? .data @@ -191,7 +187,7 @@ extension FileDownloadManager: WebKitDownloadTaskDelegate { fileTypes.append(fileType) } - os_log(log: log, "FileDownloadManager: requesting download location \"\(suggestedFilename)\"/\(fileTypes.map(\.description).joined(separator: ", "))") + Logger.fileDownload.debug("FileDownloadManager: requesting download location \"\(suggestedFilename)\"/\(fileTypes.map(\.description).joined(separator: ", "))") delegate.chooseDestination(suggestedFilename: suggestedFilename, fileTypes: fileTypes) { url, fileType in guard let url else { completionHandler(nil, nil) @@ -216,13 +212,13 @@ extension FileDownloadManager: WebKitDownloadTaskDelegate { let fileName = suggestedFilename.isEmpty ? "download".appendingPathExtension(fileType?.preferredFilenameExtension) : suggestedFilename var url = downloadLocation.appendingPathComponent(fileName) - os_log(log: log, "FileDownloadManager: using default download location for \"\(suggestedFilename)\": \"\(url.path)\"") + Logger.fileDownload.debug("FileDownloadManager: using default download location for \"\(suggestedFilename)\": \"\(url.path)\"") // make sure the app has access to the destination let folderUrl = url.deletingLastPathComponent() let fm = FileManager.default guard fm.isWritableFile(atPath: folderUrl.path) else { - os_log(log: log, "FileDownloadManager: no write permissions for \"\(folderUrl.path)\": fallback to user request") + Logger.fileDownload.debug("FileDownloadManager: no write permissions for \"\(folderUrl.path)\": fallback to user request") return await requestDestinationFromUser(for: task, suggestedFilename: suggestedFilename, suggestedFileType: fileType) } @@ -246,7 +242,7 @@ extension FileDownloadManager: WebKitDownloadTaskDelegate { self.downloads.remove(task) self.downloadTaskDelegates[task] = nil - os_log(log: log, "❎ removed task \(task)") + Logger.fileDownload.debug("❎ removed task \(task)") } } diff --git a/DuckDuckGo/FileDownload/Model/FilePresenter.swift b/DuckDuckGo/FileDownload/Model/FilePresenter.swift index 1770935af2..a7205791c3 100644 --- a/DuckDuckGo/FileDownload/Model/FilePresenter.swift +++ b/DuckDuckGo/FileDownload/Model/FilePresenter.swift @@ -19,25 +19,15 @@ import Combine import Common import Foundation +import os.log private protocol FilePresenterDelegate: AnyObject { - var logger: FilePresenterLogger { get } var url: URL? { get } func presentedItemDidMove(to newURL: URL) func accommodatePresentedItemDeletion() throws func accommodatePresentedItemEviction() throws } -protocol FilePresenterLogger { - func log(_ message: @autoclosure () -> String) -} - -extension OSLog: FilePresenterLogger { - func log(_ message: @autoclosure () -> String) { - os_log(.debug, log: self, message()) - } -} - internal class FilePresenter { private static let dispatchSourceQueue = DispatchQueue(label: "CoordinatedFile.dispatchSourceQueue") @@ -111,8 +101,6 @@ internal class FilePresenter { private var innerPresenters = [DelegatingFilePresenter]() private var dispatchSourceCancellable: AnyCancellable? - fileprivate let logger: any FilePresenterLogger - private var urlController: SecurityScopedFileURLController? final var url: URL? { lock.withLock { @@ -140,7 +128,7 @@ internal class FilePresenter { urlController.isManagingSecurityScope { urlController.updateUrlKeepingSandboxExtensionRetainCount(newURL) } else { - urlController = SecurityScopedFileURLController(url: newURL, logger: logger) + urlController = SecurityScopedFileURLController(url: newURL) } return oldValue @@ -155,14 +143,12 @@ internal class FilePresenter { /// - Parameter consumeUnbalancedStartAccessingResource: assume the `url` is already accessible (e.g. after choosing the file using Open Panel). /// would cause an unbalanced `stopAccessingSecurityScopedResource` call on the File Presenter deallocation. /// - Note: see https://stackoverflow.com/questions/25627628/sandboxed-mac-app-exhausting-security-scoped-url-resources - init(url: URL, consumeUnbalancedStartAccessingResource: Bool = false, logger: FilePresenterLogger = OSLog.disabled, createIfNeededCallback: ((URL) throws -> URL)? = nil) throws { - self.urlController = SecurityScopedFileURLController(url: url, manageSecurityScope: consumeUnbalancedStartAccessingResource, logger: logger) - - self.logger = logger + init(url: URL, consumeUnbalancedStartAccessingResource: Bool = false, createIfNeededCallback: ((URL) throws -> URL)? = nil) throws { + self.urlController = SecurityScopedFileURLController(url: url, manageSecurityScope: consumeUnbalancedStartAccessingResource) do { try setupInnerPresenter(for: url, primaryItemURL: nil, createIfNeededCallback: createIfNeededCallback) - logger.log("🗄️ added file presenter for \"\(url.path)\"") + Logger.fileDownload.debug("🗄️ added file presenter for \"\(url.path)\"") } catch { removeFilePresenters() throw error @@ -176,13 +162,12 @@ internal class FilePresenter { /// - Note: when presenting a related item the security scoped resource access will always be stopped on the File Presenter deallocation /// - Parameter consumeUnbalancedStartAccessingResource: assume the `url` is already accessible (e.g. after choosing the file using Open Panel). /// would cause an unbalanced `stopAccessingSecurityScopedResource` call on the File Presenter deallocation. - init(url: URL, primaryItemURL: URL, logger: FilePresenterLogger = OSLog.disabled, createIfNeededCallback: ((URL) throws -> URL)? = nil) throws { - self.urlController = SecurityScopedFileURLController(url: url, logger: logger) - self.logger = logger + init(url: URL, primaryItemURL: URL, createIfNeededCallback: ((URL) throws -> URL)? = nil) throws { + self.urlController = SecurityScopedFileURLController(url: url) do { try setupInnerPresenter(for: url, primaryItemURL: primaryItemURL, createIfNeededCallback: createIfNeededCallback) - logger.log("🗄️ added file presenter for \"\(url.path) primary item: \"\(primaryItemURL.path)\"") + Logger.fileDownload.debug("🗄️ added file presenter for \"\(url.path) primary item: \"\(primaryItemURL.path)\"") } catch { removeFilePresenters() throw error @@ -203,7 +188,7 @@ internal class FilePresenter { guard let createFile = createIfNeededCallback else { throw CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: url.path]) } - logger.log("🗄️💥 creating file for presenter at \"\(url.path)\"") + Logger.fileDownload.debug("🗄️💥 creating file for presenter at \"\(url.path)\"") // create new file at the presented URL using the provided callback and update URL if needed _=self._setURL( try coordinateWrite(at: url, using: createFile) @@ -237,7 +222,7 @@ internal class FilePresenter { guard fileDescriptor != -1 else { let err = errno - logger.log("🗄️❌ error opening \(url.path): \(err) – \(String(cString: strerror(err)))") + Logger.fileDownload.debug("🗄️❌ error opening \(url.path): \(err) – \(String(cString: strerror(err)))") return } @@ -246,7 +231,7 @@ internal class FilePresenter { dispatchSource.setEventHandler { [weak self] in guard let self, let url = self.url else { return } - self.logger.log("🗄️⚠️ file delete event handler: \"\(url.path)\"") + Logger.fileDownload.debug("🗄️⚠️ file delete event handler: \"\(url.path)\"") var resolvedBookmarkData: URL? { var isStale = false guard let presenter = self as? BookmarkFilePresenter, @@ -258,7 +243,7 @@ internal class FilePresenter { return url } if let existingUrl = resolvedBookmarkData { - self.logger.log("🗄️⚠️ ignoring file delete event handler as file exists: \"\(url.path)\"") + Logger.fileDownload.debug("🗄️⚠️ ignoring file delete event handler as file exists: \"\(url.path)\"") presentedItemDidMove(to: existingUrl) return } @@ -281,7 +266,7 @@ internal class FilePresenter { if innerPresenter.fallbackPresentedItemURL == nil { innerPresenter.fallbackPresentedItemURL = urlController?.url } - logger.log("🗄️ removing file presenter \(idx) for \"\(innerPresenter.presentedItemURL?.path ?? "")\"") + Logger.fileDownload.debug("🗄️ removing file presenter \(idx) for \"\(innerPresenter.presentedItemURL?.path ?? "")\"") NSFileCoordinator.removeFilePresenter(innerPresenter) } if innerPresenters.count > 1 { @@ -294,7 +279,7 @@ internal class FilePresenter { fileprivate func didSetURL(_ newValue: URL?, oldValue: URL?) { assert(newValue == nil || newValue != oldValue) - logger.log("🗄️ did update url from \"\(oldValue?.path ?? "")\" to \"\(newValue?.path ?? "")\"") + Logger.fileDownload.debug("🗄️ did update url from \"\(oldValue?.path ?? "")\" to \"\(newValue?.path ?? "")\"") urlSubject.send(newValue) } @@ -307,19 +292,19 @@ internal class FilePresenter { extension FilePresenter: FilePresenterDelegate { func presentedItemDidMove(to newURL: URL) { - logger.log("🗄️ presented item did move to \"\(newURL.path)\"") + Logger.fileDownload.debug("🗄️ presented item did move to \"\(newURL.path)\"") setURL(newURL) } func accommodatePresentedItemDeletion() throws { - logger.log("🗄️ accommodatePresentedItemDeletion (\"\(url?.path ?? "")\")") + Logger.fileDownload.debug("🗄️ accommodatePresentedItemDeletion (\"\(self.url?.path ?? "")\")") // should go before resetting the URL to correctly remove File Presenter removeFilePresenters() setURL(nil) } func accommodatePresentedItemEviction() throws { - logger.log("🗄️ accommodatePresentedItemEviction (\"\(url?.path ?? "")\")") + Logger.fileDownload.debug("🗄️ accommodatePresentedItemEviction (\"\(self.url?.path ?? "")\")") try accommodatePresentedItemDeletion() } @@ -345,17 +330,17 @@ final class BookmarkFilePresenter: FilePresenter { /// - Parameter url: represented file URL access to which is coordinated by the File Presenter. /// - Parameter consumeUnbalancedStartAccessingResource: assume the `url` is already accessible (e.g. after choosing the file using Open Panel). /// would cause an unbalanced `stopAccessingSecurityScopedResource` call on the File Presenter deallocation. - override init(url: URL, consumeUnbalancedStartAccessingResource: Bool = false, logger: FilePresenterLogger = OSLog.disabled, createIfNeededCallback: ((URL) throws -> URL)? = nil) throws { + override init(url: URL, consumeUnbalancedStartAccessingResource: Bool = false, createIfNeededCallback: ((URL) throws -> URL)? = nil) throws { - try super.init(url: url, consumeUnbalancedStartAccessingResource: consumeUnbalancedStartAccessingResource, logger: logger, createIfNeededCallback: createIfNeededCallback) + try super.init(url: url, consumeUnbalancedStartAccessingResource: consumeUnbalancedStartAccessingResource, createIfNeededCallback: createIfNeededCallback) do { try self.coordinateRead(at: url, with: .withoutChanges) { url in - logger.log("📒 updating bookmark data for \"\(url.path)\"") + Logger.fileDownload.debug("📒 updating bookmark data for \"\(url.path)\"") self._fileBookmarkData = try url.bookmarkData(options: .withSecurityScope) } } catch { - logger.log("📕 bookmark data retreival failed for \"\(url.path)\": \(error)") + Logger.fileDownload.debug("📕 bookmark data retreival failed for \"\(url.path)\": \(error)") throw error } } @@ -367,28 +352,28 @@ final class BookmarkFilePresenter: FilePresenter { /// - Note: when presenting a related item the security scoped resource access will always be stopped on the File Presenter deallocation /// - Parameter consumeUnbalancedStartAccessingResource: assume the `url` is already accessible (e.g. after choosing the file using Open Panel). /// would cause an unbalanced `stopAccessingSecurityScopedResource` call on the File Presenter deallocation. - override init(url: URL, primaryItemURL: URL, logger: FilePresenterLogger = OSLog.disabled, createIfNeededCallback: ((URL) throws -> URL)? = nil) throws { - try super.init(url: url, primaryItemURL: primaryItemURL, logger: logger, createIfNeededCallback: createIfNeededCallback) + override init(url: URL, primaryItemURL: URL, createIfNeededCallback: ((URL) throws -> URL)? = nil) throws { + try super.init(url: url, primaryItemURL: primaryItemURL, createIfNeededCallback: createIfNeededCallback) do { try self.coordinateRead(at: url, with: .withoutChanges) { url in - logger.log("📒 updating bookmark data for \"\(url.path)\"") + Logger.fileDownload.debug("📒 updating bookmark data for \"\(url.path)\"") self._fileBookmarkData = try url.bookmarkData(options: .withSecurityScope) } } catch { - logger.log("📕 bookmark data retreival failed for \"\(url.path)\": \(error)") + Logger.fileDownload.debug("📕 bookmark data retreival failed for \"\(url.path)\": \(error)") throw error } } - init(fileBookmarkData: Data, logger: FilePresenterLogger = OSLog.disabled) throws { + init(fileBookmarkData: Data) throws { self._fileBookmarkData = fileBookmarkData var isStale = false - logger.log("📒 resolving url from bookmark data") + Logger.fileDownload.debug("📒 resolving url from bookmark data") let url = try URL(resolvingBookmarkData: fileBookmarkData, options: .withSecurityScope, bookmarkDataIsStale: &isStale) - try super.init(url: url, consumeUnbalancedStartAccessingResource: true, logger: logger) + try super.init(url: url, consumeUnbalancedStartAccessingResource: true) if isStale { DispatchQueue.global().async { [weak self] in @@ -403,13 +388,13 @@ final class BookmarkFilePresenter: FilePresenter { } fileprivate func updateFileBookmarkData(for url: URL?) { - logger.log("📒 updateFileBookmarkData for \"\(url?.path ?? "")\"") + Logger.fileDownload.debug("📒 updateFileBookmarkData for \"\(url?.path ?? "")\"") var fileBookmarkData: Data? do { fileBookmarkData = try url?.bookmarkData(options: .withSecurityScope) } catch { - logger.log("📕 updateFileBookmarkData failed with \(error)") + Logger.fileDownload.debug("📕 updateFileBookmarkData failed with \(error)") } guard lock.withLock({ diff --git a/DuckDuckGo/FileDownload/Model/SecurityScopedFileURLController.swift b/DuckDuckGo/FileDownload/Model/SecurityScopedFileURLController.swift index bec94e02ac..154e4e8671 100644 --- a/DuckDuckGo/FileDownload/Model/SecurityScopedFileURLController.swift +++ b/DuckDuckGo/FileDownload/Model/SecurityScopedFileURLController.swift @@ -18,6 +18,8 @@ import Foundation import Common +import os.log +import BrowserServicesKit /// Manages security-scoped resource access to a file URL. /// @@ -29,9 +31,6 @@ import Common /// `stopAccessingSecurityScopedResource` methods to accurately reflect the current number of start and stop calls. /// The number is reflected in the associated `URL.sandboxExtensionRetainCount` value. final class SecurityScopedFileURLController { - - fileprivate let logger: any FilePresenterLogger - private(set) var url: URL let isManagingSecurityScope: Bool @@ -42,7 +41,7 @@ final class SecurityScopedFileURLController { /// - manageSecurityScope: A Boolean value indicating whether the controller should manage the URL security scope access (i.e. call stop and end accessing resource methods). /// - logger: An optional logger instance for logging file operations. Defaults to disabled. /// - Note: when `manageSecurityScope` is `true` access to the represented URL will be stopped for the whole app on the controller deallocation. - init(url: URL, manageSecurityScope: Bool = true, logger: any FilePresenterLogger = OSLog.disabled) { + init(url: URL, manageSecurityScope: Bool = true) { assert(url.isFileURL) #if APPSTORE let didStartAccess = manageSecurityScope && url.startAccessingSecurityScopedResource() @@ -51,8 +50,7 @@ final class SecurityScopedFileURLController { #endif self.url = url self.isManagingSecurityScope = didStartAccess - self.logger = logger - logger.log("\(didStartAccess ? "🧪 " : "")SecurityScopedFileURLController.init: \(url.sandboxExtensionRetainCount) – \"\(url.path)\"") + Logger.fileDownload.debug("\(didStartAccess ? "🧪 " : "")SecurityScopedFileURLController.init: \(url.sandboxExtensionRetainCount) – \"\(url.path)\"") } func updateUrlKeepingSandboxExtensionRetainCount(_ newURL: URL) { @@ -67,7 +65,7 @@ final class SecurityScopedFileURLController { deinit { if isManagingSecurityScope { let url = url - logger.log("\(isManagingSecurityScope ? "🪓 " : "")SecurityScopedFileURLController.deinit: \(url.sandboxExtensionRetainCount) – \"\(url.path)\"") + Logger.fileDownload.debug("\(self.isManagingSecurityScope ? "🪓 " : "")SecurityScopedFileURLController.deinit: \(url.sandboxExtensionRetainCount) – \"\(url.path)\"") for _ in 0..<(url as NSURL).sandboxExtensionRetainCount { url.stopAccessingSecurityScopedResource() } @@ -75,7 +73,7 @@ final class SecurityScopedFileURLController { #if DEBUG && APPSTORE url.ensureUrlIsNotWritable { #if SANDBOX_TEST_TOOL - logger.log("❗️ url \(url.path) is still writable after stopping access to it") + Logger.fileDownload.log("❗️ url \(url.path) is still writable after stopping access to it") fatalError("❗️ url \(url.path) is still writable after stopping access to it") #else breakByRaisingSigInt("❗️ url \(url.path) is still writable after stopping access to it") diff --git a/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift b/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift index 077b4cda82..54beae31f0 100644 --- a/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift +++ b/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift @@ -16,13 +16,15 @@ // limitations under the License. // +import Foundation import Combine +import BrowserServicesKit import Common -import Foundation import Navigation import UniformTypeIdentifiers import WebKit import PixelKit +import os.log protocol WebKitDownloadTaskDelegate: AnyObject { func fileDownloadTaskNeedsDestinationURL(_ task: WebKitDownloadTask, suggestedFilename: String, suggestedFileType: UTType?) async -> (URL?, UTType?) @@ -101,7 +103,6 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable /// downloads initiated from a Burner Window won‘t stay in the Downloads panel after completion let isBurner: Bool - private let log: OSLog private weak var delegate: WebKitDownloadTaskDelegate? private var download: WebKitDownload! @@ -130,17 +131,16 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable } @MainActor(unsafe) - init(download: WebKitDownload, destination: DownloadDestination, isBurner: Bool, log: OSLog = .downloads) { + init(download: WebKitDownload, destination: DownloadDestination, isBurner: Bool) { self.download = download self.progress = DownloadProgress(download: download) self.fileProgressPresenter = FileProgressPresenter(progress: progress) self.state = .initial(destination) self.isBurner = isBurner - self.log = log super.init() - progress.cancellationHandler = { [weak self, log, taskDescr=self.debugDescription] in - os_log(log: log, "❌ progress.cancellationHandler \(taskDescr)") + progress.cancellationHandler = { [weak self, taskDescr=self.debugDescription] in + Logger.fileDownload.debug("❌ progress.cancellationHandler \(taskDescr)") self?.cancel() } } @@ -159,7 +159,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable /// - if the temporary file is renamed, we try to rename the destination file accordingly (this would fail in sandboxed builds for non-preset directories) /// - if any of the two files is removed, the download is cancelled @MainActor func start(delegate: WebKitDownloadTaskDelegate) { - os_log(.debug, log: log, "🟢 start \(self)") + Logger.fileDownload.debug("🟢 start \(self)") self.delegate = delegate @@ -212,13 +212,13 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable destinationURL != newDestinationURL else { try? await Task.sleep(interval: 1); return } do { - os_log(.debug, log: log, "renaming destination file \"\(destinationURL.path)\" ➡️ \"\(destinationURL.path)\"") + Logger.fileDownload.debug("renaming destination file \"\(destinationURL.path)\" ➡️ \"\(destinationURL.path)\"") try destinationFilePresenter.coordinateMove(to: newDestinationURL, with: []) { from, to in try FileManager.default.moveItem(at: from, to: to) destinationFilePresenter.presentedItemDidMove(to: newDestinationURL) // coordinated File Presenter won‘t receive URL updates } } catch { - os_log(.debug, log: log, "renaming file failed: \(error)") + Logger.fileDownload.debug("renaming file failed: \(error)") } } @@ -235,7 +235,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable destinationURL.stopAccessingSecurityScopedResource() } } - os_log(.debug, log: log, "download task callback: creating temp directory for \"\(destinationURL.path)\"") + Logger.fileDownload.debug("download task callback: creating temp directory for \"\(destinationURL.path)\"") switch cleanupStyle { case .remove: @@ -245,7 +245,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable // validate we can write to the directory even if there‘s no existing file try Data().write(to: destinationURL) } - os_log(.debug, log: log, "🧹 removing \"\(url.path)\"") + Logger.fileDownload.debug("🧹 removing \"\(url.path)\"") try fm.removeItem(at: url) } case .clear: @@ -266,12 +266,12 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable } }) - os_log(.debug, log: log, "download task callback: temp file: \(tempURL.path)") + Logger.fileDownload.debug("download task callback: temp file: \(tempURL.path)") return tempURL } catch { await MainActor.run { - os_log(.error, log: log, "🛑 download task callback: \(self): \(error)") + Logger.fileDownload.error("🛑 download task callback: \(self): \(error)") self.download.cancel() self.finish(with: .failure(.failedToCompleteDownloadTask(underlyingError: error, resumeData: nil, isRetryable: false))) @@ -290,7 +290,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable let fileDescriptor = open(itemReplacementDirectory.path, O_EVTONLY) if fileDescriptor == -1 { let err = errno - os_log(.error, log: log, "could not open \(itemReplacementDirectory.path): \(err) – \(String(cString: strerror(err)))") + Logger.fileDownload.error("could not open \(itemReplacementDirectory.path): \(err) – \(String(cString: strerror(err)))") throw NSError(domain: NSPOSIXErrorDomain, code: Int(err), userInfo: [NSLocalizedDescriptionKey: String(cString: strerror(err))]) } let fileMonitor = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fileDescriptor, eventMask: .write, queue: .main) @@ -317,7 +317,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable /// when the download has started to a temporary directory, create a placeholder file at the destination URL and move the temp file to the destination directory as a `.duckload` @MainActor private func tempDownloadFileCreated(at tempURL: URL, destinationURL: URL) async { - os_log(.debug, log: log, "temp file created: \(self): \(tempURL.path)") + Logger.fileDownload.debug("temp file created: \(self): \(tempURL.path)") do { let presenters = if case .downloading(destination: let destination, tempFile: let tempFile) = state { @@ -330,7 +330,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable self.state = .downloading(destination: presenters.destinationFile, tempFile: presenters.tempFile) } catch { - os_log(.error, log: log, "🛑 file presenters failure: \(self): \(error)") + Logger.fileDownload.error("🛑 file presenters failure: \(self): \(error)") self.download.cancel() self.finish(with: .failure(.failedToCompleteDownloadTask(underlyingError: error, resumeData: nil, isRetryable: false))) @@ -347,10 +347,10 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable // 🧙‍♂️ now we‘re doing do some magique here 🧙‍♂️ // -------------------------------------- - os_log(.debug, log: log, "🧙‍♂️ magique.start: \"\(destinationURL.path)\" (\"\(duckloadURL.path)\") directory writable: \(fm.isWritableFile(atPath: destinationURL.deletingLastPathComponent().path))") + Logger.fileDownload.debug("🧙‍♂️ magique.start: \"\(destinationURL.path)\" (\"\(duckloadURL.path)\") directory writable: \(fm.isWritableFile(atPath: destinationURL.deletingLastPathComponent().path))") // 1. create our final destination file (let‘s say myfile.zip) and setup a File Presenter for it // doing this we preserve access to the file until it‘s actually downloaded - let destinationFilePresenter = try BookmarkFilePresenter(url: destinationURL, consumeUnbalancedStartAccessingResource: true, logger: log) { url in + let destinationFilePresenter = try BookmarkFilePresenter(url: destinationURL, consumeUnbalancedStartAccessingResource: true) { url in try fm.createFile(atPath: url.path, contents: nil) ? url : { throw CocoaError(.fileWriteNoPermission, userInfo: [NSFilePathErrorKey: url.path]) }() @@ -363,7 +363,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable // 2. mark the file as hidden until it‘s downloaded to not to confuse user // and prevent from unintentional opening of the empty file try destinationURL.setFileHidden(true) - os_log(.debug, log: log, "🧙‍♂️ \"\(destinationURL.path)\" hidden") + Logger.fileDownload.debug("🧙‍♂️ \"\(destinationURL.path)\" hidden") // 3. then we move the temporary download file to the destination directory (myfile.duckload) // this is doable in sandboxed builds by using “Related Items” i.e. using a file URL with an extra @@ -377,29 +377,29 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable try chooseAlternativeDuckloadFileNameOrRemove(&duckloadURL, destinationURL: destinationURL) } catch { // that‘s ok, we‘ll keep using the original temp file - os_log(.error, log: log, "❗️ can‘t resolve duckload file exists: \"\(duckloadURL.path)\": \(error)") + Logger.fileDownload.error("❗️ can‘t resolve duckload file exists: \"\(duckloadURL.path)\": \(error)") duckloadURL = tempURL } } let tempFilePresenter = if duckloadURL == tempURL { // we won‘t use a `.duckload` file for this download, the file will be left in the temp location instead - try BookmarkFilePresenter(url: duckloadURL, logger: log) + try BookmarkFilePresenter(url: duckloadURL) } else { // now move the temp file to `.duckload` instantiating a File Presenter with it - try BookmarkFilePresenter(url: duckloadURL, primaryItemURL: destinationURL, logger: log) { [log] duckloadURL in + try BookmarkFilePresenter(url: duckloadURL, primaryItemURL: destinationURL) { duckloadURL in do { try fm.moveItem(at: tempURL, to: duckloadURL) return duckloadURL } catch { // fallback: move failed, keep the temp file in the original location - os_log(.error, log: log, "🙁 fallback with \(error), will use \(tempURL.path)") + Logger.fileDownload.error("🙁 fallback with \(error), will use \(tempURL.path)") PixelKit.fire(DebugEvent(GeneralPixel.fileAccessRelatedItemFailed, error: error)) return tempURL } } } - os_log(.debug, log: log, "🧙‍♂️ \"\(duckloadURL.path)\" (\"\(tempFilePresenter.url?.path ?? "")\") ready") + Logger.fileDownload.debug("🧙‍♂️ \"\(duckloadURL.path)\" (\"\(tempFilePresenter.url?.path ?? "")\") ready") return (tempFile: tempFilePresenter, destinationFile: destinationFilePresenter) } @@ -426,7 +426,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable } } - os_log(.debug, log: log, "removing temp file \"\(duckloadURL.path)\"") + Logger.fileDownload.debug("Removing temp file") try FilePresenter(url: duckloadURL, primaryItemURL: destinationURL).coordinateWrite(with: .forDeleting) { duckloadURL in try fm.removeItem(at: duckloadURL) } @@ -449,9 +449,9 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable } return } - os_log(.debug, log: log, "cancel \(self)") - download.cancel { [weak self, log, taskDescr=self.debugDescription] resumeData in - os_log(.debug, log: log, "\(taskDescr): download.cancel callback") + Logger.fileDownload.debug("cancel \(self)") + download.cancel { [weak self, taskDescr=self.debugDescription] resumeData in + Logger.fileDownload.debug("\(taskDescr): download.cancel callback") DispatchQueue.main.asyncOrNow { self?.downloadDidFail(with: URLError(.cancelled), resumeData: resumeData) } @@ -467,7 +467,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable switch result { case .success(let presenter): let url = presenter.url - os_log(.debug, log: log, "finish \(self) with .success(\"\(url?.path ?? "")\")") + Logger.fileDownload.debug("finish \(self) with .success(\"\(url?.path ?? "")\")") if progress.totalUnitCount == -1 { progress.totalUnitCount = max(1, self.progress.completedUnitCount) @@ -477,7 +477,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable self.state = .downloaded(presenter) case .failure(let error): - os_log(.debug, log: log, "finish \(self) with .failure(\(error))") + Logger.fileDownload.debug("finish \(self) with .failure(\(error))") self.state = .failed(destination: error.isRetryable ? self.state.destinationFilePresenter : nil, // stop tracking removed files for non-retryable downloads tempFile: error.isRetryable ? self.state.tempFilePresenter : nil, @@ -492,8 +492,8 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable // don‘t remove itemReplacementDirectory if we‘re keeping the temp file in it for a retryable error self.state.tempFilePresenter?.url?.path.hasPrefix(itemReplacementDirectory.path) != true { - DispatchQueue.global().async { [log, itemReplacementDirectory] in - os_log(.debug, log: log, "removing \"\(itemReplacementDirectory.path)\"") + DispatchQueue.global().async { [itemReplacementDirectory] in + Logger.fileDownload.debug("removing \"\(itemReplacementDirectory.path)\"") try? FileManager.default.removeItem(at: itemReplacementDirectory) } self.itemReplacementDirectory = nil @@ -508,7 +508,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable self.finish(with: .failure(.failedToCompleteDownloadTask(underlyingError: error, resumeData: nil, isRetryable: false))) return } - os_log(.debug, log: log, "ignoring `cancel` for already completed task \(self)") + Logger.fileDownload.debug("ignoring `cancel` for already completed task \(self)") return } @@ -520,14 +520,14 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable true } - os_log(.debug, log: log, "❗️ downloadDidFail \(self): \(error), retryable: \(isRetryable)") + Logger.fileDownload.debug("❗️ downloadDidFail \(self): \(error), retryable: \(isRetryable)") self.finish(with: .failure(.failedToCompleteDownloadTask(underlyingError: error, resumeData: resumeData, isRetryable: isRetryable))) if !isRetryable { - DispatchQueue.global().async { [log, itemReplacementDirectory] in + DispatchQueue.global().async { [itemReplacementDirectory] in let fm = FileManager() try? destinationFile.coordinateWrite(with: .forDeleting) { url in - os_log(.debug, log: log, "removing \"\(url.path)\"") + Logger.fileDownload.debug("removing \"\(url.path)\"") try fm.removeItem(at: url) } try? tempFile.coordinateWrite(with: .forDeleting) { url in @@ -536,7 +536,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable if let itemReplacementDirectory, url.path.hasPrefix(itemReplacementDirectory.path) { url = itemReplacementDirectory } - os_log(.debug, log: log, "removing \"\(url.path)\"") + Logger.fileDownload.debug("removing \"\(url.path)\"") try fm.removeItem(at: url) } } @@ -570,7 +570,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable #if DEBUG let downloadDescr = download.debugDescription @MainActor(unsafe) func performRegardlessOfMainThread() { - os_log(.debug, log: log, ".deinit") + Logger.fileDownload.debug(".deinit") assert(state.isCompleted, "FileDownloadTask is deallocated without finish(with:) been called") } performRegardlessOfMainThread() @@ -594,7 +594,7 @@ extension WebKitDownloadTask: WKDownloadDelegate { @MainActor func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String) async -> URL? { - os_log(.debug, log: log, "decide destination \(self)") + Logger.fileDownload.debug("decide destination \(self)") guard let delegate = delegate else { assertionFailure("WebKitDownloadTask: delegate is gone") @@ -649,7 +649,7 @@ extension WebKitDownloadTask: WKDownloadDelegate { willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, decisionHandler: @escaping (WKDownload.RedirectPolicy) -> Void) { - os_log(log: log, "will perform HTTP redirection \(self): \(response) to \(request)") + Logger.fileDownload.debug("will perform HTTP redirection \(self): \(response) to \(request)") decisionHandler(.allow) } @@ -657,7 +657,7 @@ extension WebKitDownloadTask: WKDownloadDelegate { func download(_ download: WKDownload, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { - os_log(log: log, "did receive challenge \(self): \(challenge)") + Logger.fileDownload.debug("did receive challenge \(self): \(challenge)") download.webView?.navigationDelegate?.webView?(download.webView!, didReceive: challenge, completionHandler: completionHandler) ?? { completionHandler(.performDefaultHandling, nil) }() @@ -670,21 +670,21 @@ extension WebKitDownloadTask: WKDownloadDelegate { guard case .downloading(destination: let destinationFile, tempFile: let tempFile) = self.state else { // if we receive `downloadDidFinish:` before the File Presenters are set up (async) // - we‘ll be waiting for the `.downloading` state to come in with the presenters - os_log(log: log, "🏁 download did finish too early, we‘ll wait for the `.downloading` state: \(self)") + Logger.fileDownload.debug("🏁 download did finish too early, we‘ll wait for the `.downloading` state: \(self)") assert(itemReplacementDirectory != nil, "itemReplacementDirectory should be set") waitForDownloadDidStart { [weak self] in self?.downloadDidFinish(download) } return } - os_log(log: log, "🏁 download did finish: \(self)") + Logger.fileDownload.debug("🏁 download did finish: \(self)") do { try tempFile.coordinateWrite(with: .forMoving) { tempURL in // replace destination file with temp file - try destinationFile.coordinateWrite(with: .forReplacing) { [log] destinationURL in + try destinationFile.coordinateWrite(with: .forReplacing) { destinationURL in if destinationURL != tempURL { // could be a corner-case when downloading a `.duckload` file - os_log(.debug, log: log, "replacing \"\(destinationURL.path)\" with \"\(tempURL.path)\"") + Logger.fileDownload.debug("replacing \"\(destinationURL.path)\" with \"\(tempURL.path)\"") _=try fm.replaceItemAt(destinationURL, withItemAt: tempURL) } // set quarantine attributes @@ -693,7 +693,7 @@ extension WebKitDownloadTask: WKDownloadDelegate { } // remove temp file item replacement directory if present if let itemReplacementDirectory { - os_log(.debug, log: log, "removing \(itemReplacementDirectory.path)") + Logger.fileDownload.debug("removing \(itemReplacementDirectory.path)") try? fm.removeItem(at: itemReplacementDirectory) self.itemReplacementDirectory = nil } @@ -701,7 +701,7 @@ extension WebKitDownloadTask: WKDownloadDelegate { } catch { PixelKit.fire(DebugEvent(GeneralPixel.fileMoveToDownloadsFailed, error: error)) - os_log(.error, log: log, "fileMoveToDownloadsFailed: \(error)") + Logger.fileDownload.error("fileMoveToDownloadsFailed: \(error)") self.finish(with: .failure(.failedToCompleteDownloadTask(underlyingError: error, resumeData: nil, isRetryable: false))) } } @@ -714,7 +714,7 @@ extension WebKitDownloadTask: WKDownloadDelegate { // in case the File Presenters instantiation task is still running - we‘ll either receive the `state` change // or the task will be finished with a file error || itemReplacementDirectoryFSOCancellable == nil else { - os_log(log: log, "❌ download did fail too early, we‘ll wait for the `.downloading` state: \(self)") + Logger.fileDownload.debug("❌ download did fail too early, we‘ll wait for the `.downloading` state: \(self)") waitForDownloadDidStart { [weak self] in self?.downloadDidFail(with: error, resumeData: resumeData) } diff --git a/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift b/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift index 5364ca781a..f645d9e60f 100644 --- a/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift +++ b/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift @@ -21,6 +21,7 @@ import Common import Foundation import Navigation import PixelKit +import os.log @MainActor private func getFirstAvailableWebView() -> WKWebView? { @@ -65,21 +66,14 @@ final class DownloadListCoordinator { let progress = Progress() - private let getLogger: (() -> OSLog) - private var log: OSLog { - getLogger() - } - init(store: DownloadListStoring = DownloadListStore(), downloadManager: FileDownloadManagerProtocol = FileDownloadManager.shared, clearItemsOlderThan clearDate: Date = .daysAgo(2), - webViewProvider: (() -> WKWebView?)? = nil, - log: @autoclosure @escaping (() -> OSLog) = .downloads) { + webViewProvider: (() -> WKWebView?)? = nil) { self.store = store self.downloadManager = downloadManager self.webViewProvider = webViewProvider - self.getLogger = log load(clearingItemsOlderThan: clearDate) subscribeToDownloadManager() @@ -93,12 +87,12 @@ final class DownloadListCoordinator { switch result { case .success(let items): - os_log(.error, log: log, "coordinator: loaded \(items.count) items") + Logger.fileDownload.error("coordinator: loaded \(items.count) items") for item in items { do { try add(item, ifModifiedLaterThan: clearDate) } catch { - os_log(.debug, log: self.log, "❗️ coordinator: drop item \(item.identifier): \(error)") + Logger.fileDownload.debug("❗️ coordinator: drop item \(item.identifier): \(error)") // remove item from db removing temp files if needed without sending a `.removed` update cleanupTempFiles(for: item) filePresenters[item.identifier] = nil @@ -109,7 +103,7 @@ final class DownloadListCoordinator { } case .failure(let error): - os_log(.error, log: log, "coordinator: loading failed: \(error)") + Logger.fileDownload.error("coordinator: loading failed: \(error)") } } } @@ -156,9 +150,9 @@ final class DownloadListCoordinator { // locate destination file let destinationPresenterResult = Result { if let destinationFileBookmarkData = item.destinationFileBookmarkData { - try BookmarkFilePresenter(fileBookmarkData: destinationFileBookmarkData, logger: log) + try BookmarkFilePresenter(fileBookmarkData: destinationFileBookmarkData) } else if let destinationURL = item.destinationURL { - try BookmarkFilePresenter(url: destinationURL, logger: log) + try BookmarkFilePresenter(url: destinationURL) } else { nil } @@ -167,9 +161,9 @@ final class DownloadListCoordinator { // locate temp download file var tempFilePresenterResult = Result { if let tempFileBookmarkData = item.tempFileBookmarkData { - try BookmarkFilePresenter(fileBookmarkData: tempFileBookmarkData, logger: log) + try BookmarkFilePresenter(fileBookmarkData: tempFileBookmarkData) } else if let tempURL = item.tempURL { - try BookmarkFilePresenter(url: tempURL, logger: log) + try BookmarkFilePresenter(url: tempURL) } else { nil } @@ -213,13 +207,13 @@ final class DownloadListCoordinator { guard downloadTaskCancellables[task] == nil else { return } let item = item ?? DownloadListItem(task: task) - os_log(.debug, log: log, "coordinator: subscribing to \(item.identifier)") + Logger.fileDownload.debug("coordinator: subscribing to \(item.identifier)") self.downloadTaskCancellables[task] = task.$state .sink { [weak self] state in DispatchQueue.main.async { guard let self else { return } - os_log(.debug, log: self.log, "coordinator: state updated \(item.identifier) ➡️ \(state)") + Logger.fileDownload.debug("coordinator: state updated \(item.identifier.uuidString) ➡️ \(state.debugDescription)") switch state { case .initial: // only add item when download starts, destination URL is set @@ -241,7 +235,7 @@ final class DownloadListCoordinator { @MainActor private func addItemIfNeededAndSubscribe(to presenters: (destination: FilePresenter, tempFile: FilePresenter?), for initialItem: DownloadListItem) { - os_log(.debug, log: log, "coordinator: add/update \(initialItem.identifier)") + Logger.fileDownload.debug("coordinator: add/update \(initialItem.identifier)") updateItem(withId: initialItem.identifier) { item in if item == nil { item = initialItem } } @@ -259,14 +253,14 @@ final class DownloadListCoordinator { .scan((oldURL: nil, newURL: nil, fileBookmarkData: nil)) { (oldURL: $0.newURL, newURL: $1.0, fileBookmarkData: $1.1) } .sink { [weak self] oldURL, newURL, fileBookmarkData in DispatchQueue.main.asyncOrNow { - self?.updateItem(withId: item.identifier) { [id=item.identifier, log=(self?.log ?? .disabled)] item in + self?.updateItem(withId: item.identifier) { [id=item.identifier] item in guard !Self.checkIfFileWasRemoved(oldURL: oldURL, newURL: newURL) else { - os_log(.debug, log: log, "coordinator: destination file removed \(id)") + Logger.fileDownload.debug("coordinator: destination file removed \(id)") item = nil return } - os_log(.debug, log: log, "⚠️coordinator: destination url updated \(id): \"\(newURL?.path ?? "")\"") + Logger.fileDownload.debug("⚠️coordinator: destination url updated \(id): \"\(newURL?.path ?? "")\"") item?.destinationURL = newURL item?.destinationFileBookmarkData = fileBookmarkData // keep the filename even when the destinationURL is nullified @@ -288,14 +282,14 @@ final class DownloadListCoordinator { .scan((oldURL: nil, newURL: nil, fileBookmarkData: nil)) { (oldURL: $0.newURL, newURL: $1.0, fileBookmarkData: $1.1) } .sink { [weak self] oldURL, newURL, fileBookmarkData in DispatchQueue.main.asyncOrNow { - self?.updateItem(withId: item.identifier) { [id=item.identifier, log=(self?.log ?? .disabled)] item in + self?.updateItem(withId: item.identifier) { [id=item.identifier] item in guard !Self.checkIfFileWasRemoved(oldURL: oldURL, newURL: newURL) else { - os_log(.debug, log: log, "coordinator: temp file removed \(id)") + Logger.fileDownload.debug("coordinator: temp file removed \(id)") item = nil return } - os_log(.debug, log: log, "coordinator: temp url updated \(id): \"\(newURL?.path ?? "")\"") + Logger.fileDownload.debug("coordinator: temp url updated \(id): \"\(newURL?.path ?? "")\"") item?.tempURL = newURL item?.tempFileBookmarkData = fileBookmarkData } @@ -335,7 +329,7 @@ final class DownloadListCoordinator { task.$state.receive(on: DispatchQueue.main) .sink { [weak self] state in guard let self, state.isCompleted else { return } - os_log(.debug, log: log, "coordinator: unsubscribe from progress: \(task)") + Logger.fileDownload.debug("coordinator: unsubscribe from progress: \(task)") progress.completedUnitCount -= lastKnownProgress.completed progress.totalUnitCount -= lastKnownProgress.total @@ -346,7 +340,7 @@ final class DownloadListCoordinator { @MainActor private func downloadTask(_ task: WebKitDownloadTask, withOriginalItem initialItem: DownloadListItem, completedWith result: Subscribers.Completion) -> DownloadListItem? { - os_log(.debug, log: log, "coordinator: task did finish \(initialItem.identifier) \(task) with .\(result)") + Logger.fileDownload.debug("coordinator: task did finish \(initialItem.identifier.uuidString) \(task.debugDescription) with .\(String(describing: result))") self.downloadTaskCancellables[task] = nil @@ -409,11 +403,11 @@ final class DownloadListCoordinator { let fm = FileManager.default do { try filePresenters[item.identifier]?.tempFile?.coordinateWrite(with: .forDeleting, using: { url in - os_log(.debug, log: self.log, "🦀 coordinator: removing \"\(url.path)\" (\(item.identifier))") + Logger.fileDownload.debug("🦀 coordinator: removing \"\(url.path)\" (\(item.identifier))") try fm.removeItem(at: url) }) } catch { - os_log(.error, log: self.log, "🦀 coordinator: failed to remove temp file: \(error)") + Logger.fileDownload.error("🦀 coordinator: failed to remove temp file: \(error)") } struct DestinationFileNotEmpty: Error {} @@ -421,13 +415,13 @@ final class DownloadListCoordinator { guard let presenter = filePresenters[item.identifier]?.destination, (try? presenter.url?.resourceValues(forKeys: [.fileSizeKey]).fileSize) == 0 else { throw DestinationFileNotEmpty() } try presenter.coordinateWrite(with: .forDeleting, using: { url in - os_log(.debug, log: self.log, "🦀 coordinator: removing \"\(url.path)\" (\(item.identifier))") + Logger.fileDownload.debug("🦀 coordinator: removing \"\(url.path)\" (\(item.identifier))") try fm.removeItem(at: url) }) } catch is DestinationFileNotEmpty { // don‘t delete non-empty destination file } catch { - os_log(.error, log: self.log, "🦀 coordinator: failed to remove destination file: \(error)") + Logger.fileDownload.error("🦀 coordinator: failed to remove destination file: \(error)") } } @@ -436,7 +430,7 @@ final class DownloadListCoordinator { // Important: WebKitDownloadTask (as well as WKWebView) should be deallocated on the Main Thread dispatchPrecondition(condition: .onQueue(.main)) withExtendedLifetime(webView) { - os_log(.debug, log: self.log, "coordinator: restarting \(item.identifier): \(download)") + Logger.fileDownload.debug("coordinator: restarting \(item.identifier.uuidString): \(String(describing: download))") let destination: WebKitDownloadTask.DownloadDestination = if let presenters { .resume(destination: presenters.destination, tempFile: presenters.tempFile) } else { @@ -471,7 +465,7 @@ final class DownloadListCoordinator { @MainActor func restart(downloadWithIdentifier identifier: UUID) { - os_log(.debug, log: self.log, "coordinator: restart \(identifier)") + Logger.fileDownload.debug("coordinator: restart \(identifier)") guard let item = items[identifier], let webView = (webViewProvider ?? getFirstAvailableWebView)() else { return } do { guard var resumeData = item.error?.resumeData, @@ -510,7 +504,7 @@ final class DownloadListCoordinator { @MainActor func cleanupInactiveDownloads() { - os_log(.debug, log: self.log, "coordinator: cleanupInactiveDownloads") + Logger.fileDownload.debug("coordinator: cleanupInactiveDownloads") for (id, item) in self.items where item.progress == nil { remove(downloadWithIdentifier: id) @@ -519,7 +513,7 @@ final class DownloadListCoordinator { @MainActor func cleanupInactiveDownloads(for baseDomains: Set, tld: TLD) { - os_log(.debug, log: self.log, "coordinator: cleanupInactiveDownloads for \(baseDomains)") + Logger.fileDownload.debug("coordinator: cleanupInactiveDownloads for \(baseDomains)") for (id, item) in self.items where item.progress == nil { let websiteUrlBaseDomain = tld.eTLDplus1(item.websiteURL?.host) ?? "" @@ -533,7 +527,7 @@ final class DownloadListCoordinator { @MainActor func remove(downloadWithIdentifier identifier: UUID) { - os_log(.debug, log: self.log, "coordinator: remove \(identifier)") + Logger.fileDownload.debug("coordinator: remove \(identifier)") updateItem(withId: identifier) { item in item = nil @@ -542,7 +536,7 @@ final class DownloadListCoordinator { @MainActor func cancel(downloadWithIdentifier identifier: UUID) { - os_log(.debug, log: self.log, "coordinator: cancel \(identifier)") + Logger.fileDownload.debug("coordinator: cancel \(identifier)") guard let item = self.items[identifier] else { assertionFailure("Item with identifier \(identifier) not found") diff --git a/DuckDuckGo/Fire/Logger+Fire.swift b/DuckDuckGo/Fire/Logger+Fire.swift new file mode 100644 index 0000000000..04f675c35b --- /dev/null +++ b/DuckDuckGo/Fire/Logger+Fire.swift @@ -0,0 +1,24 @@ +// +// Logger+Fire.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import os.log + +public extension Logger { + static var fire = { Logger(subsystem: "Fire", category: "") }() +} diff --git a/DuckDuckGo/Fire/Model/Fire.swift b/DuckDuckGo/Fire/Model/Fire.swift index a89385145b..dfcef3ee80 100644 --- a/DuckDuckGo/Fire/Model/Fire.swift +++ b/DuckDuckGo/Fire/Model/Fire.swift @@ -24,6 +24,7 @@ import PrivacyDashboard import WebKit import SecureStorage import History +import os.log final class Fire { @@ -129,7 +130,7 @@ final class Fire { func burnEntity(entity: BurningEntity, includingHistory: Bool = true, completion: (() -> Void)? = nil) { - os_log("Fire started", log: .fire) + Logger.fire.debug("Fire started") let group = DispatchGroup() dispatchGroup = group @@ -180,14 +181,14 @@ final class Fire { completion?() - os_log("Fire finished", log: .fire) + Logger.fire.debug("Fire finished") } } } @MainActor func burnAll(completion: (() -> Void)? = nil) { - os_log("Fire started", log: .fire) + Logger.fire.debug("Fire started") let group = DispatchGroup() dispatchGroup = group @@ -231,7 +232,7 @@ final class Fire { self.burningData = nil completion?() - os_log("Fire finished", log: .fire) + Logger.fire.debug("Fire finished") } } } @@ -331,15 +332,15 @@ final class Fire { // MARK: - Web cache private func burnWebCache() async { - os_log("WebsiteDataStore began cookie deletion", log: .fire) + Logger.fire.debug("WebsiteDataStore began cookie deletion") await webCacheManager.clear() - os_log("WebsiteDataStore completed cookie deletion", log: .fire) + Logger.fire.debug("WebsiteDataStore completed cookie deletion") } private func burnWebCache(baseDomains: Set? = nil) async { - os_log("WebsiteDataStore began cookie deletion", log: .fire) + Logger.fire.debug("WebsiteDataStore began cookie deletion") await webCacheManager.clear(baseDomains: baseDomains) - os_log("WebsiteDataStore completed cookie deletion", log: .fire) + Logger.fire.debug("WebsiteDataStore completed cookie deletion") } // MARK: - History diff --git a/DuckDuckGo/Fire/View/FirePopoverViewController.swift b/DuckDuckGo/Fire/View/FirePopoverViewController.swift index 53b1dc91b2..54cd06ebe4 100644 --- a/DuckDuckGo/Fire/View/FirePopoverViewController.swift +++ b/DuckDuckGo/Fire/View/FirePopoverViewController.swift @@ -20,6 +20,7 @@ import Cocoa import Combine import Common import History +import os.log protocol FirePopoverViewControllerDelegate: AnyObject { @@ -328,7 +329,7 @@ final class FirePopoverViewController: NSViewController { private func setupOptionsButton() { guard let menu = optionsButton.menu, let font = optionsButton.font else { - os_log("FirePopoverViewController: Menu and/or font not present for optionsMenu", type: .error) + Logger.fire.error("FirePopoverViewController: Menu and/or font not present for optionsMenu") return } menu.removeAllItems() diff --git a/DuckDuckGo/Fireproofing/Model/FireproofDomains.swift b/DuckDuckGo/Fireproofing/Model/FireproofDomains.swift index 6552a67c4c..d9814ce301 100644 --- a/DuckDuckGo/Fireproofing/Model/FireproofDomains.swift +++ b/DuckDuckGo/Fireproofing/Model/FireproofDomains.swift @@ -19,6 +19,7 @@ import Common import Foundation import CoreData +import os.log internal class FireproofDomains { @@ -78,7 +79,7 @@ internal class FireproofDomains { return try store.load() } catch { - os_log("FireproofDomainsStore: Failed to load Fireproof Domains", type: .error) + Logger.fire.error("FireproofDomainsStore: Failed to load Fireproof Domains") return FireproofDomainsContainer() } } diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 90c59a3d9d..8cd2dcad72 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -6255,126 +6255,6 @@ } } }, - "autofill.usernames-and-passwords" : { - "comment" : "Autofill autosaved data type", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Benutzernamen und Passwörter" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Usernames and passwords" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nombres de usuario y contraseñas" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Noms d'utilisateur et mots de passe" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nome utente e password" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gebruikersnamen en wachtwoorden" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nazwy użytkowników i hasła" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nomes de utilizador e palavras-passe" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Имена пользователей и пароли" - } - } - } - }, - "autofill.view-autofill-content" : { - "comment" : "View Autofill Content Button name in the autofill settings", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Autovervollständigen-Inhalte anzeigen" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "View Autofill Content…" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ver contenido de autocompletar..." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Afficher le contenu de saisie automatique…" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Visualizza compilazione automatica del contenuto..." - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Inhoud voor automatisch invullen bekijken ..." - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wyświetl zawartość autouzupełniania…" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ver conteúdo de preenchimento automático…" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Просмотреть данные автозаполнения..." - } - } - } - }, "autofill.view-autofill-content.identities" : { "comment" : "View Identities Content Button title in the autofill Settings", "extractionState" : "extracted_with_value", @@ -13390,17998 +13270,18037 @@ } } }, - "copy" : { - "comment" : "Copy button", + "contextual.onboarding.final-screen.button" : { + "comment" : "Button on the last screen of the onboarding, it will dismiss the onboarding screen.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kopieren" + "value" : "Schlag ein!" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Copy" + "value" : "High five!" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar" + "value" : "¡Choca esos cinco!" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Copier" + "value" : "Bien joué !" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Copia" + "value" : "Batti cinque!" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiëren" + "value" : "High five!" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiuj" + "value" : "Piątka!" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar" + "value" : "Dá cá cinco!" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Копировать" + "value" : "Дай пять!" } } } }, - "copy-selection" : { - "comment" : "Copy selection menu item", + "contextual.onboarding.final-screen.message" : { + "comment" : "Message of the last screen of the onboarding to the browser app.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kopieren" + "value" : "Hinweis: Jedes Mal, wenn du mit mir browst, verliert eine gruselige Anzeige ihren Schrecken. 👌" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Copy" + "value" : "Remember: every time you browse with me a creepy ad loses its wings. 👌" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar" + "value" : "Recuerda: cada vez que navegas conmigo corto las alas a un anuncio horrible. 👌" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Copier" + "value" : "Pensez-y : chaque fois que vous naviguez avec moi, une publicité douteuse disparaît. 👌" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Copia" + "value" : "Ricorda: quando navighi con me gli annunci inquietanti non possono seguirti. 👌" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiëren" + "value" : "Denk eraan: elke keer als je met mij browset, verliest een enge advertentie zijn vleugels. 👌" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiuj" + "value" : "Pamiętaj: za każdym razem, gdy przeglądasz ze mną Internet, jakaś wstrętna reklama przestaje działać. 👌" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar" + "value" : "Lembra-te: sempre que navegas comigo, um anúncio assustador perde as suas asas. 👌" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Копировать" + "value" : "Бродить по сайтам с нами — значит подрезать крылья назойливой рекламе. 👌" } } } }, - "copy.email.address" : { - "comment" : "Context menu item", + "contextual.onboarding.final-screen.title" : { + "comment" : "Title of the last screen of the onboarding to the browser app", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "E-Mail-Adresse kopieren" + "value" : "Gut gemacht!" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Copy Email Address" + "value" : "You’ve got this!" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar dirección de correo electrónico" + "value" : "¡Lo estás haciendo muy bien!" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Copier l'adresse e-mail" + "value" : "Bien joué !" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Copia indirizzo e-mail" + "value" : "Ben fatto!" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "E-mailadres kopiëren" + "value" : "Je kunt het!" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiuj adres e-mail" + "value" : "Udało się!" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar endereço de e-mail" + "value" : "Você consegue!" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Копировать адрес эл. почты" + "value" : "Проще некуда!" } } } }, - "copy.email.addresses" : { - "comment" : "Context menu item", + "contextual.onboarding.first-search-done.message" : { + "comment" : "After the user performs their first search using the browser, this dialog explains the advantages of using DuckDuckGo", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "E-Mail-Adressen kopieren" + "value" : "Privat. Schnell. Weniger Werbung." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Copy Email Addresses" + "value" : "Private. Fast. Fewer ads." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar direcciones de correo electrónico" + "value" : "Privado. Rápido. Menos anuncios." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Copier les adresses e-mail" + "value" : "Privé. Rapide. Moins de publicités." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Copia indirizzi e-mail" + "value" : "Privato. Veloce. Meno annunci." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "E-mailadressen kopiëren" + "value" : "Privé. Snel. Minder advertenties." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Skopiuj adresy e-mail" + "value" : "Prywatna. Szybka. Z mniejszą liczbą reklam." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar endereços de e-mail" + "value" : "Privado. Rápido. Menos anúncios." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Копировать адреса эл. почты" + "value" : "Надежно. Быстро. Меньше рекламы." } } } }, - "copy.image.address" : { - "comment" : "Context menu item", + "contextual.onboarding.first-search-done.title" : { + "comment" : "After the user performs their first search using the browser, this dialog explains the advantages of using DuckDuckGo", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bildadresse kopieren" + "value" : "Das ist DuckDuckGo Search." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Copy Image Address" + "value" : "That’s DuckDuckGo Search." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar dirección de la imagen" + "value" : "Eso es DuckDuckGo Search." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Copier l'adresse de l'image" + "value" : "C'est DuckDuckGo Search." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Copia indirizzo immagine" + "value" : "È DuckDuckGo Search." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Adres afbeelding kopiëren" + "value" : "Dat is DuckDuckGo Search." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Skopiuj adres obrazu" + "value" : "To DuckDuckGo Search." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar endereço de imagem" + "value" : "É a DuckDuckGo Search." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Копировать адрес изображения" + "value" : "Это — DuckDuckGo Search." } } } }, - "Country" : { - "comment" : "Title of the section of the Identities manager where the user can add/modify a country (US,UK, Italy etc...)", + "contextual.onboarding.got-it.button" : { + "comment" : "During onboarding steps this button is shown and takes either to the next steps or closes the onboarding.", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Land" + "value" : "Verstanden" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Got it" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "País" + "value" : "Entendido" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Pays" + "value" : "J'ai compris" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Paese" + "value" : "Ho capito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Land" + "value" : "Ik snap het" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Kraj" + "value" : "Rozumiem" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "País" + "value" : "Entendi" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Страна" + "value" : "Понятно" } } } }, - "crash-report.description" : { - "comment" : "Description of the dialog where the user can send a crash report", + "contextual.onboarding.ntp.try-a-site.title" : { + "comment" : "Title of a popover on the new tab page browser that invites the user to try a visiting a website", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Klicke auf „An DuckDuckGo senden“, um den Bericht an DuckDuckGo zu senden. Crash-Berichte helfen DuckDuckGo, Probleme zu diagnostizieren und unsere Produkte zu verbessern. Mit diesem Bericht werden keine persönlichen Daten übermittelt." + "value" : "Versuche, eine Website zu besuchen!" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Click “Send to DuckDuckGo“ to submit report to DuckDuckGo. Crash reports help DuckDuckGo diagnose issues and improve our products. No personal information is sent with this report." + "value" : "Try visiting a site!" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Haz clic en \"Enviar a DuckDuckGo\" para enviar el informe a DuckDuckGo. Los informes de fallos ayudan a DuckDuckGo a diagnosticar problemas y a mejorar nuestros productos. No se envía información personal con este informe." + "value" : "¡Intenta visitar un sitio!" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Cliquez sur « Envoyer à DuckDuckGo » pour envoyer le rapport à DuckDuckGo. Les rapports de plantage aident DuckDuckGo à diagnostiquer les problèmes et à améliorer ses produits. Aucune information personnelle n'est envoyée avec ce rapport." + "value" : "Essayez de visiter un site !" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Fai clic su \"Invia a DuckDuckGo\" per inviare un report a DuckDuckGo. Le segnalazioni sugli arresti anomali aiutano DuckDuckGo a diagnosticare i problemi e a migliorare i nostri prodotti. Con questo report non vengono inviate informazioni personali." + "value" : "Prova a visitare un sito!" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Klik op 'Verzenden naar DuckDuckGo' om een rapport in te dienen bij DuckDuckGo. Crashrapporten helpen DuckDuckGo om problemen vast te stellen en onze producten te verbeteren. Er worden geen persoonsgegevens verzonden met dit rapport." + "value" : "Bezoek eens een site!" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Kliknij „Wyślij do DuckDuckGo”, aby wysłać raport do DuckDuckGo. Raporty o awariach pomagają zespołowi DuckDuckGo diagnozować problemy i ulepszać produkty. Z raportem nie są wysyłane żadne dane osobowe." + "value" : "Spróbuj odwiedzić witrynę!" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Clica em \"Enviar para a DuckDuckGo\" para enviares o relatório para a DuckDuckGo. Os relatórios de falhas ajudam a DuckDuckGo a diagnosticar problemas e a melhorar os nossos produtos. Não envias qualquer informação pessoal com este relatório." + "value" : "Experimenta visitar um site!" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Для подачи отчета нажмите кнопку «Отправить в DuckDuckGo». Отчеты о сбоях помогают нам диагностировать проблемы и совершенствовать продукты. Отчет не включает ваших личных данных." + "value" : "Попробуйте посетить сайт!" } } } }, - "crash-report.dont-send-button" : { - "comment" : "Button the user can press to not send the crash report", + "contextual.onboarding.try-a-search.message" : { + "comment" : "Message of a popover on the browser that invites the user to try a search explaining that their searches are anonymous", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nicht senden" + "value" : "Deine DuckDuckGo-Suchanfragen sind immer anonym." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Don’t Send" + "value" : "Your DuckDuckGo searches are always anonymous." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No enviar" + "value" : "Tus búsquedas en DuckDuckGo son siempre anónimas." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ne pas envoyer" + "value" : "Vos recherches sur DuckDuckGo sont toujours anonymes." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Non inviare" + "value" : "Le tue ricerche su DuckDuckGo sono sempre anonime." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Niet verzenden" + "value" : "Je DuckDuckGo-zoekopdrachten zijn altijd anoniem." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie wysyłaj" + "value" : "Wyszukiwania w DuckDuckGo zawsze są anonimowe." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Não enviar" + "value" : "As tuas pesquisas no DuckDuckGo são sempre anónimas." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не отправлять" + "value" : "Ваши поисковые запросы в DuckDuckGo всегда анонимны." } } } }, - "crash-report.send-button" : { - "comment" : "Button the user can press to send the crash report to DuckDuckGo", + "contextual.onboarding.try-a-search.title" : { + "comment" : "Title of a popover on the browser that invites the user to try a search", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "An DuckDuckGo senden" + "value" : "Probiere eine Suche aus!" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Send to DuckDuckGo" + "value" : "Try a search!" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar a DuckDuckGo" + "value" : "¡Prueba con una búsqueda!" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Envoyer à DuckDuckGo" + "value" : "Essayez une recherche !" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Invia a DuckDuckGo" + "value" : "Prova una ricerca!" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verzenden naar DuckDuckGo" + "value" : "Probeer een zoekopdracht!" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyślij do DuckDuckGo" + "value" : "Spróbuj coś wyszukać!" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar para a DuckDuckGo" + "value" : "Experimenta fazer uma pesquisa!" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отправить в DuckDuckGo" + "value" : "Попробуйте ввести запрос!" } } } }, - "crash-report.textfield.title" : { - "comment" : "Title of the text field where the problems that caused the crashed are detailed", + "contextual.onboarding.try-a-site.message" : { + "comment" : "Message of a popover on the browser that invites the user to try visiting a website to explain that we block trackers", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Problemdetails" + "value" : "Ich blockiere Tracker, damit sie dich nicht ausspionieren können." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Problem Details" + "value" : "I’ll block trackers so they can’t spy on you." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Detalles del problema" + "value" : "Bloquearé los rastreadores para que no puedan espiarte." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Détails du problème" + "value" : "Je bloquerai les traqueurs afin qu'ils ne puissent pas vous espionner." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Dettagli del problema" + "value" : "Bloccherò i sistemi di tracciamento in modo che non possano spiarti e" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Details van het probleem" + "value" : "Ik blokkeer trackers zodat ze je niet kunnen bespioneren." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Szczegóły problemu" + "value" : "Zablokuję skrypty śledzące, aby nie mogły Cię szpiegować." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Detalhes do problema" + "value" : "Bloquearei rastreadores para que não possam espiá-lo." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Информация о проблеме" + "value" : "Мы заблокируем трекеры и пресечем слежку." } } } }, - "crash-report.title" : { - "comment" : "Title of the dialog where the user can send a crash report", + "contextual.onboarding.try-a-site.title" : { + "comment" : "Title of a popover on the browser that invites the user to try a visiting a website", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo Privacy Browser wurde unerwartet beendet." + "value" : "Versuche als nächstes, eine Website zu besuchen!" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo Privacy Browser quit unexpectedly." + "value" : "Next, try visiting a site!" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo Privacy Browser se ha cerrado inesperadamente." + "value" : "¡A continuación, intenta visitar un sitio!" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo Privacy Browser s'est arrêté de manière inattendue." + "value" : "Ensuite, essayez de visiter un site !" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo Privacy Browser si è chiuso inaspettatamente." + "value" : "In seguito, prova a visitare un sito!" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo Privacy Browser is onverwacht gesloten." + "value" : "Probeer nu een site te bezoeken!" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przeglądarka DuckDuckGo Privacy Browser nieoczekiwanie przestała działać." + "value" : "Następnie spróbuj odwiedzić witrynę!" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O DuckDuckGo Privacy Browser encerrou de forma inesperada." + "value" : "Em seguida, experimenta visitar um site!" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Браузер DuckDuckGo Privacy Browser неожиданно закрылся." + "value" : "А теперь попробуйте посетить сайт!" } } } }, - "dashboard.permission.allow" : { - "comment" : "Privacy Dashboard: Website can always access input media device", + "contextual.onboarding.try-fire-button.message" : { + "comment" : "Message of a popover on the browser that invites the user to try visiting the browser Fire Button. Please leave the line break", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Immer erlauben" + "value" : "Lösche deine Browseraktivitäten sofort mit dem Fire Button.\n\nProbier’s doch mal aus! 🔥" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Always allow" + "value" : "Instantly clear your browsing activity with the Fire Button.\n\nGive it a try! 🔥" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Permitir siempre" + "value" : "Borra al instante tu actividad de navegación con el Fire Button.\n\n¡Pruébalo! 🔥" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Toujours autoriser" + "value" : "Effacez instantanément votre activité de navigation avec le Fire Button.\n\nEssayez par vous-même ! 🔥" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Consenti sempre" + "value" : "Cancella istantaneamente la tua attività di navigazione con il Fire Button.\n\nProvalo! 🔥" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Altijd toestaan" + "value" : "Wis je browser-activiteit direct met de Fire Button.\n\nProbeer het maar! 🔥" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zawsze zezwalaj" + "value" : "Natychmiast wyczyść swoją aktywność związaną z przeglądaniem za pomocą przycisku Fire Button.\n\nSpróbuj! 🔥" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Permitir sempre" + "value" : "Limpa instantaneamente a tua atividade de navegação com o Fire Button.\n\nExperimenta! 🔥" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Разрешать всегда" + "value" : "Кнопка Fire Button моментально стирает из браузера данные о посещении сайтов.\n\nУбедитесь сами! 🔥" } } } }, - "dashboard.permission.allow.on" : { - "comment" : "Permission Popover 'Always allow on' (for domainName) checkbox", + "contextual.onboarding.try-search.option1-English" : { + "comment" : "Browser Search query for how to say duck in english", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Immer einschalten erlauben" + "value" : "wie sagt man „Ente“ auf Spanisch" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Always allow on" + "value" : "how to say “duck” in spanish" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Permite siempre activo" + "value" : "cómo se dice «pato» en inglés" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Toujours autoriser sur" + "value" : "comment dire « duck » en espagnol" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Consenti sempre per" + "value" : "come si dice \"anatra\" in spagnolo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Altijd toestaan op" + "value" : "hoe zeg je 'eend' in het Spaans?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zawsze zezwalaj w" + "value" : "jak się mówi „kaczka” po hiszpańsku" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Permitir sempre em" + "value" : "como dizer \"pato\" em espanhol" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Всегда разрешать на" + "value" : "Как сказать «утка» по-испански?" } } } }, - "dashboard.permission.ask" : { - "comment" : "Privacy Dashboard: Website should always Ask for permission for input media device access", + "contextual.onboarding.try-search.option1international" : { + "comment" : "Browser Search query for how to say duck in english", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Jedes Mal fragen" + "value" : "wie sagt man „Ente“ auf Englisch" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Ask every time" + "value" : "how to say “duck” in english" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Preguntar cada vez" + "value" : "cómo se dice «pato» en inglés" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Toujours demander" + "value" : "comment dire « canard » en anglais" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Chiedi ogni volta" + "value" : "come si dice \"anatra\" in inglese" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Elke keer vragen" + "value" : "hoe zeg je 'eend' in het Engels?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pytaj za każdym razem" + "value" : "jak się mówi „kaczka” po angielsku" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Pergunte todas as vezes" + "value" : "como dizer \"pato\" em inglês" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Спрашивать каждый раз" + "value" : "Как сказать «утка» по-английски?" } } } }, - "dashboard.permission.deny" : { - "comment" : "Privacy Dashboard: Website can never access input media device", + "contextual.onboarding.try-search.option2-english" : { + "comment" : "Search query for the cast of Mighty Ducks", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Immer ablehnen" + "value" : "Besetzung von Mighty Ducks" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Always deny" + "value" : "mighty ducks cast" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Rechazar siempre" + "value" : "reparto de Mighty Ducks" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Toujours refuser" + "value" : "casting de mighty ducks" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rifiuta sempre" + "value" : "cast delle papere potenti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Altijd weigeren" + "value" : "cast van Mighty Ducks" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zawsze odmawiaj" + "value" : "obsada potężnych kaczorów" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Negar sempre" + "value" : "elenco do filme A Hora dos Campeões" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не разрешать" + "value" : "Кто играет главные роли в фильме «Могучие утята»?" } } } }, - "dashboard.popups.ask" : { - "comment" : "Make PopUp Windows always asked from user for current domain", + "contextual.onboarding.try-search.option2-international" : { + "comment" : "Search query for the cast of Avatar", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Benachrichtigen" + "value" : "Besetzung von Avatar" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Notify" + "value" : "cast of avatar" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Notificar" + "value" : "reparto de Avatar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Notifier" + "value" : "casting d'avatar" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Invia una notifica" + "value" : "cast di avatar" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Informeren" + "value" : "cast van avatar" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Powiadom" + "value" : "obsada avatara" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Notificar" + "value" : "elenco de avatar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Спрашивать разрешение" + "value" : "актерский состав аватара" } } } }, - "Data Detectors" : { - "comment" : "Main Menu Edit-Substitutions item", + "contextual.onboarding.try-search.option3" : { + "comment" : "Browser Search query for local weather", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Datendetektoren" + "value" : "Lokales Wetter" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "local weather" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Detectores de datos" + "value" : "el tiempo local" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Détecteurs de données" + "value" : "météo locale" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rilevatori di dati" + "value" : "meteo locale" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Datadetectoren" + "value" : "lokaal weer" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Detektory danych" + "value" : "pogoda lokalna" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Detetores de dados" + "value" : "meteorologia local" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Детекторы данных" + "value" : "Местная погода" } } } }, - "database.factory.failed.information" : { - "comment" : "Info to restart macOS after database init failure", + "contextual.onboarding.try-search.surprise-me-english" : { + "comment" : "Browser Search query for chocolate chip cookie recipes", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Starte deinen Mac neu und versuche es erneut" + "value" : "Rezepte für Schokoladenkekse" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Restart your Mac and try again" + "value" : "chocolate chip cookie recipes" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Reinicia tu Mac e inténtalo de nuevo" + "value" : "recetas de galletas con pepitas de chocolate" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Redémarrez votre Mac et réessayez" + "value" : "recettes de cookies aux pépites de chocolat" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riavvia il Mac e riprova" + "value" : "ricette di biscotti con gocce di cioccolato" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Start je Mac opnieuw op en probeer het opnieuw" + "value" : "Recepten voor chocoladekoekjes" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Uruchom ponownie komputer Mac i spróbuj ponownie" + "value" : "przepisy na ciastka z kawałkami czekolady" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Reinicia o teu Mac e tenta novamente" + "value" : "receitas de biscoitos de chocolate" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Перезагрузите компьютер и повторите попытку" + "value" : "рецепты печенья с шоколадной крошкой" } } } }, - "database.factory.failed.message" : { - "comment" : "Alert title when we fail to init database", + "contextual.onboarding.try-search.surprise-me-international" : { + "comment" : "Browser Search query for dinner recipes", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Beim Initialisieren der Datenbank ist ein Fehler aufgetreten" + "value" : "Dinner-Rezepte" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "There was an error initializing the database" + "value" : "dinner recipes" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Se ha producido un error al inicializar la base de datos" + "value" : "recetas para la cena" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Une erreur est survenue lors de l'initialisation de la base de données" + "value" : "recettes pour le dîner" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Si è verificato un errore durante l'inizializzazione del database" + "value" : "ricette per la cena" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Er is een fout opgetreden bij het initialiseren van de database" + "value" : "recepten voor het avondeten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wystąpił błąd podczas inicjowania bazy danych" + "value" : "przepisy na obiad" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ocorreu um erro ao inicializar a base de dados" + "value" : "receitas de jantar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "При запуске базы данных произошла ошибка" + "value" : "рецепты на ужин" } } } }, - "default.browser.prompt.button" : { - "comment" : "represents a prompt message asking the user to make DuckDuckGo their default browser.", + "contextual.onboarding.try-search.surprise-me-title" : { + "comment" : "Title for a button that triggers an unknown search query for the user.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Standard festlegen …" + "value" : "Überrasche mich!" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Set Default…" + "value" : "Surprise me!" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Establecer como predeterminado..." + "value" : "¡Sorpréndeme!" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Définir par défaut…" + "value" : "Surprenez-moi !" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Imposta come predefinito…" + "value" : "Sorprendimi!" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Hierdoor wordt Automatisch invullen alleen uitgeschakeld voor Duuck Addresses in deze browser. \n\n " + "value" : "Verras me!" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustaw jako domyślną…" + "value" : "Zaskocz mnie!" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Predefinir…" + "value" : "Surpreende-me!" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Назначить браузером по умолчанию..." + "value" : "Удиви меня!" } } } }, - "default.browser.prompt.message" : { - "comment" : "represents a prompt message asking the user to make DuckDuckGo their default browser.", + "copy" : { + "comment" : "Copy button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Stelle DuckDuckGo als Standard-Browser ein" + "value" : "Kopieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Make DuckDuckGo your default browser" + "value" : "Copy" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Establece DuckDuckGo como tu navegador predeterminado" + "value" : "Copiar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Faites de DuckDuckGo votre navigateur par défaut" + "value" : "Copier" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Imposta DuckDuckGo come browser predefinito" + "value" : "Copia" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo instellen als standaardbrowser" + "value" : "Kopiëren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustaw DuckDuckGo jako domyślną przeglądarkę." + "value" : "Kopiuj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Faz do DuckDuckGo o teu navegador predefinido" + "value" : "Copiar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выберите DuckGo браузером по умолчанию" + "value" : "Копировать" } } } }, - "delete-bookmark" : { - "comment" : "Delete Bookmark button", + "copy-selection" : { + "comment" : "Copy selection menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen löschen" + "value" : "Kopieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Delete Bookmark" + "value" : "Copy" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar marcador" + "value" : "Copiar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Supprimer le signet" + "value" : "Copier" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Elimina segnalibro" + "value" : "Copia" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bladwijzer verwijderen" + "value" : "Kopiëren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usuń zakładkę" + "value" : "Kopiuj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar marcador" + "value" : "Copiar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удалить закладку" + "value" : "Копировать" } } } }, - "details" : { - "comment" : "details button", + "copy.email.address" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Details" + "value" : "E-Mail-Adresse kopieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Details" + "value" : "Copy Email Address" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Detalles" + "value" : "Copiar dirección de correo electrónico" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Détails" + "value" : "Copier l'adresse e-mail" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Dettagli" + "value" : "Copia indirizzo e-mail" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Details" + "value" : "E-mailadres kopiëren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Szczegóły" + "value" : "Kopiuj adres e-mail" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Detalhes" + "value" : "Copiar endereço de e-mail" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Подробнее" + "value" : "Копировать адрес эл. почты" } } } }, - "Developer" : { - "comment" : "Main Menu ", + "copy.email.addresses" : { + "comment" : "Context menu item", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Entwickler" + "value" : "E-Mail-Adressen kopieren" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Copy Email Addresses" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Desarrollador" + "value" : "Copiar direcciones de correo electrónico" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Développeur" + "value" : "Copier les adresses e-mail" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Sviluppatore" + "value" : "Copia indirizzi e-mail" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ontwikkelaar" + "value" : "E-mailadressen kopiëren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Programista" + "value" : "Skopiuj adresy e-mail" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Programador" + "value" : "Copiar endereços de e-mail" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Разработка" + "value" : "Копировать адреса эл. почты" } } } }, - "disable" : { - "comment" : "Email protection Disable button text", + "copy.image.address" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Deaktivieren" + "value" : "Bildadresse kopieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Disable" + "value" : "Copy Image Address" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Deshabilitar" + "value" : "Copiar dirección de la imagen" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Désactiver" + "value" : "Copier l'adresse de l'image" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Disabilita" + "value" : "Copia indirizzo immagine" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Uitschakelen" + "value" : "Adres afbeelding kopiëren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyłącz" + "value" : "Skopiuj adres obrazu" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Desabilitar (fora)" + "value" : "Copiar endereço de imagem" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выключить" + "value" : "Копировать адрес изображения" } } } }, - "disable.auto.clear.to.enable.session.restore" : { - "comment" : "Information label in Settings. It tells user that to enable session restoration setting they have to disable burn on quit. Auto-Clear should match the string with 'auto.clear' key", - "extractionState" : "extracted_with_value", + "Country" : { + "comment" : "Title of the section of the Identities manager where the user can add/modify a country (US,UK, Italy etc...)", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Deaktiviere das automatische Löschen beim Beenden, um die Sitzungswiederherstellung einzuschalten." - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Disable auto-clear on quit to turn on session restore." + "value" : "Land" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Desactiva el borrado automático al salir para activar Restaurar sesión." + "value" : "País" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Désactivez l'effacement automatique à la fermeture pour activer la restauration de session." + "value" : "Pays" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Disabilita la cancellazione automatica all'uscita per attivare il ripristino della sessione." + "value" : "Paese" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Schakel automatisch wissen uit wanneer u stopt om het herstellen van de sessie in te schakelen." + "value" : "Land" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyłącz automatyczne czyszczenie przy wychodzeniu, aby włączyć przywracanie sesji." + "value" : "Kraj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Desativa a limpeza automática ao sair para ativar a restauração da sessão." + "value" : "País" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Чтобы активировать восстановление сеанса, отключите автоочистку данных при выходе." + "value" : "Страна" } } } }, - "disable.email.protection.mesage" : { - "comment" : "Message for alert shown when user disables email protection", + "crash-report.description" : { + "comment" : "Description of the dialog where the user can send a crash report", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Damit wird das automatische Ausfüllen von Duck Addresses nur in diesem Browser deaktiviert. \n\n Du kannst Duck Addresses immer noch manuell eingeben und weiterhin weitergeleitete E-Mails erhalten." + "value" : "Klicke auf „An DuckDuckGo senden“, um den Bericht an DuckDuckGo zu senden. Crash-Berichte helfen DuckDuckGo, Probleme zu diagnostizieren und unsere Produkte zu verbessern. Mit diesem Bericht werden keine persönlichen Daten übermittelt." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "This will only disable Autofill for Duck Addresses in this browser. \n\n You can still manually enter Duck Addresses and continue to receive forwarded email." + "value" : "Click “Send to DuckDuckGo“ to submit report to DuckDuckGo. Crash reports help DuckDuckGo diagnose issues and improve our products. No personal information is sent with this report." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Esto solo desactivará Autocompletar para Duck Addresses en este navegador. \n\n Aún puedes introducir manualmente las Duck Addresses y continuar recibiendo correos electrónicos reenviados." + "value" : "Haz clic en \"Enviar a DuckDuckGo\" para enviar el informe a DuckDuckGo. Los informes de fallos ayudan a DuckDuckGo a diagnosticar problemas y a mejorar nuestros productos. No se envía información personal con este informe." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Cela désactivera uniquement la saisie automatique des Duck Addresses dans ce navigateur. \n\n Vous pourrez toujours saisir manuellement les Duck Addresses et continuer à recevoir des e-mails transférés." + "value" : "Cliquez sur « Envoyer à DuckDuckGo » pour envoyer le rapport à DuckDuckGo. Les rapports de plantage aident DuckDuckGo à diagnostiquer les problèmes et à améliorer ses produits. Aucune information personnelle n'est envoyée avec ce rapport." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Questo disabiliterà solo la compilazione automatica per i Duck Address in questo browser. \n\n Puoi comunque inserire manualmente i Duck Address e continuare a ricevere le e-mail inoltrate." + "value" : "Fai clic su \"Invia a DuckDuckGo\" per inviare un report a DuckDuckGo. Le segnalazioni sugli arresti anomali aiutano DuckDuckGo a diagnosticare i problemi e a migliorare i nostri prodotti. Con questo report non vengono inviate informazioni personali." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Hierdoor wordt Automatisch invullen alleen uitgeschakeld voor Duck Addresses in deze browser. \n\n Je kunt nog steeds handmatig Duck Addresses invoeren en doorgestuurde e-mails blijven ontvangen." + "value" : "Klik op 'Verzenden naar DuckDuckGo' om een rapport in te dienen bij DuckDuckGo. Crashrapporten helpen DuckDuckGo om problemen vast te stellen en onze producten te verbeteren. Er worden geen persoonsgegevens verzonden met dit rapport." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "To spowoduje wyłączenie autouzupełniania jedynie w odniesieniu do adresów Duck Address w tej przeglądarce. \n\n Zachowasz możliwość ręcznego wprowadzania adresów Duck Address i otrzymywania przekazanych wiadomości e-mail." + "value" : "Kliknij „Wyślij do DuckDuckGo”, aby wysłać raport do DuckDuckGo. Raporty o awariach pomagają zespołowi DuckDuckGo diagnozować problemy i ulepszać produkty. Z raportem nie są wysyłane żadne dane osobowe." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Esta ação desativa apenas o preenchimento automático de Duck Addresses neste navegador. \n\n Podes continuar a introduzir manualmente os Duck Addresses e a receber e-mails reencaminhados." + "value" : "Clica em \"Enviar para a DuckDuckGo\" para enviares o relatório para a DuckDuckGo. Os relatórios de falhas ajudam a DuckDuckGo a diagnosticar problemas e a melhorar os nossos produtos. Não envias qualquer informação pessoal com este relatório." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Автозаполнение будет отключено только в отношении адресов Duck Address в этом браузере. \n\n Вы по-прежнему сможете вводить их вручную и получать пересылаемую почту." + "value" : "Для подачи отчета нажмите кнопку «Отправить в DuckDuckGo». Отчеты о сбоях помогают нам диагностировать проблемы и совершенствовать продукты. Отчет не включает ваших личных данных." } } } }, - "disable.email.protection.title" : { - "comment" : "Title for alert shown when user disables email protection", + "crash-report.dont-send-button" : { + "comment" : "Button the user can press to not send the crash report", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Autovervollständigen für den E-Mail-Schutz deaktivieren?" + "value" : "Nicht senden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Disable Email Protection Autofill?" + "value" : "Don’t Send" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Desactivar autocompletar para Email Protection?" + "value" : "No enviar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Désactiver la saisie automatique Email Protection ?" + "value" : "Ne pas envoyer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Disattivare la compilazione automatica di Email Protection?" + "value" : "Non inviare" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Automatisch invullen van Email Protection uitschakelen?" + "value" : "Niet verzenden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyłączyć autouzupełnianie Email Protection?" + "value" : "Nie wysyłaj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Desativar o preenchimento automático da Email Protection?" + "value" : "Não enviar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отключить автозаполнение в рамках защиты почты?" + "value" : "Не отправлять" } } } }, - "Display progress" : { + "crash-report.send-button" : { + "comment" : "Button the user can press to send the crash report to DuckDuckGo", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fortschritt anzeigen" + "value" : "An DuckDuckGo senden" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Send to DuckDuckGo" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar progreso" + "value" : "Enviar a DuckDuckGo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher la progression" + "value" : "Envoyer à DuckDuckGo" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Visualizza i progressi" + "value" : "Invia a DuckDuckGo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Voortgang weergeven" + "value" : "Verzenden naar DuckDuckGo" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyświetl postęp" + "value" : "Wyślij do DuckDuckGo" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar progresso" + "value" : "Enviar para a DuckDuckGo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показывать степень выполнения" + "value" : "Отправить в DuckDuckGo" } } } }, - "domain-is-fireproof" : { - "comment" : "Domain fireproof status", + "crash-report.textfield.title" : { + "comment" : "Title of the text field where the problems that caused the crashed are detailed", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%@ ist jetzt feuerfest" + "value" : "Problemdetails" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%@ is now Fireproof" + "value" : "Problem Details" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%@ ya está a prueba de fuego" + "value" : "Detalles del problema" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%@ est maintenant en mode coupe-feu" + "value" : "Détails du problème" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%@ è ora a prova di fuoco" + "value" : "Dettagli del problema" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%@ is nu brandveilig" + "value" : "Details van het probleem" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%@ jest teraz zabezpieczona" + "value" : "Szczegóły problemu" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%@ já tem barreira de segurança" + "value" : "Detalhes do problema" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%@ теперь огнеупорный" + "value" : "Информация о проблеме" } } } }, - "done" : { - "comment" : "Done button", + "crash-report.title" : { + "comment" : "Title of the dialog where the user can send a crash report", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fertig" + "value" : "DuckDuckGo Privacy Browser wurde unerwartet beendet." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Done" + "value" : "DuckDuckGo Privacy Browser quit unexpectedly." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Hecho" + "value" : "DuckDuckGo Privacy Browser se ha cerrado inesperadamente." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Terminé" + "value" : "DuckDuckGo Privacy Browser s'est arrêté de manière inattendue." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Fatto" + "value" : "DuckDuckGo Privacy Browser si è chiuso inaspettatamente." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Klaar" + "value" : "DuckDuckGo Privacy Browser is onverwacht gesloten." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Gotowe" + "value" : "Przeglądarka DuckDuckGo Privacy Browser nieoczekiwanie przestała działać." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Feito" + "value" : "O DuckDuckGo Privacy Browser encerrou de forma inesperada." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Готово" + "value" : "Браузер DuckDuckGo Privacy Browser неожиданно закрылся." } } } }, - "dont.quit" : { - "comment" : "Don’t Quit button", + "dashboard.permission.allow" : { + "comment" : "Privacy Dashboard: Website can always access input media device", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nicht aufgeben" + "value" : "Immer erlauben" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Don’t Quit" + "value" : "Always allow" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No salir" + "value" : "Permitir siempre" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ne pas abandonner" + "value" : "Toujours autoriser" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Non uscire" + "value" : "Consenti sempre" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Niet verlaten" + "value" : "Altijd toestaan" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie wychodź" + "value" : "Zawsze zezwalaj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Não sair" + "value" : "Permitir sempre" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не закрывать" + "value" : "Разрешать всегда" } } } }, - "dont.save" : { - "comment" : "Don't Save button", + "dashboard.permission.allow.on" : { + "comment" : "Permission Popover 'Always allow on' (for domainName) checkbox", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nicht speichern" + "value" : "Immer einschalten erlauben" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Don't Save" + "value" : "Always allow on" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No guardar" + "value" : "Permite siempre activo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ne pas enregistrer" + "value" : "Toujours autoriser sur" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Non salvare" + "value" : "Consenti sempre per" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Niet opslaan" + "value" : "Altijd toestaan op" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie zapisuj" + "value" : "Zawsze zezwalaj w" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Não guardar" + "value" : "Permitir sempre em" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не сохранять" + "value" : "Всегда разрешать на" } } } }, - "dont.update" : { - "comment" : "Don't Update button", + "dashboard.permission.ask" : { + "comment" : "Privacy Dashboard: Website should always Ask for permission for input media device access", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nicht aktualisieren" + "value" : "Jedes Mal fragen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Don't Update" + "value" : "Ask every time" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No actualizar" + "value" : "Preguntar cada vez" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ne pas mettre à jour" + "value" : "Toujours demander" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Non aggiornare" + "value" : "Chiedi ogni volta" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Niet bijwerken" + "value" : "Elke keer vragen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie aktualizuj" + "value" : "Pytaj za każdym razem" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Não atualizar" + "value" : "Pergunte todas as vezes" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не обновлять" + "value" : "Спрашивать каждый раз" } } } }, - "download.finishing" : { - "comment" : "Download being finished information text", + "dashboard.permission.deny" : { + "comment" : "Privacy Dashboard: Website can never access input media device", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Download wird abgeschlossen …" + "value" : "Immer ablehnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Finishing download…" + "value" : "Always deny" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Terminando la descarga..." + "value" : "Rechazar siempre" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fin du téléchargement…" + "value" : "Toujours refuser" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Fine del download…" + "value" : "Rifiuta sempre" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Downloaden voltooien ..." + "value" : "Altijd weigeren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Kończenie pobierania…" + "value" : "Zawsze odmawiaj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "A concluir a transferência…" + "value" : "Negar sempre" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Завершение загрузки…" + "value" : "Не разрешать" } } } }, - "download.linked.file.at" : { - "comment" : "Context menu item", + "dashboard.popups.ask" : { + "comment" : "Make PopUp Windows always asked from user for current domain", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verlinkte Datei herunterladen als …" + "value" : "Benachrichtigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Download Linked File As…" + "value" : "Notify" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Descargar archivo vinculado como..." + "value" : "Notificar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Télécharger le fichier lié au format…" + "value" : "Notifier" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Scarica il file collegato come…" + "value" : "Invia una notifica" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Gekoppeld bestand downloaden als ..." + "value" : "Informeren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pobierz połączony plik jako…" + "value" : "Powiadom" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Transferir ficheiro associado como…" + "value" : "Notificar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Загрузить файл по ссылке как…" + "value" : "Спрашивать разрешение" } } } }, - "download.starting" : { - "comment" : "Download being initiated information text", - "extractionState" : "extracted_with_value", + "Data Detectors" : { + "comment" : "Main Menu Edit-Substitutions item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Download starten …" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Starting download…" + "value" : "Datendetektoren" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Iniciando la descarga..." + "value" : "Detectores de datos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Début du téléchargement…" + "value" : "Détecteurs de données" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Avvio download…" + "value" : "Rilevatori di dati" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Beginnen met downloaden ..." + "value" : "Datadetectoren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Rozpoczynanie pobierania…" + "value" : "Detektory danych" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "A iniciar a transferência…" + "value" : "Detetores de dados" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Начинается загрузка..." + "value" : "Детекторы данных" } } } }, - "downloads.active.alert.message.and.others" : { - "comment" : "Alert text format element for “, and other files”", + "database.factory.failed.information" : { + "comment" : "Info to restart macOS after database init failure", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : ", und andere Dateien" + "value" : "Starte deinen Mac neu und versuche es erneut" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : ", and other files" + "value" : "Restart your Mac and try again" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "y otros archivos" + "value" : "Reinicia tu Mac e inténtalo de nuevo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "et d'autres fichiers" + "value" : "Redémarrez votre Mac et réessayez" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : ", e altri file" + "value" : "Riavvia il Mac e riprova" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : ", en andere bestanden" + "value" : "Start je Mac opnieuw op en probeer het opnieuw" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : " i inne pliki" + "value" : "Uruchom ponownie komputer Mac i spróbuj ponownie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : ", e outros ficheiros" + "value" : "Reinicia o teu Mac e tenta novamente" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "и другие файлы" + "value" : "Перезагрузите компьютер и повторите попытку" } } } }, - "downloads.active.alert.message.format" : { - "comment" : "Alert text format when trying to quit application while file “filename”[, and others] are being downloaded", + "database.factory.failed.message" : { + "comment" : "Alert title when we fail to init database", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Möchtest du wirklich aufhören? Der DuckDuckGo Privacy Browser lädt gerade „%1$@“%2$@ herunter. Wenn du ihn jetzt verlässt, wird DuckDuckGo Privacy Browser den Download nicht beenden." + "value" : "Beim Initialisieren der Datenbank ist ein Fehler aufgetreten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Are you sure you want to quit? DuckDuckGo Privacy Browser is currently downloading “%1$@”%2$@. If you quit now DuckDuckGo Privacy Browser won’t finish downloading this file." + "value" : "There was an error initializing the database" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Seguro que quieres salir? DuckDuckGo Privacy Browser está descargando \"%1$@\"%2$@. Si sales ahora, DuckDuckGo Privacy Browser no terminará de descargar este archivo." + "value" : "Se ha producido un error al inicializar la base de datos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Voulez-vous vraiment abandonner ? DuckDuckGo Privacy Browser télécharge actuellement « %1$@”%2$@ ». Si vous abandonnez maintenant, le navigateur ne terminera pas le téléchargement de ce fichier." + "value" : "Une erreur est survenue lors de l'initialisation de la base de données" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Uscire? DuckDuckGo Privacy Browser sta attualmente scaricando \"%1$@\"%2$@. Se esci ora, DuckDuckGo Privacy Browser non completerà il download di questo file." + "value" : "Si è verificato un errore durante l'inizializzazione del database" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Weet je zeker dat je wilt afsluiten? DuckDuckGo Privacy Browser downloadt momenteel \"%1$@\"%2$@. Als je nu afsluit, zal DuckDuckGo Privacy Browser dit bestand niet verder downloaden." + "value" : "Er is een fout opgetreden bij het initialiseren van de database" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Czy na pewno chcesz wyjść? DuckDuckGo Privacy Browser obecnie pobiera „%1$@”%2$@. Jeśli teraz wyjdziesz, DuckDuckGo Privacy Browser nie zakończy pobierania tego pliku." + "value" : "Wystąpił błąd podczas inicjowania bazy danych" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Tens a certeza de que pretendes sair? O DuckDuckGo Privacy Browser está a transferir “%1$@”%2$@. Se saíres agora, o DuckDuckGo Privacy Browser não irá concluir a transferência deste ficheiro." + "value" : "Ocorreu um erro ao inicializar a base de dados" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Действительно закрыть приложение? DuckDuckGo Privacy Browser загружает «%1$@» %2$@. Если вы закроете его, прервется загрузка файла." + "value" : "При запуске базы данных произошла ошибка" } } } }, - "downloads.active.alert.title" : { - "comment" : "Alert title when trying to quit application while files are being downloaded", + "default.browser.prompt.button" : { + "comment" : "represents a prompt message asking the user to make DuckDuckGo their default browser.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ein Download wird ausgeführt." + "value" : "Standard festlegen …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "A download is in progress." + "value" : "Set Default…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Hay una descarga en curso." + "value" : "Establecer como predeterminado..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Un téléchargement est en cours." + "value" : "Définir par défaut…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "È in corso un download." + "value" : "Imposta come predefinito…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Er wordt een download uitgevoerd." + "value" : "Hierdoor wordt Automatisch invullen alleen uitgeschakeld voor Duuck Addresses in deze browser. \n\n " } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Trwa pobieranie." + "value" : "Ustaw jako domyślną…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Está em curso uma transferência." + "value" : "Predefinir…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выполняется загрузка." + "value" : "Назначить браузером по умолчанию..." } } } }, - "downloads.always-ask" : { - "comment" : "Downloads preferences checkbox", + "default.browser.prompt.message" : { + "comment" : "represents a prompt message asking the user to make DuckDuckGo their default browser.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Immer fragen, wo Dateien gespeichert werden sollen" + "value" : "Stelle DuckDuckGo als Standard-Browser ein" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Always ask where to save files" + "value" : "Make DuckDuckGo your default browser" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Preguntar siempre dónde guardar archivos" + "value" : "Establece DuckDuckGo como tu navegador predeterminado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Toujours demander où enregistrer les fichiers" + "value" : "Faites de DuckDuckGo votre navigateur par défaut" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Chiedi sempre dove salvare i file" + "value" : "Imposta DuckDuckGo come browser predefinito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Downloadlocatie altijd vragen" + "value" : "DuckDuckGo instellen als standaardbrowser" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zawsze pytaj, gdzie zapisać pliki" + "value" : "Ustaw DuckDuckGo jako domyślną przeglądarkę." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Perguntar sempre onde guardar os ficheiros" + "value" : "Faz do DuckDuckGo o teu navegador predefinido" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Всегда спрашивать, где сохранять файлы" + "value" : "Выберите DuckGo браузером по умолчанию" } } } }, - "downloads.bytes.format" : { - "comment" : "Number of bytes out of total bytes downloaded (1Mb of 2Mb)", + "delete-bookmark" : { + "comment" : "Delete Bookmark button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$@ von %2$@" + "value" : "Lesezeichen löschen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$@ of %2$@" + "value" : "Delete Bookmark" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$@ de %2$@" + "value" : "Eliminar marcador" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$@ sur %2$@" + "value" : "Supprimer le signet" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$@ of %2$@" + "value" : "Elimina segnalibro" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$@ van %2$@" + "value" : "Bladwijzer verwijderen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$@ z %2$@" + "value" : "Usuń zakładkę" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$@ de %2$@" + "value" : "Eliminar marcador" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$@ из %2$@" + "value" : "Удалить закладку" } } } }, - "downloads.change" : { - "comment" : "Change downloads directory button", + "details" : { + "comment" : "details button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ändern …" + "value" : "Details" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Change…" + "value" : "Details" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cambiar…" + "value" : "Detalles" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Modifier…" + "value" : "Détails" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cambia…" + "value" : "Dettagli" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wijzigen …" + "value" : "Details" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zmień…" + "value" : "Szczegóły" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Alterar…" + "value" : "Detalhes" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Изменить…" + "value" : "Подробнее" } } } }, - "downloads.clear-all.item" : { - "comment" : "Contextual menu item in downloads manager to clear all downloaded items from the list", - "extractionState" : "extracted_with_value", + "Developer" : { + "comment" : "Main Menu ", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alles löschen" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Clear All" + "value" : "Entwickler" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Borrar todo" + "value" : "Desarrollador" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Tout effacer" + "value" : "Développeur" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cancella tutto" + "value" : "Sviluppatore" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wis alles" + "value" : "Ontwikkelaar" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyczyść wszystko" + "value" : "Programista" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Limpar tudo" + "value" : "Programador" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Очистить всё" + "value" : "Разработка" } } } }, - "downloads.copy-link.item" : { - "comment" : "Contextual menu item in downloads manager to copy the downloaded link", + "disable" : { + "comment" : "Email protection Disable button text", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Download-Link kopieren" + "value" : "Deaktivieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Copy Download Link" + "value" : "Disable" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar enlace de descarga" + "value" : "Deshabilitar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Copier le lien de téléchargement" + "value" : "Désactiver" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Copia il link per il download" + "value" : "Disabilita" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Downloadlink kopiëren" + "value" : "Uitschakelen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Skopiuj łącze pobierania" + "value" : "Wyłącz" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar link da transferência" + "value" : "Desabilitar (fora)" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Копировать ссылку для скачивания" + "value" : "Выключить" } } } }, - "downloads.dialog.title" : { - "comment" : "Title of the dialog that manages the Downloads in the browser", + "disable.auto.clear.to.enable.session.restore" : { + "comment" : "Information label in Settings. It tells user that to enable session restoration setting they have to disable burn on quit. Auto-Clear should match the string with 'auto.clear' key", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Downloads" + "value" : "Deaktiviere das automatische Löschen beim Beenden, um die Sitzungswiederherstellung einzuschalten." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Downloads" + "value" : "Disable auto-clear on quit to turn on session restore." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Descargas" + "value" : "Desactiva el borrado automático al salir para activar Restaurar sesión." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Téléchargements" + "value" : "Désactivez l'effacement automatique à la fermeture pour activer la restauration de session." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Download" + "value" : "Disabilita la cancellazione automatica all'uscita per attivare il ripristino della sessione." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Downloads" + "value" : "Schakel automatisch wissen uit wanneer u stopt om het herstellen van de sessie in te schakelen." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pobrane" + "value" : "Wyłącz automatyczne czyszczenie przy wychodzeniu, aby włączyć przywracanie sesji." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Transferências" + "value" : "Desativa a limpeza automática ao sair para ativar a restauração da sessão." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Загрузки" + "value" : "Чтобы активировать восстановление сеанса, отключите автоочистку данных при выходе." } } } }, - "downloads.error.canceled" : { - "comment" : "Short error description when downloaded file download was canceled", + "disable.email.protection.mesage" : { + "comment" : "Message for alert shown when user disables email protection", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Abgebrochen" + "value" : "Damit wird das automatische Ausfüllen von Duck Addresses nur in diesem Browser deaktiviert. \n\n Du kannst Duck Addresses immer noch manuell eingeben und weiterhin weitergeleitete E-Mails erhalten." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Canceled" + "value" : "This will only disable Autofill for Duck Addresses in this browser. \n\n You can still manually enter Duck Addresses and continue to receive forwarded email." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cancelado" + "value" : "Esto solo desactivará Autocompletar para Duck Addresses en este navegador. \n\n Aún puedes introducir manualmente las Duck Addresses y continuar recibiendo correos electrónicos reenviados." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Annulé" + "value" : "Cela désactivera uniquement la saisie automatique des Duck Addresses dans ce navigateur. \n\n Vous pourrez toujours saisir manuellement les Duck Addresses et continuer à recevoir des e-mails transférés." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Annullato" + "value" : "Questo disabiliterà solo la compilazione automatica per i Duck Address in questo browser. \n\n Puoi comunque inserire manualmente i Duck Address e continuare a ricevere le e-mail inoltrate." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geannuleerd" + "value" : "Hierdoor wordt Automatisch invullen alleen uitgeschakeld voor Duck Addresses in deze browser. \n\n Je kunt nog steeds handmatig Duck Addresses invoeren en doorgestuurde e-mails blijven ontvangen." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Anulowano" + "value" : "To spowoduje wyłączenie autouzupełniania jedynie w odniesieniu do adresów Duck Address w tej przeglądarce. \n\n Zachowasz możliwość ręcznego wprowadzania adresów Duck Address i otrzymywania przekazanych wiadomości e-mail." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Cancelada" + "value" : "Esta ação desativa apenas o preenchimento automático de Duck Addresses neste navegador. \n\n Podes continuar a introduzir manualmente os Duck Addresses e a receber e-mails reencaminhados." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отменено" + "value" : "Автозаполнение будет отключено только в отношении адресов Duck Address в этом браузере. \n\n Вы по-прежнему сможете вводить их вручную и получать пересылаемую почту." } } } }, - "downloads.error.move.failed" : { - "comment" : "Short error description when could not move downloaded file to the Downloads folder", + "disable.email.protection.title" : { + "comment" : "Title for alert shown when user disables email protection", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Datei konnte nicht in Downloads verschoben werden" + "value" : "Autovervollständigen für den E-Mail-Schutz deaktivieren?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Could not move file to Downloads" + "value" : "Disable Email Protection Autofill?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No se ha podido mover el archivo a Descargas" + "value" : "¿Desactivar autocompletar para Email Protection?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Impossible de déplacer le fichier vers Téléchargements" + "value" : "Désactiver la saisie automatique Email Protection ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Impossibile spostare il file in Download" + "value" : "Disattivare la compilazione automatica di Email Protection?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Kan bestand niet verplaatsen naar Downloads" + "value" : "Automatisch invullen van Email Protection uitschakelen?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie można przenieść pliku do folderu Pobrane" + "value" : "Wyłączyć autouzupełnianie Email Protection?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Não foi possível mover o ficheiro para Transferências" + "value" : "Desativar o preenchimento automático da Email Protection?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не удалось перенести файл в папку «Загрузки»" + "value" : "Отключить автозаполнение в рамках защиты почты?" } } } }, - "downloads.error.other" : { - "comment" : "Short error description when Download failed", - "extractionState" : "extracted_with_value", + "Display progress" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fehler" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Error" + "value" : "Fortschritt anzeigen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Error" + "value" : "Mostrar progreso" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Erreur" + "value" : "Afficher la progression" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Errore" + "value" : "Visualizza i progressi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Fout" + "value" : "Voortgang weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Błąd" + "value" : "Wyświetl postęp" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Erro" + "value" : "Mostrar progresso" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Ошибка" + "value" : "Показывать степень выполнения" } } } }, - "downloads.error.removed" : { - "comment" : "Short error description when downloaded file removed from Downloads folder", + "domain-is-fireproof" : { + "comment" : "Domain fireproof status", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Entfernt" + "value" : "%@ ist jetzt feuerfest" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Removed" + "value" : "%@ is now Fireproof" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminado" + "value" : "%@ ya está a prueba de fuego" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Supprimé" + "value" : "%@ est maintenant en mode coupe-feu" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rimosso" + "value" : "%@ è ora a prova di fuoco" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verwijderd" + "value" : "%@ is nu brandveilig" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usunięto" + "value" : "%@ jest teraz zabezpieczona" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Removido" + "value" : "%@ já tem barreira de segurança" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удален" + "value" : "%@ теперь огнеупорный" } } } }, - "downloads.location" : { - "comment" : "Downloads directory location", + "done" : { + "comment" : "Done button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Standort" + "value" : "Fertig" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Location" + "value" : "Done" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ubicación" + "value" : "Hecho" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Localisation" + "value" : "Terminé" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Posizione" + "value" : "Fatto" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Locatie" + "value" : "Klaar" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Lokalizacja" + "value" : "Gotowe" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Localização" + "value" : "Feito" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Местоположение" + "value" : "Готово" } } } }, - "downloads.no-recent-downloads" : { - "comment" : "Label in the downloads manager that shows that there are no recently downloaded items", + "dont.quit" : { + "comment" : "Don’t Quit button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine aktuellen Downloads" + "value" : "Nicht aufgeben" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "No recent downloads" + "value" : "Don’t Quit" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No hay descargas recientes" + "value" : "No salir" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aucun téléchargement récent" + "value" : "Ne pas abandonner" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nessun download recente" + "value" : "Non uscire" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geen recente downloads" + "value" : "Niet verlaten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Brak ostatnio pobranych plików" + "value" : "Nie wychodź" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nenhuma transferência recente" + "value" : "Não sair" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Недавних загрузок нет" + "value" : "Не закрывать" } } } }, - "downloads.open-downloads-folder" : { - "comment" : "Button in the downloads manager that allows the user to open the downloads folder", + "dont.save" : { + "comment" : "Don't Save button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Download-Ordner öffnen" + "value" : "Nicht speichern" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open Downloads Folder" + "value" : "Don't Save" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir carpeta de descargas" + "value" : "No guardar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir le dossier des téléchargements" + "value" : "Ne pas enregistrer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri la cartella download" + "value" : "Non salvare" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Downloadmap openen" + "value" : "Niet opslaan" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz folder Pobrane" + "value" : "Nie zapisuj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir pasta Transferências" + "value" : "Não guardar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть папку «Загрузки»" + "value" : "Не сохранять" } } } }, - "downloads.open-website.item" : { - "comment" : "Contextual menu item in downloads manager to open the downloaded file originating website", + "dont.update" : { + "comment" : "Don't Update button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ursprüngliche Website öffnen" + "value" : "Nicht aktualisieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open Originating Website" + "value" : "Don't Update" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir sitio web de origen" + "value" : "No actualizar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir le site Web d'origine" + "value" : "Ne pas mettre à jour" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri il sito Web di origine" + "value" : "Non aggiornare" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Oorspronkelijke website openen" + "value" : "Niet bijwerken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz witrynę źródłową" + "value" : "Nie aktualizuj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir site de origem" + "value" : "Não atualizar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть сайт-источник" + "value" : "Не обновлять" } } } }, - "downloads.open.item" : { - "comment" : "Contextual menu item in downloads manager to open the downloaded file", + "download.finishing" : { + "comment" : "Download being finished information text", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Öffnen" + "value" : "Download wird abgeschlossen …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open" + "value" : "Finishing download…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir" + "value" : "Terminando la descarga..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir" + "value" : "Fin du téléchargement…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri" + "value" : "Fine del download…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Open" + "value" : "Downloaden voltooien ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz" + "value" : "Kończenie pobierania…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Aberto" + "value" : "A concluir a transferência…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть" + "value" : "Завершение загрузки…" } } } }, - "downloads.open.on.completion" : { - "comment" : "Checkbox to open a Download Manager popover when downloads are completed", + "download.linked.file.at" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Download-Bedienfeld automatisch öffnen, wenn Downloads abgeschlossen sind" + "value" : "Verlinkte Datei herunterladen als …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Automatically open the Downloads panel when downloads complete" + "value" : "Download Linked File As…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir automáticamente el panel Descargas cuando se completen las descargas" + "value" : "Descargar archivo vinculado como..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir automatiquement le volet Téléchargements une fois les téléchargements terminés" + "value" : "Télécharger le fichier lié au format…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri automaticamente il pannello Download al termine dei download" + "value" : "Scarica il file collegato come…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Deelvenster 'Downloads' automatisch openen wanneer het downloaden is voltooid" + "value" : "Gekoppeld bestand downloaden als ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Automatycznie otwieraj panel Pobieranie po zakończeniu pobierania" + "value" : "Pobierz połączony plik jako…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir automaticamente o painel Transferências quando as transferências terminarem" + "value" : "Transferir ficheiro associado como…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Автоматически открывать панель «Загрузки» по завершении скачивания" + "value" : "Загрузить файл по ссылке как…" } } } }, - "downloads.remove-from-list.item" : { - "comment" : "Contextual menu item in downloads manager to remove the given downloaded from the list of downloaded files", + "download.starting" : { + "comment" : "Download being initiated information text", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Aus Liste entfernen" + "value" : "Download starten …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Remove from List" + "value" : "Starting download…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar de la lista" + "value" : "Iniciando la descarga..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Supprimer de la liste" + "value" : "Début du téléchargement…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rimuovi dalla lista" + "value" : "Avvio download…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verwijderen uit lijst" + "value" : "Beginnen met downloaden ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usuń z listy" + "value" : "Rozpoczynanie pobierania…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Remover da lista" + "value" : "A iniciar a transferência…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удалить из списка" + "value" : "Начинается загрузка..." } } } }, - "downloads.show-in-finder.item" : { - "comment" : "Contextual menu item in downloads manager to show the downloaded file in Finder", + "downloads.active.alert.message.and.others" : { + "comment" : "Alert text format element for “, and other files”", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Im Finder anzeigen" + "value" : ", und andere Dateien" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show in Finder" + "value" : ", and other files" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar en el Finder" + "value" : "y otros archivos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher dans le Finder" + "value" : "et d'autres fichiers" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra nel Finder" + "value" : ", e altri file" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Weergeven in Finder" + "value" : ", en andere bestanden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż w Finderze" + "value" : " i inne pliki" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar no Finder" + "value" : ", e outros ficheiros" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать в Finder" + "value" : "и другие файлы" } } } }, - "downloads.speed.format" : { - "comment" : "Download speed format (1Mb/sec)", + "downloads.active.alert.message.format" : { + "comment" : "Alert text format when trying to quit application while file “filename”[, and others] are being downloaded", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%@/s" + "value" : "Möchtest du wirklich aufhören? Der DuckDuckGo Privacy Browser lädt gerade „%1$@“%2$@ herunter. Wenn du ihn jetzt verlässt, wird DuckDuckGo Privacy Browser den Download nicht beenden." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%@/s" + "value" : "Are you sure you want to quit? DuckDuckGo Privacy Browser is currently downloading “%1$@”%2$@. If you quit now DuckDuckGo Privacy Browser won’t finish downloading this file." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%@/s" + "value" : "¿Seguro que quieres salir? DuckDuckGo Privacy Browser está descargando \"%1$@\"%2$@. Si sales ahora, DuckDuckGo Privacy Browser no terminará de descargar este archivo." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%@/s" + "value" : "Voulez-vous vraiment abandonner ? DuckDuckGo Privacy Browser télécharge actuellement « %1$@”%2$@ ». Si vous abandonnez maintenant, le navigateur ne terminera pas le téléchargement de ce fichier." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%@/s" + "value" : "Uscire? DuckDuckGo Privacy Browser sta attualmente scaricando \"%1$@\"%2$@. Se esci ora, DuckDuckGo Privacy Browser non completerà il download di questo file." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%@/sec." + "value" : "Weet je zeker dat je wilt afsluiten? DuckDuckGo Privacy Browser downloadt momenteel \"%1$@\"%2$@. Als je nu afsluit, zal DuckDuckGo Privacy Browser dit bestand niet verder downloaden." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%@/s" + "value" : "Czy na pewno chcesz wyjść? DuckDuckGo Privacy Browser obecnie pobiera „%1$@”%2$@. Jeśli teraz wyjdziesz, DuckDuckGo Privacy Browser nie zakończy pobierania tego pliku." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%@/s" + "value" : "Tens a certeza de que pretendes sair? O DuckDuckGo Privacy Browser está a transferir “%1$@”%2$@. Se saíres agora, o DuckDuckGo Privacy Browser não irá concluir a transferência deste ficheiro." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%@/с" + "value" : "Действительно закрыть приложение? DuckDuckGo Privacy Browser загружает «%1$@» %2$@. Если вы закроете его, прервется загрузка файла." } } } }, - "downloads.stop.item" : { - "comment" : "Contextual menu item in downloads manager to stop the download", + "downloads.active.alert.title" : { + "comment" : "Alert title when trying to quit application while files are being downloaded", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Stopp" + "value" : "Ein Download wird ausgeführt." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Stop" + "value" : "A download is in progress." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Detener" + "value" : "Hay una descarga en curso." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Arrêter" + "value" : "Un téléchargement est en cours." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Interrompi" + "value" : "È in corso un download." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Stoppen" + "value" : "Er wordt een download uitgevoerd." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zatrzymaj" + "value" : "Trwa pobieranie." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Parar" + "value" : "Está em curso uma transferência." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Остановить" + "value" : "Выполняется загрузка." } } } }, - "downloads.tooltip.cancel" : { - "comment" : "Mouse-over tooltip for Cancel Download button", + "downloads.always-ask" : { + "comment" : "Downloads preferences checkbox", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Download abbrechen" + "value" : "Immer fragen, wo Dateien gespeichert werden sollen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Cancel Download" + "value" : "Always ask where to save files" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cancelar descarga" + "value" : "Preguntar siempre dónde guardar archivos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Annuler le téléchargement" + "value" : "Toujours demander où enregistrer les fichiers" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Annulla il download" + "value" : "Chiedi sempre dove salvare i file" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Downloaden annuleren" + "value" : "Downloadlocatie altijd vragen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Anuluj pobieranie" + "value" : "Zawsze pytaj, gdzie zapisać pliki" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Cancelar transferência" + "value" : "Perguntar sempre onde guardar os ficheiros" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отменить загрузку" + "value" : "Всегда спрашивать, где сохранять файлы" } } } }, - "downloads.tooltip.redownload" : { - "comment" : "Mouse-over tooltip for Download [deleted file] Again button", + "downloads.bytes.format" : { + "comment" : "Number of bytes out of total bytes downloaded (1Mb of 2Mb)", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Erneut herunterladen" + "value" : "%1$@ von %2$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Download Again" + "value" : "%1$@ of %2$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Volver a descargar" + "value" : "%1$@ de %2$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Télécharger à nouveau" + "value" : "%1$@ sur %2$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Scarica di nuovo" + "value" : "%1$@ of %2$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Opnieuw downloaden" + "value" : "%1$@ van %2$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pobierz ponownie" + "value" : "%1$@ z %2$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Transferir novamente" + "value" : "%1$@ de %2$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скачать еще раз" + "value" : "%1$@ из %2$@" } } } }, - "downloads.tooltip.restart" : { - "comment" : "Mouse-over tooltip for Restart Download button", + "downloads.change" : { + "comment" : "Change downloads directory button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Download neu starten" + "value" : "Ändern …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Restart Download" + "value" : "Change…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Reiniciar descarga" + "value" : "Cambiar…" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Redémarrer le téléchargement" + "value" : "Modifier…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riavvia il download" + "value" : "Cambia…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Download opnieuw starten" + "value" : "Wijzigen …" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Uruchom pobieranie ponownie" + "value" : "Zmień…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Reiniciar transferência" + "value" : "Alterar…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Перезапустить загрузку" + "value" : "Изменить…" } } } }, - "downloads.tooltip.reveal" : { - "comment" : "Mouse-over tooltip for Show in Finder button", + "downloads.clear-all.item" : { + "comment" : "Contextual menu item in downloads manager to clear all downloaded items from the list", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Im Finder anzeigen" + "value" : "Alles löschen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show in Finder" + "value" : "Clear All" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar en el Finder" + "value" : "Borrar todo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher dans le Finder" + "value" : "Tout effacer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra nel Finder" + "value" : "Cancella tutto" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Weergeven in Finder" + "value" : "Wis alles" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż w Finderze" + "value" : "Wyczyść wszystko" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar no Finder" + "value" : "Limpar tudo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать в Finder" + "value" : "Очистить всё" } } } }, - "duck-player.always-open-in-player" : { - "comment" : "Private YouTube Player option", + "downloads.copy-link.item" : { + "comment" : "Contextual menu item in downloads manager to copy the downloaded link", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "YouTube-Videos immer im Duck Player öffnen" + "value" : "Download-Link kopieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Always open YouTube videos in Duck Player" + "value" : "Copy Download Link" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir siempre los vídeos de YouTube en Duck Player" + "value" : "Copiar enlace de descarga" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Toujours ouvrir les vidéos YouTube dans Duck Player" + "value" : "Copier le lien de téléchargement" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri sempre i video di YouTube in Duck Player" + "value" : "Copia il link per il download" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "YouTube-video's altijd openen in Duck Player" + "value" : "Downloadlink kopiëren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zawsze otwieraj filmy z YouTube w odtwarzaczu Duck Player" + "value" : "Skopiuj łącze pobierania" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir sempre os vídeos do YouTube no Duck Player" + "value" : "Copiar link da transferência" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Всегда открывать видео из YouTube в Duck Player" + "value" : "Копировать ссылку для скачивания" } } } }, - "duck-player.autoplay-preference" : { - "comment" : "Autoplay preference in settings", - "extractionState" : "stale", + "downloads.dialog.title" : { + "comment" : "Title of the dialog that manages the Downloads in the browser", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "In Duck Player geöffnete Videos automatisch abspielen" + "value" : "Downloads" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Autoplay videos opened in Duck Player" + "value" : "Downloads" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Reproducir automáticamente los vídeos abiertos en Duck Player" + "value" : "Descargas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Lecture automatique des vidéos ouvertes dans Duck Player" + "value" : "Téléchargements" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riproduzione automatica dei video aperti in Duck Player" + "value" : "Download" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Video's automatisch afspelen wanneer ze worden geopend in Duck Player" + "value" : "Downloads" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Automatyczne odtwarzanie filmów otwieranych w Duck Player" + "value" : "Pobrane" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Reproduzir automaticamente vídeos abertos no Duck Player" + "value" : "Transferências" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Автоматически проигрывать видео при открытии в Duck Player" + "value" : "Загрузки" } } } }, - "duck-player.autoplay-title" : { - "comment" : "Autoplay title in settings", - "extractionState" : "stale", + "downloads.error.canceled" : { + "comment" : "Short error description when downloaded file download was canceled", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Automatisches Abspielen" + "value" : "Abgebrochen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Autoplay" + "value" : "Canceled" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Reproducción automática" + "value" : "Cancelado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Lecture automatique" + "value" : "Annulé" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riproduzione automatica" + "value" : "Annullato" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Automatisch afspelen" + "value" : "Geannuleerd" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Automatyczne odtwarzanie" + "value" : "Anulowano" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Reprodução automática" + "value" : "Cancelada" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Автовоспроизведение" + "value" : "Отменено" } } } }, - "duck-player.contingency-title" : { - "comment" : "Title for message explaining to the user that Duck Player is not available", + "downloads.error.move.failed" : { + "comment" : "Short error description when could not move downloaded file to the Downloads folder", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player nicht verfügbar" + "value" : "Datei konnte nicht in Downloads verschoben werden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Duck Player Unavailable" + "value" : "Could not move file to Downloads" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player no disponible" + "value" : "No se ha podido mover el archivo a Descargas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player n'est pas disponible" + "value" : "Impossible de déplacer le fichier vers Téléchargements" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player non disponibile" + "value" : "Impossibile spostare il file in Download" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player is niet beschikbaar" + "value" : "Kan bestand niet verplaatsen naar Downloads" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player jest niedostępny" + "value" : "Nie można przenieść pliku do folderu Pobrane" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player indisponível" + "value" : "Não foi possível mover o ficheiro para Transferências" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player недоступен" + "value" : "Не удалось перенести файл в папку «Загрузки»" } } } }, - "duck-player.explanation" : { - "comment" : "Private YouTube Player explanation in settings", + "downloads.error.other" : { + "comment" : "Short error description when Download failed", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Mit Duck Player kannst du dir ungestört und ohne personalisierte Werbung Inhalte ansehen. Er verhindert, dass das, was du dir ansiehst, deine YouTube-Empfehlungen beeinflussen." + "value" : "Fehler" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations." + "value" : "Error" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player ofrece una experiencia de visualización limpia sin anuncios personalizados e impide que la actividad de visualización influya en tus recomendaciones de YouTube." + "value" : "Error" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player offre une expérience de visionnage épurée, sans publicités personnalisées, et empêche l'activité de visionnage d'influencer vos recommandations YouTube." + "value" : "Erreur" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player offre un'esperienza di visualizzazione pulita, senza annunci personalizzati, e impedisce che l'attività di visualizzazione incida sulle raccomandazioni di YouTube." + "value" : "Errore" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player biedt puur kijkplezier zonder gepersonaliseerde advertenties en voorkomt dat de dingen die je bekijkt je YouTube-aanbevelingen beïnvloeden." + "value" : "Fout" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player zapewnia czyste środowisko oglądania bez spersonalizowanych reklam i sprawia, że aktywność związana z oglądaniem filmów nie wpływa na rekomendacje YouTube'a." + "value" : "Błąd" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O Duck Player oferece uma experiência de visualização limpa sem anúncios personalizados e evita que as atividades de visualização influenciem as recomendações do YouTube." + "value" : "Erro" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Проигрыватель Duck Player обеспечивает беспрепятственный просмотр без персонализированной рекламы и влияния просмотренных роликов на рекомендации в YouTube." + "value" : "Ошибка" } } } }, - "duck-player.newtab-preference" : { - "comment" : "New tab preference in settings", + "downloads.error.removed" : { + "comment" : "Short error description when downloaded file removed from Downloads folder", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Öffne Duck Player nach Möglichkeit in einem neuen Tab" + "value" : "Entfernt" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open Duck Player in a new tab whenever possible" + "value" : "Removed" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abre Duck Player en una pestaña nueva siempre que sea posible" + "value" : "Eliminado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvre Duck Player dans un nouvel onglet chaque fois que possible" + "value" : "Supprimé" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri sempre Duck Player in una nuova scheda quando possibile" + "value" : "Rimosso" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Open Duck Player waar mogelijk in een nieuw tabblad" + "value" : "Verwijderd" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwieraj Duck Player w nowej karcie, gdy tylko jest to możliwe" + "value" : "Usunięto" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir o Duck Player num novo separador sempre que possível" + "value" : "Removido" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "По возможности открывать Duck Player в новой вкладке" + "value" : "Удален" } } } }, - "duck-player.newtab-title" : { - "comment" : "New Tab title in settings", - "extractionState" : "stale", + "downloads.location" : { + "comment" : "Downloads directory location", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neuer Tab" + "value" : "Standort" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "New Tab" + "value" : "Location" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nueva pestaña" + "value" : "Ubicación" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvel onglet" + "value" : "Localisation" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nuova scheda" + "value" : "Posizione" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuw tabblad" + "value" : "Locatie" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nowa karta" + "value" : "Lokalizacja" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Novo separador" + "value" : "Localização" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Новая вкладка" + "value" : "Местоположение" } } } }, - "duck-player.newtab.info-preference" : { - "comment" : "New tab preference extra info in settings", + "downloads.no-recent-downloads" : { + "comment" : "Label in the downloads manager that shows that there are no recently downloaded items", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Beim Browsen auf YouTube im Internet" + "value" : "Keine aktuellen Downloads" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "When browsing YouTube on the web" + "value" : "No recent downloads" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Al navegar en YouTube en la web" + "value" : "No hay descargas recientes" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Lorsque vous naviguez sur YouTube sur le Web" + "value" : "Aucun téléchargement récent" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Durante la navigazione di YouTube sul Web" + "value" : "Nessun download recente" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wanneer je YouTube op het web bekijkt" + "value" : "Geen recente downloads" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Podczas przeglądania YouTube w Internecie" + "value" : "Brak ostatnio pobranych plików" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ao navegar no YouTube na Internet" + "value" : "Nenhuma transferência recente" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "При просмотре YouTube в интернете" + "value" : "Недавних загрузок нет" } } } }, - "duck-player.off" : { - "comment" : "Private YouTube Player option", + "downloads.open-downloads-folder" : { + "comment" : "Button in the downloads manager that allows the user to open the downloads folder", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Niemals den Duck Player verwenden" + "value" : "Download-Ordner öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Never use Duck Player" + "value" : "Open Downloads Folder" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nunca utilizar Duck Player" + "value" : "Abrir carpeta de descargas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ne jamais utiliser Duck Player" + "value" : "Ouvrir le dossier des téléchargements" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Non utilizzare mai Duck Player" + "value" : "Apri la cartella download" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player nooit gebruiken" + "value" : "Downloadmap openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nigdy nie używaj odtwarzacza Duck Player" + "value" : "Otwórz folder Pobrane" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nunca utilizar o Duck Player" + "value" : "Abrir pasta Transferências" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не использовать Duck Player" + "value" : "Открыть папку «Загрузки»" } } } }, - "duck-player.show-buttons" : { - "comment" : "Private YouTube Player option", + "downloads.open-website.item" : { + "comment" : "Contextual menu item in downloads manager to open the downloaded file originating website", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Option zur Verwendung des Duck Players über YouTube-Vorschauen beim Hovern anzeigen" + "value" : "Ursprüngliche Website öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show option to use Duck Player over YouTube previews on hover" + "value" : "Open Originating Website" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar opción de usar Duck Player sobre las vistas previas de YouTube al pasar el cursor" + "value" : "Abrir sitio web de origen" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher l'option permettant d'utiliser Duck Player sur les aperçus YouTube au survol" + "value" : "Ouvrir le site Web d'origine" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra l'opzione per usare Duck Player nelle anteprime YouTube al passaggio del mouse" + "value" : "Apri il sito Web di origine" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Optie tonen om Duck Player te gebruiken in plaats van YouTube-voorbeelden bij het aanwijzen met de muis" + "value" : "Oorspronkelijke website openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż opcję użycia odtwarzacza Duck Player nad podglądami na YouTube po najechaniu kursorem" + "value" : "Otwórz witrynę źródłową" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar opção de utilizar o Duck Player nas pré-visualizações do YouTube ao passar o rato" + "value" : "Abrir site de origem" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показывать опцию просмотра в Duck Player при наведении курсора на значок ролика на YouTube" + "value" : "Открыть сайт-источник" } } } }, - "duck-player.title" : { - "comment" : "Private YouTube Player settings title", + "downloads.open.item" : { + "comment" : "Contextual menu item in downloads manager to open the downloaded file", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player" + "value" : "Öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Duck Player" + "value" : "Open" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player" + "value" : "Abrir" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player" + "value" : "Ouvrir" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player" + "value" : "Apri" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player" + "value" : "Open" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player" + "value" : "Otwórz" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player" + "value" : "Aberto" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Проигрыватель Duck Player" + "value" : "Открыть" } } } }, - "duck-player.video-autoplay-preference" : { - "comment" : "Autoplay preference in settings", + "downloads.open.on.completion" : { + "comment" : "Checkbox to open a Download Manager popover when downloads are completed", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Videos automatisch abspielen, wenn sie im Duck Player geöffnet werden" + "value" : "Download-Bedienfeld automatisch öffnen, wenn Downloads abgeschlossen sind" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Autoplay videos when opened in Duck Player" + "value" : "Automatically open the Downloads panel when downloads complete" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Reproducir automáticamente los vídeos cuando se abran en Duck Player" + "value" : "Abrir automáticamente el panel Descargas cuando se completen las descargas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Lecture automatique des vidéos ouvertes dans Duck Player" + "value" : "Ouvrir automatiquement le volet Téléchargements une fois les téléchargements terminés" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riproduzione automatica dei video quando li apri in Duck Player" + "value" : "Apri automaticamente il pannello Download al termine dei download" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Video's automatisch afspelen wanneer ze worden geopend in Duck Player" + "value" : "Deelvenster 'Downloads' automatisch openen wanneer het downloaden is voltooid" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Automatyczne odtwarzanie filmów po otwarciu w Duck Player" + "value" : "Automatycznie otwieraj panel Pobieranie po zakończeniu pobierania" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Reproduzir vídeos automaticamente quando abertos no Duck Player" + "value" : "Abrir automaticamente o painel Transferências quando as transferências terminarem" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Автоматически проигрывать видео при открытии в Duck Player" + "value" : "Автоматически открывать панель «Загрузки» по завершении скачивания" } } } }, - "duck-player.video-contingency-cta" : { - "comment" : "Button for the message explaining to the user that Duck Player is not available so the user can learn more", + "downloads.remove-from-list.item" : { + "comment" : "Contextual menu item in downloads manager to remove the given downloaded from the list of downloaded files", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Mehr erfahren" + "value" : "Aus Liste entfernen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Learn More" + "value" : "Remove from List" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Más información" + "value" : "Eliminar de la lista" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "En savoir plus" + "value" : "Supprimer de la liste" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ulteriori informazioni" + "value" : "Rimuovi dalla lista" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Meer informatie" + "value" : "Verwijderen uit lijst" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dowiedz się więcej" + "value" : "Usuń z listy" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Saiba mais" + "value" : "Remover da lista" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Узнать больше" + "value" : "Удалить из списка" } } } }, - "duck-player.video-contingency-message" : { - "comment" : "Message explaining to the user that Duck Player is not available", + "downloads.show-in-finder.item" : { + "comment" : "Contextual menu item in downloads manager to show the downloaded file in Finder", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Die Funktionalität des Duck Players wird durch die jüngsten Änderungen bei YouTube beeinträchtigt. Vielen Dank für dein Verständnis, während wir daran arbeiten, diese Probleme zu beheben." + "value" : "Im Finder anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Duck Player's functionality has been affected by recent changes to YouTube. We’re working to fix these issues and appreciate your understanding." + "value" : "Show in Finder" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "La funcionalidad de Duck Player se ha visto afectada por los cambios recientes en YouTube. Estamos trabajando para solucionar estos problemas y agradecemos tu comprensión." + "value" : "Mostrar en el Finder" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Les récents changements apportés à YouTube ont affecté la fonctionnalité de Duck Player. Nous nous efforçons de résoudre ces problèmes et vous remercions de votre compréhension." + "value" : "Afficher dans le Finder" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "La funzionalità di Duck Player è stata influenzata dalle recenti modifiche a YouTube. Stiamo lavorando per risolvere questi problemi. Ti ringraziamo per la comprensione." + "value" : "Mostra nel Finder" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Recente wijzigingen in YouTube hebben invloed op de functionaliteit van Duck Player. We werken aan een oplossing voor deze problemen. Bedankt voor je begrip." + "value" : "Weergeven in Finder" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ostatnie zmiany w serwisie YouTube miały wpływ na działanie Duck Player. Pracujemy nad rozwiązaniem tych problemów i dziękujemy za zrozumienie." + "value" : "Pokaż w Finderze" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "A funcionalidade do Duck Player foi afetada por alterações recentes ao YouTube. Estamos a trabalhar para resolver estes problemas e agradecemos a tua compreensão." + "value" : "Mostrar no Finder" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Недавние изменения YouTube приводят к некорректной работе проигрывателя Duck Player. Мы занимаемся решением этой проблемы и благодарим вас за понимание." + "value" : "Показать в Finder" } } } }, - "duck-player.video-preferences-title" : { - "comment" : "Video Preferences title in settings", + "downloads.speed.format" : { + "comment" : "Download speed format (1Mb/sec)", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Video-Einstellungen" + "value" : "%@/s" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Video Preferences" + "value" : "%@/s" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Preferencias de vídeo" + "value" : "%@/s" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Préférences vidéo" + "value" : "%@/s" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Preferenze video" + "value" : "%@/s" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Videovoorkeuren" + "value" : "%@/sec." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Preferencje filmów" + "value" : "%@/s" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Preferências de vídeo" + "value" : "%@/s" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Настройки видео" + "value" : "%@/с" } } } }, - "DuckDuckGo browser version" : { - "comment" : "Data import failure Report dialog description of a report field providing current DuckDuckGo Browser version", + "downloads.stop.item" : { + "comment" : "Contextual menu item in downloads manager to stop the download", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo Browserversion" + "value" : "Stopp" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Stop" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Versión del navegador DuckDuckGo" + "value" : "Detener" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Version du navigateur DuckDuckGo" + "value" : "Arrêter" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Versione del browser DuckDuckGo" + "value" : "Interrompi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo-browserversie" + "value" : "Stoppen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wersja przeglądarki DuckDuckGo" + "value" : "Zatrzymaj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Versão do navegador DuckDuckGo" + "value" : "Parar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Версия браузера DuckDuckGo" + "value" : "Остановить" } } } }, - "DuckDuckGo Help" : { - "comment" : "Main Menu Help item", + "downloads.tooltip.cancel" : { + "comment" : "Mouse-over tooltip for Cancel Download button", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo Hilfe" + "value" : "Download abbrechen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Cancel Download" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ayuda de DuckDuckGo" + "value" : "Cancelar descarga" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aide DuckDuckGo" + "value" : "Annuler le téléchargement" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Guida DuckDuckGo" + "value" : "Annulla il download" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo Help" + "value" : "Downloaden annuleren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo — pomoc" + "value" : "Anuluj pobieranie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ajuda do DuckDuckGo" + "value" : "Cancelar transferência" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Справка по DuckDuckGo" + "value" : "Отменить загрузку" } } } }, - "DuckDuckGo needs your permission to read the %@ bookmarks file. Select the %@ folder to import bookmarks." : { - "comment" : "Data import warning that DuckDuckGo browser requires file reading permissions for another browser name (%1$@), and instruction to select its (same browser name - %2$@) bookmarks folder.", + "downloads.tooltip.redownload" : { + "comment" : "Mouse-over tooltip for Download [deleted file] Again button", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo benötigt deine Berechtigung zum Lesen der %1$@-Lesezeichen-Datei. Wähle den Ordner %2$@ aus, um Lesezeichen zu importieren." + "value" : "Erneut herunterladen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo needs your permission to read the %1$@ bookmarks file. Select the %2$@ folder to import bookmarks." + "value" : "Download Again" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo necesita tu permiso para leer el archivo de marcadores %1$@. Selecciona la carpeta %2$@ para importar marcadores." + "value" : "Volver a descargar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo a besoin de votre permission pour lire le fichier de signets %1$@. Sélectionnez le dossier %2$@ pour importer les signets." + "value" : "Télécharger à nouveau" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo necessita della tua autorizzazione per leggere il file di segnalibri %1$@. Seleziona la cartella %2$@ per importare i segnalibri." + "value" : "Scarica di nuovo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo heeft je toestemming nodig om het bladwijzersbestand van %1$@ te lezen. Selecteer de map %2$@ om bladwijzers te importeren." + "value" : "Opnieuw downloaden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo potrzebuje pozwolenia na odczytanie pliku zakładek przeglądarki %1$@. Wybierz folder %2$@, aby zaimportować zakładki." + "value" : "Pobierz ponownie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O DuckDuckGo precisa da tua permissão para ler o ficheiro de marcadores %1$@. Seleciona a pasta %2$@ para importares os marcadores." + "value" : "Transferir novamente" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo запрашивает разрешение на чтение файла закладок %1$@. Чтобы импортировать закладки, выберите папку %2$@." + "value" : "Скачать еще раз" } } } }, - "Duplicate Bookmarks Skipped:" : { - "comment" : "Data import summary format of how many duplicate bookmarks (%lld) were skipped during import.", - "extractionState" : "stale", + "downloads.tooltip.restart" : { + "comment" : "Mouse-over tooltip for Restart Download button", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Doppelte Lesezeichen übersprungen:" + "value" : "Download neu starten" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Restart Download" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Marcadores duplicados omitidos:" + "value" : "Reiniciar descarga" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Signets en double ignorés :" + "value" : "Redémarrer le téléchargement" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Segnalibri duplicati ignorati:" + "value" : "Riavvia il download" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Overgeslagen dubbele bladwijzers:" + "value" : "Download opnieuw starten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pominięte zduplikowane zakładki:" + "value" : "Uruchom pobieranie ponownie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Marcadores duplicados ignorados:" + "value" : "Reiniciar transferência" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пропущены дубликаты:" + "value" : "Перезапустить загрузку" } } } }, - "duplicate.tab" : { - "comment" : "Menu item. Duplicate as a verb", + "downloads.tooltip.reveal" : { + "comment" : "Mouse-over tooltip for Show in Finder button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Tab duplizieren" + "value" : "Im Finder anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Duplicate Tab" + "value" : "Show in Finder" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Duplicar pestaña" + "value" : "Mostrar en el Finder" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Dupliquer l'onglet" + "value" : "Afficher dans le Finder" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Duplica scheda" + "value" : "Mostra nel Finder" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Tabblad kopiëren" + "value" : "Weergeven in Finder" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zduplikuj kartę" + "value" : "Pokaż w Finderze" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Separador duplicado" + "value" : "Mostrar no Finder" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Дублировать вкладку" + "value" : "Показать в Finder" } } } }, - "Duplicates Skipped:" : { + "duck-player.always-open-in-player" : { + "comment" : "Private YouTube Player option", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Duplikate übersprungen:" + "value" : "YouTube-Videos immer im Duck Player öffnen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Always open YouTube videos in Duck Player" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Duplicados omitidos:" + "value" : "Abrir siempre los vídeos de YouTube en Duck Player" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Doublons ignorés :" + "value" : "Toujours ouvrir les vidéos YouTube dans Duck Player" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Duplicati ignorati:" + "value" : "Apri sempre i video di YouTube in Duck Player" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Overgeslagen duplicaten:" + "value" : "YouTube-video's altijd openen in Duck Player" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pominięte duplikaty:" + "value" : "Zawsze otwieraj filmy z YouTube w odtwarzaczu Duck Player" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Duplicados ignorados:" + "value" : "Abrir sempre os vídeos do YouTube no Duck Player" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пропущенные дубликаты:" + "value" : "Всегда открывать видео из YouTube в Duck Player" } } } }, - "Duplicates Skipped: " : { + "duck-player.autoplay-preference" : { + "comment" : "Autoplay preference in settings", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Duplikate übersprungen: " + "value" : "In Duck Player geöffnete Videos automatisch abspielen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Autoplay videos opened in Duck Player" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Duplicados omitidos: " + "value" : "Reproducir automáticamente los vídeos abiertos en Duck Player" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Doublons ignorés : " + "value" : "Lecture automatique des vidéos ouvertes dans Duck Player" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Duplicati ignorati: " + "value" : "Riproduzione automatica dei video aperti in Duck Player" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Overgeslagen duplicaten: " + "value" : "Video's automatisch afspelen wanneer ze worden geopend in Duck Player" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pominięte duplikaty: " + "value" : "Automatyczne odtwarzanie filmów otwieranych w Duck Player" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Duplicados ignorados: " + "value" : "Reproduzir automaticamente vídeos abertos no Duck Player" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пропущенные дубликаты: " + "value" : "Автоматически проигрывать видео при открытии в Duck Player" } } } }, - "edit" : { - "comment" : "Edit button", + "duck-player.contingency-title" : { + "comment" : "Title for message explaining to the user that Duck Player is not available", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bearbeiten" + "value" : "Duck Player nicht verfügbar" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Edit" + "value" : "Duck Player Unavailable" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Editar" + "value" : "Duck Player no disponible" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Modifier" + "value" : "Duck Player n'est pas disponible" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Modifica" + "value" : "Duck Player non disponibile" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bewerken" + "value" : "Duck Player is niet beschikbaar" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Edytuj" + "value" : "Duck Player jest niedostępny" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Editar" + "value" : "Duck Player indisponível" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Редактировать" + "value" : "Duck Player недоступен" } } } }, - "edit.favorite" : { - "comment" : "Header of the view that edits a favorite bookmark", + "duck-player.explanation" : { + "comment" : "Private YouTube Player explanation in settings", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Favorit bearbeiten" + "value" : "Mit Duck Player kannst du dir ungestört und ohne personalisierte Werbung Inhalte ansehen. Er verhindert, dass das, was du dir ansiehst, deine YouTube-Empfehlungen beeinflussen." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Edit Favorite" + "value" : "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Editar favorito" + "value" : "Duck Player ofrece una experiencia de visualización limpia sin anuncios personalizados e impide que la actividad de visualización influya en tus recomendaciones de YouTube." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Modifier le favori" + "value" : "Duck Player offre une expérience de visionnage épurée, sans publicités personnalisées, et empêche l'activité de visionnage d'influencer vos recommandations YouTube." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Modifica preferito" + "value" : "Duck Player offre un'esperienza di visualizzazione pulita, senza annunci personalizzati, e impedisce che l'attività di visualizzazione incida sulle raccomandazioni di YouTube." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Favoriet bewerken" + "value" : "Duck Player biedt puur kijkplezier zonder gepersonaliseerde advertenties en voorkomt dat de dingen die je bekijkt je YouTube-aanbevelingen beïnvloeden." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Edytuj ulubione" + "value" : "Duck Player zapewnia czyste środowisko oglądania bez spersonalizowanych reklam i sprawia, że aktywność związana z oglądaniem filmów nie wpływa na rekomendacje YouTube'a." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Editar favorito" + "value" : "O Duck Player oferece uma experiência de visualização limpa sem anúncios personalizados e evita que as atividades de visualização influenciem as recomendações do YouTube." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Изменить избранное" + "value" : "Проигрыватель Duck Player обеспечивает беспрепятственный просмотр без персонализированной рекламы и влияния просмотренных роликов на рекомендации в YouTube." } } } }, - "email.copied" : { - "comment" : "Notification that the Private email address was copied to clipboard after the user generated a new address", + "duck-player.newtab-preference" : { + "comment" : "New tab preference in settings", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neue Adresse wurde in deine Zwischenablage kopiert" + "value" : "Öffne Duck Player nach Möglichkeit in einem neuen Tab" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "New address copied to your clipboard" + "value" : "Open Duck Player in a new tab whenever possible" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nueva dirección copiada en el portapapeles" + "value" : "Abre Duck Player en una pestaña nueva siempre que sea posible" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle adresse copiée dans le presse-papiers" + "value" : "Ouvre Duck Player dans un nouvel onglet chaque fois que possible" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nuovo indirizzo copiato negli appunti" + "value" : "Apri sempre Duck Player in una nuova scheda quando possibile" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuw adres gekopieerd naar je klembord" + "value" : "Open Duck Player waar mogelijk in een nieuw tabblad" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nowy adres skopiowany do schowka" + "value" : "Otwieraj Duck Player w nowej karcie, gdy tylko jest to możliwe" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Novo endereço copiado para a área de transferência" + "value" : "Abrir o Duck Player num novo separador sempre que possível" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Новый адрес скопирован в буфер обмена" + "value" : "По возможности открывать Duck Player в новой вкладке" } } } }, - "email.optionsMenu" : { - "comment" : "Menu item email feature", + "duck-player.newtab.info-preference" : { + "comment" : "New tab preference extra info in settings", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "E-Mail-Schutz" + "value" : "Beim Browsen auf YouTube im Internet" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Email Protection" + "value" : "When browsing YouTube on the web" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Protección del correo electrónico" + "value" : "Al navegar en YouTube en la web" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Protection des e-mails" + "value" : "Lorsque vous naviguez sur YouTube sur le Web" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Protezione email" + "value" : "Durante la navigazione di YouTube sul Web" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "E-mailbescherming" + "value" : "Wanneer je YouTube op het web bekijkt" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ochrona poczty e-mail" + "value" : "Podczas przeglądania YouTube w Internecie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Proteção de e-mail" + "value" : "Ao navegar no YouTube na Internet" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Защита электронной почты" + "value" : "При просмотре YouTube в интернете" } } } }, - "email.optionsMenu.createAddress" : { - "comment" : "Create an email alias sub menu item", + "duck-player.off" : { + "comment" : "Private YouTube Player option", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Private Duck-Adresse generieren" + "value" : "Niemals den Duck Player verwenden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Generate Private Duck Address" + "value" : "Never use Duck Player" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Generar Duck Address privada" + "value" : "Nunca utilizar Duck Player" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Générer une Duck Address privée" + "value" : "Ne jamais utiliser Duck Player" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Genera Duck Address privato" + "value" : "Non utilizzare mai Duck Player" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Privé-Duck Address genereren" + "value" : "Duck Player nooit gebruiken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wygeneruj prywatny adres Duck Address" + "value" : "Nigdy nie używaj odtwarzacza Duck Player" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Gerar um Duck Address Privado" + "value" : "Nunca utilizar o Duck Player" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Создать адрес на Duck" + "value" : "Не использовать Duck Player" } } } }, - "email.optionsMenu.manageAccount" : { - "comment" : "Manage private email account sub menu item", + "duck-player.show-buttons" : { + "comment" : "Private YouTube Player option", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Konto verwalten" + "value" : "Option zur Verwendung des Duck Players über YouTube-Vorschauen beim Hovern anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Manage Account" + "value" : "Show option to use Duck Player over YouTube previews on hover" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Gestionar cuenta" + "value" : "Mostrar opción de usar Duck Player sobre las vistas previas de YouTube al pasar el cursor" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Gérer le compte" + "value" : "Afficher l'option permettant d'utiliser Duck Player sur les aperçus YouTube au survol" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Gestisci account" + "value" : "Mostra l'opzione per usare Duck Player nelle anteprime YouTube al passaggio del mouse" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Account beheren" + "value" : "Optie tonen om Duck Player te gebruiken in plaats van YouTube-voorbeelden bij het aanwijzen met de muis" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zarządzaj kontem" + "value" : "Pokaż opcję użycia odtwarzacza Duck Player nad podglądami na YouTube po najechaniu kursorem" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Gerir conta" + "value" : "Mostrar opção de utilizar o Duck Player nas pré-visualizações do YouTube ao passar o rato" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Управление учетной записью" + "value" : "Показывать опцию просмотра в Duck Player при наведении курсора на значок ролика на YouTube" } } } }, - "email.optionsMenu.turnOff" : { - "comment" : "Disable email sub menu item", + "duck-player.title" : { + "comment" : "Private YouTube Player settings title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Autovervollständigen für den E-Mail-Schutz deaktivieren" + "value" : "Duck Player" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Disable Email Protection Autofill" + "value" : "Duck Player" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Desactivar Autocompletar Email Protection" + "value" : "Duck Player" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Désactiver la saisie automatique Email Protection" + "value" : "Duck Player" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Disattiva la compilazione automatica di Email Protection" + "value" : "Duck Player" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Automatisch invullen van Email Protection uitschakelen" + "value" : "Duck Player" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyłącz autouzupełnianie w funkcji Email Protection" + "value" : "Duck Player" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Desativar o preenchimento automático da Email Protection" + "value" : "Duck Player" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отключить автозаполнение в рамках защиты почты" + "value" : "Проигрыватель Duck Player" } } } }, - "email.optionsMenu.turnOn" : { - "comment" : "Sub menu item to enable Email Protection", + "duck-player.video-autoplay-preference" : { + "comment" : "Autoplay preference in settings", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Email Protection aktivieren" + "value" : "Videos automatisch abspielen, wenn sie im Duck Player geöffnet werden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Enable Email Protection" + "value" : "Autoplay videos when opened in Duck Player" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Activar Email Protection" + "value" : "Reproducir automáticamente los vídeos cuando se abran en Duck Player" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Activez Email Protection" + "value" : "Lecture automatique des vidéos ouvertes dans Duck Player" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Abilita Email Protection" + "value" : "Riproduzione automatica dei video quando li apri in Duck Player" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "E-mailbeveiliging inschakelen" + "value" : "Video's automatisch afspelen wanneer ze worden geopend in Duck Player" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Włącz ochronę poczty Email Protection" + "value" : "Automatyczne odtwarzanie filmów po otwarciu w Duck Player" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ativa a Email Protection" + "value" : "Reproduzir vídeos automaticamente quando abertos no Duck Player" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Включите защиту электронной почты" + "value" : "Автоматически проигрывать видео при открытии в Duck Player" } } } }, - "email.protection.explanation" : { - "comment" : "Email protection feature explanation in settings. The feature blocks email trackers and hides original email address.", + "duck-player.video-contingency-cta" : { + "comment" : "Button for the message explaining to the user that Duck Player is not available so the user can learn more", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "E-Mail-Tracker blockieren und deine Adresse verbergen, ohne den E-Mail-Anbieter zu wechseln." + "value" : "Mehr erfahren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Block email trackers and hide your address without switching your email provider." + "value" : "Learn More" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Bloquea los rastreadores de correo electrónico y oculta tu dirección sin cambiar de proveedor de correo electrónico." + "value" : "Más información" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Bloquez les traqueurs d'e-mails et masquez votre adresse sans changer de fournisseur de messagerie." + "value" : "En savoir plus" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Blocca i sistemi di tracciamento delle e-mail e nascondi il tuo indirizzo, senza cambiare il provider di posta elettronica." + "value" : "Ulteriori informazioni" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Blokkeer e-mailtrackers en verberg je adres zonder van e-mailprovider te wisselen." + "value" : "Meer informatie" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zablokuj mechanizmy śledzące pocztę e-mail i ukryj swój adres bez zmiany dostawcy poczty e-mail." + "value" : "Dowiedz się więcej" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Bloqueia os rastreadores de e-mail e oculta o teu endereço sem alterares o teu fornecedor de e-mail." + "value" : "Saiba mais" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Чтобы блокировать трекеры и скрывать свой адрес, вовсе не нужно менять почтовый сервис." + "value" : "Узнать больше" } } } }, - "Enter Full Screen" : { - "comment" : "Main Menu View item", + "duck-player.video-contingency-message" : { + "comment" : "Message explaining to the user that Duck Player is not available", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Vollbildmodus aktivieren" + "value" : "Die Funktionalität des Duck Players wird durch die jüngsten Änderungen bei YouTube beeinträchtigt. Vielen Dank für dein Verständnis, während wir daran arbeiten, diese Probleme zu beheben." + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Duck Player's functionality has been affected by recent changes to YouTube. We’re working to fix these issues and appreciate your understanding." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Entrar en pantalla completa" + "value" : "La funcionalidad de Duck Player se ha visto afectada por los cambios recientes en YouTube. Estamos trabajando para solucionar estos problemas y agradecemos tu comprensión." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Passer en mode plein écran" + "value" : "Les récents changements apportés à YouTube ont affecté la fonctionnalité de Duck Player. Nous nous efforçons de résoudre ces problèmes et vous remercions de votre compréhension." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Accedi alla visualizzazione a schermo intero" + "value" : "La funzionalità di Duck Player è stata influenzata dalle recenti modifiche a YouTube. Stiamo lavorando per risolvere questi problemi. Ti ringraziamo per la comprensione." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Volledig scherm openen" + "value" : "Recente wijzigingen in YouTube hebben invloed op de functionaliteit van Duck Player. We werken aan een oplossing voor deze problemen. Bedankt voor je begrip." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wejdź w tryb pełnoekranowy" + "value" : "Ostatnie zmiany w serwisie YouTube miały wpływ na działanie Duck Player. Pracujemy nad rozwiązaniem tych problemów i dziękujemy za zrozumienie." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ecrã completo" + "value" : "A funcionalidade do Duck Player foi afetada por alterações recentes ao YouTube. Estamos a trabalhar para resolver estes problemas e agradecemos a tua compreensão." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Войти в полноэкранный режим" + "value" : "Недавние изменения YouTube приводят к некорректной работе проигрывателя Duck Player. Мы занимаемся решением этой проблемы и благодарим вас за понимание." } } } }, - "Error message & code" : { - "comment" : "Title of the section of a dialog (form where the user can report feedback) where the error message and the error code are shown", + "duck-player.video-preferences-title" : { + "comment" : "Video Preferences title in settings", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fehlermeldung & Code" + "value" : "Video-Einstellungen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Video Preferences" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mensaje y código de error" + "value" : "Preferencias de vídeo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Message et code d'erreur " + "value" : "Préférences vidéo" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Messaggio e codice di errore" + "value" : "Preferenze video" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Foutmelding en code" + "value" : "Videovoorkeuren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Komunikat o błędzie i kod błędu" + "value" : "Preferencje filmów" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mensagem e código de erro" + "value" : "Preferências de vídeo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сообщение и код ошибки" + "value" : "Настройки видео" } } } }, - "error.unknown.try.again" : { - "comment" : "Generic error message on a dialog for when the cause is not known.", - "extractionState" : "extracted_with_value", + "DuckDuckGo browser version" : { + "comment" : "Data import failure Report dialog description of a report field providing current DuckDuckGo Browser version", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ein unbekannter Fehler ist aufgetreten" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "An unknown error has occurred" + "value" : "DuckDuckGo Browserversion" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Se ha producido un error desconocido" + "value" : "Versión del navegador DuckDuckGo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Une erreur inconnue s'est produite" + "value" : "Version du navigateur DuckDuckGo" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Si è verificato un errore sconosciuto" + "value" : "Versione del browser DuckDuckGo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Er is een onbekende fout opgetreden" + "value" : "DuckDuckGo-browserversie" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wystąpił nieznany błąd" + "value" : "Wersja przeglądarki DuckDuckGo" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ocorreu um erro desconhecido" + "value" : "Versão do navegador DuckDuckGo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Произошла неизвестная ошибка" + "value" : "Версия браузера DuckDuckGo" } } } }, - "export.bookmarks.failed.informative" : { - "comment" : "Alert message when exporting bookmarks fails", - "extractionState" : "extracted_with_value", + "DuckDuckGo Help" : { + "comment" : "Main Menu Help item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitte überprüfe, dass an dem von dir gewählten Ort keine Datei existiert." - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Please check that no file exists at the location you selected." + "value" : "DuckDuckGo Hilfe" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Comprueba que no exista ningún archivo en la ubicación que has seleccionado." + "value" : "Ayuda de DuckDuckGo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Veuillez vérifier qu'aucun fichier n'existe à l'emplacement que vous avez sélectionné." + "value" : "Aide DuckDuckGo" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Verifica che non esistano file nella posizione selezionata." + "value" : "Guida DuckDuckGo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Controleer of er geen bestand bestaat op de geselecteerde locatie." + "value" : "DuckDuckGo Help" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Sprawdź, czy w wybranej lokalizacji nie ma żadnego pliku." + "value" : "DuckDuckGo — pomoc" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Verifica se não existe nenhum ficheiro na localização que selecionaste." + "value" : "Ajuda do DuckDuckGo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Убедитесь, что в выбранном месте нет файла." + "value" : "Справка по DuckDuckGo" } } } }, - "export.bookmarks.failed.message" : { - "comment" : "Alert title when exporting login data fails", - "extractionState" : "extracted_with_value", + "DuckDuckGo needs your permission to read the %@ bookmarks file. Select the %@ folder to import bookmarks." : { + "comment" : "Data import warning that DuckDuckGo browser requires file reading permissions for another browser name (%1$@), and instruction to select its (same browser name - %2$@) bookmarks folder.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen konnten nicht exportiert werden …" + "value" : "DuckDuckGo benötigt deine Berechtigung zum Lesen der %1$@-Lesezeichen-Datei. Wähle den Ordner %2$@ aus, um Lesezeichen zu importieren." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Failed to Export Bookmarks…" + "value" : "DuckDuckGo needs your permission to read the %1$@ bookmarks file. Select the %2$@ folder to import bookmarks." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No se han podido exportar marcadores..." + "value" : "DuckDuckGo necesita tu permiso para leer el archivo de marcadores %1$@. Selecciona la carpeta %2$@ para importar marcadores." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Échec de l'exportation des signets…" + "value" : "DuckDuckGo a besoin de votre permission pour lire le fichier de signets %1$@. Sélectionnez le dossier %2$@ pour importer les signets." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Impossibile esportare segnalibri…" + "value" : "DuckDuckGo necessita della tua autorizzazione per leggere il file di segnalibri %1$@. Seleziona la cartella %2$@ per importare i segnalibri." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Kan bladwijzers niet exporteren ..." + "value" : "DuckDuckGo heeft je toestemming nodig om het bladwijzersbestand van %1$@ te lezen. Selecteer de map %2$@ om bladwijzers te importeren." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie udało się wyeksportować zakładek…" + "value" : "DuckDuckGo potrzebuje pozwolenia na odczytanie pliku zakładek przeglądarki %1$@. Wybierz folder %2$@, aby zaimportować zakładki." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Falha ao exportar marcadores…" + "value" : "O DuckDuckGo precisa da tua permissão para ler o ficheiro de marcadores %1$@. Seleciona a pasta %2$@ para importares os marcadores." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не удалось экспортировать закладки..." + "value" : "DuckDuckGo запрашивает разрешение на чтение файла закладок %1$@. Чтобы импортировать закладки, выберите папку %2$@." } } } }, - "export.bookmarks.file.name.suffix" : { - "comment" : "The last part of the suggested file for exporting bookmarks", + "duplicate.tab" : { + "comment" : "Menu item. Duplicate as a verb", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen" + "value" : "Tab duplizieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Bookmarks" + "value" : "Duplicate Tab" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Marcadores" + "value" : "Duplicar pestaña" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Signets" + "value" : "Dupliquer l'onglet" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Segnalibri" + "value" : "Duplica scheda" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bladwijzers" + "value" : "Tabblad kopiëren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zakładki" + "value" : "Zduplikuj kartę" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Marcadores" + "value" : "Separador duplicado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Закладки" + "value" : "Дублировать вкладку" } } } }, - "export.bookmarks.menu.item" : { - "comment" : "Export bookmarks menu item", - "extractionState" : "extracted_with_value", + "Duplicates Skipped:" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen exportieren …" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Export Bookmarks…" + "value" : "Duplikate übersprungen:" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Exportar marcadores..." + "value" : "Duplicados omitidos:" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Exporter les signets…" + "value" : "Doublons ignorés :" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Esporta segnalibri…" + "value" : "Duplicati ignorati:" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bladwijzers exporteren ..." + "value" : "Overgeslagen duplicaten:" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Eksportuj zakładki…" + "value" : "Pominięte duplikaty:" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Exportar marcadores…" + "value" : "Duplicados ignorados:" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Экспортировать закладки…" + "value" : "Пропущенные дубликаты:" } } } }, - "export.logins.data" : { - "comment" : "Opens Export Logins Data dialog", - "extractionState" : "extracted_with_value", + "Duplicates Skipped: " : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter exportieren …" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Export Passwords…" + "value" : "Duplikate übersprungen: " } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Exportar contraseñas…" + "value" : "Duplicados omitidos: " } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Exporter les mots de passe…" + "value" : "Doublons ignorés : " } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Esporta password…" + "value" : "Duplicati ignorati: " } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoorden exporteren ..." + "value" : "Overgeslagen duplicaten: " } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Eksportuj hasła…" + "value" : "Pominięte duplikaty: " } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Exportar palavras-passe…" + "value" : "Duplicados ignorados: " } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Экспортировать пароли…" + "value" : "Пропущенные дубликаты: " } } } }, - "export.logins.failed.informative" : { - "comment" : "Alert message when exporting login data fails", + "edit" : { + "comment" : "Edit button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitte stelle sicher, dass an dem von dir gewählten Ort keine Datei existiert." + "value" : "Bearbeiten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Please check that no file exists at the location you selected." + "value" : "Edit" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Comprueba que no exista ningún archivo en la ubicación que has seleccionado." + "value" : "Editar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Veuillez vérifier qu'aucun fichier n'existe à l'emplacement que vous avez sélectionné." + "value" : "Modifier" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Verifica che non esistano file nella posizione selezionata." + "value" : "Modifica" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Controleer of er geen bestand bestaat op de geselecteerde locatie." + "value" : "Bewerken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Sprawdź, czy w wybranej lokalizacji nie ma żadnego pliku." + "value" : "Edytuj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Verifica se não existe nenhum ficheiro na localização que selecionaste." + "value" : "Editar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Убедитесь, что в выбранном месте нет файла." + "value" : "Редактировать" } } } }, - "export.logins.failed.message" : { - "comment" : "Alert title when exporting login data fails", + "edit.favorite" : { + "comment" : "Header of the view that edits a favorite bookmark", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter konnten nicht exportiert werden" + "value" : "Favorit bearbeiten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Failed to Export Passwords" + "value" : "Edit Favorite" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Se ha producido un error al exportar contraseñas" + "value" : "Editar favorito" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Échec de l'exportation des mots de passe" + "value" : "Modifier le favori" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Impossibile esportare le password" + "value" : "Modifica preferito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Kan wachtwoorden niet exporteren" + "value" : "Favoriet bewerken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie udało się wyeksportować haseł" + "value" : "Edytuj ulubione" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Falha ao exportar palavras-passe" + "value" : "Editar favorito" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не удалось экспортировать пароли" + "value" : "Изменить избранное" } } } }, - "export.logins.file.name.suffix" : { - "comment" : "The last part of the suggested file name for exporting logins", + "email.copied" : { + "comment" : "Notification that the Private email address was copied to clipboard after the user generated a new address", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter" + "value" : "Neue Adresse wurde in deine Zwischenablage kopiert" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Passwords" + "value" : "New address copied to your clipboard" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Contraseñas" + "value" : "Nueva dirección copiada en el portapapeles" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mots de passe" + "value" : "Nouvelle adresse copiée dans le presse-papiers" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Password" + "value" : "Nuovo indirizzo copiato negli appunti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoorden" + "value" : "Nieuw adres gekopieerd naar je klembord" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Hasła" + "value" : "Nowy adres skopiowany do schowka" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Palavras-passe" + "value" : "Novo endereço copiado para a área de transferência" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пароли" + "value" : "Новый адрес скопирован в буфер обмена" } } } }, - "export.logins.warning" : { - "comment" : "Warning text presented when exporting logins.", + "email.optionsMenu" : { + "comment" : "Menu item email feature", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Diese Datei enthält deine Passwörter im Klartext und sollte an einem sicheren Ort gespeichert und nach Beendigung gelöscht werden.\nJeder, der Zugriff auf diese Datei hat, kann deine Passwörter lesen." + "value" : "E-Mail-Schutz" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "This file contains your passwords in plain text and should be saved in a secure location and deleted when you are done.\nAnyone with access to this file will be able to read your passwords." + "value" : "Email Protection" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Este archivo contiene tus contraseñas en texto sin formato y debe guardarse en un lugar seguro y eliminarse cuando hayas terminado.\nCualquiera que tenga acceso a este archivo podrá leer tus contraseñas." + "value" : "Protección del correo electrónico" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ce fichier contient vos mots de passe en texte brut ; il doit être enregistré dans un emplacement sûr et supprimé lorsque vous aurez terminé.\nToute personne ayant accès à ce fichier pourra lire vos mots de passe." + "value" : "Protection des e-mails" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Questo file contiene le tue password in testo semplice e deve essere salvato in un luogo sicuro ed eliminato quando hai finito.\nChiunque abbia accesso a questo file potrà leggere le tue password." + "value" : "Protezione email" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Dit bestand bevat je wachtwoorden in platte tekst en moet op een veilige locatie worden opgeslagen en verwijderd wanneer je klaar bent.\nIedereen met toegang tot dit bestand kan je wachtwoorden lezen." + "value" : "E-mailbescherming" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ten plik zawiera hasła w postaci zwykłego tekstu i powinien zostać zapisany w bezpiecznym miejscu, a na zakończenie usunięty.\nKażda osoba mająca dostęp do tego pliku będzie mogła odczytać Twoje hasła." + "value" : "Ochrona poczty e-mail" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Este ficheiro contém as tuas palavras-passe em texto simples e deve ser guardado numa localização segura e apagado quando terminares.\nQualquer pessoa com acesso a este ficheiro poderá ler as tuas palavras-passe." + "value" : "Proteção de e-mail" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Этот файл содержит ваши пароли в виде обычного текста. Сохраните его в надежном месте и удалите по окончании.\nЛюбой, у кого есть доступ к этому файлу, сможет увидеть ваши пароли." + "value" : "Защита электронной почты" } } } }, - "Favorite This Page…" : { - "comment" : "Main Menu History item", + "email.optionsMenu.createAddress" : { + "comment" : "Create an email alias sub menu item", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Diese Seite als Favorit hinzufügen …" + "value" : "Private Duck-Adresse generieren" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Generate Private Duck Address" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Marcar esta página como favorita..." + "value" : "Generar Duck Address privada" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ajouter cette page aux favoris…" + "value" : "Générer une Duck Address privée" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aggiungi questa pagina ai Preferiti…" + "value" : "Genera Duck Address privato" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Deze pagina markeren als favoriet ..." + "value" : "Privé-Duck Address genereren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dodaj tę stronę do ulubionych…" + "value" : "Wygeneruj prywatny adres Duck Address" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Adicionar esta página aos Favoritos…" + "value" : "Gerar um Duck Address Privado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Добавить страницу в избранное..." + "value" : "Создать адрес на Duck" } } } }, - "favorites" : { - "comment" : "Title text for the Favorites menu item", + "email.optionsMenu.manageAccount" : { + "comment" : "Manage private email account sub menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Favoriten" + "value" : "Konto verwalten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Favorites" + "value" : "Manage Account" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Favoritos" + "value" : "Gestionar cuenta" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Favoris" + "value" : "Gérer le compte" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Preferiti" + "value" : "Gestisci account" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Favorieten" + "value" : "Account beheren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ulubione" + "value" : "Zarządzaj kontem" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Favoritos" + "value" : "Gerir conta" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Избранное" + "value" : "Управление учетной записью" } } } }, - "feedback.bug.description" : { - "comment" : "Label in the feedback form that users can submit to say that a website is not working properly in DuckDuckGo", + "email.optionsMenu.turnOff" : { + "comment" : "Disable email sub menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitte beschreibe das Problem so detailliert wie möglich:" + "value" : "Autovervollständigen für den E-Mail-Schutz deaktivieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Please describe the problem in as much detail as possible:" + "value" : "Disable Email Protection Autofill" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Describe el problema con el mayor detalle posible:" + "value" : "Desactivar Autocompletar Email Protection" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Veuillez décrire le problème avec autant de détails que possible :" + "value" : "Désactiver la saisie automatique Email Protection" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Descrivi il problema nel modo più dettagliato possibile:" + "value" : "Disattiva la compilazione automatica di Email Protection" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Beschrijf het probleem zo gedetailleerd mogelijk:" + "value" : "Automatisch invullen van Email Protection uitschakelen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Opisz problem jak najbardziej szczegółowo:" + "value" : "Wyłącz autouzupełnianie w funkcji Email Protection" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Descreve o problema o mais detalhadamente possível:" + "value" : "Desativar o preenchimento automático da Email Protection" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Как можно подробнее опишите проблему:" + "value" : "Отключить автозаполнение в рамках защиты почты" } } } }, - "feedback.disclaimer" : { - "comment" : "Disclaimer in breakage form - a form that users can submit to say that a website is not working properly in DuckDuckGo", + "email.optionsMenu.turnOn" : { + "comment" : "Sub menu item to enable Email Protection", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Berichte, die an DuckDuckGo gesendet werden, sind 100% anonym und enthalten nur deine Nachricht, die DuckDuckGo-Browser-Version und deine macOS-Version." + "value" : "Email Protection aktivieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Reports sent to DuckDuckGo are 100% anonymous and only include your message, the DuckDuckGo browser version, and your macOS version." + "value" : "Enable Email Protection" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Los informes enviados a DuckDuckGo son 100% anónimos y solo incluyen tu mensaje, la versión de navegador de DuckDuckGo y tu versión de macOS." + "value" : "Activar Email Protection" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Les rapports envoyés à DuckDuckGo sont 100% anonymes et n'incluent que votre message, la version du navigateur DuckDuckGo et la version de votre macOS." + "value" : "Activez Email Protection" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "I report inviati a DuckDuckGo sono anonimi al 100% e includono solo il tuo messaggio, la versione del browser DuckDuckGo e la tua versione macOS." + "value" : "Abilita Email Protection" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Rapporten die naar DuckDuckGo worden verzonden, zijn 100% anoniem en bevatten alleen je bericht, de versie van de DuckDuckGo-browser en je versie van macOS." + "value" : "E-mailbeveiliging inschakelen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Raporty wysyłane do DuckDuckGo są w 100% anonimowe i zawierają jedynie wiadomość, wersję przeglądarki DuckDuckGo oraz wersję systemu macOS." + "value" : "Włącz ochronę poczty Email Protection" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Os relatórios enviados para a DuckDuckGo são 100% anónimos e incluem apenas a tua mensagem, a versão do navegador DuckDuckGo e a versão do teu macOS." + "value" : "Ativa a Email Protection" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отчеты, отправляемые в DuckDuckGo, на 100% анонимны. В отчет входит только написанное вами сообщение и сведения о версии браузера DuckDuckGo и операционной системы (macOS)." + "value" : "Включите защиту электронной почты" } } } }, - "feedback.feature.request.description" : { - "comment" : "Label in the feedback form for feature requests.", + "email.protection.explanation" : { + "comment" : "Email protection feature explanation in settings. The feature blocks email trackers and hides original email address.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Welche Funktion möchtest du sehen?" + "value" : "E-Mail-Tracker blockieren und deine Adresse verbergen, ohne den E-Mail-Anbieter zu wechseln." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "What feature would you like to see?" + "value" : "Block email trackers and hide your address without switching your email provider." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Qué función te gustaría ver?" + "value" : "Bloquea los rastreadores de correo electrónico y oculta tu dirección sin cambiar de proveedor de correo electrónico." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Quelle fonctionnalité aimeriez-vous voir ?" + "value" : "Bloquez les traqueurs d'e-mails et masquez votre adresse sans changer de fournisseur de messagerie." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Quale funzionalità vorresti vedere?" + "value" : "Blocca i sistemi di tracciamento delle e-mail e nascondi il tuo indirizzo, senza cambiare il provider di posta elettronica." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Welke functie wil je zien?" + "value" : "Blokkeer e-mailtrackers en verberg je adres zonder van e-mailprovider te wisselen." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Na jakiej funkcji Ci zależy?" + "value" : "Zablokuj mechanizmy śledzące pocztę e-mail i ukryj swój adres bez zmiany dostawcy poczty e-mail." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Que funcionalidade gostarias de ver?" + "value" : "Bloqueia os rastreadores de e-mail e oculta o teu endereço sem alterares o teu fornecedor de e-mail." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Какую функцию вы хотели бы увидеть?" + "value" : "Чтобы блокировать трекеры и скрывать свой адрес, вовсе не нужно менять почтовый сервис." } } } }, - "feedback.other.description" : { - "comment" : "Label in the feedback form", - "extractionState" : "extracted_with_value", + "Enter Full Screen" : { + "comment" : "Main Menu View item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitte gib uns dein Feedback:" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Please give us your feedback:" + "value" : "Vollbildmodus aktivieren" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Danos tu opinión:" + "value" : "Entrar en pantalla completa" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Veuillez nous faire part de vos commentaires :" + "value" : "Passer en mode plein écran" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Comunicaci la tua opinione:" + "value" : "Accedi alla visualizzazione a schermo intero" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geef ons je feedback:" + "value" : "Volledig scherm openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przekaż nam swoją opinię:" + "value" : "Wejdź w tryb pełnoekranowy" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Envia-nos o teu feedback:" + "value" : "Ecrã completo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Оставьте комментарий здесь:" + "value" : "Войти в полноэкранный режим" } } } }, - "find-in-page.text-field.placeholder" : { - "comment" : "Placeholder text for the text field where the user inputs strings to searcg in the web page", - "extractionState" : "extracted_with_value", + "Error message & code" : { + "comment" : "Title of the section of a dialog (form where the user can report feedback) where the error message and the error code are shown", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Auf Seite suchen" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Find in page" + "value" : "Fehlermeldung & Code" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Buscar en la página" + "value" : "Mensaje y código de error" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Rechercher dans la page" + "value" : "Message et code d'erreur " } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Trova nella pagina" + "value" : "Messaggio e codice di errore" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Zoek op pagina" + "value" : "Foutmelding en code" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Znajdź na stronie" + "value" : "Komunikat o błędzie i kod błędu" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Encontrar na página" + "value" : "Mensagem e código de erro" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Найти на странице" + "value" : "Сообщение и код ошибки" } } } }, - "find.in.page" : { - "comment" : "Find in page status (e.g. 1 of 99)", + "error.unknown.try.again" : { + "comment" : "Generic error message on a dialog for when the cause is not known.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d von %2$d" + "value" : "Ein unbekannter Fehler ist aufgetreten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d of %2$d" + "value" : "An unknown error has occurred" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d de %2$d" + "value" : "Se ha producido un error desconocido" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d sur %2$d" + "value" : "Une erreur inconnue s'est produite" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d di %2$d" + "value" : "Si è verificato un errore sconosciuto" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d van %2$d" + "value" : "Er is een onbekende fout opgetreden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d z %2$d" + "value" : "Wystąpił nieznany błąd" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d de %2$d" + "value" : "Ocorreu um erro desconhecido" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d из %2$d" + "value" : "Произошла неизвестная ошибка" } } } }, - "find.in.page.menu.item" : { - "comment" : "Menu item title", + "export.bookmarks.failed.informative" : { + "comment" : "Alert message when exporting bookmarks fails", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Auf Seite suchen …" + "value" : "Bitte überprüfe, dass an dem von dir gewählten Ort keine Datei existiert." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Find in Page…" + "value" : "Please check that no file exists at the location you selected." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Buscar en la página..." + "value" : "Comprueba que no exista ningún archivo en la ubicación que has seleccionado." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Rechercher dans la page…" + "value" : "Veuillez vérifier qu'aucun fichier n'existe à l'emplacement que vous avez sélectionné." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Trova nella pagina…" + "value" : "Verifica che non esistano file nella posizione selezionata." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Zoeken op pagina ..." + "value" : "Controleer of er geen bestand bestaat op de geselecteerde locatie." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Znajdź na stronie…" + "value" : "Sprawdź, czy w wybranej lokalizacji nie ma żadnego pliku." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Encontrar na página…" + "value" : "Verifica se não existe nenhum ficheiro na localização que selecionaste." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Найти на странице…" + "value" : "Убедитесь, что в выбранном месте нет файла." } } } }, - "fire.active-tabs-info" : { - "comment" : "Info in the Fire Button popover", + "export.bookmarks.failed.message" : { + "comment" : "Alert title when exporting login data fails", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Schließe aktive Tabs (%1$d) und lösche alle Browserverläufe und Cookies (Sites: %2$d)." + "value" : "Lesezeichen konnten nicht exportiert werden …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Close active tabs (%1$d) and clear all browsing history and cookies (sites: %2$d)." + "value" : "Failed to Export Bookmarks…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cierra las pestañas activas (%1$d) y borra todo el historial de navegación y las cookies (sitios: %2$d)." + "value" : "No se han podido exportar marcadores..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fermez les onglets actifs (%1$d) et effacez tout l'historique de navigation et les cookies (sites : %2$d)." + "value" : "Échec de l'exportation des signets…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Chiudi schede attive (%1$d) e cancella tutta la cronologia di navigazione e i cookie (siti: %2$d)." + "value" : "Impossibile esportare segnalibri…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Actieve tabbladen (%1$d) sluiten en alle surfgeschiedenis en cookies (sites: %2$d) wissen." + "value" : "Kan bladwijzers niet exporteren ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zamknij aktywne karty (%1$d) i wyczyść całą historię przeglądania i wszystkie pliki cookie (witryny: %2$d)." + "value" : "Nie udało się wyeksportować zakładek…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Fecha os separadores ativos (%1$d) e limpa todo o histórico de navegação e todos os cookies (sites: %2$d)." + "value" : "Falha ao exportar marcadores…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Функция Fire Button закроет активные вкладки (%1$d), сотрет всю историю посещений и удалит все куки-файлы (сайтов: %2$d)." + "value" : "Не удалось экспортировать закладки..." } } } }, - "fire.all-data.description" : { - "comment" : "Description of the 'All Data' configuration option for the fire button", + "export.bookmarks.file.name.suffix" : { + "comment" : "The last part of the suggested file for exporting bookmarks", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lösche alle Tabs und zugehörigen Site-Daten" + "value" : "Lesezeichen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Clear all tabs and related site data" + "value" : "Bookmarks" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Borrar todas las pestañas y datos del sitio relacionados" + "value" : "Marcadores" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Effacer tous les onglets et les données de site associées" + "value" : "Signets" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cancella tutte le schede e i dati correlati del sito" + "value" : "Segnalibri" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alle tabbladen en gerelateerde websitegegevens verwijderen" + "value" : "Bladwijzers" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyczyść wszystkie karty i powiązane dane witryn" + "value" : "Zakładki" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Limpar todos os separadores e os dados do site relacionados" + "value" : "Marcadores" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Очистить все вкладки и данные сайтов" + "value" : "Закладки" } } } }, - "fire.all-sites" : { - "comment" : "Configuration option for fire button", + "export.bookmarks.menu.item" : { + "comment" : "Export bookmarks menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alle Standorte" + "value" : "Lesezeichen exportieren …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "All sites" + "value" : "Export Bookmarks…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Todos los sitios" + "value" : "Exportar marcadores..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Tous les sites" + "value" : "Exporter les signets…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Tutti i siti" + "value" : "Esporta segnalibri…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alle websites" + "value" : "Bladwijzers exporteren ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wszystkie witryny" + "value" : "Eksportuj zakładki…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Todos os sites" + "value" : "Exportar marcadores…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Все сайты" + "value" : "Экспортировать закладки…" } } } }, - "fire.current-window.description" : { - "comment" : "Description of the 'Current Window' configuration option for the fire button", + "export.logins.data" : { + "comment" : "Opens Export Logins Data dialog", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Aktuelles Fenster und zugehörige Standortdaten löschen" + "value" : "Passwörter exportieren …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Clear current window and related site data" + "value" : "Export Passwords…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Borrar la ventana actual y los datos relacionados del sitio" + "value" : "Exportar contraseñas…" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Effacer la fenêtre actuelle et les données de site associées" + "value" : "Exporter les mots de passe…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cancella la finestra corrente e i dati correlati del sito" + "value" : "Esporta password…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Huidige venster en gerelateerde websitegegevens verwijderen" + "value" : "Wachtwoorden exporteren ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyczyść bieżące okno i powiązane dane witryn" + "value" : "Eksportuj hasła…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Limpar a janela atual e os dados relacionados com o site" + "value" : "Exportar palavras-passe…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Очистить текущее окно и данные сайтов" + "value" : "Экспортировать пароли…" } } } }, - "fire.currentTab" : { - "comment" : "Configuration option for fire button", + "export.logins.failed.informative" : { + "comment" : "Alert message when exporting login data fails", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alle besuchten Seiten im aktuellen Tab" + "value" : "Bitte stelle sicher, dass an dem von dir gewählten Ort keine Datei existiert." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "All sites visited in current tab" + "value" : "Please check that no file exists at the location you selected." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Todos los sitios visitados en la pestaña actual" + "value" : "Comprueba que no exista ningún archivo en la ubicación que has seleccionado." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Tous les sites visités dans l'onglet actuel" + "value" : "Veuillez vérifier qu'aucun fichier n'existe à l'emplacement que vous avez sélectionné." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Tutti i siti visitati nella scheda corrente" + "value" : "Verifica che non esistano file nella posizione selezionata." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alle bezochte websites in het huidige tabblad" + "value" : "Controleer of er geen bestand bestaat op de geselecteerde locatie." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wszystkie strony odwiedzone w bieżącej karcie" + "value" : "Sprawdź, czy w wybranej lokalizacji nie ma żadnego pliku." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Todos os sites visitados no separador atual" + "value" : "Verifica se não existe nenhum ficheiro na localização que selecionaste." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Все сайты, посещенные в текущей вкладке" + "value" : "Убедитесь, что в выбранном месте нет файла." } } } }, - "fire.currentWindow" : { - "comment" : "Configuration option for fire button", + "export.logins.failed.message" : { + "comment" : "Alert title when exporting login data fails", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alle besuchten Seiten im aktuellen Fenster" + "value" : "Passwörter konnten nicht exportiert werden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "All sites visited in current window" + "value" : "Failed to Export Passwords" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Todos los sitios visitados en la ventana actual" + "value" : "Se ha producido un error al exportar contraseñas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Tous les sites visités dans la fenêtre actuelle" + "value" : "Échec de l'exportation des mots de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Tutti i siti visitati nella finestra corrente" + "value" : "Impossibile esportare le password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alle bezochte websites in het huidige venster" + "value" : "Kan wachtwoorden niet exporteren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wszystkie strony odwiedzone w bieżącym oknie" + "value" : "Nie udało się wyeksportować haseł" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Todos os sites visitados na janela atual" + "value" : "Falha ao exportar palavras-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Все сайты, посещенные в текущем окне" + "value" : "Не удалось экспортировать пароли" } } } }, - "fire.dialog.all-windows-will-close" : { - "comment" : "Warning label shown in an expanded view of the fire popover", + "export.logins.file.name.suffix" : { + "comment" : "The last part of the suggested file name for exporting logins", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alle Fenster werden geschlossen" + "value" : "Passwörter" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "All windows will close" + "value" : "Passwords" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Todas las ventanas se cerrarán" + "value" : "Contraseñas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Toutes les fenêtres se fermeront" + "value" : "Mots de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Tutte le finestre si chiuderanno" + "value" : "Password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alle vensters worden gesloten" + "value" : "Wachtwoorden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wszystkie okna zostaną zamknięte" + "value" : "Hasła" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Serão fechadas todas as janelas" + "value" : "Palavras-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Все окна будут закрыты" + "value" : "Пароли" } } } }, - "fire.dialog.clear.sites" : { - "comment" : "Category of domains in fire button dialog", + "export.logins.warning" : { + "comment" : "Warning text presented when exporting logins.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ausgewählte Standorte werden gelöscht" + "value" : "Diese Datei enthält deine Passwörter im Klartext und sollte an einem sicheren Ort gespeichert und nach Beendigung gelöscht werden.\nJeder, der Zugriff auf diese Datei hat, kann deine Passwörter lesen." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Selected sites will be cleared" + "value" : "This file contains your passwords in plain text and should be saved in a secure location and deleted when you are done.\nAnyone with access to this file will be able to read your passwords." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Se borrarán los sitios seleccionados" + "value" : "Este archivo contiene tus contraseñas en texto sin formato y debe guardarse en un lugar seguro y eliminarse cuando hayas terminado.\nCualquiera que tenga acceso a este archivo podrá leer tus contraseñas." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Les sites sélectionnés seront effacés" + "value" : "Ce fichier contient vos mots de passe en texte brut ; il doit être enregistré dans un emplacement sûr et supprimé lorsque vous aurez terminé.\nToute personne ayant accès à ce fichier pourra lire vos mots de passe." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "I siti selezionati saranno cancellati" + "value" : "Questo file contiene le tue password in testo semplice e deve essere salvato in un luogo sicuro ed eliminato quando hai finito.\nChiunque abbia accesso a questo file potrà leggere le tue password." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geselecteerde websites worden gewist" + "value" : "Dit bestand bevat je wachtwoorden in platte tekst en moet op een veilige locatie worden opgeslagen en verwijderd wanneer je klaar bent.\nIedereen met toegang tot dit bestand kan je wachtwoorden lezen." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wybrane witryny zostaną wyczyszczone" + "value" : "Ten plik zawiera hasła w postaci zwykłego tekstu i powinien zostać zapisany w bezpiecznym miejscu, a na zakończenie usunięty.\nKażda osoba mająca dostęp do tego pliku będzie mogła odczytać Twoje hasła." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Os sites selecionados serão limpos" + "value" : "Este ficheiro contém as tuas palavras-passe em texto simples e deve ser guardado numa localização segura e apagado quando terminares.\nQualquer pessoa com acesso a este ficheiro poderá ler as tuas palavras-passe." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Будут стерты данные выбранных сайтов" + "value" : "Этот файл содержит ваши пароли в виде обычного текста. Сохраните его в надежном месте и удалите по окончании.\nЛюбой, у кого есть доступ к этому файлу, сможет увидеть ваши пароли." } } } }, - "fire.dialog.close-burner-window" : { - "comment" : "Button that allows the user to close and burn the browser burner window", - "extractionState" : "extracted_with_value", + "Favorite This Page…" : { + "comment" : "Main Menu History item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Dieses Fenster schließen und löschen" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Close and Burn This Window" + "value" : "Diese Seite als Favorit hinzufügen …" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cerrar y quemar esta ventana" + "value" : "Marcar esta página como favorita..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fermer et brûler cette fenêtre" + "value" : "Ajouter cette page aux favoris…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Chiudi ed elimina i dati per questa finestra" + "value" : "Aggiungi questa pagina ai Preferiti…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Dit venster sluiten en verwijderen" + "value" : "Deze pagina markeren als favoriet ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zamknij i zniszcz to okno" + "value" : "Dodaj tę stronę do ulubionych…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Fechar e queimar esta janela" + "value" : "Adicionar esta página aos Favoritos…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Закрыть окно и стереть данные" + "value" : "Добавить страницу в избранное..." } } } }, - "fire.dialog.deliting.data" : { - "comment" : "Text shown in dialog while removing browsing data", + "favorites" : { + "comment" : "Title text for the Favorites menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Browserdaten werden gelöscht …" + "value" : "Favoriten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Deleting browsing data…" + "value" : "Favorites" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar los datos de navegación..." + "value" : "Favoritos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Suppression des données de navigation…" + "value" : "Favoris" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminazione dei dati di navigazione…" + "value" : "Preferiti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Surfgegevens verwijderen..." + "value" : "Favorieten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usuwanie danych przeglądania…" + "value" : "Ulubione" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "A eliminar dados de navegação…" + "value" : "Favoritos" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удаление данных о посещении сайтов..." + "value" : "Избранное" } } } }, - "fire.dialog.details" : { - "comment" : "Button to show more details", + "feedback.bug.description" : { + "comment" : "Label in the feedback form that users can submit to say that a website is not working properly in DuckDuckGo", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Details" + "value" : "Bitte beschreibe das Problem so detailliert wie möglich:" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Details" + "value" : "Please describe the problem in as much detail as possible:" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Detalles" + "value" : "Describe el problema con el mayor detalle posible:" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Détails" + "value" : "Veuillez décrire le problème avec autant de détails que possible :" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Dettagli" + "value" : "Descrivi il problema nel modo più dettagliato possibile:" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Details" + "value" : "Beschrijf het probleem zo gedetailleerd mogelijk:" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Szczegóły" + "value" : "Opisz problem jak najbardziej szczegółowo:" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Detalhes" + "value" : "Descreve o problema o mais detalhadamente possível:" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Подробнее" + "value" : "Как можно подробнее опишите проблему:" } } } }, - "fire.dialog.fire-window.close-tabs" : { - "comment" : "Title of the dialog where the user can close browser tabs and clear data.", + "feedback.disclaimer" : { + "comment" : "Disclaimer in breakage form - a form that users can submit to say that a website is not working properly in DuckDuckGo", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Tabs schließen und Daten löschen" + "value" : "Berichte, die an DuckDuckGo gesendet werden, sind 100% anonym und enthalten nur deine Nachricht, die DuckDuckGo-Browser-Version und deine macOS-Version." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Close Tabs and Clear Data" + "value" : "Reports sent to DuckDuckGo are 100% anonymous and only include your message, the DuckDuckGo browser version, and your macOS version." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cerrar pestañas y borrar datos" + "value" : "Los informes enviados a DuckDuckGo son 100% anónimos y solo incluyen tu mensaje, la versión de navegador de DuckDuckGo y tu versión de macOS." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fermer les onglets et effacer les données" + "value" : "Les rapports envoyés à DuckDuckGo sont 100% anonymes et n'incluent que votre message, la version du navigateur DuckDuckGo et la version de votre macOS." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Chiudi le schede e cancella i dati" + "value" : "I report inviati a DuckDuckGo sono anonimi al 100% e includono solo il tuo messaggio, la versione del browser DuckDuckGo e la tua versione macOS." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Tabbladen sluiten en gegevens wissen" + "value" : "Rapporten die naar DuckDuckGo worden verzonden, zijn 100% anoniem en bevatten alleen je bericht, de versie van de DuckDuckGo-browser en je versie van macOS." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zamknij karty i wyczyść dane" + "value" : "Raporty wysyłane do DuckDuckGo są w 100% anonimowe i zawierają jedynie wiadomość, wersję przeglądarki DuckDuckGo oraz wersję systemu macOS." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Fechar separadores e limpar os dados" + "value" : "Os relatórios enviados para a DuckDuckGo são 100% anónimos e incluem apenas a tua mensagem, a versão do navegador DuckDuckGo e a versão do teu macOS." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Закрыть вкладки и удалить данные" + "value" : "Отчеты, отправляемые в DuckDuckGo, на 100% анонимны. В отчет входит только написанное вами сообщение и сведения о версии браузера DuckDuckGo и операционной системы (macOS)." } } } }, - "fire.dialog.fire-window.description" : { - "comment" : "Explanation of what a fire window is.", + "feedback.feature.request.description" : { + "comment" : "Label in the feedback form for feature requests.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ein isoliertes Fenster, das keine Daten speichert" + "value" : "Welche Funktion möchtest du sehen?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "An isolated window that doesn’t save any data" + "value" : "What feature would you like to see?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Una ventana aislada que no guarda ningún dato" + "value" : "¿Qué función te gustaría ver?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Une fenêtre isolée qui n'enregistre aucune donnée" + "value" : "Quelle fonctionnalité aimeriez-vous voir ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Una finestra isolata che non salva alcun dato" + "value" : "Quale funzionalità vorresti vedere?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Een geïsoleerd venster dat geen gegevens opslaat" + "value" : "Welke functie wil je zien?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Odizolowane okno, które nie zapisuje żadnych danych" + "value" : "Na jakiej funkcji Ci zależy?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Uma janela isolada que não guarda quaisquer dados" + "value" : "Que funcionalidade gostarias de ver?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Изолированное окно, где не сохраняются никакие данные" + "value" : "Какую функцию вы хотели бы увидеть?" } } } }, - "fire.dialog.fire-window.title" : { - "comment" : "Title of the part of the dialog where the user can open a fire window.", + "feedback.other.description" : { + "comment" : "Label in the feedback form", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neues Fire Window öffnen" + "value" : "Bitte gib uns dein Feedback:" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open New Fire Window" + "value" : "Please give us your feedback:" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir nueva Fire Window" + "value" : "Danos tu opinión:" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir une nouvelle Fire Window" + "value" : "Veuillez nous faire part de vos commentaires :" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri una nuova Fire Window" + "value" : "Comunicaci la tua opinione:" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuw Fire Window openen" + "value" : "Geef ons je feedback:" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz nowe okno Fire Window" + "value" : "Przekaż nam swoją opinię:" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir nova Fire Window" + "value" : "Envia-nos o teu feedback:" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть новое окно Fire Window" + "value" : "Оставьте комментарий здесь:" } } } }, - "fire.dialog.fireproof.sites" : { - "comment" : "Category of domains in fire button dialog", + "find-in-page.text-field.placeholder" : { + "comment" : "Placeholder text for the text field where the user inputs strings to searcg in the web page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fireproof Standorte werden nicht geräumt" + "value" : "Auf Seite suchen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Fireproof sites won't be cleared" + "value" : "Find in page" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No se borrarán los sitios Fireproof" + "value" : "Buscar en la página" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Les sites en mode coupe-feu (Fireproof) ne seront pas effacés" + "value" : "Rechercher dans la page" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "I dati dei siti Fireproof non saranno eliminati" + "value" : "Trova nella pagina" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Fireproof websites worden niet verwijderd" + "value" : "Zoek op pagina" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Witryny Fireproof nie zostaną wyczyszczone" + "value" : "Znajdź na stronie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Os sites Fireproof não serão limpos" + "value" : "Encontrar na página" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Данные огнеупорных сайтов не стираются" + "value" : "Найти на странице" } } } }, - "fire.dialog.tab-will-close" : { - "comment" : "Warning label shown in an expanded view of the fire popover", + "find.in.page" : { + "comment" : "Find in page status (e.g. 1 of 99)", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Aktueller Tab wird geschlossen" + "value" : "%1$d von %2$d" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Current tab will close" + "value" : "%1$d of %2$d" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Se cerrará la pestaña actual" + "value" : "%1$d de %2$d" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "L'onglet actuel se fermera" + "value" : "%1$d sur %2$d" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "La scheda corrente si chiuderà" + "value" : "%1$d di %2$d" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Huidige tabblad wordt gesloten" + "value" : "%1$d van %2$d" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Bieżąca karta zostanie zamknięta" + "value" : "%1$d z %2$d" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O separador atual será fechado" + "value" : "%1$d de %2$d" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Текущая вкладка будет закрыта" + "value" : "%1$d из %2$d" } } } }, - "fire.dialog.tab-will-reload" : { - "comment" : "Warning label shown in an expanded view of the fire popover", + "find.in.page.menu.item" : { + "comment" : "Menu item title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Angepinnter Tab wird neu geladen" + "value" : "Auf Seite suchen …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Pinned tab will reload" + "value" : "Find in Page…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "La pestaña anclada se volverá a cargar" + "value" : "Buscar en la página..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "L'onglet épinglé se rechargera" + "value" : "Rechercher dans la page…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "La scheda bloccata si ricaricherà" + "value" : "Trova nella pagina…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Vastgezet tabblad wordt opnieuw geladen" + "value" : "Zoeken op pagina ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przypięta karta zostanie załadowana ponownie" + "value" : "Znajdź na stronie…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O separador afixado será recarregado" + "value" : "Encontrar na página…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Закрепленная вкладка будет перезагружена" + "value" : "Найти на странице…" } } } }, - "fire.dialog.window-will-close" : { - "comment" : "Warning label shown in an expanded view of the fire popover", + "fire.active-tabs-info" : { + "comment" : "Info in the Fire Button popover", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Aktuelles Fenster wird geschlossen" + "value" : "Schließe aktive Tabs (%1$d) und lösche alle Browserverläufe und Cookies (Sites: %2$d)." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Current window will close" + "value" : "Close active tabs (%1$d) and clear all browsing history and cookies (sites: %2$d)." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "La ventana actual se cerrará" + "value" : "Cierra las pestañas activas (%1$d) y borra todo el historial de navegación y las cookies (sitios: %2$d)." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "La fenêtre actuelle se fermera" + "value" : "Fermez les onglets actifs (%1$d) et effacez tout l'historique de navigation et les cookies (sites : %2$d)." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "La finestra corrente sarà chiusa" + "value" : "Chiudi schede attive (%1$d) e cancella tutta la cronologia di navigazione e i cookie (siti: %2$d)." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Huidig venster wordt gesloten" + "value" : "Actieve tabbladen (%1$d) sluiten en alle surfgeschiedenis en cookies (sites: %2$d) wissen." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Bieżące okno zostanie zamknięte" + "value" : "Zamknij aktywne karty (%1$d) i wyczyść całą historię przeglądania i wszystkie pliki cookie (witryny: %2$d)." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "A janela atual será fechada" + "value" : "Fecha os separadores ativos (%1$d) e limpa todo o histórico de navegação e todos os cookies (sites: %2$d)." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Текущее окно будет закрыто" + "value" : "Функция Fire Button закроет активные вкладки (%1$d), сотрет всю историю посещений и удалит все куки-файлы (сайтов: %2$d)." } } } }, - "fire.info.dialog.description" : { - "comment" : "Description in the dialog that explains the Fire feature.", + "fire.all-data.description" : { + "comment" : "Description of the 'All Data' configuration option for the fire button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Daten, der Browserverlauf und Cookies können sich mit der Zeit in deinem Browser ansammeln. Mit dem Fire Button löschst du alles." + "value" : "Lösche alle Tabs und zugehörigen Site-Daten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Data, browsing history, and cookies can build up in your browser over time. Use the Fire Button to clear it all away." + "value" : "Clear all tabs and related site data" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Los datos, el historial de navegación y las cookies pueden acumularse en tu navegador con el tiempo. Utiliza el Fire Button para quemarlos." + "value" : "Borrar todas las pestañas y datos del sitio relacionados" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Les données, l'historique de navigation et les cookies peuvent s'accumuler dans votre navigateur au fil du temps. Utilisez le Fire Button pour les supprimer." + "value" : "Effacer tous les onglets et les données de site associées" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Con il passare del tempo, nel tuo browser possono accumularsi dati, cronologia di navigazione e cookie. Usa il pulsante Fire Button per eliminare tutto." + "value" : "Cancella tutte le schede e i dati correlati del sito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Gegevens, browsegeschiedenis en cookies kunnen na verloop van tijd je browser overbelasten. Gebruik de Fire Button om alles te verbranden." + "value" : "Alle tabbladen en gerelateerde websitegegevens verwijderen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "W przeglądarce z czasem może zgromadzić się sporo danych, historii przeglądania i plików cookie. Przyciskiem Fire Button możesz to wszystko wyczyścić." + "value" : "Wyczyść wszystkie karty i powiązane dane witryn" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Os dados, o histórico de navegação e os cookies podem acumular-se no teu navegador ao longo do tempo. Utiliza o Fire Button para limpar tudo." + "value" : "Limpar todos os separadores e os dados do site relacionados" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Со временем в браузере накапливаются данные, история просмотров и куки-файлы. Кнопка Fire Button позволяет «сжечь» весь лишний мусор." + "value" : "Очистить все вкладки и данные сайтов" } } } }, - "fire.info.dialog.title" : { - "comment" : "Title of the dialog that explains the Fire feature.", + "fire.all-sites" : { + "comment" : "Configuration option for fire button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine Spuren hinterlassen" + "value" : "Alle Standorte" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Leave No Trace" + "value" : "All sites" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No dejes rastro" + "value" : "Todos los sitios" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ne laissez aucune trace" + "value" : "Tous les sites" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Non lasciare traccia" + "value" : "Tutti i siti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Laat geen sporen achter" + "value" : "Alle websites" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie zostawiaj śladów" + "value" : "Wszystkie witryny" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Não deixar vestígios" + "value" : "Todos os sites" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "И никаких следов" + "value" : "Все сайты" } } } }, - "fire.one-tab-info" : { - "comment" : "Info in the Fire Button popover", + "fire.current-window.description" : { + "comment" : "Description of the 'Current Window' configuration option for the fire button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Schließe diesen Tab und lösche Browserverlauf und Cookies (Sites: %d)." + "value" : "Aktuelles Fenster und zugehörige Standortdaten löschen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Close this tab and clear its browsing history and cookies (sites: %d)." + "value" : "Clear current window and related site data" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cierra esta pestaña y borra tu historial de navegación y cookies (sitios: %d)." + "value" : "Borrar la ventana actual y los datos relacionados del sitio" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fermez cet onglet et effacez son historique de navigation et ses cookies (sites : %d)." + "value" : "Effacer la fenêtre actuelle et les données de site associées" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Chiudi questa scheda e cancella la cronologia di navigazione e i cookie (siti: %d)." + "value" : "Cancella la finestra corrente e i dati correlati del sito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Sluit dit tabblad en wis de surfgeschiedenis en cookies (sites: %d)." + "value" : "Huidige venster en gerelateerde websitegegevens verwijderen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zamknij tę kartę i wyczyść związaną z nią historię przeglądania i pliki cookie (witryny: %d)." + "value" : "Wyczyść bieżące okno i powiązane dane witryn" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Fecha este separador e limpa o respetivo histórico de navegação e cookies (sites: %d)." + "value" : "Limpar a janela atual e os dados relacionados com o site" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Функция Fire Button закроет данную вкладку, сотрет ее историю посещений и удалит куки-файлы (сайтов: %d)." + "value" : "Очистить текущее окно и данные сайтов" } } } }, - "fire.select-site-to-clear" : { - "comment" : "Info label in the fire button popover", + "fire.currentTab" : { + "comment" : "Configuration option for fire button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wähle eine Site aus, um deren Daten zu löschen." + "value" : "Alle besuchten Seiten im aktuellen Tab" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Select a site to clear its data." + "value" : "All sites visited in current tab" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Selecciona un sitio del que borrar sus datos." + "value" : "Todos los sitios visitados en la pestaña actual" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Sélectionnez un site pour effacer ses données." + "value" : "Tous les sites visités dans l'onglet actuel" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Seleziona un sito per cancellarne i dati." + "value" : "Tutti i siti visitati nella scheda corrente" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Selecteer een website om de gegevens te wissen." + "value" : "Alle bezochte websites in het huidige tabblad" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wybierz witrynę, aby wyczyścić jej dane." + "value" : "Wszystkie strony odwiedzone w bieżącej karcie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Seleciona um site para limpar os dados." + "value" : "Todos os sites visitados no separador atual" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выберите сайт, чьи данные вы хотите стереть." + "value" : "Все сайты, посещенные в текущей вкладке" } } } }, - "fireproof" : { - "comment" : "Fireproof button", + "fire.currentWindow" : { + "comment" : "Configuration option for fire button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Feuerfest machen" + "value" : "Alle besuchten Seiten im aktuellen Fenster" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Fireproof" + "value" : "All sites visited in current window" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "A prueba de fuego" + "value" : "Todos los sitios visitados en la ventana actual" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mode coupe-feu" + "value" : "Tous les sites visités dans la fenêtre actuelle" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Proteggi dal fuoco" + "value" : "Tutti i siti visitati nella finestra corrente" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Brandveilig" + "value" : "Alle bezochte websites in het huidige venster" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zabezpiecz" + "value" : "Wszystkie strony odwiedzone w bieżącym oknie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Barreira de segurança" + "value" : "Todos os sites visitados na janela atual" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сделать огнеупорным" + "value" : "Все сайты, посещенные в текущем окне" } } } }, - "fireproof.checkbox.title" : { - "comment" : "Fireproof settings checkbox title", + "fire.dialog.all-windows-will-close" : { + "comment" : "Warning label shown in an expanded view of the fire popover", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bei der Anmeldung nach Fireproof-Websites fragen" + "value" : "Alle Fenster werden geschlossen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Ask to Fireproof websites when signing in" + "value" : "All windows will close" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Preguntar si marcar sitios web como Fireproof al iniciar sesión" + "value" : "Todas las ventanas se cerrarán" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Demander à placer des sites Web en mode coupe-feu (Fireproof) lors de la connexion" + "value" : "Toutes les fenêtres se fermeront" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Chiedi di applicare la funzionalità Fireproof per i siti web al momento dell'accesso" + "value" : "Tutte le finestre si chiuderanno" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Vragen om websites te fireproofen bij inloggen" + "value" : "Alle vensters worden gesloten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Poproś o zabezpieczanie witryn funkcją Fireproof podczas logowania" + "value" : "Wszystkie okna zostaną zamknięte" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Pedir para utilizar a funcionalidade Fireproof em sites ao iniciar sessão" + "value" : "Serão fechadas todas as janelas" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Предлагать огнеупорность сайтов при входе" + "value" : "Все окна будут закрыты" } } } }, - "fireproof.confirmation.message" : { - "comment" : "Fireproof confirmation message", + "fire.dialog.clear.sites" : { + "comment" : "Category of domains in fire button dialog", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wenn du diese Website feuerfest machst, bleibst du nach Verwendung der Schaltfläche „Feuer“ angemeldet." + "value" : "Ausgewählte Standorte werden gelöscht" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Fireproofing this site will keep you signed in after using the Fire Button." + "value" : "Selected sites will be cleared" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Marcar esta página como a prueba de fuego hará que la sesión se mantenga iniciada después de usar el botón Fuego." + "value" : "Se borrarán los sitios seleccionados" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Placer ce site en mode coupe-feu et utiliser le bouton en forme de flamme ne vous déconnectera pas." + "value" : "Les sites sélectionnés seront effacés" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Proteggere dal fuoco questo sito manterrà valido l'accesso dopo aver utilizzato il Fire Button (icona del fuoco)." + "value" : "I siti selezionati saranno cancellati" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Als je deze site brandveilig maakt, blijf je ook na het gebruiken van de vuurknop ingelogd." + "value" : "Geselecteerde websites worden gewist" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zabezpieczenie tej witryny za pomocą przycisku Zabezpiecz nie spowoduje wylogowania Cię z witryny." + "value" : "Wybrane witryny zostaną wyczyszczone" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Utilizar uma barreira de segurança neste site vai mantê-lo ligado após utilizar o botão de segurança." + "value" : "Os sites selecionados serão limpos" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вам не придется заново входить в свою учетную запись даже после применения кнопки «Пожар»." + "value" : "Будут стерты данные выбранных сайтов" } } } }, - "fireproof.confirmation.title" : { - "comment" : "Fireproof confirmation title", + "fire.dialog.close-burner-window" : { + "comment" : "Button that allows the user to close and burn the browser burner window", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Möchtest du %@ feuerfest machen?" + "value" : "Dieses Fenster schließen und löschen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Would you like to Fireproof %@?" + "value" : "Close and Burn This Window" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Quieres marcar %@ como a prueba de fuego?" + "value" : "Cerrar y quemar esta ventana" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Souhaitez-vous placer %@ en mode coupe-feu ?" + "value" : "Fermer et brûler cette fenêtre" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Vuoi proteggere %@?" + "value" : "Chiudi ed elimina i dati per questa finestra" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wil je %@ brandveilig maken?" + "value" : "Dit venster sluiten en verwijderen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Chcesz zabezpieczyć %@?" + "value" : "Zamknij i zniszcz to okno" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Gostaria de usar a barreira de segurança em %@?" + "value" : "Fechar e queimar esta janela" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сделать сайт %@ огнеупорным?" + "value" : "Закрыть окно и стереть данные" } } } }, - "fireproof.domains.remove.all" : { - "comment" : "Label of a button that allows the user to remove all the websites from the fireproofed list", + "fire.dialog.deliting.data" : { + "comment" : "Text shown in dialog while removing browsing data", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alle entfernen" + "value" : "Browserdaten werden gelöscht …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Remove All" + "value" : "Deleting browsing data…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar todo" + "value" : "Eliminar los datos de navegación..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Tout supprimer" + "value" : "Suppression des données de navigation…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Elimina tutto" + "value" : "Eliminazione dei dati di navigazione…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alles verwijderen" + "value" : "Surfgegevens verwijderen..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usuń wszystko" + "value" : "Usuwanie danych przeglądania…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Remover tudo" + "value" : "A eliminar dados de navegação…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удалить все" + "value" : "Удаление данных о посещении сайтов..." } } } }, - "fireproof.explanation" : { - "comment" : "Fireproofing mechanism explanation", + "fire.dialog.details" : { + "comment" : "Button to show more details", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wenn du eine Website feuerfest machst, werden Cookies nicht gelöscht und du bleibst angemeldet – auch nach Verwendung der Schaltfläche „Feuer“." + "value" : "Details" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "When you Fireproof a site, cookies won't be erased and you'll stay signed in, even after using the Fire Button." + "value" : "Details" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cuando marcas una web como «a prueba de fuego» no se borrarán las cookies y tu sesión permanecerá iniciada, incluso después de utilizar el botón Fuego." + "value" : "Detalles" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Quand vous placez un site en mode coupe-feu, les cookies sont conservés et vous restez connecté(e), même après avoir utilisé le bouton en forme de flamme." + "value" : "Détails" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Quando attivi la Protezione per un sito, i cookie non verranno cancellati e rimarrai connesso, anche dopo aver usato il Fire Button (icona del fuoco)." + "value" : "Dettagli" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Als je een site brandveilig maakt, worden de cookies niet gewist en blijf je ingelogd, zelfs nadat je de vuurknop hebt gebruikt." + "value" : "Details" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Kiedy użyjesz w witrynie funkcji zabezpieczenia, pliki cookie nie zostaną usunięte i pozostaniesz zalogowany(-a) nawet po użyciu przycisku Zabezpiecz." + "value" : "Szczegóły" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Quando se utiliza uma barreira de segurança num site, os cookies não serão apagados e permanecerá ligado, mesmo depois de utilizar o botão de segurança." + "value" : "Detalhes" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Если сделать сайт «огнеупорным» (Fireproof), мы не будем стирать его куки-файлы, даже если вы воспользуетесь кнопкой «Тревога» (Fire Button)." + "value" : "Подробнее" } } } }, - "fireproof.manage-sites" : { - "comment" : "Fireproof settings button caption", + "fire.dialog.fire-window.close-tabs" : { + "comment" : "Title of the dialog where the user can close browser tabs and clear data.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fireproof-Sites verwalten …" + "value" : "Tabs schließen und Daten löschen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Manage Fireproof Sites…" + "value" : "Close Tabs and Clear Data" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Gestionar sitios Fireproof..." + "value" : "Cerrar pestañas y borrar datos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Gérer les sites en mode coupe-feu (Fireproof)…" + "value" : "Fermer les onglets et effacer les données" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Gestisci i siti Fireproof…" + "value" : "Chiudi le schede e cancella i dati" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Fireproof websites beheren ..." + "value" : "Tabbladen sluiten en gegevens wissen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zarządzaj witrynami Fireproof…" + "value" : "Zamknij karty i wyczyść dane" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Gerir sites com Fireproof…" + "value" : "Fechar separadores e limpar os dados" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Управление огнеупорными сайтами..." + "value" : "Закрыть вкладки и удалить данные" } } } }, - "fireproof.sites" : { - "comment" : "Fireproof sites list title", + "fire.dialog.fire-window.description" : { + "comment" : "Explanation of what a fire window is.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Feuerfeste Seiten" + "value" : "Ein isoliertes Fenster, das keine Daten speichert" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Fireproof Sites" + "value" : "An isolated window that doesn’t save any data" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Sitios web a prueba de fuego" + "value" : "Una ventana aislada que no guarda ningún dato" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Sites coupe-feu" + "value" : "Une fenêtre isolée qui n'enregistre aucune donnée" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Siti ignifughi" + "value" : "Una finestra isolata che non salva alcun dato" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Brandveilige websites" + "value" : "Een geïsoleerd venster dat geen gegevens opslaat" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Bezpieczne witryny internetowe" + "value" : "Odizolowane okno, które nie zapisuje żadnych danych" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Sites com barreira de segurança" + "value" : "Uma janela isolada que não guarda quaisquer dados" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Огнеупорные сайты" + "value" : "Изолированное окно, где не сохраняются никакие данные" } } } }, - "folder.dialog.add" : { - "comment" : "Button to confim a bookmark folder creation", + "fire.dialog.fire-window.title" : { + "comment" : "Title of the part of the dialog where the user can open a fire window.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hinzufügen" + "value" : "Neues Fire Window öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Add" + "value" : "Open New Fire Window" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Añadir" + "value" : "Abrir nueva Fire Window" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ajouter" + "value" : "Ouvrir une nouvelle Fire Window" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aggiungi" + "value" : "Apri una nuova Fire Window" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Toevoegen" + "value" : "Nieuw Fire Window openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dodaj" + "value" : "Otwórz nowe okno Fire Window" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Adicionar" + "value" : "Abrir nova Fire Window" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Добавить" + "value" : "Открыть новое окно Fire Window" } } } }, - "folder.optionsMenu.deleteFolder" : { - "comment" : "Option for deleting a folder", + "fire.dialog.fireproof.sites" : { + "comment" : "Category of domains in fire button dialog", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ordner löschen" + "value" : "Fireproof Standorte werden nicht geräumt" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Delete Folder" + "value" : "Fireproof sites won't be cleared" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar carpeta" + "value" : "No se borrarán los sitios Fireproof" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Supprimer le dossier" + "value" : "Les sites en mode coupe-feu (Fireproof) ne seront pas effacés" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Elimina cartella" + "value" : "I dati dei siti Fireproof non saranno eliminati" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Map verwijderen" + "value" : "Fireproof websites worden niet verwijderd" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usuń folder" + "value" : "Witryny Fireproof nie zostaną wyczyszczone" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar pasta" + "value" : "Os sites Fireproof não serão limpos" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удалить папку" + "value" : "Данные огнеупорных сайтов не стираются" } } } }, - "folder.optionsMenu.newFolder" : { - "comment" : "Option for creating a new folder", + "fire.dialog.tab-will-close" : { + "comment" : "Warning label shown in an expanded view of the fire popover", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neuer Ordner" + "value" : "Aktueller Tab wird geschlossen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "New Folder" + "value" : "Current tab will close" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nueva carpeta" + "value" : "Se cerrará la pestaña actual" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nouveau dossier" + "value" : "L'onglet actuel se fermera" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nuova cartella" + "value" : "La scheda corrente si chiuderà" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuwe map" + "value" : "Huidige tabblad wordt gesloten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nowy folder" + "value" : "Bieżąca karta zostanie zamknięta" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nova pasta" + "value" : "O separador atual será fechado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Новая папка" + "value" : "Текущая вкладка будет закрыта" } } } }, - "folder.optionsMenu.renameFolder" : { - "comment" : "Option for renaming a folder", + "fire.dialog.tab-will-reload" : { + "comment" : "Warning label shown in an expanded view of the fire popover", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ordner umbenennen" + "value" : "Angepinnter Tab wird neu geladen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Rename Folder" + "value" : "Pinned tab will reload" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cambiar nombre de carpeta" + "value" : "La pestaña anclada se volverá a cargar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Renommer le dossier" + "value" : "L'onglet épinglé se rechargera" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rinomina cartella" + "value" : "La scheda bloccata si ricaricherà" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Naam van map wijzigen" + "value" : "Vastgezet tabblad wordt opnieuw geladen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zmień nazwę folderu" + "value" : "Przypięta karta zostanie załadowana ponownie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Alterar nome da pasta" + "value" : "O separador afixado será recarregado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Переименовать папку" + "value" : "Закрепленная вкладка будет перезагружена" } } } }, - "generic.delete.button" : { - "comment" : "Label of a button that allows the user to delete an item", + "fire.dialog.window-will-close" : { + "comment" : "Warning label shown in an expanded view of the fire popover", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Löschen" + "value" : "Aktuelles Fenster wird geschlossen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Delete" + "value" : "Current window will close" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar" + "value" : "La ventana actual se cerrará" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Supprimer" + "value" : "La fenêtre actuelle se fermera" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Elimina" + "value" : "La finestra corrente sarà chiusa" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verwijderen" + "value" : "Huidig venster wordt gesloten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usuń" + "value" : "Bieżące okno zostanie zamknięte" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar" + "value" : "A janela atual será fechada" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удалить" + "value" : "Текущее окно будет закрыто" } } } }, - "generic.discard.button" : { - "comment" : "Label of a button that allows the user discard an action/change", + "fire.info.dialog.description" : { + "comment" : "Description in the dialog that explains the Fire feature.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verwerfen" + "value" : "Daten, der Browserverlauf und Cookies können sich mit der Zeit in deinem Browser ansammeln. Mit dem Fire Button löschst du alles." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Discard" + "value" : "Data, browsing history, and cookies can build up in your browser over time. Use the Fire Button to clear it all away." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Descartar" + "value" : "Los datos, el historial de navegación y las cookies pueden acumularse en tu navegador con el tiempo. Utiliza el Fire Button para quemarlos." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Abandonner" + "value" : "Les données, l'historique de navigation et les cookies peuvent s'accumuler dans votre navigateur au fil du temps. Utilisez le Fire Button pour les supprimer." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Abbandona" + "value" : "Con il passare del tempo, nel tuo browser possono accumularsi dati, cronologia di navigazione e cookie. Usa il pulsante Fire Button per eliminare tutto." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ongedaan maken" + "value" : "Gegevens, browsegeschiedenis en cookies kunnen na verloop van tijd je browser overbelasten. Gebruik de Fire Button om alles te verbranden." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Odrzuć" + "value" : "W przeglądarce z czasem może zgromadzić się sporo danych, historii przeglądania i plików cookie. Przyciskiem Fire Button możesz to wszystko wyczyścić." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar" + "value" : "Os dados, o histórico de navegação e os cookies podem acumular-se no teu navegador ao longo do tempo. Utiliza o Fire Button para limpar tudo." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отменить" + "value" : "Со временем в браузере накапливаются данные, история просмотров и куки-файлы. Кнопка Fire Button позволяет «сжечь» весь лишний мусор." } } } }, - "generic.remove.button" : { - "comment" : "Label of a button that allows the user to remove an item", + "fire.info.dialog.title" : { + "comment" : "Title of the dialog that explains the Fire feature.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Entfernen" + "value" : "Keine Spuren hinterlassen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Remove" + "value" : "Leave No Trace" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar" + "value" : "No dejes rastro" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Supprimer" + "value" : "Ne laissez aucune trace" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rimuovi" + "value" : "Non lasciare traccia" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verwijderen" + "value" : "Laat geen sporen achter" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usuń" + "value" : "Nie zostawiaj śladów" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Remover" + "value" : "Não deixar vestígios" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удалить" + "value" : "И никаких следов" } } } }, - "got.it" : { - "comment" : "Got it button", + "fire.one-tab-info" : { + "comment" : "Info in the Fire Button popover", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verstanden." + "value" : "Schließe diesen Tab und lösche Browserverlauf und Cookies (Sites: %d)." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Got It!" + "value" : "Close this tab and clear its browsing history and cookies (sites: %d)." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Entendido" + "value" : "Cierra esta pestaña y borra tu historial de navegación y cookies (sitios: %d)." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "J'ai compris !" + "value" : "Fermez cet onglet et effacez son historique de navigation et ses cookies (sites : %d)." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ho capito!" + "value" : "Chiudi questa scheda e cancella la cronologia di navigazione e i cookie (siti: %d)." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ik snap het!" + "value" : "Sluit dit tabblad en wis de surfgeschiedenis en cookies (sites: %d)." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Rozumiem!" + "value" : "Zamknij tę kartę i wyczyść związaną z nią historię przeglądania i pliki cookie (witryny: %d)." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Entendi!" + "value" : "Fecha este separador e limpa o respetivo histórico de navegação e cookies (sites: %d)." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Понятно!" + "value" : "Функция Fire Button закроет данную вкладку, сотрет ее историю посещений и удалит куки-файлы (сайтов: %d)." } } } }, - "gpc.checkbox.title" : { - "comment" : "GPC settings checkbox title", + "fire.select-site-to-clear" : { + "comment" : "Info label in the fire button popover", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Global Privacy Control aktivieren" + "value" : "Wähle eine Site aus, um deren Daten zu löschen." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Enable Global Privacy Control" + "value" : "Select a site to clear its data." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Activar Global Privacy Control" + "value" : "Selecciona un sitio del que borrar sus datos." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Activer Global Privacy Control" + "value" : "Sélectionnez un site pour effacer ses données." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Abilita Global Privacy Control" + "value" : "Seleziona un sito per cancellarne i dati." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Algemene privacycontrole inschakelen" + "value" : "Selecteer een website om de gegevens te wissen." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Włącz Globalną Kontrolę Prywatności" + "value" : "Wybierz witrynę, aby wyczyścić jej dane." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ativar o Global Privacy Control" + "value" : "Seleciona um site para limpar os dados." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Включить глобальный контроль конфиденциальности (GPC)" + "value" : "Выберите сайт, чьи данные вы хотите стереть." } } } }, - "gpc.explanation" : { - "comment" : "GPC explanation in settings", + "fireproof" : { + "comment" : "Fireproof button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sagt den teilnehmenden Websites, dass sie deine Daten nicht verkaufen oder weitergeben dürfen." + "value" : "Feuerfest machen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Tells participating websites not to sell or share your data." + "value" : "Fireproof" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Indica a los sitios web participantes que no vendan ni compartan tus datos." + "value" : "A prueba de fuego" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Indique aux sites Web participants de ne pas vendre ou partager vos données." + "value" : "Mode coupe-feu" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Indica ai siti Web partecipanti di non vendere o condividere i tuoi dati." + "value" : "Proteggi dal fuoco" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Vertelt deelnemende websites dat ze je gegevens niet mogen verkopen of delen." + "value" : "Brandveilig" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Instruuje uczestniczące witryny, aby nie sprzedawały ani nie udostępniały Twoich danych." + "value" : "Zabezpiecz" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Indica aos sites participantes que não devem vender nem partilhar os teus dados." + "value" : "Barreira de segurança" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Требует от сайтов, участвующих в программе GPC, не продавать и не передавать ваши данные." + "value" : "Сделать огнеупорным" } } } }, - "Help" : { - "comment" : "Main Menu Help", + "fireproof.checkbox.title" : { + "comment" : "Fireproof settings checkbox title", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hilfe" + "value" : "Bei der Anmeldung nach Fireproof-Websites fragen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Ask to Fireproof websites when signing in" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ayuda" + "value" : "Preguntar si marcar sitios web como Fireproof al iniciar sesión" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aide" + "value" : "Demander à placer des sites Web en mode coupe-feu (Fireproof) lors de la connexion" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aiuto" + "value" : "Chiedi di applicare la funzionalità Fireproof per i siti web al momento dell'accesso" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Help" + "value" : "Vragen om websites te fireproofen bij inloggen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pomoc" + "value" : "Poproś o zabezpieczanie witryn funkcją Fireproof podczas logowania" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ajuda" + "value" : "Pedir para utilizar a funcionalidade Fireproof em sites ao iniciar sessão" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Справка" + "value" : "Предлагать огнеупорность сайтов при входе" } } } }, - "History" : { - "comment" : "Main Menu ", + "fireproof.confirmation.message" : { + "comment" : "Fireproof confirmation message", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verlauf" + "value" : "Wenn du diese Website feuerfest machst, bleibst du nach Verwendung der Schaltfläche „Feuer“ angemeldet." + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Fireproofing this site will keep you signed in after using the Fire Button." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Historial" + "value" : "Marcar esta página como a prueba de fuego hará que la sesión se mantenga iniciada después de usar el botón Fuego." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Historique" + "value" : "Placer ce site en mode coupe-feu et utiliser le bouton en forme de flamme ne vous déconnectera pas." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cronologia" + "value" : "Proteggere dal fuoco questo sito manterrà valido l'accesso dopo aver utilizzato il Fire Button (icona del fuoco)." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geschiedenis" + "value" : "Als je deze site brandveilig maakt, blijf je ook na het gebruiken van de vuurknop ingelogd." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Historia" + "value" : "Zabezpieczenie tej witryny za pomocą przycisku Zabezpiecz nie spowoduje wylogowania Cię z witryny." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Histórico" + "value" : "Utilizar uma barreira de segurança neste site vai mantê-lo ligado após utilizar o botão de segurança." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "История" + "value" : "Вам не придется заново входить в свою учетную запись даже после применения кнопки «Пожар»." } } } }, - "history.menu.clear.all.history.description" : { - "comment" : "Description in the alert with the confirmation to clear all data", + "fireproof.confirmation.title" : { + "comment" : "Fireproof confirmation title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Cookies und Website-Daten für alle Websites werden ebenfalls gelöscht, es sei denn, die Website ist Fireproof." + "value" : "Möchtest du %@ feuerfest machen?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Cookies and site data for all sites will also be cleared, unless the site is Fireproof." + "value" : "Would you like to Fireproof %@?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "También se borrarán las cookies y los datos del sitio para todos los sitios, a menos que el sitio sea Fireproof." + "value" : "¿Quieres marcar %@ como a prueba de fuego?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Les cookies et les données de tous les sites seront également effacés, sauf si le site est en mode coupe-feu (Fireproof)." + "value" : "Souhaitez-vous placer %@ en mode coupe-feu ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Saranno cancellati anche i cookie e i dati di tutti i siti, a meno che il sito non sia Fireproof." + "value" : "Vuoi proteggere %@?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Cookies en sitegegevens voor alle sites worden ook gewist, tenzij de site Fireproof is." + "value" : "Wil je %@ brandveilig maken?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zostaną także wyczyszczone pliki cookie i dane związane z wszystkimi witrynami, chyba że są to witryny Fireproof." + "value" : "Chcesz zabezpieczyć %@?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Os cookies e os dados de todos os sites também serão limpos, a menos que o site seja Fireproof." + "value" : "Gostaria de usar a barreira de segurança em %@?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Также будут удалены данные и куки-файлы со всех сайтов (кроме огнеупорных)." + "value" : "Сделать сайт %@ огнеупорным?" } } } }, - "history.menu.clear.all.history.question" : { - "comment" : "Alert with the confirmation to clear all history and data", + "fireproof.domains.remove.all" : { + "comment" : "Label of a button that allows the user to remove all the websites from the fireproofed list", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Den gesamten Verlauf löschen und alle Tabs schließen?" + "value" : "Alle entfernen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Clear all history and \nclose all tabs?" + "value" : "Remove All" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Borrar todo el historial y \ncerrar todas las pestañas?" + "value" : "Eliminar todo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Effacer tout l'historique et fermer tous les onglets ?" + "value" : "Tout supprimer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cancellare tutta la cronologia e \nchiudere tutte le schede?" + "value" : "Elimina tutto" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alle geschiedenis wissen en alle tabbladen sluiten?" + "value" : "Alles verwijderen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyczyścić całą historię \ni zamknąć wszystkie karty?" + "value" : "Usuń wszystko" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Limpar o histórico completo e \nfechar todos os separadores?" + "value" : "Remover tudo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Очистить всю историю и закрыть все вкладки?" + "value" : "Удалить все" } } } }, - "history.menu.clear.data.description" : { - "comment" : "Description in the alert with the confirmation to clear browsing history", + "fireproof.explanation" : { + "comment" : "Fireproofing mechanism explanation", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Cookies und andere Daten von Seiten, die an diesem Tag besucht wurden, werden ebenfalls gelöscht, es sei denn, die Seite ist mit Fireproof davon ausgeschlossen. Der Verlauf von anderen Tagen wird nicht gelöscht." + "value" : "Wenn du eine Website feuerfest machst, werden Cookies nicht gelöscht und du bleibst angemeldet – auch nach Verwendung der Schaltfläche „Feuer“." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Cookies and other data for sites visited on this day will also be cleared unless the site is Fireproof. History from other days will not be cleared." + "value" : "When you Fireproof a site, cookies won't be erased and you'll stay signed in, even after using the Fire Button." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Las cookies y otros datos de los sitios visitados ese día también se borrarán, a menos que el sitio sea Fireproof. El historial de otros días no se borrará." + "value" : "Cuando marcas una web como «a prueba de fuego» no se borrarán las cookies y tu sesión permanecerá iniciada, incluso después de utilizar el botón Fuego." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Les cookies et autres données des sites visités ce jour-là seront également effacés, sauf si le site est en mode coupe-feu (Fireproof). L'historique des autres jours ne sera pas effacé." + "value" : "Quand vous placez un site en mode coupe-feu, les cookies sont conservés et vous restez connecté(e), même après avoir utilisé le bouton en forme de flamme." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Anche i cookie e gli altri dati relativi ai siti visitati in questo giorno saranno cancellati, a meno che il sito non sia Fireproof. La cronologia degli altri giorni non sarà cancellata." + "value" : "Quando attivi la Protezione per un sito, i cookie non verranno cancellati e rimarrai connesso, anche dopo aver usato il Fire Button (icona del fuoco)." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Cookies en andere gegevens van websites die op deze dag bezocht zijn, worden ook gewist, tenzij de site Fireproof is. Geschiedenis van andere dagen wordt niet gewist." + "value" : "Als je een site brandveilig maakt, worden de cookies niet gewist en blijf je ingelogd, zelfs nadat je de vuurknop hebt gebruikt." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zostaną także wyczyszczone pliki cookie i inne dane związane z witrynami odwiedzonymi tego dnia, chyba że są to witryny Fireproof. Historia z innych dni nie zostanie wyczyszczona." + "value" : "Kiedy użyjesz w witrynie funkcji zabezpieczenia, pliki cookie nie zostaną usunięte i pozostaniesz zalogowany(-a) nawet po użyciu przycisku Zabezpiecz." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Os cookies e os dados dos sites visitados neste dia também serão limpos, a menos que o site seja Fireproof. O histórico de outros dias não será limpo." + "value" : "Quando se utiliza uma barreira de segurança num site, os cookies não serão apagados e permanecerá ligado, mesmo depois de utilizar o botão de segurança." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Также будут удалены куки-файлы и другие данные с посещенных за этот день сайтов (кроме огнеупорных). История за другие дни не удаляется." + "value" : "Если сделать сайт «огнеупорным» (Fireproof), мы не будем стирать его куки-файлы, даже если вы воспользуетесь кнопкой «Тревога» (Fire Button)." } } } }, - "history.menu.clear.data.question" : { - "comment" : "Alert with the confirmation to clear all data", + "fireproof.manage-sites" : { + "comment" : "Fireproof settings button caption", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verlauf für %@ löschen?" + "value" : "Fireproof-Sites verwalten …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Clear History for %@?" + "value" : "Manage Fireproof Sites…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Borrar historial para %@?" + "value" : "Gestionar sitios Fireproof..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Effacer l'historique de %@ ?" + "value" : "Gérer les sites en mode coupe-feu (Fireproof)…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cancella cronologia per %@?" + "value" : "Gestisci i siti Fireproof…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geschiedenis van %@ wissen?" + "value" : "Fireproof websites beheren ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyczyścić historię dotyczącą %@?" + "value" : "Zarządzaj witrynami Fireproof…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Limpar histórico de %@?" + "value" : "Gerir sites com Fireproof…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Стереть историю за %@?" + "value" : "Управление огнеупорными сайтами..." } } } }, - "history.menu.clear.data.today.description" : { - "comment" : "Description in the alert with the confirmation to clear browsing history", + "fireproof.sites" : { + "comment" : "Fireproof sites list title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Cookies und andere Daten für heute besuchte Seiten werden ebenfalls gelöscht, es sei denn, die Seite ist Fireproof. Der Verlauf von anderen Tagen wird nicht gelöscht." + "value" : "Feuerfeste Seiten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Cookies and other data for sites visited today will also be cleared unless the site is Fireproof. History from other days will not be cleared." + "value" : "Fireproof Sites" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Las cookies y otros datos de los sitios visitados hoy también se borrarán, a menos que el sitio sea Fireproof. El historial de otros días no se borrará." + "value" : "Sitios web a prueba de fuego" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Les cookies et autres données des sites visités aujourd'hui seront également effacés, sauf si le site est en mode coupe-feu (Fireproof). L'historique des autres jours ne sera pas effacé." + "value" : "Sites coupe-feu" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Saranno cancellati anche i cookie e gli altri dati relativi ai siti visitati oggi, a meno che il sito non sia Fireproof. La cronologia degli altri giorni non sarà cancellata." + "value" : "Siti ignifughi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Cookies en andere gegevens van websites die vandaag bezocht zijn, worden ook gewist, tenzij de site Fireproof is. Geschiedenis van andere dagen wordt niet gewist." + "value" : "Brandveilige websites" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zostaną także wyczyszczone pliki cookie i inne dane związane z odwiedzonymi dzisiaj witrynami, chyba że są to witryny Fireproof. Historia z innych dni nie zostanie wyczyszczona." + "value" : "Bezpieczne witryny internetowe" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Os cookies e outros dados dos sites visitados hoje também serão limpos, a menos que o site seja Fireproof. O histórico de outros dias não será limpo." + "value" : "Sites com barreira de segurança" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Также будут удалены куки-файлы и другие данные с посещенных за сегодня сайтов (кроме огнеупорных). История за другие дни не пострадает." + "value" : "Огнеупорные сайты" } } } }, - "history.menu.clear.data.today.question" : { - "comment" : "Alert with the confirmation to clear all data", + "folder.dialog.add" : { + "comment" : "Button to confim a bookmark folder creation", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verlauf für heute löschen und alle Tabs schließen?" + "value" : "Hinzufügen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Clear history for today \nand close all tabs?" + "value" : "Add" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Borrar historial de hoy y \ncerrar todas las pestañas?" + "value" : "Añadir" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Effacer l'historique d'aujourd'hui et fermer tous les onglets ?" + "value" : "Ajouter" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cancellare la cronologia per oggi \ne chiudere tutte le schede?" + "value" : "Aggiungi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geschiedenis van vandaag wissen \nen alle tabbladen sluiten?" + "value" : "Toevoegen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyczyścić dzisiejszą historię \ni zamknąć wszystkie karty?" + "value" : "Dodaj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Limpar o histórico de hoje e \nfechar todos os separadores?" + "value" : "Adicionar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Стереть историю за сегодня \nи закрыть все вкладки?" + "value" : "Добавить" } } } }, - "history.menu.clear.this.history" : { - "comment" : "Menu item to clear parts of history and data", + "folder.optionsMenu.deleteFolder" : { + "comment" : "Option for deleting a folder", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Diese Geschichte löschen …" + "value" : "Ordner löschen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Clear This History…" + "value" : "Delete Folder" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Borrar este historial..." + "value" : "Eliminar carpeta" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Effacer cet historique…" + "value" : "Supprimer le dossier" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cancella questa cronologia..." + "value" : "Elimina cartella" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Deze geschiedenis wissen ..." + "value" : "Map verwijderen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyczyść tę historię…" + "value" : "Usuń folder" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Limpar este histórico…" + "value" : "Eliminar pasta" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Стереть эту запись…" + "value" : "Удалить папку" } } } }, - "history.menu.older" : { - "comment" : "Menu item representing older history", + "folder.optionsMenu.newFolder" : { + "comment" : "Option for creating a new folder", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Älter …" + "value" : "Neuer Ordner" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Older…" + "value" : "New Folder" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Más antiguo..." + "value" : "Nueva carpeta" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ancien…" + "value" : "Nouveau dossier" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Precedente..." + "value" : "Nuova cartella" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ouder ..." + "value" : "Nieuwe map" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Starsze…" + "value" : "Nowy folder" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mais antigo…" + "value" : "Nova pasta" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Более старые записи..." + "value" : "Новая папка" } } } }, - "history.menu.recently.visited" : { - "comment" : "Section header of the history menu", + "folder.optionsMenu.renameFolder" : { + "comment" : "Option for renaming a folder", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kürzlich besucht" + "value" : "Ordner umbenennen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Recently Visited" + "value" : "Rename Folder" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Visitado recientemente" + "value" : "Cambiar nombre de carpeta" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Visites récentes" + "value" : "Renommer le dossier" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Visite recenti" + "value" : "Rinomina cartella" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Onlangs bezocht" + "value" : "Naam van map wijzigen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ostatnio odwiedzone" + "value" : "Zmień nazwę folderu" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Visitados recentemente" + "value" : "Alterar nome da pasta" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Недавно посещенные" + "value" : "Переименовать папку" } } } }, - "Home" : { - "comment" : "Main Menu View item", + "generic.delete.button" : { + "comment" : "Label of a button that allows the user to delete an item", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Startseite" + "value" : "Löschen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Delete" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Inicio" + "value" : "Eliminar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Accueil" + "value" : "Supprimer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Schermata iniziale" + "value" : "Elimina" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Home" + "value" : "Verwijderen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Strona główna" + "value" : "Usuń" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Página inicial" + "value" : "Eliminar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Домашняя" + "value" : "Удалить" } } } }, - "Home Button" : { - "comment" : "Main Menu > View > Home Button item", + "generic.discard.button" : { + "comment" : "Label of a button that allows the user discard an action/change", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Home-Button" + "value" : "Verwerfen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Discard" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Botón Inicio" + "value" : "Descartar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Bouton d'accueil" + "value" : "Abandonner" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Pulsante Home" + "value" : "Abbandona" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Home-knop" + "value" : "Ongedaan maken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przycisk Początek" + "value" : "Odrzuć" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Botão Início" + "value" : "Eliminar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Кнопка «Домой»" + "value" : "Отменить" } } } }, - "home.page.burn.fireproof.site.alert" : { - "comment" : "Message for an alert displayed when trying to burn a fireproof website", + "generic.remove.button" : { + "comment" : "Label of a button that allows the user to remove an item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Der Verlauf dieser Seite wird gelöscht, aber die zugehörigen Daten bleiben erhalten, da diese Seite zu Fireproof hinzugefügt wurde" + "value" : "Entfernen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "History will be cleared for this site, but related data will remain, because this site is Fireproof" + "value" : "Remove" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Se borrará el historial de este sitio, pero los datos relacionados permanecerán, porque este sitio es Fireproof" + "value" : "Eliminar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "L'historique de ce site sera effacé, mais les données associées resteront, car ce site est en mode coupe-feu (Fireproof)" + "value" : "Supprimer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "La cronologia sarà cancellata per questo sito, ma i dati correlati rimarranno, perché questo sito è Fireproof" + "value" : "Rimuovi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "De geschiedenis van deze site wordt gewist, maar de gerelateerde gegevens blijven behouden, omdat deze site Fireproof is" + "value" : "Verwijderen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Historia dotycząca tej witryny zostanie wyczyszczona, ale powiązane dane pozostaną, ponieważ jest to witryna Fireproof" + "value" : "Usuń" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O histórico será limpo para este site, mas os dados relacionados permanecem, porque este site é Fireproof" + "value" : "Remover" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "История посещений этого сайта будет стерта, но остальные данные останутся, так как сайт огнеупорный." + "value" : "Удалить" } } } }, - "home.page.clear.history" : { - "comment" : "Button caption for the burn fireproof website alert", + "got.it" : { + "comment" : "Got it button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verlauf löschen" + "value" : "Verstanden." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Clear History" + "value" : "Got It!" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Borrar historial" + "value" : "Entendido" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Effacer l'historique" + "value" : "J'ai compris !" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Elimina cronologia" + "value" : "Ho capito!" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geschiedenis wissen" + "value" : "Ik snap het!" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyczyść historię" + "value" : "Rozumiem!" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Limpar histórico" + "value" : "Entendi!" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Очистить историю" + "value" : "Понятно!" } } } }, - "home.page.empty.state.item.message" : { - "comment" : "This string represents the message for an empty state item on the home page, encouraging the user to keep browsing to see how many trackers were blocked", + "gpc.checkbox.title" : { + "comment" : "GPC settings checkbox title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Browse weiter, um zu sehen, wie viele Tracker blockiert wurden" + "value" : "Global Privacy Control aktivieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Keep browsing to see how many trackers were blocked" + "value" : "Enable Global Privacy Control" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Sigue navegando para ver cuántos rastreadores se han bloqueado" + "value" : "Activar Global Privacy Control" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Continuez à naviguer pour voir combien de traqueurs ont été bloqués." + "value" : "Activer Global Privacy Control" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Continua la navigazione per vedere quanti sistemi di tracciamento sono stati bloccati" + "value" : "Abilita Global Privacy Control" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Blijf bladeren om te zien hoeveel trackers zijn geblokkeerd" + "value" : "Algemene privacycontrole inschakelen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przeglądaj dalej, aby zobaczyć liczbę zablokowanych mechanizmów śledzących" + "value" : "Włącz Globalną Kontrolę Prywatności" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Continua a navegar para veres quantos rastreadores foram bloqueados" + "value" : "Ativar o Global Privacy Control" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Продолжайте бродить по интернету, а потом посмотрите, сколько трекеров мы заблокировали." + "value" : "Включить глобальный контроль конфиденциальности (GPC)" } } } }, - "home.page.empty.state.item.title" : { - "comment" : "This string represents the title for an empty state item on the home page, indicating that recently visited sites will appear here", + "gpc.explanation" : { + "comment" : "GPC explanation in settings", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zuletzt besuchte Seiten werden hier angezeigt" + "value" : "Sagt den teilnehmenden Websites, dass sie deine Daten nicht verkaufen oder weitergeben dürfen." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Recently visited sites appear here" + "value" : "Tells participating websites not to sell or share your data." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Los sitios visitados recientemente aparecen aquí" + "value" : "Indica a los sitios web participantes que no vendan ni compartan tus datos." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Les sites récemment visités apparaissent ici" + "value" : "Indique aux sites Web participants de ne pas vendre ou partager vos données." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "I siti visitati di recente si visualizzano qui" + "value" : "Indica ai siti Web partecipanti di non vendere o condividere i tuoi dati." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Onlangs bezochte websites verschijnen hier" + "value" : "Vertelt deelnemende websites dat ze je gegevens niet mogen verkopen of delen." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Tutaj są widoczne ostatnio odwiedzone witryny" + "value" : "Instruuje uczestniczące witryny, aby nie sprzedawały ani nie udostępniały Twoich danych." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Os sites visitados recentemente aparecem aqui" + "value" : "Indica aos sites participantes que não devem vender nem partilhar os teus dados." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Недавно посещенные сайты появятся здесь" + "value" : "Требует от сайтов, участвующих в программе GPC, не продавать и не передавать ваши данные." } } } }, - "home.page.no.trackers.blocked" : { - "comment" : "This string represents a message on the home page indicating that no trackers were blocked", - "extractionState" : "extracted_with_value", + "Help" : { + "comment" : "Main Menu Help", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine Tracker blockiert" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "No trackers blocked" + "value" : "Hilfe" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No se han bloqueado rastreadores" + "value" : "Ayuda" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aucun traqueur bloqué" + "value" : "Aide" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nessun sistema di tracciamento bloccato" + "value" : "Aiuto" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geen trackers geblokkeerd" + "value" : "Help" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie zablokowano żadnych mechanizmów śledzących" + "value" : "Pomoc" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nenhum rastreador bloqueado" + "value" : "Ajuda" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Заблокированных трекеров пока нет" + "value" : "Справка" } } } }, - "home.page.no.trackers.found" : { - "comment" : "This string represents a message on the home page indicating that no trackers were found", - "extractionState" : "extracted_with_value", + "History" : { + "comment" : "Main Menu ", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine Tracker gefunden" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "No trackers found" + "value" : "Verlauf" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No se han encontrado rastreadores" + "value" : "Historial" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aucun traqueur trouvé" + "value" : "Historique" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nessun sistema di tracciamento trovato" + "value" : "Cronologia" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geen trackers gevonden" + "value" : "Geschiedenis" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie znaleziono żadnych mechanizmów śledzących" + "value" : "Historia" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nenhum rastreador encontrado" + "value" : "Histórico" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Ни одного трекера не найдено" + "value" : "История" } } } }, - "home.page.protection.duration" : { - "comment" : "Past 7 days in uppercase.", + "history.menu.clear.all.history.description" : { + "comment" : "Description in the alert with the confirmation to clear all data", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Vergangene 7 Tage" + "value" : "Cookies und Website-Daten für alle Websites werden ebenfalls gelöscht, es sei denn, die Website ist Fireproof." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "PAST 7 DAYS" + "value" : "Cookies and site data for all sites will also be cleared, unless the site is Fireproof." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Últimos 7 días" + "value" : "También se borrarán las cookies y los datos del sitio para todos los sitios, a menos que el sitio sea Fireproof." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "7 derniers jours" + "value" : "Les cookies et les données de tous les sites seront également effacés, sauf si le site est en mode coupe-feu (Fireproof)." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ultimi 7 giorni" + "value" : "Saranno cancellati anche i cookie e i dati di tutti i siti, a meno che il sito non sia Fireproof." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Afgelopen 7 dagen" + "value" : "Cookies en sitegegevens voor alle sites worden ook gewist, tenzij de site Fireproof is." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ostatnie 7 dni" + "value" : "Zostaną także wyczyszczone pliki cookie i dane związane z wszystkimi witrynami, chyba że są to witryny Fireproof." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Últimos 7 dias" + "value" : "Os cookies e os dados de todos os sites também serão limpos, a menos que o site seja Fireproof." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "За последние 7 дней" + "value" : "Также будут удалены данные и куки-файлы со всех сайтов (кроме огнеупорных)." } } } }, - "home.page.protection.summary.info" : { - "comment" : "This string represents a message in the protection summary on the home page, indicating that there is no recent activity", + "history.menu.clear.all.history.question" : { + "comment" : "Alert with the confirmation to clear all history and data", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine aktuelle Aktivität" + "value" : "Den gesamten Verlauf löschen und alle Tabs schließen?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "No recent activity" + "value" : "Clear all history and \nclose all tabs?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No hay actividad reciente" + "value" : "¿Borrar todo el historial y \ncerrar todas las pestañas?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aucune activité récente" + "value" : "Effacer tout l'historique et fermer tous les onglets ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nessuna attività recente" + "value" : "Cancellare tutta la cronologia e \nchiudere tutte le schede?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geen recente activiteit" + "value" : "Alle geschiedenis wissen en alle tabbladen sluiten?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Brak ostatniej aktywności" + "value" : "Wyczyścić całą historię \ni zamknąć wszystkie karty?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nenhuma atividade recente" + "value" : "Limpar o histórico completo e \nfechar todos os separadores?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Недавней активности пока нет" + "value" : "Очистить всю историю и закрыть все вкладки?" } } } }, - "home.page.protection.summary.message" : { - "comment" : "The number of tracking attempts blocked in the last 7 days, shown on a new tab, translate as: Tracking attempts blocked: %@", + "history.menu.clear.data.description" : { + "comment" : "Description in the alert with the confirmation to clear browsing history", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%@ Tracking-Versuche blockiert" + "value" : "Cookies und andere Daten von Seiten, die an diesem Tag besucht wurden, werden ebenfalls gelöscht, es sei denn, die Seite ist mit Fireproof davon ausgeschlossen. Der Verlauf von anderen Tagen wird nicht gelöscht." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%@ tracking attempts blocked" + "value" : "Cookies and other data for sites visited on this day will also be cleared unless the site is Fireproof. History from other days will not be cleared." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%@ intentos de rastreo bloqueados" + "value" : "Las cookies y otros datos de los sitios visitados ese día también se borrarán, a menos que el sitio sea Fireproof. El historial de otros días no se borrará." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%@ tentatives de pistage bloquées" + "value" : "Les cookies et autres données des sites visités ce jour-là seront également effacés, sauf si le site est en mode coupe-feu (Fireproof). L'historique des autres jours ne sera pas effacé." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%@ tentativi di tracciamento bloccati" + "value" : "Anche i cookie e gli altri dati relativi ai siti visitati in questo giorno saranno cancellati, a meno che il sito non sia Fireproof. La cronologia degli altri giorni non sarà cancellata." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%@ trackingpogingen geblokkeerd" + "value" : "Cookies en andere gegevens van websites die op deze dag bezocht zijn, worden ook gewist, tenzij de site Fireproof is. Geschiedenis van andere dagen wordt niet gewist." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zablokowane próby śledzenia: %@" + "value" : "Zostaną także wyczyszczone pliki cookie i inne dane związane z witrynami odwiedzonymi tego dnia, chyba że są to witryny Fireproof. Historia z innych dni nie zostanie wyczyszczona." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Tentativas de rastreamento bloqueadas: %@" + "value" : "Os cookies e os dados dos sites visitados neste dia também serão limpos, a menos que o site seja Fireproof. O histórico de outros dias não será limpo." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пресечено попыток отслеживания: %@" + "value" : "Также будут удалены куки-файлы и другие данные с посещенных за этот день сайтов (кроме огнеупорных). История за другие дни не удаляется." } } } }, - "If your computer prompts you to enter a password prior to import, DuckDuckGo will not see that password.\n\nImported passwords are stored securely using encryption." : { - "comment" : "Warning that Chromium data import would require entering system passwords.", - "extractionState" : "stale", + "history.menu.clear.data.question" : { + "comment" : "Alert with the confirmation to clear all data", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wenn dein Computer dich vor dem Import auffordert, ein Passwort einzugeben, wird DuckDuckGo das Passwort nicht sehen.\n\nImportierte Passwörter werden durch Verschlüsselung sicher gespeichert." + "value" : "Verlauf für %@ löschen?" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Clear History for %@?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Si tu ordenador te pide que introduzcas una contraseña antes de importar, DuckDuckGo no la verá.\n\nLas contraseñas importadas se almacenan de forma segura mediante encriptación." + "value" : "¿Borrar historial para %@?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Si votre ordinateur vous invite à saisir un mot de passe avant l'importation, DuckDuckGo ne verra pas ce dernier.\n\nLes mots de passe importés sont stockés en toute sécurité grâce à un cryptage." + "value" : "Effacer l'historique de %@ ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Se il computer richiede di immettere una password prima dell'importazione, DuckDuckGo non visualizza la password.\n\nLe password importate vengono archiviate in modo sicuro tramite crittografia." + "value" : "Cancella cronologia per %@?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Als je computer je vraagt om vóór het importeren een wachtwoord in te voeren, zal DuckDuckGo dat wachtwoord niet zien.\n\nGeïmporteerde wachtwoorden worden veilig opgeslagen door middel van versleuteling." + "value" : "Geschiedenis van %@ wissen?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Jeśli komputer poprosi Cię o wprowadzenie hasła przed rozpoczęciem importowania, DuckDuckGo nie zobaczy tego hasła.\n\nZaimportowane hasła są bezpiecznie przechowywane przy użyciu szyfrowania." + "value" : "Wyczyścić historię dotyczącą %@?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Se for apresentada uma solicitação no teu computador para introduzires uma palavra-passe antes da importação, o DuckDuckGo não a irá ver.\n\nAs palavras-passe importadas são armazenadas com segurança por meio de encriptação." + "value" : "Limpar histórico de %@?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Если перед импортированием компьютер попросит вас ввести пароль, DuckDuckGo его не увидит.\n\nИмпортированные пароли надежно хранятся в зашифрованном виде." + "value" : "Стереть историю за %@?" } } } }, - "Import" : { - "comment" : "Menu item", - "extractionState" : "stale", + "history.menu.clear.data.today.description" : { + "comment" : "Description in the alert with the confirmation to clear browsing history", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Importieren" + "value" : "Cookies und andere Daten für heute besuchte Seiten werden ebenfalls gelöscht, es sei denn, die Seite ist Fireproof. Der Verlauf von anderen Tagen wird nicht gelöscht." + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Cookies and other data for sites visited today will also be cleared unless the site is Fireproof. History from other days will not be cleared." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importar" + "value" : "Las cookies y otros datos de los sitios visitados hoy también se borrarán, a menos que el sitio sea Fireproof. El historial de otros días no se borrará." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importer" + "value" : "Les cookies et autres données des sites visités aujourd'hui seront également effacés, sauf si le site est en mode coupe-feu (Fireproof). L'historique des autres jours ne sera pas effacé." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importa" + "value" : "Saranno cancellati anche i cookie e gli altri dati relativi ai siti visitati oggi, a meno che il sito non sia Fireproof. La cronologia degli altri giorni non sarà cancellata." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Importeren" + "value" : "Cookies en andere gegevens van websites die vandaag bezocht zijn, worden ook gewist, tenzij de site Fireproof is. Geschiedenis van andere dagen wordt niet gewist." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Import" + "value" : "Zostaną także wyczyszczone pliki cookie i inne dane związane z odwiedzonymi dzisiaj witrynami, chyba że są to witryny Fireproof. Historia z innych dni nie zostanie wyczyszczona." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importar" + "value" : "Os cookies e outros dados dos sites visitados hoje também serão limpos, a menos que o site seja Fireproof. O histórico de outros dias não será limpo." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Импорт" + "value" : "Также будут удалены куки-файлы и другие данные с посещенных за сегодня сайтов (кроме огнеупорных). История за другие дни не пострадает." } } } }, - "Import Bookmarks" : { - "comment" : "Title of dialog with instruction for the user to import bookmarks from another browser", + "history.menu.clear.data.today.question" : { + "comment" : "Alert with the confirmation to clear all data", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen importieren" + "value" : "Verlauf für heute löschen und alle Tabs schließen?" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Clear history for today \nand close all tabs?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importar marcadores" + "value" : "¿Borrar historial de hoy y \ncerrar todas las pestañas?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importer des favoris" + "value" : "Effacer l'historique d'aujourd'hui et fermer tous les onglets ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importa segnalibri" + "value" : "Cancellare la cronologia per oggi \ne chiudere tutte le schede?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bladwijzers importeren" + "value" : "Geschiedenis van vandaag wissen \nen alle tabbladen sluiten?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importuj zakładki" + "value" : "Wyczyścić dzisiejszą historię \ni zamknąć wszystkie karty?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importar marcadores" + "value" : "Limpar o histórico de hoje e \nfechar todos os separadores?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Импортировать закладки" + "value" : "Стереть историю за сегодня \nи закрыть все вкладки?" } } } }, - "Import Passwords" : { - "comment" : "Title of dialog with instruction for the user to import passwords from another browser", + "history.menu.clear.this.history" : { + "comment" : "Menu item to clear parts of history and data", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter importieren" + "value" : "Diese Geschichte löschen …" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Clear This History…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importar contraseñas" + "value" : "Borrar este historial..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importer les mots de passe" + "value" : "Effacer cet historique…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importa password" + "value" : "Cancella questa cronologia..." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoorden importeren" + "value" : "Deze geschiedenis wissen ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importuj hasła" + "value" : "Wyczyść tę historię…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importar palavras-passe" + "value" : "Limpar este histórico…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Импорт паролей" + "value" : "Стереть эту запись…" } } } }, - "Import Results:" : { - "comment" : "Data Import result summary headline", + "history.menu.older" : { + "comment" : "Menu item representing older history", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ergebnisse importieren:" + "value" : "Älter …" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Older…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importar resultados:" + "value" : "Más antiguo..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Résultats de l'importation :" + "value" : "Ancien…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Risultati dell'importazione:" + "value" : "Precedente..." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Resultaten van de import:" + "value" : "Ouder ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyniki importowania:" + "value" : "Starsze…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importar resultados:" + "value" : "Mais antigo…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Результаты импорта:" + "value" : "Более старые записи..." } } } }, - "import.bookmarks.bookmarks" : { - "comment" : "Title text for the Bookmarks import option", + "history.menu.recently.visited" : { + "comment" : "Section header of the history menu", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen" + "value" : "Kürzlich besucht" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Bookmarks" + "value" : "Recently Visited" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Marcadores" + "value" : "Visitado recientemente" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Signets" + "value" : "Visites récentes" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Segnalibri" + "value" : "Visite recenti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bladwijzers" + "value" : "Onlangs bezocht" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zakładki" + "value" : "Ostatnio odwiedzone" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Marcadores" + "value" : "Visitados recentemente" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Закладки" + "value" : "Недавно посещенные" } } } }, - "import.bookmarks.html.title" : { - "comment" : "Title text for the HTML Bookmarks importer", - "extractionState" : "extracted_with_value", + "Home" : { + "comment" : "Main Menu View item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "HTML-Lesezeichen-Datei (für andere Browser)" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "HTML Bookmarks File (for other browsers)" + "value" : "Startseite" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Archivo de marcadores HTML (para otros navegadores)" + "value" : "Inicio" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fichier de signets HTML (pour les autres navigateurs)" + "value" : "Accueil" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "File segnalibri HTML (per altri browser)" + "value" : "Schermata iniziale" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "HTML-bladwijzers (voor andere browsers)" + "value" : "Home" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Plik zakładek HTML (dla innych przeglądarek)" + "value" : "Strona główna" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ficheiro HTML de marcadores (para outros navegadores)" + "value" : "Página inicial" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Файл закладок в HTML (для других браузеров)" + "value" : "Домашняя" } } } }, - "import.bookmarks.indefinite.progress.text" : { - "comment" : "Operation progress info message about indefinite number of bookmarks being imported", - "extractionState" : "extracted_with_value", + "Home Button" : { + "comment" : "Main Menu > View > Home Button item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen werden importiert …" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Importing bookmarks…" + "value" : "Home-Button" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importando marcadores…" + "value" : "Botón Inicio" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importation des signets…" + "value" : "Bouton d'accueil" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importazione segnalibri in corso…" + "value" : "Pulsante Home" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bladwijzers importeren ..." + "value" : "Home-knop" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importowanie zakładek…" + "value" : "Przycisk Początek" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "A importar marcadores…" + "value" : "Botão Início" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Закладки импортируются..." + "value" : "Кнопка «Домой»" } } } }, - "import.bookmarks.number.progress.text" : { - "comment" : "Operation progress info message about %d number of bookmarks being imported", + "home.page.burn.fireproof.site.alert" : { + "comment" : "Message for an alert displayed when trying to burn a fireproof website", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen werden importiert (%d) …" + "value" : "Der Verlauf dieser Seite wird gelöscht, aber die zugehörigen Daten bleiben erhalten, da diese Seite zu Fireproof hinzugefügt wurde" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Importing bookmarks (%d)…" + "value" : "History will be cleared for this site, but related data will remain, because this site is Fireproof" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importando favoritos (%d)..." + "value" : "Se borrará el historial de este sitio, pero los datos relacionados permanecerán, porque este sitio es Fireproof" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importation des signets (%d)…" + "value" : "L'historique de ce site sera effacé, mais les données associées resteront, car ce site est en mode coupe-feu (Fireproof)" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importazione segnalibri (%d) in corso…" + "value" : "La cronologia sarà cancellata per questo sito, ma i dati correlati rimarranno, perché questo sito è Fireproof" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bladwijzers importeren (%d) …" + "value" : "De geschiedenis van deze site wordt gewist, maar de gerelateerde gegevens blijven behouden, omdat deze site Fireproof is" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importowanie zakładek (%d)…" + "value" : "Historia dotycząca tej witryny zostanie wyczyszczona, ale powiązane dane pozostaną, ponieważ jest to witryna Fireproof" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "A importar marcadores (%d)…" + "value" : "O histórico será limpo para este site, mas os dados relacionados permanecem, porque este site é Fireproof" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Закладки импортируются (%d)…" + "value" : "История посещений этого сайта будет стерта, но остальные данные останутся, так как сайт огнеупорный." } } } }, - "import.bookmarks.safari.permission-button.title" : { - "comment" : "Text for the Safari data import permission button", + "home.page.clear.history" : { + "comment" : "Button caption for the burn fireproof website alert", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Safari-Ordner auswählen …" + "value" : "Verlauf löschen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Select Safari Folder…" + "value" : "Clear History" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Seleccionar carpeta de Safari..." + "value" : "Borrar historial" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Sélectionner le dossier Safari…" + "value" : "Effacer l'historique" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Seleziona cartella Safari…" + "value" : "Elimina cronologia" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Selecteer Safari-map ..." + "value" : "Geschiedenis wissen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wybierz folder Safari…" + "value" : "Wyczyść historię" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Selecionar pasta Safari…" + "value" : "Limpar histórico" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выбрать папку Safari..." + "value" : "Очистить историю" } } } }, - "import.bookmarks.select-html-file" : { - "comment" : "Button text for selecting HTML Bookmarks file", + "home.page.empty.state.item.message" : { + "comment" : "This string represents the message for an empty state item on the home page, encouraging the user to keep browsing to see how many trackers were blocked", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "HTML-Datei mit Lesezeichen auswählen …" + "value" : "Browse weiter, um zu sehen, wie viele Tracker blockiert wurden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Select Bookmarks HTML File…" + "value" : "Keep browsing to see how many trackers were blocked" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Seleccionar Archivo HTML de marcadores..." + "value" : "Sigue navegando para ver cuántos rastreadores se han bloqueado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Sélectionner le fichier de signets HTML…" + "value" : "Continuez à naviguer pour voir combien de traqueurs ont été bloqués." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Seleziona file HTML segnalibri…" + "value" : "Continua la navigazione per vedere quanti sistemi di tracciamento sono stati bloccati" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Selecteer HTML-bestand met bladwijzers ..." + "value" : "Blijf bladeren om te zien hoeveel trackers zijn geblokkeerd" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wybierz plik HTML zakładek…" + "value" : "Przeglądaj dalej, aby zobaczyć liczbę zablokowanych mechanizmów śledzących" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Selecionar ficheiro HTML de marcadores…" + "value" : "Continua a navegar para veres quantos rastreadores foram bloqueados" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выбрать HTML-файл с закладками..." + "value" : "Продолжайте бродить по интернету, а потом посмотрите, сколько трекеров мы заблокировали." } } } }, - "import.browser.data" : { - "comment" : "Import Browser Data dialog title", + "home.page.empty.state.item.title" : { + "comment" : "This string represents the title for an empty state item on the home page, indicating that recently visited sites will appear here", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "In DuckDuckGo importieren" + "value" : "Zuletzt besuchte Seiten werden hier angezeigt" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Import to DuckDuckGo" + "value" : "Recently visited sites appear here" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importar a DuckDuckGo" + "value" : "Los sitios visitados recientemente aparecen aquí" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importer dans DuckDuckGo" + "value" : "Les sites récemment visités apparaissent ici" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importa in DuckDuckGo" + "value" : "I siti visitati di recente si visualizzano qui" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Importeren naar DuckDuckGo" + "value" : "Onlangs bezochte websites verschijnen hier" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importuj do DuckDuckGo" + "value" : "Tutaj są widoczne ostatnio odwiedzone witryny" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importar para o DuckDuckGo" + "value" : "Os sites visitados recentemente aparecem aqui" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Импорт в DuckDuckGo" + "value" : "Недавно посещенные сайты появятся здесь" } } } }, - "import.browser.data.bookmarks" : { - "comment" : "Opens Import Browser Data dialog", + "home.page.no.trackers.blocked" : { + "comment" : "This string represents a message on the home page indicating that no trackers were blocked", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen importieren …" + "value" : "Keine Tracker blockiert" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Import Bookmarks…" + "value" : "No trackers blocked" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importar marcadores" + "value" : "No se han bloqueado rastreadores" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importer les signets…" + "value" : "Aucun traqueur bloqué" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importa segnalibri…" + "value" : "Nessun sistema di tracciamento bloccato" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bladwijzers importeren …" + "value" : "Geen trackers geblokkeerd" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importuj zakładki..." + "value" : "Nie zablokowano żadnych mechanizmów śledzących" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importar marcadores…" + "value" : "Nenhum rastreador bloqueado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Импортировать закладки..." + "value" : "Заблокированных трекеров пока нет" } } } }, - "import.browser.data.passwords" : { - "comment" : "Opens Import Browser Data dialog", + "home.page.no.trackers.found" : { + "comment" : "This string represents a message on the home page indicating that no trackers were found", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter importieren …" + "value" : "Keine Tracker gefunden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Import Passwords…" + "value" : "No trackers found" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importar contraseñas" + "value" : "No se han encontrado rastreadores" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importer les mots de passe…" + "value" : "Aucun traqueur trouvé" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importa password…" + "value" : "Nessun sistema di tracciamento trovato" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open en ontgrendel **%2$s**\n%3$d Selecteer **Bestand → Kluis exporteren** in de menubalk\n%4$d Selecteer de bestandsindeling: **.csv**\n%5$d Voer je Bitwarden-masterwachtwoord in\n%6$d Klik op %7$@ en bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%8$d %9$@t" + "value" : "Geen trackers gevonden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importuj hasła..." + "value" : "Nie znaleziono żadnych mechanizmów śledzących" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importar palavras-passe…" + "value" : "Nenhum rastreador encontrado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Импортировать пароли..." + "value" : "Ни одного трекера не найдено" } } } }, - "import.browser.data.shortcuts" : { - "comment" : "Import Browser Data dialog title for final stage when choosing shortcuts to enable", + "home.page.protection.duration" : { + "comment" : "Past 7 days in uppercase.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fast fertig!" + "value" : "Vergangene 7 Tage" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Almost done!" + "value" : "PAST 7 DAYS" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¡Casi listo!" + "value" : "Últimos 7 días" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "C'est presque terminé !" + "value" : "7 derniers jours" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Quasi finito!" + "value" : "Ultimi 7 giorni" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bijna klaar!" + "value" : "Afgelopen 7 dagen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Prawie gotowe!" + "value" : "Ostatnie 7 dni" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Quase pronto!" + "value" : "Últimos 7 dias" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Почти готово!" + "value" : "За последние 7 дней" } } } }, - "import.browser.data.shortcuts.subtitle" : { - "comment" : "Subtitle explaining how users can find toolbar shortcuts.", + "home.page.protection.summary.info" : { + "comment" : "This string represents a message in the protection summary on the home page, indicating that there is no recent activity", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Du kannst jederzeit mit der rechten Maustaste auf die Symbolleiste des Browsers klicken, um weitere Shortcuts wie diese zu finden." + "value" : "Keine aktuelle Aktivität" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "You can always right-click on the browser toolbar to find more shortcuts like these." + "value" : "No recent activity" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Siempre puedes hacer clic con el botón derecho en la barra de herramientas del navegador para encontrar más accesos directos como estos." + "value" : "No hay actividad reciente" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Vous pouvez toujours faire un clic droit sur la barre d'outils du navigateur pour accéder à d'autres raccourcis de ce type." + "value" : "Aucune activité récente" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Per trovare altre scorciatoie come queste, puoi sempre fare clic con il tasto destro del mouse sulla barra degli strumenti del browser." + "value" : "Nessuna attività recente" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Je kunt altijd met de rechtermuisknop op de werkbalk van de browser klikken om meer van deze snelkoppelingen te vinden." + "value" : "Geen recente activiteit" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zawsze możesz kliknąć prawym przyciskiem myszy pasek narzędzi przeglądarki, aby znaleźć więcej takich skrótów." + "value" : "Brak ostatniej aktywności" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Podes sempre clicar com o botão direito do rato na barra de ferramentas do navegador para encontrares mais atalhos como estes." + "value" : "Nenhuma atividade recente" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Другие ярлыки можно всегда найти, щелкнув по панели инструментов браузера правой кнопкой мыши." + "value" : "Недавней активности пока нет" } } } }, - "import.browser.data.source.subtitle" : { - "comment" : "Subtitle explaining where users can find imported passwords.", + "home.page.protection.summary.message" : { + "comment" : "The number of tracking attempts blocked in the last 7 days, shown on a new tab, translate as: Tracking attempts blocked: %@", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Du kannst deine Passwörter unter DuckDuckGo-Einstellungen > Passwörter & Autovervollständigen verwalten." + "value" : "%@ Tracking-Versuche blockiert" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Access and manage your passwords in DuckDuckGo Settings > Passwords & Autofill." + "value" : "%@ tracking attempts blocked" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Consulta y gestiona tus contraseñas en Ajustes de DuckDuckGo > Contraseñas y autocompletar." + "value" : "%@ intentos de rastreo bloqueados" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Accédez à vos mots de passe et gérez-les dans Paramètres de DuckDuckGo > Mots de passe et saisie automatique." + "value" : "%@ tentatives de pistage bloquées" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Accedi e gestisci le tue password in Impostazioni di DuckDuckGo > Password e compilazione automatica." + "value" : "%@ tentativi di tracciamento bloccati" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Open en beheer je wachtwoorden in DuckDuckGo Instellingen > Wachtwoorden en automatisch aanvullen." + "value" : "%@ trackingpogingen geblokkeerd" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Uzyskaj dostęp do swoich haseł i zarządzaj nimi w obszarze Ustawienia DuckDuckGo > Hasła i autouzupełnianie." + "value" : "Zablokowane próby śledzenia: %@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Acede e faz a gestão das tuas palavras-passe em Definições do DuckDuckGo > Palavras-passe e preenchimento automático." + "value" : "Tentativas de rastreamento bloqueadas: %@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Просмотреть и проконтролировать пароли в DuckDuckGo можно в разделе «Настройки > Пароли и автозаполнение»." + "value" : "Пресечено попыток отслеживания: %@" } } } }, - "import.browser.data.source.title" : { - "comment" : "Import Browser Data title for option to choose source browser to import from", - "extractionState" : "extracted_with_value", + "Import Bookmarks" : { + "comment" : "Title of dialog with instruction for the user to import bookmarks from another browser", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Importieren aus" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Import From" + "value" : "Lesezeichen importieren" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importar desde" + "value" : "Importar marcadores" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importer depuis" + "value" : "Importer des favoris" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importa da" + "value" : "Importa segnalibri" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Importeren vanuit" + "value" : "Bladwijzers importeren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importuj z" + "value" : "Importuj zakładki" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importar de" + "value" : "Importar marcadores" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Импорт из" + "value" : "Импортировать закладки" } } } }, - "import.csv.instructions.bitwarden" : { - "comment" : "Instructions to import Passwords as CSV from Bitwarden.\n%2$s - app name (Bitwarden)\n%7$@ - hamburger menu icon\n%9$@ - “Select Bitwarden CSV File” button\n**bold text**; _italic text_", - "extractionState" : "extracted_with_value", + "Import Passwords" : { + "comment" : "Title of dialog with instruction for the user to import passwords from another browser", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne und entsperre **%2$s**\n%3$d Wähle **Datei → Tresor exportieren** in der Menüleiste\n%4$d Wähle das Dateiformat: **.csv**\n%5$d Gib dein Bitwarden Haupt-Passwort ein\n%6$d Klicke auf %7$@ und speichere die Datei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%8$d %9$@" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$d Open and unlock **%2$s**\n%3$d Select **File → Export vault** from the Menu Bar\n%4$d Select the File Format: **.csv**\n%5$d Enter your Bitwarden main password\n%6$d Click %7$@ and save the file someplace you can find it (e.g., Desktop)\n%8$d %9$@" + "value" : "Passwörter importieren" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre y desbloquea **%2$s**\n%3$d Selecciona **Archivo → Exportar caja fuerte** en la barra de menú\n%4$d Selecciona el formato de archivo: **.csv**\n%5$d Introduce tu contraseña principal de Bitwarden\n%6$d Haz clic en %7$@ y guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%8$d %9$@" + "value" : "Importar contraseñas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez et déverrouillez **%2$s**\n%3$d Sélectionnez **Fichier → Exporter le coffre-fort** dans la barre de menu\n%4$d Sélectionnez le format de fichier : **.csv**\n%5$d Saisissez votre mot de passe Bitwarden principal\n%6$d Cliquez sur %7$@ et enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%8$d %9$@" + "value" : "Importer les mots de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri e sblocca **%2$s**\n%3$d Seleziona **File → Esporta vault** dalla Barra dei menu\n%4$d Seleziona il Formato del file: **.csv**\n%5$d Inserisci la tua password principale Bitwarden\n%6$d Clicca su %7$@ e salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%8$d %9$@" + "value" : "Importa password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open en ontgrendel **%2$s**\n%3$d Selecteer **Bestand → Kluis exporteren** in de menubalk\n%4$d Selecteer de bestandsindeling: **.csv**\n%5$d Voer je Bitwarden-hoofdwachtwoord in\n%6$d Klik op %7$@ en bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%8$d %9$@" + "value" : "Wachtwoorden importeren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz i odblokuj aplikację **%2$s**\n%3$d Wybierz **Plik → Eksportuj sejf** na pasku menu\n%4$d Wybierz format pliku: **.csv**\n%5$d Wprowadź hasło główne aplikacji Bitwarden\n%6$d Kliknij %7$@ i zapisz plik w łatwo dostępnym miejscu (np. na pulpicie)\n%8$d %9$@" + "value" : "Importuj hasła" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre e desbloqueia o **%2$s**\n%3$d Seleciona **Ficheiro → Exportar cofre** na barra de menus\n%4$d Seleciona o formato do ficheiro: **.csv**\n%5$d Introduz a tua palavra-passe principal do Bitwarden\n%6$d Clica em %7$@ e guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%8$d %9$@" + "value" : "Importar palavras-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте и разблокируйте **%2$s**\n%3$d На панели меню выберите **Файл → Экспортировать хранилище** \n%4$d Выберите формат файла: **.csv**\n%5$d Введите основной пароль к Bitwarden\n%6$d Нажмите %7$@ и сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%8$d %9$@" + "value" : "Импорт паролей" } } } }, - "import.csv.instructions.brave" : { - "comment" : "Instructions to import Passwords as CSV from Brave browser.\n%N$d - step number\n%2$s - browser name (Brave)\n%4$@, %6$@ - hamburger menu icon\n%10$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", - "extractionState" : "extracted_with_value", + "Import Results:" : { + "comment" : "Data Import result summary headline", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **%2$s**\n%3$d Klicke auf %4$@, um das Anwendungsmenü zu öffnen, und klicke dann auf **Passwort-Manager**\n%5$d Klicke auf %6$@ **oben links** im Passwort-Manager und wähle **Einstellungen**\n%7$d Finde „Passwörter exportieren“ und klicke auf **Datei herunterladen**\n%8$d Speichere die Datei mit den Passwörtern an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop) %9$d %10$@" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$d Open **%2$s**\n%3$d Click %4$@ to open the application menu then click **Password Manager**\n%5$d Click %6$@ **at the top left** of the Password Manager and select **Settings**\n%7$d Find “Export Passwords” and click **Download File**\n%8$d Save the passwords file someplace you can find it (e.g., Desktop)\n%9$d %10$@" + "value" : "Ergebnisse importieren:" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **%2$s**\n%3$d Haz clic en %4$@ para abrir el menú de la aplicación y a continuación haz clic en **Administrador de contraseñas**\n%5$d Haz clic en %6$@ **en la parte superior izquierda** del Administrador de contraseñas y selecciona **Configuración**\n%7$d Busca \"Exportar contraseñas\" y haz clic en **Descargar archivo**\n%8$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%9$d %10$@" + "value" : "Importar resultados:" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **%2$s**\n%3$d Cliquez sur %4$@ pour ouvrir le menu des applications, puis sur **Gestionnaire de mots de passe**\n%5$d Cliquez sur %6$@ **en haut à gauche** du gestionnaire de mots de passe et sélectionnez **Paramètres**\n%7$d Recherchez « Exporter les mots de passe » et cliquez sur **Télécharger le fichier**\n%8$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%9$d %10$@" + "value" : "Résultats de l'importation :" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **%2$s**\n%3$d Fai clic su %4$@ per aprire il menu dell'applicazione, quindi fai clic su **Password Manager**\n%5$d Fai clic su %6$@ **in alto a sinistra** di Password Manager e seleziona **Impostazioni**\n%7$d Trova \"Esporta password\" e fai clic su **Download file**\n%8$d Salva il file password dove puoi trovarlo (ad esempio, sul desktop)\n%9$d %10$@" + "value" : "Risultati dell'importazione:" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **%2$s**\n%3$d Klik op %4$@ om het toepassingsmenu te openen en klik vervolgens op **Wachtwoordbeheer**%5$d Klik op %6$@ **linksboven** in de wachtwoordbeheerder en selecteer **Instellingen**\n%7$d Zoek 'Wachtwoorden exporteren' en klik op **Bestand downloaden**\n%8$d Bewaar het bestand met de wachtwoorden op een plek waar je het kunt vinden (bijv. het bureaublad)\n%9$d %10$@" + "value" : "Resultaten van de import:" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Kliknij %4$@, aby otworzyć menu aplikacji, a następnie kliknij **Menedżer haseł**\n%5$d Kliknij %6$@ **w lewym górnym rogu** Menedżera haseł i wybierz **Ustawienia**\n%7$d Znajdź pozycję „Eksportuj hasła” i kliknij **Pobierz plik**\n%8$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%9$d %10$@" + "value" : "Wyniki importowania:" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **%2$s**\n%3$d Clica em %4$@ para abrir o menu da aplicação e, em seguida, clica em **Gestor de palavras-passe**\n%5$d Clica em %6$@ **na parte superior esquerda** do Gestor de palavras-passe e seleciona **Definições**\n%7$d Encontra a opção \"Exportar palavras-passe\" e clica em **Transferir ficheiro**\n%8$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%9$d %10$@" + "value" : "Importar resultados:" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **%2$s**\n%3$d Нажмите значок %4$@, чтобы открыть меню приложения, а затем выберите **Менеджер паролей**\n%5$d Нажмите значок %6$@ **сверху слева** от заголовка «Менеджер паролей» и выберите **Настройки**\n%7$d Найдите опцию «Экспортировать пароли» и нажмите **Загрузить файл**\n%8$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%9$d %10$@" + "value" : "Результаты импорта:" } } } }, - "import.csv.instructions.chrome" : { - "comment" : "Instructions to import Passwords as CSV from Google Chrome browser.\n%N$d - step number\n%2$s - browser name (Chrome)\n%4$@ - hamburger menu icon\n%8$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", + "import.bookmarks.bookmarks" : { + "comment" : "Title text for the Bookmarks import option", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **%2$s**\n%3$d Klicke in einem neuen Tab auf %4$@ und dann auf **Google Password Manager → Einstellungen**\n%5$d Finde „Passwörter exportieren“ und klicke auf **Datei herunterladen**\n%6$d Speichere die Datei mit den Passwörtern an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" + "value" : "Lesezeichen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **%2$s**\n%3$d In a fresh tab, click %4$@ then **Google Password Manager → Settings**\n%5$d Find “Export Passwords” and click **Download File**\n%6$d Save the passwords file someplace you can find it (e.g., Desktop)\n%7$d %8$@" + "value" : "Bookmarks" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **%2$s**\n%3$d En una nueva pestaña, haz clic en %4$@ y luego en **Gestor de contraseñas de Google → Configuración**\n%5$d Busca \"Exportar contraseñas\" y haz clic en **Descargar archivo**\n%6$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" + "value" : "Marcadores" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **%2$s**\n%3$d Dans un nouvel onglet, cliquez sur %4$@ puis sur **Gestionnaire de mots de passe Google → Paramètres**\n%5$d Recherchez « Exporter les mots de passe » et cliquez sur **Télécharger le fichier**\n%6$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" + "value" : "Signets" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **%2$s**\n%3$d In una nuova scheda, fai clic su %4$@, quindi su **Gestore delle password di Google → Impostazioni**\n%5$d Trova \"Esporta password\" e fai clic su **Scarica file**\n%6$d Salva il file delle password dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" + "value" : "Segnalibri" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **%2$s**\n%3$d Klik in een nieuw tabblad op %4$@ en vervolgens op **Google Wachtwoordbeheerder → Instellingen**\n%5$d Zoek \"Wachtwoorden exporteren\" en klik op **Bestand downloaden**\n%6$d Bewaar het wachtwoordbestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" + "value" : "Bladwijzers" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na nowej karcie kliknij %4$@, a następnie **Menedżer haseł Google → Ustawienia**\n%5$d Znajdź pozycję „Eksportuj hasła” i kliknij **Pobierz plik**\n%6$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" + "value" : "Zakładki" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **%2$s**\n%3$d Num novo separador, clica em %4$@ e, em seguida, clica em **Gestor de Palavras-passe da Google → Definições**\n%5$d Encontra a opção \"Exportar palavras-passe\" e clica em **Transferir ficheiro**\n%6$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" + "value" : "Marcadores" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **%2$s**\n%3$d В новой вкладке нажмите на значок %4$@, а затем: **Менеджер паролей Google → Настройки**\n%5$d Найдите опцию «Экспортировать пароли» и нажмите **Скачать файл**\n%6$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" + "value" : "Закладки" } } } }, - "import.csv.instructions.chromium" : { - "comment" : "Instructions to import Passwords as CSV from Chromium-based browsers.\n%N$d - step number\n%2$s - browser name\n%4$@ - hamburger menu icon\n%8$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", + "import.bookmarks.html.title" : { + "comment" : "Title text for the HTML Bookmarks importer", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **%2$s**\n%3$d Klicke in einer neuen Registerkarte auf %4$@ und dann auf **Passwort-Manager → Einstellungen**\n%5$d Suche nach „Passwörter exportieren“ und klicke auf **Datei herunterladen**\n%6$d Speichere die Datei mit den Passwörtern an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" + "value" : "HTML-Lesezeichen-Datei (für andere Browser)" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **%2$s**\n%3$d In a fresh tab, click %4$@ then **Password Manager → Settings**\n%5$d Find “Export Passwords” and click **Download File**\n%6$d Save the passwords file someplace you can find it (e.g., Desktop)\n%7$d %8$@" + "value" : "HTML Bookmarks File (for other browsers)" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **%2$s**\n%3$d En una nueva pestaña, haz clic en %4$@ y luego en **Gestor de contraseñas → Configuración**\n%5$d Busca \"Exportar contraseñas\" y haz clic en **Descargar archivo**\n%6$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" + "value" : "Archivo de marcadores HTML (para otros navegadores)" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **%2$s**\n%3$d Dans un nouvel onglet, cliquez sur %4$@ puis sur **Gestionnaire de mots de passe → Paramètres**\n%5$d Recherchez « Exporter les mots de passe » et cliquez sur **Télécharger le fichier**\n%6$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" + "value" : "Fichier de signets HTML (pour les autres navigateurs)" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **%2$s**\n%3$d In una nuova scheda, fai clic su %4$@, quindi su **Gestore password → Impostazioni**\n%5$d Trova \"Esporta password\" e fai clic su **Scarica file**\n%6$d Salva il file delle password dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" + "value" : "File segnalibri HTML (per altri browser)" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **%2$s**\n%3$d Klik in een nieuw tabblad op %4$@ en vervolgens op **Wachtwoordbeheerder → Instellingen**\n%5$d Zoek \"Wachtwoorden exporteren\" en klik op **Bestand downloaden**\n%6$d Bewaar het wachtwoordbestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" + "value" : "HTML-bladwijzers (voor andere browsers)" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na nowej karcie kliknij %4$@, a następnie **Menedżer haseł → Ustawienia**\n%5$d Znajdź pozycję „Eksportuj hasła” i kliknij **Pobierz plik**\n%6$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" + "value" : "Plik zakładek HTML (dla innych przeglądarek)" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **%2$s**\n%3$d Num novo separador, clica em %4$@ e, em seguida, clica em **Gestor de palavras-passe → Definições**\n%5$d Encontra a opção \"Exportar palavras-passe\" e clica em **Transferir ficheiro**\n%6$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" + "value" : "Ficheiro HTML de marcadores (para outros navegadores)" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **%2$s**%3$d В новой вкладке нажмите на значок %4$@, а\n затем: **Менеджер паролей → Настройки**\n%5$d Найдите опцию «Экспортировать пароли» и нажмите **Скачать файл**\n%6$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" + "value" : "Файл закладок в HTML (для других браузеров)" } } } }, - "import.csv.instructions.coccoc" : { - "comment" : "Instructions to import Passwords as CSV from Cốc Cốc browser.\n%N$d - step number\n%2$s - browser name (Cốc Cốc)\n%5$@ - hamburger menu icon\n%8$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", + "import.bookmarks.indefinite.progress.text" : { + "comment" : "Operation progress info message about indefinite number of bookmarks being imported", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **%2$s**\n%3$d Gib „_coccoc://settings/passwords_“ in die Adressleiste ein\n%4$d Klicke auf %5$@ (rechts neben _Gespeicherte Passwörter_) und wähle **Passwörter exportieren**\n%6$d Speichere die Passwortdatei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" + "value" : "Lesezeichen werden importiert …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **%2$s**\n%3$d Type “_coccoc://settings/passwords_” into the Address bar\n%4$d Click %5$@ (on the right from _Saved Passwords_) and select **Export passwords**\n%6$d Save the passwords file someplace you can find it (e.g., Desktop)\n%7$d %8$@" + "value" : "Importing bookmarks…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **%2$s**\n%3$d Escribe \"_coccoc://settings/passwords_\" en la barra de direcciones\n%4$d Haz clic en %5$@ (a la derecha de _Contraseñas guardadas_) y selecciona **Exportar contraseñas**\n%6$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" + "value" : "Importando marcadores…" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **%2$s**\n%3$d Saisissez « _coccoc://settings/passwords_ » dans la barre d'adresse\n%4$d Cliquez sur %5$@ (à droite de _Saved Passwords_) et sélectionnez **Exporter les mots de passe**\n%6$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" + "value" : "Importation des signets…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **%2$s**\n%3$d Digita \"_coccoc://settings/passwords_\" nella barra degli indirizzi\n%4$d Fai clic su %5$@ (a destra di _Saved Passwords_) e seleziona **Esporta password**\n%6$d Salva il file delle password dove puoi trovarlo (ad esempio sul desktop)\n%7$d %8$@" + "value" : "Importazione segnalibri in corso…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **%2$s**\n%3$d Typ “_coccoc://settings/passwords_” in de adresbalk\n%4$d Klik op %5$@ (aan de rechterkant van _Opgeslagen wachtwoorden_) en selecteer **Wachtwoorden exporteren**\n%6$d Bewaar het wachtwoordbestand op een plek waar u het kunt vinden (bijv. het bureaublad) \n %7$d %8$@" + "value" : "Bladwijzers importeren ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Wpisz „_coccoc://settings/passwords_” w pasku adresu\n%4$d Kliknij %5$@ (po prawej stronie pozycji _Saved Passwords_) i wybierz **Export passwords**\n%6$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" + "value" : "Importowanie zakładek…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **%2$s**\n%3$d Escreve \"_coccoc://settings/passwords_\" na barra de endereço\n%4$d Clica em %5$@ (à direita de _Palavras-passe guardadas_) e seleciona **Exportar palavras-passe**\n%6$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" + "value" : "A importar marcadores…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **%2$s**\n%3$d Введите текст «_coccoc://settings/passwords_» в адресную строку\n%4$d Нажмите значок %5$@ (справа от заголовка «Сохраненные пароли») и выберите **Экспортировать пароли**\n%6$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" + "value" : "Закладки импортируются..." } } } }, - "import.csv.instructions.firefox" : { - "comment" : "Instructions to import Passwords as CSV from Firefox.\n%N$d - step number\n%2$s - browser name (Firefox)\n%4$@, %6$@ - hamburger menu icon\n%9$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", + "import.bookmarks.number.progress.text" : { + "comment" : "Operation progress info message about %d number of bookmarks being imported", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **%2$s**\n%3$d Klicke auf %4$@, um das Anwendungsmenü zu öffnen, und klicke dann auf **Passwörter**\n%5$d Klicke auf %6$@ und dann auf **Logins exportieren...**\n%7$d Speichere die Passwortdatei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%8$d %9$@" + "value" : "Lesezeichen werden importiert (%d) …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **%2$s**\n%3$d Click %4$@ to open the application menu then click **Passwords**\n%5$d Click %6$@ then **Export Logins…**\n%7$d Save the passwords file someplace you can find it (e.g., Desktop)\n%8$d %9$@" + "value" : "Importing bookmarks (%d)…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **%2$s**\n%3$d Haz clic en %4$@ para abrir el menú de la aplicación y, a continuación, haz clic en **Contraseñas**\n%5$d Haz clic en %6$@ y luego en **Exportar inicios de sesión... **\n%7$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%8$d %9$@" + "value" : "Importando favoritos (%d)..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **%2$s**\n%3$d Cliquez sur %4$@ pour ouvrir le menu des applications puis cliquez sur **Mots de passe**\n%5$d Cliquez sur %6$@ puis sur **Exporter les identifiants...**\n%7$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%8$d %9$@" + "value" : "Importation des signets (%d)…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **%2$s**\n%3$d Fai clic su %4$@ per aprire il menu dell'applicazione, quindi fai clic su **Password**\n%5$d Fai clic su %6$@, quindi su **Esporta accessi…**\n%7$d Salva il file delle password dove puoi trovarlo (ad esempio, sul desktop)\n%8$d %9$@" + "value" : "Importazione segnalibri (%d) in corso…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **%2$s**\n%3$d Klik op %4$@ om het toepassingsmenu te openen en klik vervolgens op **Wachtwoorden**\n%5$d Klik op %6$@ en vervolgens op **Logins exporteren...**\n%7$d Bewaar het bestand met de wachtwoorden op een plek waar je het kunt vinden (bijv. het bureaublad)\n%8$d %9$@" + "value" : "Bladwijzers importeren (%d) …" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Kliknij %4$@, aby otworzyć menu aplikacji, a następnie kliknij **Hasła**\n%5$d Kliknij %6$@, a następnie **Eksportuj dane logowania...**\n%7$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%8$d %9$@" + "value" : "Importowanie zakładek (%d)…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **%2$s**\n%3$d Clica em %4$@ para abrir o menu da aplicação e, em seguida, clica em **Palavras-passe**\n%5$d Clica em %6$@ e, em seguida, em **Exportar inícios de sessão…**\n%7$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%8$d %9$@" + "value" : "A importar marcadores (%d)…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **%2$s**\n%3$d Нажмите значок %4$@, чтобы открыть меню приложения, а затем откройте раздел **Пароли**\n%5$d Нажмите значок %6$@ и выберите опцию **Экспортировать логины…**\n%7$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%8$d %9$@" + "value" : "Закладки импортируются (%d)…" } } } }, - "import.csv.instructions.generic" : { - "comment" : "Instructions to import a generic CSV passwords file.\n%N$d - step number\n%3$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", + "import.bookmarks.safari.permission-button.title" : { + "comment" : "Text for the Safari data import permission button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Der CSV-Importer versucht, die Spaltenüberschriften an ihre Position anzupassen.\nWenn es keine Kopfzeile gibt, unterstützt er zwei Formate:\n%1$d URL, Benutzername, Passwort\n%2$d Titel, URL, Benutzername, Passwort\n%3$@" + "value" : "Safari-Ordner auswählen …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "The CSV importer will try to match column headers to their position.\nIf there is no header, it supports two formats:\n%1$d URL, Username, Password\n%2$d Title, URL, Username, Password\n%3$@" + "value" : "Select Safari Folder…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "El importador de CSV intentará hacer coincidir los encabezados de columna con su posición.\nSi no hay encabezado, admite dos formatos:\n%1$d URL, Nombre de usuario, Contraseña\n%2$d Título, URL, Nombre de usuario, Contraseña\n%3$@" + "value" : "Seleccionar carpeta de Safari..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "L'importateur CSV tentera de faire correspondre les en-têtes de colonne à leur position.\nS'il n'y a pas d'en-tête, il prend en charge deux formats :\n%1$d URL, Nom d'utilisateur, Mot de passe\n%2$d Titre, URL, Nom d'utilisateur, Mot de passe\n%3$@" + "value" : "Sélectionner le dossier Safari…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "L'importatore CSV cercherà di abbinare le intestazioni delle colonne alla loro posizione.\nSe non è presente alcuna intestazione, supporta due formati: %1$d URL, Nome utente, Password\n%2$d Titolo, URL, Nome utente, Password\n%3$@" + "value" : "Seleziona cartella Safari…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "De CSV-importtool zal proberen de kopteksten van de kolommen aan hun positie te koppelen.\nAls er geen koptekst is, ondersteunt deze twee indelingen:\n%1$d URL, gebruikersnaam, wachtwoord\n%2$d Titel, URL, gebruikersnaam, wachtwoord\n%3$@" + "value" : "Selecteer Safari-map ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Narzędzie importu plików CSV spróbuje dopasować nagłówki kolumn do ich pozycji.\nW przypadku braku nagłówka obsługuje ono dwa formaty:\n%1$d adres URL, nazwa użytkownika, hasło\n%2$d tytuł, adres URL, nazwa użytkownika, hasło\n%3$@" + "value" : "Wybierz folder Safari…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O importador de CSV vai tentar combinar os cabeçalhos das colunas com as respetivas posições.\nSe não existir um cabeçalho, suporta dois formatos:\n%1$d URL, Nome de utilizador, Palavra-passe\n%2$d Título, URL, Nome de utilizador, Palavra-passe\n%3$@" + "value" : "Selecionar pasta Safari…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Средство импорта файлов CSV сопоставит заголовки столбцов с их позициями.\nЕсли в файле нет заголовка, средство поддерживает два формата:\n%1$d URL, имя пользователя, пароль\n%2$d Заголовок, URL, имя пользователя, пароль\n%3$@" + "value" : "Выбрать папку Safari..." } } } }, - "import.csv.instructions.lastpass" : { - "comment" : "Instructions to import Passwords as CSV from LastPass.\n%2$s - app name (LastPass)\n%8$@ - “Select LastPass CSV File” button\n**bold text**; _italic text_", + "import.bookmarks.select-html-file" : { + "comment" : "Button text for selecting HTML Bookmarks file", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Klicke auf das **%2$s** Symbol in deinem Browser und gib dein Haupt-Passwort ein\n%3$d Wähle **Meinen Tresor öffnen**\n%4$d Wähle in der Seitenleiste **Erweiterte Optionen → Exportieren**\n%5$d Gib dein LastPass Haupt-Passwort ein\n%6$d Wähle das Dateiformat: **Comma Delimited Text (.csv)**\n%7$d %8$@" + "value" : "HTML-Datei mit Lesezeichen auswählen …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Click on the **%2$s** icon in your browser and enter your main password\n%3$d Select **Open My Vault**\n%4$d From the sidebar select **Advanced Options → Export**\n%5$d Enter your LastPass main password\n%6$d Select the File Format: **Comma Delimited Text (.csv)**\n%7$d %8$@" + "value" : "Select Bookmarks HTML File…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Haz clic en el icono **%2$s** de tu navegador e introduce tu contraseña principal\n%3$d Selecciona **Abrir mi caja fuerte**\n%4$d En la barra lateral, selecciona **Opciones avanzadas → Exportar**\n%5$d Introduce tu contraseña principal de LastPass\n%6$d Selecciona el formato de archivo: **Texto delimitado por comas (.csv)**\n%7$d %8$@." + "value" : "Seleccionar Archivo HTML de marcadores..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Cliquez sur l'icône **%2$s** de votre navigateur et saisissez votre mot de passe principal\n%3$d Sélectionnez **Ouvrir mon coffre-fort**\n%4$d Dans la barre latérale, sélectionnez **Options avancées → Exporter**\n%5$d Saisissez votre mot de passe LastPass principal\n%6$d Sélectionnez le format de fichier** : **Texte séparé par des virgules (.csv)**\n%7$d %8$@" + "value" : "Sélectionner le fichier de signets HTML…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Clicca sull'icona **%2$s** nel browser e inserisci la tua password principale\n%3$d Seleziona **Apri il Mio Vault**\n%4$d Dalla barra laterale seleziona **Opzioni avanzate → Esporta**\n%5$d Inserisci la tua password principale LastPass\n%6$d Seleziona il formato del file: **Testo delimitato da virgolette (.csv)**\n%7$d %8$@" + "value" : "Seleziona file HTML segnalibri…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Klik op het pictogram **%2$s** in je browser en voer je hoofdwachtwoord in\n%3$d Selecteer **Open mijn kluis**\n%4$d Klik in de zijbalk op **Geavanceerde opties → Exporteren**\n%5$d Voer je LastPass-hoofdwachtwoord in\n%6$d Selecteer de bestandsindeling: **Door komma's gescheiden tekst (.csv)**\n%7$d %8$@" + "value" : "Selecteer HTML-bestand met bladwijzers ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Kliknij ikonę **%2$s** w przeglądarce i wprowadź hasło główne\n%3$d Wybierz **Otwórz mój sejf**\n%4$d Na pasku bocznym wybierz **Opcje zaawansowane → Eksportuj**\n%5$d Wprowadź hasło główne splikacji LastPass\n%6$d Wybierz format pliku: **Tekst rozdzielany przecinkami (.csv)**\n%7$d %8$@" + "value" : "Wybierz plik HTML zakładek…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Clica no ícone da aplicação **%2$s** no teu navegador e introduz a tua palavra-passe principal\n%3$d Seleciona **Abrir o meu cofre**\n%4$d Na barra lateral, seleciona **Opções avançadas → Exportar**\n%5$d Introduz a tua palavra-passe principal da aplicação LastPass\n%6$d Seleciona o formato do ficheiro: **Texto delimitado por vírgula (.csv)**\n%7$d %8$@" + "value" : "Selecionar ficheiro HTML de marcadores…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Нажмите на значок **%2$s** в браузере и введите основной пароль\n%3$d Выберите меню **Открыть мое хранилище**\n%4$d На боковой панели выберите **Дополнительные опции → Экспортировать**\n%5$d Введите основной пароль к LastPass\n%6$d Сохраните файл в формате: **текст с разделяющими запятыми (.csv)**\n%7$d %8$@" + "value" : "Выбрать HTML-файл с закладками..." } } } }, - "import.csv.instructions.onePassword7" : { - "comment" : "Instructions to import Passwords as CSV from 1Password 7.\n%2$s - app name (1Password)\n%9$@ - “Select 1Password CSV File” button\n**bold text**; _italic text_", + "import.browser.data" : { + "comment" : "Import Browser Data dialog title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne und entsperre **%2$s**\n%3$d Wähle den Tresor aus, den du exportieren möchtest (du kannst immer nur einen Tresor auf einmal exportieren)\n%4$d Wähle **Datei → Exportieren → Alle Elemente** aus der Menüleiste\n%5$d Gib dein 1Password Haupt- oder Account-Passwort ein\n%6$d Wähle das Dateiformat: **iCloud-Schlüsselbund (.csv)**\n%7$d Speichere die Passwortdatei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%8$d %9$@" + "value" : "In DuckDuckGo importieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open and unlock **%2$s**\n%3$d Select the vault you want to export (you can only export one vault at a time)\n%4$d Select **File → Export → All Items** from the Menu Bar\n%5$d Enter your 1Password main or account password\n%6$d Select the File Format: **iCloud Keychain (.csv)**\n%7$d Save the passwords file someplace you can find it (e.g., Desktop)\n%8$d %9$@" + "value" : "Import to DuckDuckGo" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre y desbloquea **%2$s**\n%3$d Selecciona la caja fuerte que quieres exportar (solo puedes exportar una caja fuerte a la vez)\n%4$d Selecciona **Archivo → Exportar → Todos los elementos** en la barra de menú\n%5$d Introduce tu contraseña principal o de cuenta de 1Password\n%6$d Selecciona el formato de archivo: **Llavero de iCloud (.csv) **\n%7$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%8$d %9$@" + "value" : "Importar a DuckDuckGo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez et déverrouillez **%2$s**\n%3$d Sélectionnez le coffre-fort à exporter (vous ne pouvez exporter qu'un seul coffre-fort à la fois)\n%4$d Sélectionnez **Fichier → Exporter → Tous les éléments** dans la barre de menu\n%5$d Saisissez votre mot de passe principal ou celui de votre compte 1Password\n%6$d Sélectionnez le format de fichier :**Trousseau iCloud (.csv)**\n%7$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%8$d %9$@" + "value" : "Importer dans DuckDuckGo" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri e sblocca **%2$s**\n%3$d Seleziona il vault da esportare (puoi esportarne solo uno per volta)\n%4$d Seleziona **File → Esporta → Tutti gli elementi** dalla Barra dei menu\n%5$d Inserisci la tua password principale o dell'account 1Password\n%6$d Seleziona il formato del file: **Portachiavi iCloud (.csv)**\n%7$d Salva il file delle password dove puoi trovarlo (ad esempio, sul desktop)\n%8$d %9$@" + "value" : "Importa in DuckDuckGo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open en ontgrendel **%2$s**\n%3$d Selecteer de kluis die je wilt exporteren (je kunt slechts één kluis per keer exporteren)\n%4$d Selecteer **Bestand → Exporteren → Alle items** in de menubalk\n%5$d Voer je 1Password hoofd- of accountwachtwoord in\n%6$d Selecteer de bestandsindeling: **iCloud Keychain (.csv)**\n%7$d Bewaar het bestand met de wachtwoorden op een plek waar je het kunt vinden (bijv. het bureaublad)\n%8$d %9$@" + "value" : "Importeren naar DuckDuckGo" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz i odblokuj aplikację **%2$s**\n%3$d Wybierz sejf, który chcesz wyeksportować (możesz eksportować tylko jeden sejf naraz)\n%4$d Na pasku menu wybierz **Plik → Eksportuj → Wszystkie elementy**\n%5$d Wprowadź hasło główne lub hasło konta 1Password\n%6$d Wybierz format pliku: **iCloud Keychain (.csv)**\n%7$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na pulpicie)\n%8$d %9$@" + "value" : "Importuj do DuckDuckGo" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre e desbloqueia a aplicação **%2$s**\n%3$d Seleciona o cofre que pretendes exportar (só podes exportar um cofre de cada vez)\n%4$d Seleciona **Ficheiro → Exportar → Todos os elementos** na barra de menus\n%5$d Introduz a tua palavra-passe principal da aplicação ou da conta 1Password\n%6$d Seleciona o formato do ficheiro: **Porta-chaves em iCloud (.csv)**\n%7$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%8$d %9$@" + "value" : "Importar para o DuckDuckGo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте и разблокируйте **%2$s**\n%3$d Выберите хранилище, которое вы хотите экспортировать (можно экспортировать только по одному хранилищу за раз)\n%4$d На панели меню выберите опцию **Файл → Экспортировать → Все элементы**\n%5$d Введите основной пароль или пароль к учетной записи 1Password\n%6$d Выберите формат файла: **iCloud Keychain (.csv)**\n%7$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%8$d %9$@" + "value" : "Импорт в DuckDuckGo" } } } }, - "import.csv.instructions.onePassword8" : { - "comment" : "Instructions to import Passwords as CSV from 1Password 8.\n%2$s - app name (1Password)\n%8$@ - “Select 1Password CSV File” button\n**bold text**; _italic text_", + "import.browser.data.bookmarks" : { + "comment" : "Opens Import Browser Data dialog", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne und entsperre **%2$s**\n%3$d Wähle **Datei → Exportieren** in der Menüleiste und wähle das Konto aus, das du exportieren möchtest\n%4$d Gib dein 1Password-Kontopasswort ein\n%5$d Wähle das Dateiformat aus: **CSV (nur Logins und Passwörter)**\n%6$d Klicke auf „Daten exportieren“ und speichere die Datei an einem Ort, an dem du sie findest (z. B. auf dem Desktop)\n%7$d %8$@" + "value" : "Lesezeichen importieren …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open and unlock **%2$s**\n%3$d Select **File → Export** from the Menu Bar and choose the account you want to export\n%4$d Enter your 1Password account password\n%5$d Select the File Format: **CSV (Logins and Passwords only)**\n%6$d Click Export Data and save the file someplace you can find it (e.g., Desktop)\n%7$d %8$@" + "value" : "Import Bookmarks…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre y desbloquea **%2$s**\n%3$d Selecciona **Archivo → Exportar** en la barra de menú y elige la cuenta que deseas exportar\n%4$d Introduce tu contraseña de 1Password\n%5$d Selecciona el formato de archivo: *CSV (solo contraseñas e inicios de sesión)**\n%6$d Haz clic en Exportar datos y guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" + "value" : "Importar marcadores" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez et déverrouillez **%2$s**\n%3$d Sélectionnez **Fichier → Exporter** dans la barre de menu et choisissez le compte que vous souhaitez exporter\n%4$d Saisissez le mot de passe de votre compte 1Password\n%5$d Sélectionnez le format de fichier : **CSV (identifiants et mots de passe uniquement)**\n%6$d Cliquez sur Exporter les données et enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" + "value" : "Importer les signets…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri e sblocca **%2$s**\n%3$d Seleziona **File → Esporta** dalla Barra dei menu e scegli l'account da esportare\n%4$d Inserisci la password del tuo account 1Password\n%5$d Seleziona il formato file: **CSV (solo accesso e password)**\n%6$d Fai clic su Esporta dati e salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" + "value" : "Importa segnalibri…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open en ontgrendel **%2$s**\n%3$d Selecteer **Bestand → Exporteren** in de menubalk en kies het account dat je wilt exporteren\n%4$d Voer het wachtwoord van je 1Password-account in\n%5$d Selecteer het bestandsformaat: **CSV (alleen aanmeldingen en wachtwoorden)**\n%6$d Klik op Gegevens exporteren en bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" + "value" : "Bladwijzers importeren …" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz i odblokuj aplikację **%2$s**\n%3$d Wybierz **File → Export** na pasku menu i wybierz konto, które chcesz wyeksportować\n%4$d Wprowadź hasło konta 1Password\n%5$d Wybierz format pliku: **CSV (Logins and Passwords only)**\n%6$d Kliknij Export Data i zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" + "value" : "Importuj zakładki..." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre e desbloqueia a aplicação **%2$s**\n%3$d Seleciona **Ficheiro → Exportar** na barra de menus e escolhe a conta que pretendes exportar\n%4$d Introduz a tua palavra-passe da conta da 1Password\n%5$d Seleciona o formato do ficheiro: **CSV (só Inícios de sessão e Palavras-passe)**\n%6$d Clica em Exportar dados e guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" + "value" : "Importar marcadores…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте и разблокируйте **%2$s**\n%3$d В панели меню откройте раздел **Файл → Экспортировать** и выберите учетную запись, которую нужно экспортировать\n%4$d Введите пароль учетной записи 1Password\n%5$d Выберите формат файла: **CSV (только для логинов и паролей)**\n%6$d Нажмите кнопку «Экспортировать данные» и сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" + "value" : "Импортировать закладки..." } } } }, - "import.csv.instructions.opera" : { - "comment" : "Instructions to import Passwords as CSV from Opera browser.\n%N$d - step number\n%2$s - browser name (Opera)\n%8$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", + "import.browser.data.passwords" : { + "comment" : "Opens Import Browser Data dialog", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Ansicht → Passwort-Manager anzeigen**\n%4$d Wähle **Einstellungen**\n%5$d Finde „Passwörter exportieren“ und klicke auf **Datei herunterladen**\n%6$d Speichere die Datei mit den Passwörtern an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" + "value" : "Passwörter importieren …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **View → Show Password Manager**\n%4$d Select **Settings**\n%5$d Find “Export Passwords” and click **Download File**\n%6$d Save the passwords file someplace you can find it (e.g., Desktop)\n%7$d %8$@" + "value" : "Import Passwords…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **%2$s**%3$d Utiliza la barra de menú para seleccionar **Ver → Mostrar administrador de contraseñas**\n%4$d Selecciona **Configuración**\n%5$d Busca \"Exportar contraseñas\" y haz clic en **Descargar archivo**\n%6$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" + "value" : "Importar contraseñas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Voir → Afficher le gestionnaire de mots de passe**\n%4$d Sélectionnez **Paramètres**\n%5$d Recherchez « Exporter les mots de passe » et cliquez sur **Télécharger le fichier**\n%6$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" + "value" : "Importer les mots de passe…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **%2$s**\n%3$d Usa la Barra dei menu per selezionare **Visualizza il gestore delle password**\n%4$d Seleziona **Impostazioni**\n%5$d Trova \"Esporta password\" e fai clic su **Scarica file**\n%6$d Salva il file password dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" + "value" : "Importa password…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Bekijken → Wachtwoordbeheerder weergeven**\n%4$d Selecteer **Instellingen**\n%5$d Zoek 'Wachtwoorden exporteren' en klik op **Bestand downloaden**\n%6$d Bewaar het bestand met de wachtwoorden op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" + "value" : "%1$d Open en ontgrendel **%2$s**\n%3$d Selecteer **Bestand → Kluis exporteren** in de menubalk\n%4$d Selecteer de bestandsindeling: **.csv**\n%5$d Voer je Bitwarden-masterwachtwoord in\n%6$d Klik op %7$@ en bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%8$d %9$@t" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Widok → Pokaż menedżera haseł**\n%4$d Wybierz **Ustawienia**\n%5$d Znajdź pozycję „Eksportuj hasła” i kliknij **Pobierz plik**\n%6$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" + "value" : "Importuj hasła..." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Visualização → Mostrar gestor de palavras-passe**\n%4$d Seleciona **Definições**\n%5$d Encontra a opção \"Exportar palavras-passe\" e clica em **Transferir ficheiro**\n%6$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" + "value" : "Importar palavras-passe…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **%2$s**\n%3$d В панели меню выберите **Вид → Показать менеджер паролей**\n%4$d Откройте **Настройки**\n%5$d Найдите опцию «Экспортировать пароли» и нажмите **Загрузить файл**\n%6$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" + "value" : "Импортировать пароли..." } } } }, - "import.csv.instructions.operagx" : { - "comment" : "Instructions to import Passwords as CSV from Opera GX browsers.\n%N$d - step number\n%2$s - browser name (Opera GX)\n%5$@ - menu button icon\n%8$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", + "import.browser.data.shortcuts" : { + "comment" : "Import Browser Data dialog title for final stage when choosing shortcuts to enable", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Ansicht → Passwortmanager anzeigen**\n%4$d Klicke auf %5$@ (rechts von _Gespeicherte Passwörter_) und wähle **Passwörter exportieren**\n%6$d Speichere die Passwortdatei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" + "value" : "Fast fertig!" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **View → Show Password Manager**\n%4$d Click %5$@ (on the right from _Saved Passwords_) and select **Export passwords**\n%6$d Save the passwords file someplace you can find it (e.g., Desktop)\n%7$d %8$@" + "value" : "Almost done!" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **%2$s**\n%3$d Utiliza la barra de menú para seleccionar **Ver → Mostrar administrador de contraseñas\n%4$d Haz clic en %5$@ (a la derecha de _Contraseñas guardadas_) y selecciona **Exportar contraseñas**\n%6$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" + "value" : "¡Casi listo!" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Voir → Afficher le gestionnaire de mots de passe**\n%4$d Cliquez sur %5$@ (à droite de _Saved Passwords_) et sélectionnez **Exporter les mots de passe**\n%6$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" + "value" : "C'est presque terminé !" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **%2$s**\n%3$d Usa la Barra dei menu per selezionare **Visualizza → Mostra Gestore password**\n%4$d Fai clic su %5$@ (a destra di _Password salvate_ ) e seleziona **Esporta password**\n%6$d Salva il file delle password dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" + "value" : "Quasi finito!" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Bekijken → Wachtwoordbeheerder weergeven**\n%4$d Klik op %5$@ (rechts van _Bewaarde wachtwoorden_) en selecteer **Wachtwoorden exporteren**\n%6$d Bewaar het bestand met de wachtwoorden op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" + "value" : "Bijna klaar!" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Widok → Pokaż menedżera haseł**\n%4$d Kliknij %5$@ (po prawej stronie pozycji _Zachowane hasła_) i wybierz **Eksportuj hasła**\n%6$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" + "value" : "Prawie gotowe!" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Visualização → Mostrar gestor de palavras-passe**\n%4$d Clica em %5$@ (à direita de _Palavras-passe guardadas_) e seleciona **Exportar palavras-passe**\n%6$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" + "value" : "Quase pronto!" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **%2$s**\n%3$d В панели меню выберите **Вид → Показать менеджер паролей**\n%4$d Нажмите значок %5$@ (справа от раздела «Сохраненные пароли») и выберите опцию **Экспортировать пароли**\n%6$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" + "value" : "Почти готово!" } } } }, - "import.csv.instructions.safari" : { - "comment" : "Instructions to import Passwords as CSV from Safari.\n%N$d - step number\n%5$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", + "import.browser.data.shortcuts.subtitle" : { + "comment" : "Subtitle explaining how users can find toolbar shortcuts.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **Safari**\n%2$d Wähle **Datei → Exportieren → Passwörter**\n%3$d Speichere die Datei mit den Passwörtern an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%4$d %5$@" + "value" : "Du kannst jederzeit mit der rechten Maustaste auf die Symbolleiste des Browsers klicken, um weitere Shortcuts wie diese zu finden." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **Safari**\n%2$d Select **File → Export → Passwords**\n%3$d Save the passwords file someplace you can find it (e.g., Desktop)\n%4$d %5$@" + "value" : "You can always right-click on the browser toolbar to find more shortcuts like these." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **Safari**\n%2$d Selecciona **Archivo → Exportar → Contraseñas**\n%3$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%4$d %5$@" + "value" : "Siempre puedes hacer clic con el botón derecho en la barra de herramientas del navegador para encontrar más accesos directos como estos." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **Safari**\n%2$d Sélectionnez **Fichier → Exporter → Mots de passe**\n%3$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%4$d %5$@" + "value" : "Vous pouvez toujours faire un clic droit sur la barre d'outils du navigateur pour accéder à d'autres raccourcis de ce type." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **Safari**\n%2$d Seleziona **File → Esporta → Password**\n%3$d Salva il file delle password dove puoi trovarlo (ad esempio, sul desktop)\n%4$d %5$@" + "value" : "Per trovare altre scorciatoie come queste, puoi sempre fare clic con il tasto destro del mouse sulla barra degli strumenti del browser." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **Safari**\n%2$d Selecteer **Bestand → Exporteren → Wachtwoorden**\n%3$d Bewaar het bestand met de wachtwoorden op een plek waar je het kunt vinden (bijv. het bureaublad)\n%4$d %5$@" + "value" : "Je kunt altijd met de rechtermuisknop op de werkbalk van de browser klikken om meer van deze snelkoppelingen te vinden." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **Safari**\n%2$d Wybierz **Plik → Eksportuj → Hasła**\n%3$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%4$d %5$@" + "value" : "Zawsze możesz kliknąć prawym przyciskiem myszy pasek narzędzi przeglądarki, aby znaleźć więcej takich skrótów." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **Safari**\n%2$d Seleciona **Ficheiro → Exportar → Palavras-passe**\n%3$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%4$d %5$@" + "value" : "Podes sempre clicar com o botão direito do rato na barra de ferramentas do navegador para encontrares mais atalhos como estes." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **Safari**\n%2$d Выберите раздел **Файл → Экспортировать → Пароли**\n%3$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%4$d %5$@" + "value" : "Другие ярлыки можно всегда найти, щелкнув по панели инструментов браузера правой кнопкой мыши." } } } }, - "import.csv.instructions.vivaldi" : { - "comment" : "Instructions to import Passwords exported as CSV from Vivaldi browser.\n%N$d - step number\n%2$s - browser name (Vivaldi)\n%5$@ - menu button icon\n%8$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", + "import.browser.data.source.subtitle" : { + "comment" : "Subtitle explaining where users can find imported passwords.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **%2$s**\n%3$d Gib „_chrome://settings/passwords_“ in die Adressleiste ein\n%4$d Klicke auf %5$@ (rechts von _Gespeicherte Passwörter_) und wähle **Passwörter exportieren**\n%6$d Speichere die Datei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" + "value" : "Du kannst deine Passwörter unter DuckDuckGo-Einstellungen > Passwörter & Autovervollständigen verwalten." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **%2$s**\n%3$d Type “_chrome://settings/passwords_” into the Address bar\n%4$d Click %5$@ (on the right from _Saved Passwords_) and select **Export passwords**\n%6$d Save the file someplace you can find it (e.g., Desktop)\n%7$d %8$@" + "value" : "Access and manage your passwords in DuckDuckGo Settings > Passwords & Autofill." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **%2$s**\n%3$d Escribe \"_chrome://settings/passwords_\" en la barra de direcciones\n%4$d Haz clic en %5$@ (a la derecha de _Contraseñas guardadas_) y selecciona **Exportar contraseñas**\n%6$d Guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" + "value" : "Consulta y gestiona tus contraseñas en Ajustes de DuckDuckGo > Contraseñas y autocompletar." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **%2$s**\n%3$d Saisissez « _chrome://settings/passwords_ » dans la barre d'adresse\n%4$d Cliquez sur %5$@ (à droite de _Saved Passwords_) et sélectionnez **Exporter les mots de passe**\n%6$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" + "value" : "Accédez à vos mots de passe et gérez-les dans Paramètres de DuckDuckGo > Mots de passe et saisie automatique." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **%2$s**\n%3$d Tipo \"_chrome://settings/passwords_\" nella barra degli indirizzi\n%4$d Fai clic su %5$@ (a destra su _Password salvate_) e seleziona **Esporta password**\n%6$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@.\"" + "value" : "Accedi e gestisci le tue password in Impostazioni di DuckDuckGo > Password e compilazione automatica." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **%2$s**\n%3$d Typ “_chrome://settings/passwords_” in de adresbalk\n%4$d Klik op %5$@ (aan de rechterkant van _Opgeslagen wachtwoorden_) en selecteer **Wachtwoorden exporteren**\n%6$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad) \n %7$d %8$@" + "value" : "Open en beheer je wachtwoorden in DuckDuckGo Instellingen > Wachtwoorden en automatisch aanvullen." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Wpisz „_chrome://settings/passwords_” w pasku adresu\n%4$d Kliknij %5$@ (po prawej stronie pozycji _Zachowane hasła_) i wybierz **Eksportuj hasła**\n%6$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" + "value" : "Uzyskaj dostęp do swoich haseł i zarządzaj nimi w obszarze Ustawienia DuckDuckGo > Hasła i autouzupełnianie." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **%2$s**\n%3$d Escreve \"_chrome://settings/passwords_\" na barra de endereço\n%4$d Clica em %5$@ (à direita de _Palavras-passe guardadas_) e seleciona **Exportar palavras-passe**\n%6$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" + "value" : "Acede e faz a gestão das tuas palavras-passe em Definições do DuckDuckGo > Palavras-passe e preenchimento automático." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **%2$s**\n%3$d Введите текст «_chrome://settings/passwords_» в адресную строку\n%4$d Нажмите на значок %5$@ (справа от заголовка «Сохраненные пароли») и выберите **Экспортировать пароли**\n%6$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" + "value" : "Просмотреть и проконтролировать пароли в DuckDuckGo можно в разделе «Настройки > Пароли и автозаполнение»." } } } }, - "import.csv.instructions.yandex" : { - "comment" : "Instructions to import Passwords as CSV from Yandex Browser.\n%N$d - step number\n%2$s - browser name (Yandex)\n%4$@ - hamburger menu icon\n%8$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", + "import.browser.data.source.title" : { + "comment" : "Import Browser Data title for option to choose source browser to import from", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **%2$s**\n%3$d Klicke auf %4$@, um das Anwendungsmenü zu öffnen, und klicke dann auf **Passwörter und Karten**\n%5$d Klicke auf %6$@ und dann auf **Passwörter exportieren**\n%7$d Wähle **In eine Textdatei (nicht sicher)** und klicke auf **Exportieren**\n%8$d Speichere die Passwortdatei an einem Ort, an dem du sie findest (z. B. auf dem Desktop)\n%9$d %10$@" + "value" : "Importieren aus" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **%2$s**\n%3$d Click %4$@ to open the application menu then click **Passwords and cards**\n%5$d Click %6$@ then **Export passwords**\n%7$d Choose **To a text file (not secure)** and click **Export**\n%8$d Save the passwords file someplace you can find it (e.g., Desktop)\n%9$d %10$@" + "value" : "Import From" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **%2$s**\n%3$d Haz clic en %4$@ para abrir el menú de la aplicación y, a continuación, haz clic en **Contraseñas y tarjetas**\n%5$d Haz clic en %6$@ y luego en **Exportar contraseñas**\n%7$d Elige **A un archivo de texto (no seguro)** y haz clic en **Exportar**\n%8$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n %9$d %10$@" + "value" : "Importar desde" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **%2$s**\n%3$d Cliquez sur %4$@ pour ouvrir le menu des applications, puis sur **Mots de passe et cartes**\n%5$d Cliquez sur %6$@ puis sur **Exporter les mots de passe**\n%7$d Choisissez **Vers un fichier texte (non sécurisé)** et cliquez sur **Exporter**\n%8$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%9$d %10$@" + "value" : "Importer depuis" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **%2$s**\n%3$d Fai clic su %4$@ per aprire il menu dell'applicazione, quindi su **Password e schede**\n%5$d Fai clic su %6$@, quindi su **Esporta password**\n%7$d Scegli **Per un file di testo (non sicuro)** e fai clic su **Esporta**\n%8$d Salva il file password dove puoi trovarlo (ad esempio, sul desktop)\n%9$d %10$@" + "value" : "Importa da" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **%2$s**\n%3$d Klik op %4$@ om het toepassingsmenu te openen en klik vervolgens op **Wachtwoorden en kaarten**\n%5$d Klik op %6$@ en vervolgens op **Wachtwoorden exporteren**\n%7$d Kies **Naar een tekstbestand (niet beveiligd)** en klik op **Exporteren**\n%8$d Bewaar het bestand met de wachtwoorden op een plek waar je het kunt vinden (bijv. het bureaublad)\n%9$d %10$@" + "value" : "Importeren vanuit" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Kliknij %4$@, aby otworzyć menu aplikacji, a następnie kliknij **Passwords and cards**\n%5$d Kliknij %6$@, a następnie **Export passwords**\n%7$d Wybierz **To a text file (not secure)** i kliknij **Export**\n%8$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%9$d %10$@" + "value" : "Importuj z" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **%2$s**\n%3$d Clica em %4$@ para abrir o menu da aplicação e, em seguida, clica em **Palavras-passe e cartões**\n%5$d Clica em %6$@ e, em seguida, clica em **Exportar palavras-passe**\n%7$d Seleciona **Para um ficheiro de texto (não protegido)** e clica em **Exportar**\n%8$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%9$d %10$@" + "value" : "Importar de" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **%2$s**\n%3$d Нажмите значок %4$@, чтобы открыть меню приложения и выберите пункт **Пароли и карты**\n%5$d Нажмите значок %6$@, а затем — опцию **Экспортировать пароли**\n%7$d Выберите вариант **В текстовый файл (небезопасно)** и нажмите **Экспортировать**\n%8$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%9$d %10$@" + "value" : "Импорт из" } } } }, - "import.data.alert.cancel" : { - "comment" : "Cancel button for data import alerts", + "import.csv.instructions.bitwarden" : { + "comment" : "Instructions to import Passwords as CSV from Bitwarden.\n%2$s - app name (Bitwarden)\n%7$@ - hamburger menu icon\n%9$@ - “Select Bitwarden CSV File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Abbrechen" + "value" : "%1$d Öffne und entsperre **%2$s**\n%3$d Wähle **Datei → Tresor exportieren** in der Menüleiste\n%4$d Wähle das Dateiformat: **.csv**\n%5$d Gib dein Bitwarden Haupt-Passwort ein\n%6$d Klicke auf %7$@ und speichere die Datei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%8$d %9$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Cancel" + "value" : "%1$d Open and unlock **%2$s**\n%3$d Select **File → Export vault** from the Menu Bar\n%4$d Select the File Format: **.csv**\n%5$d Enter your Bitwarden main password\n%6$d Click %7$@ and save the file someplace you can find it (e.g., Desktop)\n%8$d %9$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cancelar" + "value" : "%1$d Abre y desbloquea **%2$s**\n%3$d Selecciona **Archivo → Exportar caja fuerte** en la barra de menú\n%4$d Selecciona el formato de archivo: **.csv**\n%5$d Introduce tu contraseña principal de Bitwarden\n%6$d Haz clic en %7$@ y guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%8$d %9$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Annuler" + "value" : "%1$d Ouvrez et déverrouillez **%2$s**\n%3$d Sélectionnez **Fichier → Exporter le coffre-fort** dans la barre de menu\n%4$d Sélectionnez le format de fichier : **.csv**\n%5$d Saisissez votre mot de passe Bitwarden principal\n%6$d Cliquez sur %7$@ et enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%8$d %9$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Annulla" + "value" : "%1$d Apri e sblocca **%2$s**\n%3$d Seleziona **File → Esporta vault** dalla Barra dei menu\n%4$d Seleziona il Formato del file: **.csv**\n%5$d Inserisci la tua password principale Bitwarden\n%6$d Clicca su %7$@ e salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%8$d %9$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Annuleren" + "value" : "%1$d Open en ontgrendel **%2$s**\n%3$d Selecteer **Bestand → Kluis exporteren** in de menubalk\n%4$d Selecteer de bestandsindeling: **.csv**\n%5$d Voer je Bitwarden-hoofdwachtwoord in\n%6$d Klik op %7$@ en bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%8$d %9$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Anuluj" + "value" : "%1$d Otwórz i odblokuj aplikację **%2$s**\n%3$d Wybierz **Plik → Eksportuj sejf** na pasku menu\n%4$d Wybierz format pliku: **.csv**\n%5$d Wprowadź hasło główne aplikacji Bitwarden\n%6$d Kliknij %7$@ i zapisz plik w łatwo dostępnym miejscu (np. na pulpicie)\n%8$d %9$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Cancelar" + "value" : "%1$d Abre e desbloqueia o **%2$s**\n%3$d Seleciona **Ficheiro → Exportar cofre** na barra de menus\n%4$d Seleciona o formato do ficheiro: **.csv**\n%5$d Introduz a tua palavra-passe principal do Bitwarden\n%6$d Clica em %7$@ e guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%8$d %9$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отменить" + "value" : "%1$d Откройте и разблокируйте **%2$s**\n%3$d На панели меню выберите **Файл → Экспортировать хранилище** \n%4$d Выберите формат файла: **.csv**\n%5$d Введите основной пароль к Bitwarden\n%6$d Нажмите %7$@ и сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%8$d %9$@" } } } }, - "import.data.alert.import" : { - "comment" : "Import button for data import alerts", + "import.csv.instructions.brave" : { + "comment" : "Instructions to import Passwords as CSV from Brave browser.\n%N$d - step number\n%2$s - browser name (Brave)\n%4$@, %6$@ - hamburger menu icon\n%10$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Importieren" + "value" : "%1$d Öffne **%2$s**\n%3$d Klicke auf %4$@, um das Anwendungsmenü zu öffnen, und klicke dann auf **Passwort-Manager**\n%5$d Klicke auf %6$@ **oben links** im Passwort-Manager und wähle **Einstellungen**\n%7$d Finde „Passwörter exportieren“ und klicke auf **Datei herunterladen**\n%8$d Speichere die Datei mit den Passwörtern an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop) %9$d %10$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Import" + "value" : "%1$d Open **%2$s**\n%3$d Click %4$@ to open the application menu then click **Password Manager**\n%5$d Click %6$@ **at the top left** of the Password Manager and select **Settings**\n%7$d Find “Export Passwords” and click **Download File**\n%8$d Save the passwords file someplace you can find it (e.g., Desktop)\n%9$d %10$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importar" + "value" : "%1$d Abre **%2$s**\n%3$d Haz clic en %4$@ para abrir el menú de la aplicación y a continuación haz clic en **Administrador de contraseñas**\n%5$d Haz clic en %6$@ **en la parte superior izquierda** del Administrador de contraseñas y selecciona **Configuración**\n%7$d Busca \"Exportar contraseñas\" y haz clic en **Descargar archivo**\n%8$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%9$d %10$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importer" + "value" : "%1$d Ouvrez **%2$s**\n%3$d Cliquez sur %4$@ pour ouvrir le menu des applications, puis sur **Gestionnaire de mots de passe**\n%5$d Cliquez sur %6$@ **en haut à gauche** du gestionnaire de mots de passe et sélectionnez **Paramètres**\n%7$d Recherchez « Exporter les mots de passe » et cliquez sur **Télécharger le fichier**\n%8$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%9$d %10$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importa" + "value" : "%1$d Apri **%2$s**\n%3$d Fai clic su %4$@ per aprire il menu dell'applicazione, quindi fai clic su **Password Manager**\n%5$d Fai clic su %6$@ **in alto a sinistra** di Password Manager e seleziona **Impostazioni**\n%7$d Trova \"Esporta password\" e fai clic su **Download file**\n%8$d Salva il file password dove puoi trovarlo (ad esempio, sul desktop)\n%9$d %10$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Importeren" + "value" : "%1$d Open **%2$s**\n%3$d Klik op %4$@ om het toepassingsmenu te openen en klik vervolgens op **Wachtwoordbeheer**%5$d Klik op %6$@ **linksboven** in de wachtwoordbeheerder en selecteer **Instellingen**\n%7$d Zoek 'Wachtwoorden exporteren' en klik op **Bestand downloaden**\n%8$d Bewaar het bestand met de wachtwoorden op een plek waar je het kunt vinden (bijv. het bureaublad)\n%9$d %10$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Import" + "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Kliknij %4$@, aby otworzyć menu aplikacji, a następnie kliknij **Menedżer haseł**\n%5$d Kliknij %6$@ **w lewym górnym rogu** Menedżera haseł i wybierz **Ustawienia**\n%7$d Znajdź pozycję „Eksportuj hasła” i kliknij **Pobierz plik**\n%8$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%9$d %10$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importar" + "value" : "%1$d Abre o **%2$s**\n%3$d Clica em %4$@ para abrir o menu da aplicação e, em seguida, clica em **Gestor de palavras-passe**\n%5$d Clica em %6$@ **na parte superior esquerda** do Gestor de palavras-passe e seleciona **Definições**\n%7$d Encontra a opção \"Exportar palavras-passe\" e clica em **Transferir ficheiro**\n%8$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%9$d %10$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Импорт" + "value" : "%1$d Откройте **%2$s**\n%3$d Нажмите значок %4$@, чтобы открыть меню приложения, а затем выберите **Менеджер паролей**\n%5$d Нажмите значок %6$@ **сверху слева** от заголовка «Менеджер паролей» и выберите **Настройки**\n%7$d Найдите опцию «Экспортировать пароли» и нажмите **Загрузить файл**\n%8$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%9$d %10$@" } } } }, - "import.data.done" : { - "comment" : "Button text for finishing the data import", + "import.csv.instructions.chrome" : { + "comment" : "Instructions to import Passwords as CSV from Google Chrome browser.\n%N$d - step number\n%2$s - browser name (Chrome)\n%4$@ - hamburger menu icon\n%8$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fertig" + "value" : "%1$d Öffne **%2$s**\n%3$d Klicke in einem neuen Tab auf %4$@ und dann auf **Google Password Manager → Einstellungen**\n%5$d Finde „Passwörter exportieren“ und klicke auf **Datei herunterladen**\n%6$d Speichere die Datei mit den Passwörtern an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Done" + "value" : "%1$d Open **%2$s**\n%3$d In a fresh tab, click %4$@ then **Google Password Manager → Settings**\n%5$d Find “Export Passwords” and click **Download File**\n%6$d Save the passwords file someplace you can find it (e.g., Desktop)\n%7$d %8$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Hecho" + "value" : "%1$d Abre **%2$s**\n%3$d En una nueva pestaña, haz clic en %4$@ y luego en **Gestor de contraseñas de Google → Configuración**\n%5$d Busca \"Exportar contraseñas\" y haz clic en **Descargar archivo**\n%6$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Terminé" + "value" : "%1$d Ouvrez **%2$s**\n%3$d Dans un nouvel onglet, cliquez sur %4$@ puis sur **Gestionnaire de mots de passe Google → Paramètres**\n%5$d Recherchez « Exporter les mots de passe » et cliquez sur **Télécharger le fichier**\n%6$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Fatto" + "value" : "%1$d Apri **%2$s**\n%3$d In una nuova scheda, fai clic su %4$@, quindi su **Gestore delle password di Google → Impostazioni**\n%5$d Trova \"Esporta password\" e fai clic su **Scarica file**\n%6$d Salva il file delle password dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Klaar" + "value" : "%1$d Open **%2$s**\n%3$d Klik in een nieuw tabblad op %4$@ en vervolgens op **Google Wachtwoordbeheerder → Instellingen**\n%5$d Zoek \"Wachtwoorden exporteren\" en klik op **Bestand downloaden**\n%6$d Bewaar het wachtwoordbestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Gotowe" + "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na nowej karcie kliknij %4$@, a następnie **Menedżer haseł Google → Ustawienia**\n%5$d Znajdź pozycję „Eksportuj hasła” i kliknij **Pobierz plik**\n%6$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Feito" + "value" : "%1$d Abre o **%2$s**\n%3$d Num novo separador, clica em %4$@ e, em seguida, clica em **Gestor de Palavras-passe da Google → Definições**\n%5$d Encontra a opção \"Exportar palavras-passe\" e clica em **Transferir ficheiro**\n%6$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Готово" + "value" : "%1$d Откройте **%2$s**\n%3$d В новой вкладке нажмите на значок %4$@, а затем: **Менеджер паролей Google → Настройки**\n%5$d Найдите опцию «Экспортировать пароли» и нажмите **Скачать файл**\n%6$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" } } } }, - "import.data.initiate" : { - "comment" : "Button text for importing data", + "import.csv.instructions.chromium" : { + "comment" : "Instructions to import Passwords as CSV from Chromium-based browsers.\n%N$d - step number\n%2$s - browser name\n%4$@ - hamburger menu icon\n%8$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Importieren" + "value" : "%1$d Öffne **%2$s**\n%3$d Klicke in einer neuen Registerkarte auf %4$@ und dann auf **Passwort-Manager → Einstellungen**\n%5$d Suche nach „Passwörter exportieren“ und klicke auf **Datei herunterladen**\n%6$d Speichere die Datei mit den Passwörtern an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Import" + "value" : "%1$d Open **%2$s**\n%3$d In a fresh tab, click %4$@ then **Password Manager → Settings**\n%5$d Find “Export Passwords” and click **Download File**\n%6$d Save the passwords file someplace you can find it (e.g., Desktop)\n%7$d %8$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importar" + "value" : "%1$d Abre **%2$s**\n%3$d En una nueva pestaña, haz clic en %4$@ y luego en **Gestor de contraseñas → Configuración**\n%5$d Busca \"Exportar contraseñas\" y haz clic en **Descargar archivo**\n%6$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importer" + "value" : "%1$d Ouvrez **%2$s**\n%3$d Dans un nouvel onglet, cliquez sur %4$@ puis sur **Gestionnaire de mots de passe → Paramètres**\n%5$d Recherchez « Exporter les mots de passe » et cliquez sur **Télécharger le fichier**\n%6$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importa" + "value" : "%1$d Apri **%2$s**\n%3$d In una nuova scheda, fai clic su %4$@, quindi su **Gestore password → Impostazioni**\n%5$d Trova \"Esporta password\" e fai clic su **Scarica file**\n%6$d Salva il file delle password dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Importeren" + "value" : "%1$d Open **%2$s**\n%3$d Klik in een nieuw tabblad op %4$@ en vervolgens op **Wachtwoordbeheerder → Instellingen**\n%5$d Zoek \"Wachtwoorden exporteren\" en klik op **Bestand downloaden**\n%6$d Bewaar het wachtwoordbestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Import" + "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na nowej karcie kliknij %4$@, a następnie **Menedżer haseł → Ustawienia**\n%5$d Znajdź pozycję „Eksportuj hasła” i kliknij **Pobierz plik**\n%6$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importar" + "value" : "%1$d Abre o **%2$s**\n%3$d Num novo separador, clica em %4$@ e, em seguida, clica em **Gestor de palavras-passe → Definições**\n%5$d Encontra a opção \"Exportar palavras-passe\" e clica em **Transferir ficheiro**\n%6$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Импорт" + "value" : "%1$d Откройте **%2$s**%3$d В новой вкладке нажмите на значок %4$@, а\n затем: **Менеджер паролей → Настройки**\n%5$d Найдите опцию «Экспортировать пароли» и нажмите **Скачать файл**\n%6$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" } } } }, - "import.data.manual" : { - "comment" : "Button text for initiating manual data import using a HTML or CSV file when automatic import has failed", + "import.csv.instructions.coccoc" : { + "comment" : "Instructions to import Passwords as CSV from Cốc Cốc browser.\n%N$d - step number\n%2$s - browser name (Cốc Cốc)\n%5$@ - hamburger menu icon\n%8$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Manueller Import …" + "value" : "%1$d Öffne **%2$s**\n%3$d Gib „_coccoc://settings/passwords_“ in die Adressleiste ein\n%4$d Klicke auf %5$@ (rechts neben _Gespeicherte Passwörter_) und wähle **Passwörter exportieren**\n%6$d Speichere die Passwortdatei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Manual import…" + "value" : "%1$d Open **%2$s**\n%3$d Type “_coccoc://settings/passwords_” into the Address bar\n%4$d Click %5$@ (on the right from _Saved Passwords_) and select **Export passwords**\n%6$d Save the passwords file someplace you can find it (e.g., Desktop)\n%7$d %8$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importación manual..." + "value" : "%1$d Abre **%2$s**\n%3$d Escribe \"_coccoc://settings/passwords_\" en la barra de direcciones\n%4$d Haz clic en %5$@ (a la derecha de _Contraseñas guardadas_) y selecciona **Exportar contraseñas**\n%6$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importation manuelle…" + "value" : "%1$d Ouvrez **%2$s**\n%3$d Saisissez « _coccoc://settings/passwords_ » dans la barre d'adresse\n%4$d Cliquez sur %5$@ (à droite de _Saved Passwords_) et sélectionnez **Exporter les mots de passe**\n%6$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importazione manuale…" + "value" : "%1$d Apri **%2$s**\n%3$d Digita \"_coccoc://settings/passwords_\" nella barra degli indirizzi\n%4$d Fai clic su %5$@ (a destra di _Saved Passwords_) e seleziona **Esporta password**\n%6$d Salva il file delle password dove puoi trovarlo (ad esempio sul desktop)\n%7$d %8$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open je vorige browser\n%2$d Open **Bladwijzerbeheerder**\n%3$d Exporteer je bladwijzers naar HTML …\n%4$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%5$d %6$d %$@" + "value" : "%1$d Open **%2$s**\n%3$d Typ “_coccoc://settings/passwords_” in de adresbalk\n%4$d Klik op %5$@ (aan de rechterkant van _Opgeslagen wachtwoorden_) en selecteer **Wachtwoorden exporteren**\n%6$d Bewaar het wachtwoordbestand op een plek waar u het kunt vinden (bijv. het bureaublad) \n %7$d %8$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importowanie ręczne..." + "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Wpisz „_coccoc://settings/passwords_” w pasku adresu\n%4$d Kliknij %5$@ (po prawej stronie pozycji _Saved Passwords_) i wybierz **Export passwords**\n%6$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importação manual…" + "value" : "%1$d Abre o **%2$s**\n%3$d Escreve \"_coccoc://settings/passwords_\" na barra de endereço\n%4$d Clica em %5$@ (à direita de _Palavras-passe guardadas_) e seleciona **Exportar palavras-passe**\n%6$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Импортировать вручную..." + "value" : "%1$d Откройте **%2$s**\n%3$d Введите текст «_coccoc://settings/passwords_» в адресную строку\n%4$d Нажмите значок %5$@ (справа от заголовка «Сохраненные пароли») и выберите **Экспортировать пароли**\n%6$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" } } } }, - "import.data.requires-password.body" : { - "comment" : "Alert body text when the data import needs a password", + "import.csv.instructions.firefox" : { + "comment" : "Instructions to import Passwords as CSV from Firefox.\n%N$d - step number\n%2$s - browser name (Firefox)\n%4$@, %6$@ - hamburger menu icon\n%9$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo wird dein %1$@-Primärpasswort nicht speichern oder weitergeben, aber DuckDuckGo braucht es, um auf Passwörter von %1$@ zuzugreifen und sie zu importieren." + "value" : "%1$d Öffne **%2$s**\n%3$d Klicke auf %4$@, um das Anwendungsmenü zu öffnen, und klicke dann auf **Passwörter**\n%5$d Klicke auf %6$@ und dann auf **Logins exportieren...**\n%7$d Speichere die Passwortdatei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%8$d %9$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo won't save or share your %1$@ Primary Password, but DuckDuckGo needs it to access and import passwords from %1$@." + "value" : "%1$d Open **%2$s**\n%3$d Click %4$@ to open the application menu then click **Passwords**\n%5$d Click %6$@ then **Export Logins…**\n%7$d Save the passwords file someplace you can find it (e.g., Desktop)\n%8$d %9$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo no guardará ni compartirá tu contraseña principal de %1$@, pero DuckDuckGo la necesita para acceder e importar contraseñas desde %1$@." + "value" : "%1$d Abre **%2$s**\n%3$d Haz clic en %4$@ para abrir el menú de la aplicación y, a continuación, haz clic en **Contraseñas**\n%5$d Haz clic en %6$@ y luego en **Exportar inicios de sesión... **\n%7$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%8$d %9$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo n'enregistrera pas ni ne partagera votre mot de passe principal %1$@, mais DuckDuckGo en a besoin pour accéder aux mots de passe et les importer depuis %1$@." + "value" : "%1$d Ouvrez **%2$s**\n%3$d Cliquez sur %4$@ pour ouvrir le menu des applications puis cliquez sur **Mots de passe**\n%5$d Cliquez sur %6$@ puis sur **Exporter les identifiants...**\n%7$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%8$d %9$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo non salva né condivide la tua password principale %1$@, ma DuckDuckGo ne ha bisogno per accedere e importare password da %1$@." + "value" : "%1$d Apri **%2$s**\n%3$d Fai clic su %4$@ per aprire il menu dell'applicazione, quindi fai clic su **Password**\n%5$d Fai clic su %6$@, quindi su **Esporta accessi…**\n%7$d Salva il file delle password dove puoi trovarlo (ad esempio, sul desktop)\n%8$d %9$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo slaat je primaire wachtwoord van %1$@ niet op of deelt het niet, maar DuckDuckGo heeft het nodig om wachtwoorden van %1$@ te openen en te importeren." + "value" : "%1$d Open **%2$s**\n%3$d Klik op %4$@ om het toepassingsmenu te openen en klik vervolgens op **Wachtwoorden**\n%5$d Klik op %6$@ en vervolgens op **Logins exporteren...**\n%7$d Bewaar het bestand met de wachtwoorden op een plek waar je het kunt vinden (bijv. het bureaublad)\n%8$d %9$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo nie zapisze ani nie udostępni Twojego głównego hasła programu %1$@, ale potrzebuje go, aby uzyskać dostęp do haseł i zaimportować je z programu %1$@." + "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Kliknij %4$@, aby otworzyć menu aplikacji, a następnie kliknij **Hasła**\n%5$d Kliknij %6$@, a następnie **Eksportuj dane logowania...**\n%7$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%8$d %9$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O DuckDuckGo não guarda nem partilha a tua palavra-passe principal do %1$@, mas precisa dela para aceder e importar palavras-passe do %1$@." + "value" : "%1$d Abre o **%2$s**\n%3$d Clica em %4$@ para abrir o menu da aplicação e, em seguida, clica em **Palavras-passe**\n%5$d Clica em %6$@ e, em seguida, em **Exportar inícios de sessão…**\n%7$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%8$d %9$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo не сохранит и никому не передаст ваш основной пароль от %1$@, однако он необходим нам для доступа к паролям из %1$@ и их дальнейшего импортирования." + "value" : "%1$d Откройте **%2$s**\n%3$d Нажмите значок %4$@, чтобы открыть меню приложения, а затем откройте раздел **Пароли**\n%5$d Нажмите значок %6$@ и выберите опцию **Экспортировать логины…**\n%7$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%8$d %9$@" } } } }, - "import.data.requires-password.title" : { - "comment" : "Alert title text when the data import needs a password", + "import.csv.instructions.generic" : { + "comment" : "Instructions to import a generic CSV passwords file.\n%N$d - step number\n%3$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Primäres Passwort für %@ eingeben" + "value" : "Der CSV-Importer versucht, die Spaltenüberschriften an ihre Position anzupassen.\nWenn es keine Kopfzeile gibt, unterstützt er zwei Formate:\n%1$d URL, Benutzername, Passwort\n%2$d Titel, URL, Benutzername, Passwort\n%3$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Enter Primary Password for %@" + "value" : "The CSV importer will try to match column headers to their position.\nIf there is no header, it supports two formats:\n%1$d URL, Username, Password\n%2$d Title, URL, Username, Password\n%3$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Introduce la contraseña principal para %@" + "value" : "El importador de CSV intentará hacer coincidir los encabezados de columna con su posición.\nSi no hay encabezado, admite dos formatos:\n%1$d URL, Nombre de usuario, Contraseña\n%2$d Título, URL, Nombre de usuario, Contraseña\n%3$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Saisissez le mot de passe principal pour %@" + "value" : "L'importateur CSV tentera de faire correspondre les en-têtes de colonne à leur position.\nS'il n'y a pas d'en-tête, il prend en charge deux formats :\n%1$d URL, Nom d'utilisateur, Mot de passe\n%2$d Titre, URL, Nom d'utilisateur, Mot de passe\n%3$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Inserisci la password principale per %@" + "value" : "L'importatore CSV cercherà di abbinare le intestazioni delle colonne alla loro posizione.\nSe non è presente alcuna intestazione, supporta due formati: %1$d URL, Nome utente, Password\n%2$d Titolo, URL, Nome utente, Password\n%3$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Voer primair wachtwoord in voor %@" + "value" : "De CSV-importtool zal proberen de kopteksten van de kolommen aan hun positie te koppelen.\nAls er geen koptekst is, ondersteunt deze twee indelingen:\n%1$d URL, gebruikersnaam, wachtwoord\n%2$d Titel, URL, gebruikersnaam, wachtwoord\n%3$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wprowadź hasło główne programu %@" + "value" : "Narzędzie importu plików CSV spróbuje dopasować nagłówki kolumn do ich pozycji.\nW przypadku braku nagłówka obsługuje ono dwa formaty:\n%1$d adres URL, nazwa użytkownika, hasło\n%2$d tytuł, adres URL, nazwa użytkownika, hasło\n%3$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Introduzir palavra-passe principal do %@" + "value" : "O importador de CSV vai tentar combinar os cabeçalhos das colunas com as respetivas posições.\nSe não existir um cabeçalho, suporta dois formatos:\n%1$d URL, Nome de utilizador, Palavra-passe\n%2$d Título, URL, Nome de utilizador, Palavra-passe\n%3$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Введите основной пароль от %@" + "value" : "Средство импорта файлов CSV сопоставит заголовки столбцов с их позициями.\nЕсли в файле нет заголовка, средство поддерживает два формата:\n%1$d URL, имя пользователя, пароль\n%2$d Заголовок, URL, имя пользователя, пароль\n%3$@" } } } }, - "import.data.skip" : { - "comment" : "Button text to skip an import step", + "import.csv.instructions.lastpass" : { + "comment" : "Instructions to import Passwords as CSV from LastPass.\n%2$s - app name (LastPass)\n%8$@ - “Select LastPass CSV File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Überspringen" + "value" : "%1$d Klicke auf das **%2$s** Symbol in deinem Browser und gib dein Haupt-Passwort ein\n%3$d Wähle **Meinen Tresor öffnen**\n%4$d Wähle in der Seitenleiste **Erweiterte Optionen → Exportieren**\n%5$d Gib dein LastPass Haupt-Passwort ein\n%6$d Wähle das Dateiformat: **Comma Delimited Text (.csv)**\n%7$d %8$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Skip" + "value" : "%1$d Click on the **%2$s** icon in your browser and enter your main password\n%3$d Select **Open My Vault**\n%4$d From the sidebar select **Advanced Options → Export**\n%5$d Enter your LastPass main password\n%6$d Select the File Format: **Comma Delimited Text (.csv)**\n%7$d %8$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Omitir" + "value" : "%1$d Haz clic en el icono **%2$s** de tu navegador e introduce tu contraseña principal\n%3$d Selecciona **Abrir mi caja fuerte**\n%4$d En la barra lateral, selecciona **Opciones avanzadas → Exportar**\n%5$d Introduce tu contraseña principal de LastPass\n%6$d Selecciona el formato de archivo: **Texto delimitado por comas (.csv)**\n%7$d %8$@." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ignorer" + "value" : "%1$d Cliquez sur l'icône **%2$s** de votre navigateur et saisissez votre mot de passe principal\n%3$d Sélectionnez **Ouvrir mon coffre-fort**\n%4$d Dans la barre latérale, sélectionnez **Options avancées → Exporter**\n%5$d Saisissez votre mot de passe LastPass principal\n%6$d Sélectionnez le format de fichier** : **Texte séparé par des virgules (.csv)**\n%7$d %8$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Salta" + "value" : "%1$d Clicca sull'icona **%2$s** nel browser e inserisci la tua password principale\n%3$d Seleziona **Apri il Mio Vault**\n%4$d Dalla barra laterale seleziona **Opzioni avanzate → Esporta**\n%5$d Inserisci la tua password principale LastPass\n%6$d Seleziona il formato del file: **Testo delimitato da virgolette (.csv)**\n%7$d %8$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Overslaan" + "value" : "%1$d Klik op het pictogram **%2$s** in je browser en voer je hoofdwachtwoord in\n%3$d Selecteer **Open mijn kluis**\n%4$d Klik in de zijbalk op **Geavanceerde opties → Exporteren**\n%5$d Voer je LastPass-hoofdwachtwoord in\n%6$d Selecteer de bestandsindeling: **Door komma's gescheiden tekst (.csv)**\n%7$d %8$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pomiń" + "value" : "%1$d Kliknij ikonę **%2$s** w przeglądarce i wprowadź hasło główne\n%3$d Wybierz **Otwórz mój sejf**\n%4$d Na pasku bocznym wybierz **Opcje zaawansowane → Eksportuj**\n%5$d Wprowadź hasło główne splikacji LastPass\n%6$d Wybierz format pliku: **Tekst rozdzielany przecinkami (.csv)**\n%7$d %8$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ignorar" + "value" : "%1$d Clica no ícone da aplicação **%2$s** no teu navegador e introduz a tua palavra-passe principal\n%3$d Seleciona **Abrir o meu cofre**\n%4$d Na barra lateral, seleciona **Opções avançadas → Exportar**\n%5$d Introduz a tua palavra-passe principal da aplicação LastPass\n%6$d Seleciona o formato do ficheiro: **Texto delimitado por vírgula (.csv)**\n%7$d %8$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пропустить" + "value" : "%1$d Нажмите на значок **%2$s** в браузере и введите основной пароль\n%3$d Выберите меню **Открыть мое хранилище**\n%4$d На боковой панели выберите **Дополнительные опции → Экспортировать**\n%5$d Введите основной пароль к LastPass\n%6$d Сохраните файл в формате: **текст с разделяющими запятыми (.csv)**\n%7$d %8$@" } } } }, - "import.data.skip.bookmarks" : { - "comment" : "Button text to skip bookmarks manual import", + "import.csv.instructions.onePassword7" : { + "comment" : "Instructions to import Passwords as CSV from 1Password 7.\n%2$s - app name (1Password)\n%9$@ - “Select 1Password CSV File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen überspringen" + "value" : "%1$d Öffne und entsperre **%2$s**\n%3$d Wähle den Tresor aus, den du exportieren möchtest (du kannst immer nur einen Tresor auf einmal exportieren)\n%4$d Wähle **Datei → Exportieren → Alle Elemente** aus der Menüleiste\n%5$d Gib dein 1Password Haupt- oder Account-Passwort ein\n%6$d Wähle das Dateiformat: **iCloud-Schlüsselbund (.csv)**\n%7$d Speichere die Passwortdatei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%8$d %9$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Skip bookmarks" + "value" : "%1$d Open and unlock **%2$s**\n%3$d Select the vault you want to export (you can only export one vault at a time)\n%4$d Select **File → Export → All Items** from the Menu Bar\n%5$d Enter your 1Password main or account password\n%6$d Select the File Format: **iCloud Keychain (.csv)**\n%7$d Save the passwords file someplace you can find it (e.g., Desktop)\n%8$d %9$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Omitir marcadores" + "value" : "%1$d Abre y desbloquea **%2$s**\n%3$d Selecciona la caja fuerte que quieres exportar (solo puedes exportar una caja fuerte a la vez)\n%4$d Selecciona **Archivo → Exportar → Todos los elementos** en la barra de menú\n%5$d Introduce tu contraseña principal o de cuenta de 1Password\n%6$d Selecciona el formato de archivo: **Llavero de iCloud (.csv) **\n%7$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%8$d %9$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ignorer les signets" + "value" : "%1$d Ouvrez et déverrouillez **%2$s**\n%3$d Sélectionnez le coffre-fort à exporter (vous ne pouvez exporter qu'un seul coffre-fort à la fois)\n%4$d Sélectionnez **Fichier → Exporter → Tous les éléments** dans la barre de menu\n%5$d Saisissez votre mot de passe principal ou celui de votre compte 1Password\n%6$d Sélectionnez le format de fichier :**Trousseau iCloud (.csv)**\n%7$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%8$d %9$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Salta segnalibri" + "value" : "%1$d Apri e sblocca **%2$s**\n%3$d Seleziona il vault da esportare (puoi esportarne solo uno per volta)\n%4$d Seleziona **File → Esporta → Tutti gli elementi** dalla Barra dei menu\n%5$d Inserisci la tua password principale o dell'account 1Password\n%6$d Seleziona il formato del file: **Portachiavi iCloud (.csv)**\n%7$d Salva il file delle password dove puoi trovarlo (ad esempio, sul desktop)\n%8$d %9$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bladwijzers overslaan" + "value" : "%1$d Open en ontgrendel **%2$s**\n%3$d Selecteer de kluis die je wilt exporteren (je kunt slechts één kluis per keer exporteren)\n%4$d Selecteer **Bestand → Exporteren → Alle items** in de menubalk\n%5$d Voer je 1Password hoofd- of accountwachtwoord in\n%6$d Selecteer de bestandsindeling: **iCloud Keychain (.csv)**\n%7$d Bewaar het bestand met de wachtwoorden op een plek waar je het kunt vinden (bijv. het bureaublad)\n%8$d %9$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pomiń zakładki" + "value" : "%1$d Otwórz i odblokuj aplikację **%2$s**\n%3$d Wybierz sejf, który chcesz wyeksportować (możesz eksportować tylko jeden sejf naraz)\n%4$d Na pasku menu wybierz **Plik → Eksportuj → Wszystkie elementy**\n%5$d Wprowadź hasło główne lub hasło konta 1Password\n%6$d Wybierz format pliku: **iCloud Keychain (.csv)**\n%7$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na pulpicie)\n%8$d %9$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ignorar marcadores" + "value" : "%1$d Abre e desbloqueia a aplicação **%2$s**\n%3$d Seleciona o cofre que pretendes exportar (só podes exportar um cofre de cada vez)\n%4$d Seleciona **Ficheiro → Exportar → Todos os elementos** na barra de menus\n%5$d Introduz a tua palavra-passe principal da aplicação ou da conta 1Password\n%6$d Seleciona o formato do ficheiro: **Porta-chaves em iCloud (.csv)**\n%7$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%8$d %9$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пропустить закладки" + "value" : "%1$d Откройте и разблокируйте **%2$s**\n%3$d Выберите хранилище, которое вы хотите экспортировать (можно экспортировать только по одному хранилищу за раз)\n%4$d На панели меню выберите опцию **Файл → Экспортировать → Все элементы**\n%5$d Введите основной пароль или пароль к учетной записи 1Password\n%6$d Выберите формат файла: **iCloud Keychain (.csv)**\n%7$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%8$d %9$@" } } } }, - "import.data.skip.passwords" : { - "comment" : "Button text to skip bookmarks manual import", + "import.csv.instructions.onePassword8" : { + "comment" : "Instructions to import Passwords as CSV from 1Password 8.\n%2$s - app name (1Password)\n%8$@ - “Select 1Password CSV File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter überspringen" + "value" : "%1$d Öffne und entsperre **%2$s**\n%3$d Wähle **Datei → Exportieren** in der Menüleiste und wähle das Konto aus, das du exportieren möchtest\n%4$d Gib dein 1Password-Kontopasswort ein\n%5$d Wähle das Dateiformat aus: **CSV (nur Logins und Passwörter)**\n%6$d Klicke auf „Daten exportieren“ und speichere die Datei an einem Ort, an dem du sie findest (z. B. auf dem Desktop)\n%7$d %8$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Skip passwords" + "value" : "%1$d Open and unlock **%2$s**\n%3$d Select **File → Export** from the Menu Bar and choose the account you want to export\n%4$d Enter your 1Password account password\n%5$d Select the File Format: **CSV (Logins and Passwords only)**\n%6$d Click Export Data and save the file someplace you can find it (e.g., Desktop)\n%7$d %8$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Omitir contraseñas" + "value" : "%1$d Abre y desbloquea **%2$s**\n%3$d Selecciona **Archivo → Exportar** en la barra de menú y elige la cuenta que deseas exportar\n%4$d Introduce tu contraseña de 1Password\n%5$d Selecciona el formato de archivo: *CSV (solo contraseñas e inicios de sesión)**\n%6$d Haz clic en Exportar datos y guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ignorer les mots de passe" + "value" : "%1$d Ouvrez et déverrouillez **%2$s**\n%3$d Sélectionnez **Fichier → Exporter** dans la barre de menu et choisissez le compte que vous souhaitez exporter\n%4$d Saisissez le mot de passe de votre compte 1Password\n%5$d Sélectionnez le format de fichier : **CSV (identifiants et mots de passe uniquement)**\n%6$d Cliquez sur Exporter les données et enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Salta password" + "value" : "%1$d Apri e sblocca **%2$s**\n%3$d Seleziona **File → Esporta** dalla Barra dei menu e scegli l'account da esportare\n%4$d Inserisci la password del tuo account 1Password\n%5$d Seleziona il formato file: **CSV (solo accesso e password)**\n%6$d Fai clic su Esporta dati e salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoorden overslaan" + "value" : "%1$d Open en ontgrendel **%2$s**\n%3$d Selecteer **Bestand → Exporteren** in de menubalk en kies het account dat je wilt exporteren\n%4$d Voer het wachtwoord van je 1Password-account in\n%5$d Selecteer het bestandsformaat: **CSV (alleen aanmeldingen en wachtwoorden)**\n%6$d Klik op Gegevens exporteren en bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pomiń hasła" + "value" : "%1$d Otwórz i odblokuj aplikację **%2$s**\n%3$d Wybierz **File → Export** na pasku menu i wybierz konto, które chcesz wyeksportować\n%4$d Wprowadź hasło konta 1Password\n%5$d Wybierz format pliku: **CSV (Logins and Passwords only)**\n%6$d Kliknij Export Data i zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ignorar palavras-passe" + "value" : "%1$d Abre e desbloqueia a aplicação **%2$s**\n%3$d Seleciona **Ficheiro → Exportar** na barra de menus e escolhe a conta que pretendes exportar\n%4$d Introduz a tua palavra-passe da conta da 1Password\n%5$d Seleciona o formato do ficheiro: **CSV (só Inícios de sessão e Palavras-passe)**\n%6$d Clica em Exportar dados e guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пропустить пароли" + "value" : "%1$d Откройте и разблокируйте **%2$s**\n%3$d В панели меню откройте раздел **Файл → Экспортировать** и выберите учетную запись, которую нужно экспортировать\n%4$d Введите пароль учетной записи 1Password\n%5$d Выберите формат файла: **CSV (только для логинов и паролей)**\n%6$d Нажмите кнопку «Экспортировать данные» и сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" } } } }, - "import.html.instructions.chromium" : { - "comment" : "Instructions to import Bookmarks exported as HTML from Chromium-based browsers.\n%N$d - step number\n%2$s - browser name\n%5$@ - hamburger menu icon\n%8$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", + "import.csv.instructions.opera" : { + "comment" : "Instructions to import Passwords as CSV from Opera browser.\n%N$d - step number\n%2$s - browser name (Opera)\n%8$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Lesezeichen → Lesezeichen-Manager**\n%4$d Klicke auf %5$@ und dann auf **Lesezeichen exportieren**\n%6$d Speichere die Datei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" + "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Ansicht → Passwort-Manager anzeigen**\n%4$d Wähle **Einstellungen**\n%5$d Finde „Passwörter exportieren“ und klicke auf **Datei herunterladen**\n%6$d Speichere die Datei mit den Passwörtern an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **Bookmarks → Bookmark Manager**\n%4$d Click %5$@ then **Export Bookmarks**\n%6$d Save the file someplace you can find it (e.g., Desktop)\n%7$d %8$@" + "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **View → Show Password Manager**\n%4$d Select **Settings**\n%5$d Find “Export Passwords” and click **Download File**\n%6$d Save the passwords file someplace you can find it (e.g., Desktop)\n%7$d %8$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **%2$s**\n%3$d Utiliza la barra de menú para seleccionar **Marcadores → Administrador de marcadores**\n%4$d Haz clic en %5$@ y luego en **Exportar marcadores**\n%6$d Guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" + "value" : "%1$d Abre **%2$s**%3$d Utiliza la barra de menú para seleccionar **Ver → Mostrar administrador de contraseñas**\n%4$d Selecciona **Configuración**\n%5$d Busca \"Exportar contraseñas\" y haz clic en **Descargar archivo**\n%6$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Signets → Gestionnaire de signets**\n%4$d Cliquez sur %5$@ puis sur **Exporter les signets**\n%6$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" + "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Voir → Afficher le gestionnaire de mots de passe**\n%4$d Sélectionnez **Paramètres**\n%5$d Recherchez « Exporter les mots de passe » et cliquez sur **Télécharger le fichier**\n%6$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **%2$s\n**%3$d Usa la barra dei menu per selezionare **Segnalibri → Gestore segnalibri**\n%4$d Fai clic su %5$@, quindi su **Esporta segnalibri**\n%6$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" + "value" : "%1$d Apri **%2$s**\n%3$d Usa la Barra dei menu per selezionare **Visualizza il gestore delle password**\n%4$d Seleziona **Impostazioni**\n%5$d Trova \"Esporta password\" e fai clic su **Scarica file**\n%6$d Salva il file password dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Bladwijzers → Bladwijzerbeheerder**\n%4$d Klik op %5$@ en vervolgens op **Bladwijzers exporteren**\n%6$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" + "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Bekijken → Wachtwoordbeheerder weergeven**\n%4$d Selecteer **Instellingen**\n%5$d Zoek 'Wachtwoorden exporteren' en klik op **Bestand downloaden**\n%6$d Bewaar het bestand met de wachtwoorden op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Zakładki → Menedżer zakładek**\n%4$d Kliknij %5$@, a następnie **Eksportuj zakładki**\n%6$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" + "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Widok → Pokaż menedżera haseł**\n%4$d Wybierz **Ustawienia**\n%5$d Znajdź pozycję „Eksportuj hasła” i kliknij **Pobierz plik**\n%6$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Marcadores → Gestor de marcadores**\n%4$d Clica em %5$@ e, em seguida, clica em **Exportar marcadores**\n%6$d Guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" + "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Visualização → Mostrar gestor de palavras-passe**\n%4$d Seleciona **Definições**\n%5$d Encontra a opção \"Exportar palavras-passe\" e clica em **Transferir ficheiro**\n%6$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **%2$s**\n%3$d На панели меню выберите **Закладки → Менеджер закладок**\n%4$d Нажмите значок %5$@ и выберите опцию **Экспортировать закладки**\n%6$d Сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" + "value" : "%1$d Откройте **%2$s**\n%3$d В панели меню выберите **Вид → Показать менеджер паролей**\n%4$d Откройте **Настройки**\n%5$d Найдите опцию «Экспортировать пароли» и нажмите **Загрузить файл**\n%6$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" } } } }, - "import.html.instructions.firefox" : { - "comment" : "Instructions to import Bookmarks exported as HTML from Firefox based browsers.\n%N$d - step number\n%2$s - browser name (Firefox)\n%5$@ - hamburger menu icon\n%8$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", + "import.csv.instructions.operagx" : { + "comment" : "Instructions to import Passwords as CSV from Opera GX browsers.\n%N$d - step number\n%2$s - browser name (Opera GX)\n%5$@ - menu button icon\n%8$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Lesezeichen → Lesezeichen verwalten**\n%4$d Klicke auf %5$@ und dann auf **Lesezeichen nach HTML exportieren ...**\n%6$d Speichere die Datei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" + "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Ansicht → Passwortmanager anzeigen**\n%4$d Klicke auf %5$@ (rechts von _Gespeicherte Passwörter_) und wähle **Passwörter exportieren**\n%6$d Speichere die Passwortdatei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **Bookmarks → Manage Bookmarks**\n%4$d Click %5$@ then **Export bookmarks to HTML…**\n%6$d Save the file someplace you can find it (e.g., Desktop)\n%7$d %8$@" + "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **View → Show Password Manager**\n%4$d Click %5$@ (on the right from _Saved Passwords_) and select **Export passwords**\n%6$d Save the passwords file someplace you can find it (e.g., Desktop)\n%7$d %8$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **%2$s**\n%3$d Utiliza la barra de menús para seleccionar **Marcadores → Gestionar marcadores**\n%4$d Haz clic en %5$@ y luego en **Exportar marcadores a HTML...**\n%6$d Guarda el archivo en algún lugar donde puedas encontrarlo (por ejemplo, el Escritorio)\n%7$d %8$@" + "value" : "%1$d Abre **%2$s**\n%3$d Utiliza la barra de menú para seleccionar **Ver → Mostrar administrador de contraseñas\n%4$d Haz clic en %5$@ (a la derecha de _Contraseñas guardadas_) y selecciona **Exportar contraseñas**\n%6$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Signets → Gérer les signets**\n%4$d Cliquez sur %5$@ puis sur **Exporter les signets vers HTML…**\n%6$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" + "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Voir → Afficher le gestionnaire de mots de passe**\n%4$d Cliquez sur %5$@ (à droite de _Saved Passwords_) et sélectionnez **Exporter les mots de passe**\n%6$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **%2$s**\n%3$d Usa la Barra dei menu per selezionare **Segnalibri → Gestisci segnalibri**\n%4$d Fai clic su %5$@, quindi su **Esporta segnalibri in HTML…**\n%6$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" + "value" : "%1$d Apri **%2$s**\n%3$d Usa la Barra dei menu per selezionare **Visualizza → Mostra Gestore password**\n%4$d Fai clic su %5$@ (a destra di _Password salvate_ ) e seleziona **Esporta password**\n%6$d Salva il file delle password dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Bladwijzers → Bladwijzers beheren**\n%4$d Klik op %5$@ en vervolgens op **Bladwijzers exporteren naar HTML ...**\n%6$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" + "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Bekijken → Wachtwoordbeheerder weergeven**\n%4$d Klik op %5$@ (rechts van _Bewaarde wachtwoorden_) en selecteer **Wachtwoorden exporteren**\n%6$d Bewaar het bestand met de wachtwoorden op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Zakładki → Zarządzaj zakładkami**\n%4$d Kliknij %5$@, a następnie **Eksportuj zakładki do pliku HTML**\n%6$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" + "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Widok → Pokaż menedżera haseł**\n%4$d Kliknij %5$@ (po prawej stronie pozycji _Zachowane hasła_) i wybierz **Eksportuj hasła**\n%6$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Marcadores → Gerir marcadores**\n%4$d Clica em %5$@ e, em seguida, clica em **Exportar marcadores para HTML…**\n%6$d Guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" + "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Visualização → Mostrar gestor de palavras-passe**\n%4$d Clica em %5$@ (à direita de _Palavras-passe guardadas_) e seleciona **Exportar palavras-passe**\n%6$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **%2$s**\n%3$d На панели меню выберите **Закладки → Управление закладками**\n%4$d Нажмите на значок %5$@ и выберите опцию **Экспорт закладок в HTML…**\n%6$d Сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" + "value" : "%1$d Откройте **%2$s**\n%3$d В панели меню выберите **Вид → Показать менеджер паролей**\n%4$d Нажмите значок %5$@ (справа от раздела «Сохраненные пароли») и выберите опцию **Экспортировать пароли**\n%6$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" } } } }, - "import.html.instructions.generic" : { - "comment" : "Instructions to import a generic HTML Bookmarks file.\n%N$d - step number\n%6$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", + "import.csv.instructions.safari" : { + "comment" : "Instructions to import Passwords as CSV from Safari.\n%N$d - step number\n%5$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne deinen alten Browser\n%2$d Öffne den **Bookmark Manager**\n%3$d Exportiere Lesezeichen nach HTML …\n%4$d Speichere die Datei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%5$d %6$@" + "value" : "%1$d Öffne **Safari**\n%2$d Wähle **Datei → Exportieren → Passwörter**\n%3$d Speichere die Datei mit den Passwörtern an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%4$d %5$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open your old browser\n%2$d Open **Bookmark Manager**\n%3$d Export bookmarks to HTML…\n%4$d Save the file someplace you can find it (e.g., Desktop)\n%5$d %6$@" + "value" : "%1$d Open **Safari**\n%2$d Select **File → Export → Passwords**\n%3$d Save the passwords file someplace you can find it (e.g., Desktop)\n%4$d %5$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre tu antiguo navegador\n%2$d Abre **Administrador de marcadores**\n%3$d Exportar marcadores a HTML...\n%4$d Guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%5$d %6$@" + "value" : "%1$d Abre **Safari**\n%2$d Selecciona **Archivo → Exportar → Contraseñas**\n%3$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%4$d %5$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez votre ancien navigateur\n%2$d Ouvrez **Gestionnaire de signets**\n%3$d Exportez les signets vers HTML…\n%4$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%5$d %6$@" + "value" : "%1$d Ouvrez **Safari**\n%2$d Sélectionnez **Fichier → Exporter → Mots de passe**\n%3$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%4$d %5$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri il tuo vecchio browser\n%2$d Apri **Gestore segnalibri**\n%3$d Esporta segnalibri in HTML…\n%4$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%5$d %6$@" + "value" : "%1$d Apri **Safari**\n%2$d Seleziona **File → Esporta → Password**\n%3$d Salva il file delle password dove puoi trovarlo (ad esempio, sul desktop)\n%4$d %5$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open je vorige browser\n%2$d Open **Bladwijzerbeheerder**\n%3$d Exporteer je bladwijzers naar HTML…\n%4$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%5$d %6$@" + "value" : "%1$d Open **Safari**\n%2$d Selecteer **Bestand → Exporteren → Wachtwoorden**\n%3$d Bewaar het bestand met de wachtwoorden op een plek waar je het kunt vinden (bijv. het bureaublad)\n%4$d %5$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz starą przeglądarkę\n%2$d Otwórz **Menedżera zakładek**\n%3$d Wyeksportuj zakładki do pliku HTML\n%4$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%5$d %6$@" + "value" : "%1$d Otwórz przeglądarkę **Safari**\n%2$d Wybierz **Plik → Eksportuj → Hasła**\n%3$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%4$d %5$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o teu navegador antigo\n%2$d Abre **Gestor de marcadores**\n%3$d Exportar marcadores para HTML…\n%4$d Guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%5$d %6$@" + "value" : "%1$d Abre o **Safari**\n%2$d Seleciona **Ficheiro → Exportar → Palavras-passe**\n%3$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%4$d %5$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Запустите старый браузер\n%2$d Откройте **Менеджер закладок**\n%3$d Экспортируйте закладки в HTML\n%4$d Сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%5$d %6$@" + "value" : "%1$d Откройте **Safari**\n%2$d Выберите раздел **Файл → Экспортировать → Пароли**\n%3$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%4$d %5$@" } } } }, - "import.html.instructions.opera" : { - "comment" : "Instructions to import Bookmarks exported as HTML from Opera browser.\n%N$d - step number\n%2$s - browser name (Opera)\n%8$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", + "import.csv.instructions.vivaldi" : { + "comment" : "Instructions to import Passwords exported as CSV from Vivaldi browser.\n%N$d - step number\n%2$s - browser name (Vivaldi)\n%5$@ - menu button icon\n%8$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Lesezeichen → Lesezeichen**\n%4$d Klicke unten links auf **Vollständige Lesezeichenansicht öffnen …**\n%5$d Klicke unten links auf **Importieren/Exportieren …** und wähle **Lesezeichen exportieren**\n%6$d Speichere die Datei an einem Ort, an dem du sie wiederfindest (z. B. auf dem Desktop)\n%7$d %8$@" + "value" : "%1$d Öffne **%2$s**\n%3$d Gib „_chrome://settings/passwords_“ in die Adressleiste ein\n%4$d Klicke auf %5$@ (rechts von _Gespeicherte Passwörter_) und wähle **Passwörter exportieren**\n%6$d Speichere die Datei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **Bookmarks → Bookmarks**\n%4$d Click **Open full Bookmarks view…** in the bottom left\n%5$d Click **Import/Export…** in the bottom left and select **Export Bookmarks**\n%6$d Save the file someplace you can find it (e.g., Desktop)\n%7$d %8$@" + "value" : "%1$d Open **%2$s**\n%3$d Type “_chrome://settings/passwords_” into the Address bar\n%4$d Click %5$@ (on the right from _Saved Passwords_) and select **Export passwords**\n%6$d Save the file someplace you can find it (e.g., Desktop)\n%7$d %8$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **%2$s**\n%3$d Utiliza la barra de menú para seleccionar **Marcadores → Marcadores**\n%4$d Haz clic en **Abrir vista completa de Marcadores...** en la parte inferior izquierda\n%5$d Haz clic en **Importar/Exportar...** en la parte inferior izquierda y selecciona **Exportar Marcadores**\n%6$d Guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" + "value" : "%1$d Abre **%2$s**\n%3$d Escribe \"_chrome://settings/passwords_\" en la barra de direcciones\n%4$d Haz clic en %5$@ (a la derecha de _Contraseñas guardadas_) y selecciona **Exportar contraseñas**\n%6$d Guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Signets → Signets **\n%4$d Cliquez sur **Ouvrir la vue complète des signets…** en bas à gauche\n%5$d Cliquez sur **Importer/Exporter…** en bas à gauche et sélectionnez **Exporter les signets**\n%6$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" + "value" : "%1$d Ouvrez **%2$s**\n%3$d Saisissez « _chrome://settings/passwords_ » dans la barre d'adresse\n%4$d Cliquez sur %5$@ (à droite de _Saved Passwords_) et sélectionnez **Exporter les mots de passe**\n%6$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **%2$s**\n%3$d Usa la Barra dei menu per selezionare **Segnalibri → Segnalibri\n**%4$d Fai clic su **Apri la visualizzazione Segnalibri completa…** in basso a sinistra\n%5$d Fai clic su **Importa/Esporta…** in basso a sinistra e seleziona **Esporta segnalibri**\n%6$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" + "value" : "%1$d Apri **%2$s**\n%3$d Tipo \"_chrome://settings/passwords_\" nella barra degli indirizzi\n%4$d Fai clic su %5$@ (a destra su _Password salvate_) e seleziona **Esporta password**\n%6$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@.\"" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Bladwijzers → Bladwijzers**\n%4$d Klik op **Volledige bladwijzerweergave openen...** in de linkerbenedenhoek\n%5$d Klik op **Importeren/Exporteren…** in de linkerbenedenhoek en selecteer **Bladwijzers exporteren**\n%6$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" + "value" : "%1$d Open **%2$s**\n%3$d Typ “_chrome://settings/passwords_” in de adresbalk\n%4$d Klik op %5$@ (aan de rechterkant van _Opgeslagen wachtwoorden_) en selecteer **Wachtwoorden exporteren**\n%6$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad) \n %7$d %8$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Zakładki → Zakładki**\n%4$d Kliknij **Otwórz pełny widok zakładek...** w lewym dolnym rogu\n%5$d Kliknij **Importuj/Eksportuj...** w lewym dolnym rogu i wybierz **Eksportuj zakładki**\n%6$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" + "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Wpisz „_chrome://settings/passwords_” w pasku adresu\n%4$d Kliknij %5$@ (po prawej stronie pozycji _Zachowane hasła_) i wybierz **Eksportuj hasła**\n%6$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Favoritos → Favoritos**\n%4$d Clica em **Abrir vista completa de Favoritos…** na parte inferior esquerda\n%5$d Clica em **Importar/exportar…** na parte inferior esquerda e seleciona **Exportar favoritos**\n%6$d Guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" + "value" : "%1$d Abre o **%2$s**\n%3$d Escreve \"_chrome://settings/passwords_\" na barra de endereço\n%4$d Clica em %5$@ (à direita de _Palavras-passe guardadas_) e seleciona **Exportar palavras-passe**\n%6$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **%2$s**\n%3$d В панели меню выберите **Закладки → Закладки**\n%4$d **Разверните панель закладок** с помощью значка внизу слева\n%5$d Затем нажмите кнопку **Импорт/Экспорт…** слева внизу и выберите **Экспортировать закладки**\n%6$d Сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" + "value" : "%1$d Откройте **%2$s**\n%3$d Введите текст «_chrome://settings/passwords_» в адресную строку\n%4$d Нажмите на значок %5$@ (справа от заголовка «Сохраненные пароли») и выберите **Экспортировать пароли**\n%6$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" } } } }, - "import.html.instructions.operagx" : { - "comment" : "Instructions to import Bookmarks exported as HTML from Opera GX browser.\n%N$d - step number\n%2$s - browser name (Opera GX)\n%7$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", + "import.csv.instructions.yandex" : { + "comment" : "Instructions to import Passwords as CSV from Yandex Browser.\n%N$d - step number\n%2$s - browser name (Yandex)\n%4$@ - hamburger menu icon\n%8$@ - “Select Passwords CSV File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Lesezeichen → Lesezeichen**\n%4$d Klicke unten links auf **Importieren/Exportieren …** und wähle **Lesezeichen exportieren**\n%5$d Speichere die Datei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%6$d %7$@" + "value" : "%1$d Öffne **%2$s**\n%3$d Klicke auf %4$@, um das Anwendungsmenü zu öffnen, und klicke dann auf **Passwörter und Karten**\n%5$d Klicke auf %6$@ und dann auf **Passwörter exportieren**\n%7$d Wähle **In eine Textdatei (nicht sicher)** und klicke auf **Exportieren**\n%8$d Speichere die Passwortdatei an einem Ort, an dem du sie findest (z. B. auf dem Desktop)\n%9$d %10$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **Bookmarks → Bookmarks**\n%4$d Click **Import/Export…** in the bottom left and select **Export Bookmarks**\n%5$d Save the file someplace you can find it (e.g., Desktop)\n%6$d %7$@" + "value" : "%1$d Open **%2$s**\n%3$d Click %4$@ to open the application menu then click **Passwords and cards**\n%5$d Click %6$@ then **Export passwords**\n%7$d Choose **To a text file (not secure)** and click **Export**\n%8$d Save the passwords file someplace you can find it (e.g., Desktop)\n%9$d %10$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **%2$s**\n%3$d Utiliza la barra de menú para seleccionar **Marcadores → Marcadores**\n%4$d Haz clic en **Importar/Exportar...** en la parte inferior izquierda y selecciona **Exportar marcadores**\n%5$d Guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%6$d %7$@" + "value" : "%1$d Abre **%2$s**\n%3$d Haz clic en %4$@ para abrir el menú de la aplicación y, a continuación, haz clic en **Contraseñas y tarjetas**\n%5$d Haz clic en %6$@ y luego en **Exportar contraseñas**\n%7$d Elige **A un archivo de texto (no seguro)** y haz clic en **Exportar**\n%8$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n %9$d %10$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Signets → Signets **\n%4$d Cliquez sur **Importer/Exporter…** en bas à gauche et sélectionnez **Exporter les signets**\n%5$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%6$d %7$@" + "value" : "%1$d Ouvrez **%2$s**\n%3$d Cliquez sur %4$@ pour ouvrir le menu des applications, puis sur **Mots de passe et cartes**\n%5$d Cliquez sur %6$@ puis sur **Exporter les mots de passe**\n%7$d Choisissez **Vers un fichier texte (non sécurisé)** et cliquez sur **Exporter**\n%8$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%9$d %10$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **%2$s**\n%3$d Usa la Barra dei menu per selezionare **Segnalibri → Segnalibri**\n%4$d Fai clic su **Importa/Esporta…** in basso a sinistra e seleziona **Esporta segnalibri**\n%5$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%6$d %7$@" + "value" : "%1$d Apri **%2$s**\n%3$d Fai clic su %4$@ per aprire il menu dell'applicazione, quindi su **Password e schede**\n%5$d Fai clic su %6$@, quindi su **Esporta password**\n%7$d Scegli **Per un file di testo (non sicuro)** e fai clic su **Esporta**\n%8$d Salva il file password dove puoi trovarlo (ad esempio, sul desktop)\n%9$d %10$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Bladwijzers → Bladwijzers**\n%4$d Klik op **Importeren/Exporteren…** in de linkerbenedenhoek en selecteer **Bladwijzers exporteren**\n%5$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%6$d %7$@" + "value" : "%1$d Open **%2$s**\n%3$d Klik op %4$@ om het toepassingsmenu te openen en klik vervolgens op **Wachtwoorden en kaarten**\n%5$d Klik op %6$@ en vervolgens op **Wachtwoorden exporteren**\n%7$d Kies **Naar een tekstbestand (niet beveiligd)** en klik op **Exporteren**\n%8$d Bewaar het bestand met de wachtwoorden op een plek waar je het kunt vinden (bijv. het bureaublad)\n%9$d %10$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Zakładki → Zakładki**\n%4$d Kliknij **Importuj/Eksportuj...** w lewym dolnym rogu i wybierz **Eksportuj zakładki**\n%5$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%6$d %7$@" + "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Kliknij %4$@, aby otworzyć menu aplikacji, a następnie kliknij **Passwords and cards**\n%5$d Kliknij %6$@, a następnie **Export passwords**\n%7$d Wybierz **To a text file (not secure)** i kliknij **Export**\n%8$d Zapisz plik haseł w łatwo dostępnym miejscu (np. na biurku)\n%9$d %10$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Favoritos → Favoritos**\n%4$d Clica em **Importar/exportar…** na parte inferior esquerda e seleciona **Exportar favoritos**\n%5$d Guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%6$d %7$@" + "value" : "%1$d Abre o **%2$s**\n%3$d Clica em %4$@ para abrir o menu da aplicação e, em seguida, clica em **Palavras-passe e cartões**\n%5$d Clica em %6$@ e, em seguida, clica em **Exportar palavras-passe**\n%7$d Seleciona **Para um ficheiro de texto (não protegido)** e clica em **Exportar**\n%8$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%9$d %10$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **%2$s**\n%3$d В панели меню выберите **Закладки → Закладки**\n%4$d Нажмите кнопку **Импорт/Экспорт…** слева внизу и выберите **Экспортировать закладки**\n%5$d Сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%6$d %7$@" + "value" : "%1$d Откройте **%2$s**\n%3$d Нажмите значок %4$@, чтобы открыть меню приложения и выберите пункт **Пароли и карты**\n%5$d Нажмите значок %6$@, а затем — опцию **Экспортировать пароли**\n%7$d Выберите вариант **В текстовый файл (небезопасно)** и нажмите **Экспортировать**\n%8$d Сохраните файл с паролями там, где вы легко его найдете (например, на рабочем столе)\n%9$d %10$@" } } } }, - "import.html.instructions.safari" : { - "comment" : "Instructions to import Bookmarks exported as HTML from Safari.\n%N$d - step number\n%5$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", + "import.data.alert.cancel" : { + "comment" : "Cancel button for data import alerts", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **Safari**\n%2$d Wähle **Datei → Exportieren → Lesezeichen**\n%3$d Speichere die Datei mit den Passwörtern an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop) %4$d %5$@" + "value" : "Abbrechen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **Safari**\n%2$d Select **File → Export → Bookmarks**\n%3$d Save the passwords file someplace you can find it (e.g., Desktop)\n%4$d %5$@" + "value" : "Cancel" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **Safari**\n%2$d Selecciona **Archivo → Exportar → Marcadores**\n%3$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%4$d %5$@" + "value" : "Cancelar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **Safari**\n%2$d Sélectionnez **Fichier → Exporter → Signets**\n%3$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%4$d %5$@" + "value" : "Annuler" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **Safari**\n%2$d Seleziona **File → Esporta → Segnalibri**\n%3$d Salva il file delle password dove puoi trovarlo (ad esempio, sul desktop)\n%4$d %5$@" + "value" : "Annulla" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **Safari**\n%2$d Selecteer **Bestand → Exporteren → Bladwijzers**\n%3$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%4$d %5$@" + "value" : "Annuleren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **Safari**\n%2$d Wybierz **Plik → Eksportuj → Zakładki**\n%3$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%4$d %5$@" + "value" : "Anuluj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **Safari**\n%2$d Selecione **Ficheiro → Exportar → Marcadores**\n%3$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%4$d %5$@" + "value" : "Cancelar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **Safari**\n%2$d Выберите раздел **Файл → Экспортировать → Закладки**\n%3$d Сохраните файл с закладками там, где вы легко его найдете (например, на рабочем столе)\n%4$d %5$@" + "value" : "Отменить" } } } }, - "import.html.instructions.vivaldi" : { - "comment" : "Instructions to import Bookmarks exported as HTML from Vivaldi browser.\n%N$d - step number\n%2$s - browser name (Vivaldi)\n%6$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", + "import.data.alert.import" : { + "comment" : "Import button for data import alerts", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Datei → Lesezeichen exportieren …**\n%4$d Speichere die Datei an einem Ort, an dem du sie finden kannst (z.\nB. auf dem Desktop)\n%5$d %6$@" + "value" : "Importieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **File → Export Bookmarks…**\n%4$d Save the file someplace you can find it (e.g., Desktop)\n%5$d %6$@" + "value" : "Import" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **%2$s**\n%3$d Utiliza la barra de menú para seleccionar **Archivo → Exportar marcadores...**\n%4$d Guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%5$d %6$@" + "value" : "Importar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Fichier → Exporter les signets…**\n%4$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%5$d %6$@" + "value" : "Importer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **%2$s**\n%3$d Usa la Barra dei menu per selezionare **File → Esporta segnalibri…**\n%4$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%5$d %6$@" + "value" : "Importa" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Bestand → Bladwijzers exporteren…**\n%4$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%5$d %6$@" + "value" : "Importeren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Plik → Eksportuj zakładki...**\n%4$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%5$d %6$@" + "value" : "Import" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Ficheiro → Exportar marcadores…**\n%4$d Guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%5$d %6$@" + "value" : "Importar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **%2$s**\n%3$d В панели меню выберите раздел **Файл → Экспортировать закладки…**\n%4$d Сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%5$d %6$@" + "value" : "Импорт" } } } }, - "import.html.instructions.yandex" : { - "comment" : "Instructions to import Bookmarks exported as HTML from Yandex Browser.\n%N$d - step number\n%2$s - browser name (Yandex)\n%5$@ - hamburger menu icon\n%8$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", + "import.data.done" : { + "comment" : "Button text for finishing the data import", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Favoriten → Lesezeichen-Manager**\n%4$d Klicke auf %5$@ und dann auf **Lesezeichen in HTML-Datei exportieren**\n%6$d Speichere die Datei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" + "value" : "Fertig" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **Favorites → Bookmark Manager**\n%4$d Click %5$@ then **Export bookmarks to HTML file**\n%6$d Save the file someplace you can find it (e.g., Desktop)\n%7$d %8$@" + "value" : "Done" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre **%2$s**\n%3$d Utiliza la barra de menús para seleccionar **Favoritos → Administrador de marcadores**\n%4$d Haz clic en %5$@ y luego en **Exportar marcadores a archivo HTML**\n%6$d Guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" + "value" : "Hecho" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Favoris → Gestionnaire de signets**\n%4$d Cliquez sur %5$@ puis sur **Exporter les signets vers un fichier HTML**\n%6$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" + "value" : "Terminé" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Apri **%2$s**\n%3$d Usa la Barra dei menu per selezionare **Preferiti → Gestore segnalibri**\n%4$d Fai clic su %5$@, quindi su **Esporta segnalibri nel file HTML**\n%6$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" + "value" : "Fatto" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Favorieten → Bladwijzerbeheerder**\n%4$d Klik op %5$@ en vervolgens op **Bladwijzers exporteren naar HTML-bestand**\n%6$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" + "value" : "Klaar" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Favorites → Bookmark Manager**\n%4$d Kliknij %5$@, a następnie **Export bookmarks to HTML file**\n%6$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" + "value" : "Gotowe" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Favoritos → Gestor de marcadores**\n%4$d Clica em %5$@ e, em seguida, clica em **Exportar marcadores para ficheiro HTML**\n%6$d Guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" + "value" : "Feito" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$d Откройте **%2$s**\n%3$d На панели меню выберите раздел **Избранное → Менеджер закладок**\n%4$d Нажмите на значок %5$@ и выберите опцию **Экспорт закладок в файл HTML**\n%6$d Сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" + "value" : "Готово" } } } }, - "import.logins.csv.title" : { - "comment" : "Title text for the CSV importer", + "import.data.initiate" : { + "comment" : "Button text for importing data", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "CSV-Passwortdatei (für andere Browser)" + "value" : "Importieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "CSV Passwords File (for other browsers)" + "value" : "Import" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Archivo de contraseñas CSV (para otros navegadores)" + "value" : "Importar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fichier de mots de passe CSV (pour les autres navigateurs)" + "value" : "Importer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "File CSV password (per altri browser)" + "value" : "Importa" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "CSV-wachtwoordbestand (voor andere browsers)" + "value" : "Importeren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Plik haseł CSV (dla innych przeglądarek)" + "value" : "Import" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ficheiro de palavras-passe CSV (para outros navegadores)" + "value" : "Importar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Файл с паролями в CSV (для других браузеров)" + "value" : "Импорт" } } } }, - "import.logins.passwords" : { - "comment" : "Title text for the Passwords import option", + "import.data.manual" : { + "comment" : "Button text for initiating manual data import using a HTML or CSV file when automatic import has failed", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter" + "value" : "Manueller Import …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Passwords" + "value" : "Manual import…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Contraseñas" + "value" : "Importación manual..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mots de passe" + "value" : "Importation manuelle…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Password" + "value" : "Importazione manuale…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoorden" + "value" : "%1$d Open je vorige browser\n%2$d Open **Bladwijzerbeheerder**\n%3$d Exporteer je bladwijzers naar HTML …\n%4$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%5$d %6$d %$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Hasła" + "value" : "Importowanie ręczne..." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Palavras-passe" + "value" : "Importação manual…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пароли" + "value" : "Импортировать вручную..." } } } }, - "import.logins.select-csv-file" : { - "comment" : "Button text for selecting a CSV file", + "import.data.requires-password.body" : { + "comment" : "Alert body text when the data import needs a password", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter-CSV-Datei auswählen …" + "value" : "DuckDuckGo wird dein %1$@-Primärpasswort nicht speichern oder weitergeben, aber DuckDuckGo braucht es, um auf Passwörter von %1$@ zuzugreifen und sie zu importieren." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Select Passwords CSV File…" + "value" : "DuckDuckGo won't save or share your %1$@ Primary Password, but DuckDuckGo needs it to access and import passwords from %1$@." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Seleccionar archivo CSV de contraseñas..." + "value" : "DuckDuckGo no guardará ni compartirá tu contraseña principal de %1$@, pero DuckDuckGo la necesita para acceder e importar contraseñas desde %1$@." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Sélectionner un fichier de mots de passe CSV…" + "value" : "DuckDuckGo n'enregistrera pas ni ne partagera votre mot de passe principal %1$@, mais DuckDuckGo en a besoin pour accéder aux mots de passe et les importer depuis %1$@." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Seleziona file CSV password…" + "value" : "DuckDuckGo non salva né condivide la tua password principale %1$@, ma DuckDuckGo ne ha bisogno per accedere e importare password da %1$@." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Selecteer CSV-wachtwoordbestand ..." + "value" : "DuckDuckGo slaat je primaire wachtwoord van %1$@ niet op of deelt het niet, maar DuckDuckGo heeft het nodig om wachtwoorden van %1$@ te openen en te importeren." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wybierz plik CSV z hasłami…" + "value" : "DuckDuckGo nie zapisze ani nie udostępni Twojego głównego hasła programu %1$@, ale potrzebuje go, aby uzyskać dostęp do haseł i zaimportować je z programu %1$@." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Selecionar ficheiro de palavras-passe CSV…" + "value" : "O DuckDuckGo não guarda nem partilha a tua palavra-passe principal do %1$@, mas precisa dela para aceder e importar palavras-passe do %1$@." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выберите CSV-файл с паролями…" + "value" : "DuckDuckGo не сохранит и никому не передаст ваш основной пароль от %1$@, однако он необходим нам для доступа к паролям из %1$@ и их дальнейшего импортирования." } } } }, - "import.logins.select-csv-file.source" : { - "comment" : "Button text for selecting a CSV file exported from (LastPass or Bitwarden or 1Password - %@)", + "import.data.requires-password.title" : { + "comment" : "Alert title text when the data import needs a password", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wähle %@ CSV-Datei …" + "value" : "Primäres Passwort für %@ eingeben" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Select %@ CSV File…" + "value" : "Enter Primary Password for %@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Seleccionar Archivo CSV de %@..." + "value" : "Introduce la contraseña principal para %@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Sélectionner un fichier CSV %@…" + "value" : "Saisissez le mot de passe principal pour %@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Seleziona file CSV %@…" + "value" : "Inserisci la password principale per %@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Selecteer CSV-bestand van %@ ..." + "value" : "Voer primair wachtwoord in voor %@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wybierz plik CSV %@…" + "value" : "Wprowadź hasło główne programu %@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Selecionar ficheiro CSV %@…" + "value" : "Introduzir palavra-passe principal do %@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выберите CSV-файл из %@…" + "value" : "Введите основной пароль от %@" } } } }, - "import.nodata.bookmarks.subtitle" : { - "comment" : "Data import error subtitle: suggestion to import Bookmarks manually by selecting a CSV or HTML file. The placeholder here represents the source browser, e.g Firefox.", + "import.data.skip" : { + "comment" : "Button text to skip an import step", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wenn du %@ Lesezeichen hast, versuche stattdessen, sie manuell zu importieren." + "value" : "Überspringen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "If you have %@ bookmarks, try importing them manually instead." + "value" : "Skip" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Si tienes %@ marcadores, intenta importarlos manualmente." + "value" : "Omitir" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Si vous avez %@ signets, essayez plutôt de les importer manuellement." + "value" : "Ignorer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Se hai %@ segnalibri, prova a importarli manualmente." + "value" : "Salta" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Als je %@ bladwijzers hebt, probeer ze dan handmatig te importeren." + "value" : "Overslaan" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Jeśli masz zakładki %@, spróbuj zaimportować je ręcznie." + "value" : "Pomiń" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Se tens favoritos do %@, tenta importá-los manualmente." + "value" : "Ignorar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Если у вас есть %@-файл с закладками, попробуйте импортировать его вручную." + "value" : "Пропустить" } } } }, - "import.nodata.passwords.subtitle" : { - "comment" : "Data import error subtitle: suggestion to import passwords manually by selecting a CSV or HTML file. The placeholder here represents the source browser, e.g Firefox.", + "import.data.skip.bookmarks" : { + "comment" : "Button text to skip bookmarks manual import", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wenn du %@ Passwörter hast, versuche stattdessen, sie manuell zu importieren." + "value" : "Lesezeichen überspringen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "If you have %@ passwords, try importing them manually instead." + "value" : "Skip bookmarks" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Si tiene %@ contraseñas, intenta importarlas manualmente." + "value" : "Omitir marcadores" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Si vous avez %@ mots de passe, essayez plutôt de les importer manuellement." + "value" : "Ignorer les signets" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Se hai %@ password, prova a importarle manualmente." + "value" : "Salta segnalibri" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Als je %@ wachtwoorden hebt, probeer ze dan handmatig te importeren." + "value" : "Bladwijzers overslaan" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Jeśli masz hasła %@, spróbuj zaimportować je ręcznie." + "value" : "Pomiń zakładki" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Se tens palavras-passe do %@, tenta importá-las manualmente." + "value" : "Ignorar marcadores" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Если у вас есть %@-файл с паролями, попробуйте импортировать его вручную." + "value" : "Пропустить закладки" } } } }, - "import.onePassword.app.version.info" : { - "comment" : "Instructions how to find an installed 1Password password manager app version.\n%1$s, %2$s - app name (1Password)", + "import.data.skip.passwords" : { + "comment" : "Button text to skip bookmarks manual import", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Du findest deine Version, indem du in der Menüleiste **%1$s → Über %2$s** auswählst." + "value" : "Passwörter überspringen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "You can find your version by selecting **%1$s → About %2$s** from the Menu Bar." + "value" : "Skip passwords" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Puedes encontrar tu versión seleccionando **%1$s → Acerca de %2$s** en la barra de menús." + "value" : "Omitir contraseñas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Vous pouvez trouver votre version en sélectionnant **%1$s → À propos de %2$s** dans la barre de menu." + "value" : "Ignorer les mots de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Puoi trovare la tua versione selezionando **%1$s → Informazioni %2$s** dalla Barra dei menu." + "value" : "Salta password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Je kunt je versie vinden in de menubalk naar **%1$s → Over %2$s** te gaan." + "value" : "Wachtwoorden overslaan" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Aby znaleźć zainstalowaną wersję, wybierz **%1$s → About %2$s** na pasku menu." + "value" : "Pomiń hasła" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Podes encontrar a tua versão selecionando **%1$s → Acerca de %2$s** na barra de menus." + "value" : "Ignorar palavras-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Чтобы узнать версию своего приложения, выберите на панели меню **%1$s → Сведения о %2$s**." + "value" : "Пропустить пароли" } } } }, - "import.passwords.indefinite.progress.text" : { - "comment" : "Operation progress info message about indefinite number of passwords being imported", + "import.html.instructions.chromium" : { + "comment" : "Instructions to import Bookmarks exported as HTML from Chromium-based browsers.\n%N$d - step number\n%2$s - browser name\n%5$@ - hamburger menu icon\n%8$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter werden importiert …" + "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Lesezeichen → Lesezeichen-Manager**\n%4$d Klicke auf %5$@ und dann auf **Lesezeichen exportieren**\n%6$d Speichere die Datei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Importing passwords…" + "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **Bookmarks → Bookmark Manager**\n%4$d Click %5$@ then **Export Bookmarks**\n%6$d Save the file someplace you can find it (e.g., Desktop)\n%7$d %8$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importando contraseñas…" + "value" : "%1$d Abre **%2$s**\n%3$d Utiliza la barra de menú para seleccionar **Marcadores → Administrador de marcadores**\n%4$d Haz clic en %5$@ y luego en **Exportar marcadores**\n%6$d Guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importation des mots de passe…" + "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Signets → Gestionnaire de signets**\n%4$d Cliquez sur %5$@ puis sur **Exporter les signets**\n%6$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importazione password in corso…" + "value" : "%1$d Apri **%2$s\n**%3$d Usa la barra dei menu per selezionare **Segnalibri → Gestore segnalibri**\n%4$d Fai clic su %5$@, quindi su **Esporta segnalibri**\n%6$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoorden importeren ..." + "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Bladwijzers → Bladwijzerbeheerder**\n%4$d Klik op %5$@ en vervolgens op **Bladwijzers exporteren**\n%6$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importowanie haseł…" + "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Zakładki → Menedżer zakładek**\n%4$d Kliknij %5$@, a następnie **Eksportuj zakładki**\n%6$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "A importar palavras-passe…" + "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Marcadores → Gestor de marcadores**\n%4$d Clica em %5$@ e, em seguida, clica em **Exportar marcadores**\n%6$d Guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пароли импортируются…" + "value" : "%1$d Откройте **%2$s**\n%3$d На панели меню выберите **Закладки → Менеджер закладок**\n%4$d Нажмите значок %5$@ и выберите опцию **Экспортировать закладки**\n%6$d Сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" } } } }, - "import.passwords.number.progress.text" : { - "comment" : "Operation progress info message about %d number of passwords being imported", + "import.html.instructions.firefox" : { + "comment" : "Instructions to import Bookmarks exported as HTML from Firefox based browsers.\n%N$d - step number\n%2$s - browser name (Firefox)\n%5$@ - hamburger menu icon\n%8$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter werden importiert (%d) …" + "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Lesezeichen → Lesezeichen verwalten**\n%4$d Klicke auf %5$@ und dann auf **Lesezeichen nach HTML exportieren ...**\n%6$d Speichere die Datei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Importing passwords (%d)…" + "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **Bookmarks → Manage Bookmarks**\n%4$d Click %5$@ then **Export bookmarks to HTML…**\n%6$d Save the file someplace you can find it (e.g., Desktop)\n%7$d %8$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importando contraseñas (%d)..." + "value" : "%1$d Abre **%2$s**\n%3$d Utiliza la barra de menús para seleccionar **Marcadores → Gestionar marcadores**\n%4$d Haz clic en %5$@ y luego en **Exportar marcadores a HTML...**\n%6$d Guarda el archivo en algún lugar donde puedas encontrarlo (por ejemplo, el Escritorio)\n%7$d %8$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importation des mots de passe (%d)…" + "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Signets → Gérer les signets**\n%4$d Cliquez sur %5$@ puis sur **Exporter les signets vers HTML…**\n%6$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importazione password (%d) in corso…" + "value" : "%1$d Apri **%2$s**\n%3$d Usa la Barra dei menu per selezionare **Segnalibri → Gestisci segnalibri**\n%4$d Fai clic su %5$@, quindi su **Esporta segnalibri in HTML…**\n%6$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoorden importeren (%d) …" + "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Bladwijzers → Bladwijzers beheren**\n%4$d Klik op %5$@ en vervolgens op **Bladwijzers exporteren naar HTML ...**\n%6$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importowanie haseł (%d)…" + "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Zakładki → Zarządzaj zakładkami**\n%4$d Kliknij %5$@, a następnie **Eksportuj zakładki do pliku HTML**\n%6$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "A importar palavras-passe (%d)…" + "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Marcadores → Gerir marcadores**\n%4$d Clica em %5$@ e, em seguida, clica em **Exportar marcadores para HTML…**\n%6$d Guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пароли импортируются (%d)…" + "value" : "%1$d Откройте **%2$s**\n%3$d На панели меню выберите **Закладки → Управление закладками**\n%4$d Нажмите на значок %5$@ и выберите опцию **Экспорт закладок в HTML…**\n%6$d Сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" } } } }, - "import.shortcuts.bookmarks.subtitle" : { - "comment" : "Description for the setting to enable the bookmarks bar", + "import.html.instructions.generic" : { + "comment" : "Instructions to import a generic HTML Bookmarks file.\n%N$d - step number\n%6$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Halte deine Lieblingslesezeichen griffbereit" + "value" : "%1$d Öffne deinen alten Browser\n%2$d Öffne den **Bookmark Manager**\n%3$d Exportiere Lesezeichen nach HTML …\n%4$d Speichere die Datei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%5$d %6$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Put your favorite bookmarks in easy reach" + "value" : "%1$d Open your old browser\n%2$d Open **Bookmark Manager**\n%3$d Export bookmarks to HTML…\n%4$d Save the file someplace you can find it (e.g., Desktop)\n%5$d %6$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Pon tus marcadores favoritos a tu alcance" + "value" : "%1$d Abre tu antiguo navegador\n%2$d Abre **Administrador de marcadores**\n%3$d Exportar marcadores a HTML...\n%4$d Guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%5$d %6$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Placez vos signets préférés à portée de main" + "value" : "%1$d Ouvrez votre ancien navigateur\n%2$d Ouvrez **Gestionnaire de signets**\n%3$d Exportez les signets vers HTML…\n%4$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%5$d %6$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Tieni i tuoi segnalibri preferiti a portata di mano" + "value" : "%1$d Apri il tuo vecchio browser\n%2$d Apri **Gestore segnalibri**\n%3$d Esporta segnalibri in HTML…\n%4$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%5$d %6$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Breng je favoriete bladwijzers binnen handbereik" + "value" : "%1$d Open je vorige browser\n%2$d Open **Bladwijzerbeheerder**\n%3$d Exporteer je bladwijzers naar HTML…\n%4$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%5$d %6$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Umieść ulubione zakładki w łatwo dostępnym miejscu" + "value" : "%1$d Otwórz starą przeglądarkę\n%2$d Otwórz **Menedżera zakładek**\n%3$d Wyeksportuj zakładki do pliku HTML\n%4$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%5$d %6$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Coloca os teus marcadores favoritos num local de fácil alcance" + "value" : "%1$d Abre o teu navegador antigo\n%2$d Abre **Gestor de marcadores**\n%3$d Exportar marcadores para HTML…\n%4$d Guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%5$d %6$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Любимые закладки всегда под рукой" + "value" : "%1$d Запустите старый браузер\n%2$d Откройте **Менеджер закладок**\n%3$d Экспортируйте закладки в HTML\n%4$d Сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%5$d %6$@" } } } }, - "import.shortcuts.bookmarks.title" : { - "comment" : "Title for the setting to enable the bookmarks bar", + "import.html.instructions.opera" : { + "comment" : "Instructions to import Bookmarks exported as HTML from Opera browser.\n%N$d - step number\n%2$s - browser name (Opera)\n%8$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichenleiste anzeigen" + "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Lesezeichen → Lesezeichen**\n%4$d Klicke unten links auf **Vollständige Lesezeichenansicht öffnen …**\n%5$d Klicke unten links auf **Importieren/Exportieren …** und wähle **Lesezeichen exportieren**\n%6$d Speichere die Datei an einem Ort, an dem du sie wiederfindest (z. B. auf dem Desktop)\n%7$d %8$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Bookmarks Bar" + "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **Bookmarks → Bookmarks**\n%4$d Click **Open full Bookmarks view…** in the bottom left\n%5$d Click **Import/Export…** in the bottom left and select **Export Bookmarks**\n%6$d Save the file someplace you can find it (e.g., Desktop)\n%7$d %8$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar barra de marcadores" + "value" : "%1$d Abre **%2$s**\n%3$d Utiliza la barra de menú para seleccionar **Marcadores → Marcadores**\n%4$d Haz clic en **Abrir vista completa de Marcadores...** en la parte inferior izquierda\n%5$d Haz clic en **Importar/Exportar...** en la parte inferior izquierda y selecciona **Exportar Marcadores**\n%6$d Guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher la barre des signets" + "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Signets → Signets **\n%4$d Cliquez sur **Ouvrir la vue complète des signets…** en bas à gauche\n%5$d Cliquez sur **Importer/Exporter…** en bas à gauche et sélectionnez **Exporter les signets**\n%6$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra la barra dei segnalibri" + "value" : "%1$d Apri **%2$s**\n%3$d Usa la Barra dei menu per selezionare **Segnalibri → Segnalibri\n**%4$d Fai clic su **Apri la visualizzazione Segnalibri completa…** in basso a sinistra\n%5$d Fai clic su **Importa/Esporta…** in basso a sinistra e seleziona **Esporta segnalibri**\n%6$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bladwijzerbalk tonen" + "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Bladwijzers → Bladwijzers**\n%4$d Klik op **Volledige bladwijzerweergave openen...** in de linkerbenedenhoek\n%5$d Klik op **Importeren/Exporteren…** in de linkerbenedenhoek en selecteer **Bladwijzers exporteren**\n%6$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż pasek zakładek" + "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Zakładki → Zakładki**\n%4$d Kliknij **Otwórz pełny widok zakładek...** w lewym dolnym rogu\n%5$d Kliknij **Importuj/Eksportuj...** w lewym dolnym rogu i wybierz **Eksportuj zakładki**\n%6$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar barra de marcadores" + "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Favoritos → Favoritos**\n%4$d Clica em **Abrir vista completa de Favoritos…** na parte inferior esquerda\n%5$d Clica em **Importar/exportar…** na parte inferior esquerda e seleciona **Exportar favoritos**\n%6$d Guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показывать панель закладок" + "value" : "%1$d Откройте **%2$s**\n%3$d В панели меню выберите **Закладки → Закладки**\n%4$d **Разверните панель закладок** с помощью значка внизу слева\n%5$d Затем нажмите кнопку **Импорт/Экспорт…** слева внизу и выберите **Экспортировать закладки**\n%6$d Сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" } } } }, - "import.shortcuts.passwords.subtitle" : { - "comment" : "Description for the setting to enable the passwords shortcut", + "import.html.instructions.operagx" : { + "comment" : "Instructions to import Bookmarks exported as HTML from Opera GX browser.\n%N$d - step number\n%2$s - browser name (Opera GX)\n%7$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter in der Adressleiste aufbewahren" + "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Lesezeichen → Lesezeichen**\n%4$d Klicke unten links auf **Importieren/Exportieren …** und wähle **Lesezeichen exportieren**\n%5$d Speichere die Datei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%6$d %7$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Keep passwords nearby in the address bar" + "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **Bookmarks → Bookmarks**\n%4$d Click **Import/Export…** in the bottom left and select **Export Bookmarks**\n%5$d Save the file someplace you can find it (e.g., Desktop)\n%6$d %7$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mantén las contraseñas a mano en la barra de direcciones" + "value" : "%1$d Abre **%2$s**\n%3$d Utiliza la barra de menú para seleccionar **Marcadores → Marcadores**\n%4$d Haz clic en **Importar/Exportar...** en la parte inferior izquierda y selecciona **Exportar marcadores**\n%5$d Guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%6$d %7$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Gardez vos mots de passe à portée de main dans la barre d'adresse" + "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Signets → Signets **\n%4$d Cliquez sur **Importer/Exporter…** en bas à gauche et sélectionnez **Exporter les signets**\n%5$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%6$d %7$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Tieni le password a portata di mano nella barra degli indirizzi" + "value" : "%1$d Apri **%2$s**\n%3$d Usa la Barra dei menu per selezionare **Segnalibri → Segnalibri**\n%4$d Fai clic su **Importa/Esporta…** in basso a sinistra e seleziona **Esporta segnalibri**\n%5$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%6$d %7$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Houd wachtwoorden bij via de adresbalk" + "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Bladwijzers → Bladwijzers**\n%4$d Klik op **Importeren/Exporteren…** in de linkerbenedenhoek en selecteer **Bladwijzers exporteren**\n%5$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%6$d %7$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Trzymaj hasła w pobliżu, na pasku adresu" + "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Zakładki → Zakładki**\n%4$d Kliknij **Importuj/Eksportuj...** w lewym dolnym rogu i wybierz **Eksportuj zakładki**\n%5$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%6$d %7$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mantém as palavras-passe por perto na barra de endereços" + "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Favoritos → Favoritos**\n%4$d Clica em **Importar/exportar…** na parte inferior esquerda e seleciona **Exportar favoritos**\n%5$d Guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%6$d %7$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удобное хранение паролей в адресной строке" + "value" : "%1$d Откройте **%2$s**\n%3$d В панели меню выберите **Закладки → Закладки**\n%4$d Нажмите кнопку **Импорт/Экспорт…** слева внизу и выберите **Экспортировать закладки**\n%5$d Сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%6$d %7$@" } } } }, - "import.shortcuts.passwords.title" : { - "comment" : "Title for the setting to enable the passwords shortcut", + "import.html.instructions.safari" : { + "comment" : "Instructions to import Bookmarks exported as HTML from Safari.\n%N$d - step number\n%5$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwort-Shortcut anzeigen" + "value" : "%1$d Öffne **Safari**\n%2$d Wähle **Datei → Exportieren → Lesezeichen**\n%3$d Speichere die Datei mit den Passwörtern an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop) %4$d %5$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Passwords Shortcut" + "value" : "%1$d Open **Safari**\n%2$d Select **File → Export → Bookmarks**\n%3$d Save the passwords file someplace you can find it (e.g., Desktop)\n%4$d %5$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar acceso directo a contraseñas" + "value" : "%1$d Abre **Safari**\n%2$d Selecciona **Archivo → Exportar → Marcadores**\n%3$d Guarda el archivo de contraseñas donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%4$d %5$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher le raccourci des mots de passe" + "value" : "%1$d Ouvrez **Safari**\n%2$d Sélectionnez **Fichier → Exporter → Signets**\n%3$d Enregistrez le fichier des mots de passe à un endroit où le trouver facilement (par exemple, sur le bureau)\n%4$d %5$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra scorciatoia per le password" + "value" : "%1$d Apri **Safari**\n%2$d Seleziona **File → Esporta → Segnalibri**\n%3$d Salva il file delle password dove puoi trovarlo (ad esempio, sul desktop)\n%4$d %5$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Snelkoppeling voor wachtwoorden weergeven" + "value" : "%1$d Open **Safari**\n%2$d Selecteer **Bestand → Exporteren → Bladwijzers**\n%3$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%4$d %5$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż skrót do haseł" + "value" : "%1$d Otwórz przeglądarkę **Safari**\n%2$d Wybierz **Plik → Eksportuj → Zakładki**\n%3$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%4$d %5$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar atalho de palavras-passe" + "value" : "%1$d Abre o **Safari**\n%2$d Selecione **Ficheiro → Exportar → Marcadores**\n%3$d Guarda o ficheiro de palavras-passe onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%4$d %5$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показывать ярлык для паролей" + "value" : "%1$d Откройте **Safari**\n%2$d Выберите раздел **Файл → Экспортировать → Закладки**\n%3$d Сохраните файл с закладками там, где вы легко его найдете (например, на рабочем столе)\n%4$d %5$@" } } } }, - "invite.dialog.get.started.button" : { - "comment" : "Get Started button on an invite dialog", + "import.html.instructions.vivaldi" : { + "comment" : "Instructions to import Bookmarks exported as HTML from Vivaldi browser.\n%N$d - step number\n%2$s - browser name (Vivaldi)\n%6$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Los geht's!" + "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Datei → Lesezeichen exportieren …**\n%4$d Speichere die Datei an einem Ort, an dem du sie finden kannst (z.\nB. auf dem Desktop)\n%5$d %6$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Get Started" + "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **File → Export Bookmarks…**\n%4$d Save the file someplace you can find it (e.g., Desktop)\n%5$d %6$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Empezar" + "value" : "%1$d Abre **%2$s**\n%3$d Utiliza la barra de menú para seleccionar **Archivo → Exportar marcadores...**\n%4$d Guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%5$d %6$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Commencer" + "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Fichier → Exporter les signets…**\n%4$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%5$d %6$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Iniziamo" + "value" : "%1$d Apri **%2$s**\n%3$d Usa la Barra dei menu per selezionare **File → Esporta segnalibri…**\n%4$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%5$d %6$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Aan de slag" + "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Bestand → Bladwijzers exporteren…**\n%4$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%5$d %6$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Rozpocznij" + "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Plik → Eksportuj zakładki...**\n%4$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%5$d %6$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Comece" + "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Ficheiro → Exportar marcadores…**\n%4$d Guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%5$d %6$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Приступим!" + "value" : "%1$d Откройте **%2$s**\n%3$d В панели меню выберите раздел **Файл → Экспортировать закладки…**\n%4$d Сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%5$d %6$@" } } } }, - "invite.dialog.unrecognized.code.message" : { - "comment" : "Message to show after user enters an unrecognized invite code", + "import.html.instructions.yandex" : { + "comment" : "Instructions to import Bookmarks exported as HTML from Yandex Browser.\n%N$d - step number\n%2$s - browser name (Yandex)\n%5$@ - hamburger menu icon\n%8$@ - “Select Bookmarks HTML File” button\n**bold text**; _italic text_", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wir haben diesen Einladungscode nicht erkannt." + "value" : "%1$d Öffne **%2$s**\n%3$d Wähle in der Menüleiste **Favoriten → Lesezeichen-Manager**\n%4$d Klicke auf %5$@ und dann auf **Lesezeichen in HTML-Datei exportieren**\n%6$d Speichere die Datei an einem Ort, an dem du sie finden kannst (z. B. auf dem Desktop)\n%7$d %8$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "We didn’t recognize this Invite Code." + "value" : "%1$d Open **%2$s**\n%3$d Use the Menu Bar to select **Favorites → Bookmark Manager**\n%4$d Click %5$@ then **Export bookmarks to HTML file**\n%6$d Save the file someplace you can find it (e.g., Desktop)\n%7$d %8$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No hemos reconocido este código de invitación." + "value" : "%1$d Abre **%2$s**\n%3$d Utiliza la barra de menús para seleccionar **Favoritos → Administrador de marcadores**\n%4$d Haz clic en %5$@ y luego en **Exportar marcadores a archivo HTML**\n%6$d Guarda el archivo donde puedas encontrarlo posteriormente (por ejemplo, en el escritorio)\n%7$d %8$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nous n'avons pas reconnu ce code d'invitation." + "value" : "%1$d Ouvrez **%2$s**\n%3$d Passez par la barre de menu pour sélectionner **Favoris → Gestionnaire de signets**\n%4$d Cliquez sur %5$@ puis sur **Exporter les signets vers un fichier HTML**\n%6$d Enregistrez le fichier à un endroit où le trouver facilement (par exemple, sur le bureau)\n%7$d %8$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Non abbiamo riconosciuto questo codice di invito." + "value" : "%1$d Apri **%2$s**\n%3$d Usa la Barra dei menu per selezionare **Preferiti → Gestore segnalibri**\n%4$d Fai clic su %5$@, quindi su **Esporta segnalibri nel file HTML**\n%6$d Salva il file dove puoi trovarlo (ad esempio, sul desktop)\n%7$d %8$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Deze uitnodigingscode wordt niet herkend." + "value" : "%1$d Open **%2$s**\n%3$d Ga in de menubalk naar **Favorieten → Bladwijzerbeheerder**\n%4$d Klik op %5$@ en vervolgens op **Bladwijzers exporteren naar HTML-bestand**\n%6$d Bewaar het bestand op een plek waar je het kunt vinden (bijv. het bureaublad)\n%7$d %8$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie rozpoznaliśmy tego kodu zaproszenia." + "value" : "%1$d Otwórz przeglądarkę **%2$s**\n%3$d Na pasku menu wybierz **Favorites → Bookmark Manager**\n%4$d Kliknij %5$@, a następnie **Export bookmarks to HTML file**\n%6$d Zapisz plik w łatwo dostępnym miejscu (np. na biurku)\n%7$d %8$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Não reconhecemos este código de convite." + "value" : "%1$d Abre o **%2$s**\n%3$d Utiliza a barra de menus para selecionar **Favoritos → Gestor de marcadores**\n%4$d Clica em %5$@ e, em seguida, clica em **Exportar marcadores para ficheiro HTML**\n%6$d Guarda o ficheiro onde consigas encontrá-lo (por exemplo, no ambiente de trabalho)\n%7$d %8$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не удалось распознать код приглашения." + "value" : "%1$d Откройте **%2$s**\n%3$d На панели меню выберите раздел **Избранное → Менеджер закладок**\n%4$d Нажмите на значок %5$@ и выберите опцию **Экспорт закладок в файл HTML**\n%6$d Сохраните файл там, где вы легко его найдете (например, на рабочем столе)\n%7$d %8$@" } } } }, - "JavaScript Console" : { - "comment" : "Main Menu View-Developer item", + "import.logins.csv.title" : { + "comment" : "Title text for the CSV importer", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "JavaScript-Konsole" + "value" : "CSV-Passwortdatei (für andere Browser)" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "CSV Passwords File (for other browsers)" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Consola de JavaScript" + "value" : "Archivo de contraseñas CSV (para otros navegadores)" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Console JavaScript" + "value" : "Fichier de mots de passe CSV (pour les autres navigateurs)" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Console JavaScript" + "value" : "File CSV password (per altri browser)" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "JavaScript-console" + "value" : "CSV-wachtwoordbestand (voor andere browsers)" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Konsola JavaScript" + "value" : "Plik haseł CSV (dla innych przeglądarek)" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Consola de JavaScript" + "value" : "Ficheiro de palavras-passe CSV (para outros navegadores)" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Консоль JavaScript" + "value" : "Файл с паролями в CSV (для других браузеров)" } } } }, - "learnmore.link" : { - "comment" : "Learn More link", + "import.logins.passwords" : { + "comment" : "Title text for the Passwords import option", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Mehr erfahren" + "value" : "Passwörter" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Learn More" + "value" : "Passwords" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Más información" + "value" : "Contraseñas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "En savoir plus" + "value" : "Mots de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ulteriori informazioni" + "value" : "Password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Meer informatie" + "value" : "Wachtwoorden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dowiedz się więcej" + "value" : "Hasła" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Saiba mais" + "value" : "Palavras-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Узнать больше" + "value" : "Пароли" } } } }, - "Let’s try doing it manually. It won’t take long." : { - "comment" : "Suggestion to switch to a Manual File Data Import when data import fails.", + "import.logins.select-csv-file" : { + "comment" : "Button text for selecting a CSV file", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Versuchen wir, es manuell zu machen. Es wird nicht lange dauern." + "value" : "Passwörter-CSV-Datei auswählen …" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Select Passwords CSV File…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Vamos a intentar hacerlo de forma manual. No tardará mucho." + "value" : "Seleccionar archivo CSV de contraseñas..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Essayons de le faire manuellement. Cela ne prendra pas longtemps." + "value" : "Sélectionner un fichier de mots de passe CSV…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Proviamo a farlo manualmente. Non ci vorrà molto." + "value" : "Seleziona file CSV password…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Laten we het handmatig proberen. Het duurt niet lang." + "value" : "Selecteer CSV-wachtwoordbestand ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Spróbujmy zrobić to ręcznie. To nie potrwa długo." + "value" : "Wybierz plik CSV z hasłami…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Vamos tentar fazer a importação manualmente. Não vai demorar muito." + "value" : "Selecionar ficheiro de palavras-passe CSV…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Давайте попробуем вручную. Это не займет много времени." + "value" : "Выберите CSV-файл с паролями…" } } } }, - "letsmove.alert.message" : { - "comment" : "Message of the alert shown if the app is launched not from the /Applications folder – suggesting to move it there", + "import.logins.select-csv-file.source" : { + "comment" : "Button text for selecting a CSV file exported from (LastPass or Bitwarden or 1Password - %@)", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Die DuckDuckGo-App muss sich im Ordner Anwendungen befinden, damit einige Funktionen richtig funktionieren." + "value" : "Wähle %@ CSV-Datei …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "The DuckDuckGo app needs to be in the Applications folder for some features to work properly." + "value" : "Select %@ CSV File…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "La aplicación DuckDuckGo debe estar en la carpeta Aplicaciones para que algunas características funcionen correctamente." + "value" : "Seleccionar Archivo CSV de %@..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "L'application DuckDuckGo doit se trouver dans le dossier Applications pour que certaines fonctionnalités marchent correctement." + "value" : "Sélectionner un fichier CSV %@…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Affinché alcune funzionalità funzionino correttamente, l'app DuckDuckGo deve essere nella cartella Applicazioni." + "value" : "Seleziona file CSV %@…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Sommige functies in de DuckDuckGo-app werken alleen goed als de app in de map 'Applications' staat." + "value" : "Selecteer CSV-bestand van %@ ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Aplikacja DuckDuckGo musi znajdować się w folderze Aplikacje, aby zapewnić prawidłowe działanie określonych funkcji." + "value" : "Wybierz plik CSV %@…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "A aplicação DuckDuckGo tem de estar na pasta Aplicações para que algumas funcionalidades funcionem corretamente." + "value" : "Selecionar ficheiro CSV %@…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Для нормальной работы некоторых функций нужно, чтобы приложение DuckDuckGo находилось в папке «Приложения»." + "value" : "Выберите CSV-файл из %@…" } } } }, - "letsmove.alert.title" : { - "comment" : "Title of the alert shown if the app is launched not from the /Applications folder – suggesting to move it there", + "import.nodata.bookmarks.subtitle" : { + "comment" : "Data import error subtitle: suggestion to import Bookmarks manually by selecting a CSV or HTML file. The placeholder here represents the source browser, e.g Firefox.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Du hast es fast geschafft" + "value" : "Wenn du %@ Lesezeichen hast, versuche stattdessen, sie manuell zu importieren." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "You‘re almost there" + "value" : "If you have %@ bookmarks, try importing them manually instead." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ya casi estamos." + "value" : "Si tienes %@ marcadores, intenta importarlos manualmente." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Vous y êtes presque !" + "value" : "Si vous avez %@ signets, essayez plutôt de les importer manuellement." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ci sei quasi" + "value" : "Se hai %@ segnalibri, prova a importarli manualmente." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Je bent er bijna" + "value" : "Als je %@ bladwijzers hebt, probeer ze dan handmatig te importeren." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Prawie gotowe" + "value" : "Jeśli masz zakładki %@, spróbuj zaimportować je ręcznie." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Estás quase lá" + "value" : "Se tens favoritos do %@, tenta importá-los manualmente." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Почти готово" + "value" : "Если у вас есть %@-файл с закладками, попробуйте импортировать его вручную." } } } }, - "letsmove.could.not.move" : { - "comment" : "Error message when moving the app to the /Applications folder failed", + "import.nodata.passwords.subtitle" : { + "comment" : "Data import error subtitle: suggestion to import passwords manually by selecting a CSV or HTML file. The placeholder here represents the source browser, e.g Firefox.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Konnte nicht in den Ordner „Anwendungen“ verschoben werden" + "value" : "Wenn du %@ Passwörter hast, versuche stattdessen, sie manuell zu importieren." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Could not move to Applications folder" + "value" : "If you have %@ passwords, try importing them manually instead." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No se ha podido mover a la carpeta Aplicaciones" + "value" : "Si tiene %@ contraseñas, intenta importarlas manualmente." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Impossible de déplacer vers le dossier Applications" + "value" : "Si vous avez %@ mots de passe, essayez plutôt de les importer manuellement." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Impossibile passare alla cartella Applicazioni" + "value" : "Se hai %@ password, prova a importarle manualmente." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Kan de app niet verplaatsen naar de map 'Applications'" + "value" : "Als je %@ wachtwoorden hebt, probeer ze dan handmatig te importeren." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie można przenieść do folderu Aplikacje" + "value" : "Jeśli masz hasła %@, spróbuj zaimportować je ręcznie." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Não foi possível mover para a pasta Aplicações" + "value" : "Se tens palavras-passe do %@, tenta importá-las manualmente." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не удалось перенести DuckDuckGo в папку «Приложения»" + "value" : "Если у вас есть %@-файл с паролями, попробуйте импортировать его вручную." } } } }, - "letsmove.dont.move.button" : { - "comment" : "Do Not Move to the /Applications folder button title", + "import.onePassword.app.version.info" : { + "comment" : "Instructions how to find an installed 1Password password manager app version.\n%1$s, %2$s - app name (1Password)", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nicht bewegen" + "value" : "Du findest deine Version, indem du in der Menüleiste **%1$s → Über %2$s** auswählst." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Do Not Move" + "value" : "You can find your version by selecting **%1$s → About %2$s** from the Menu Bar." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No mover" + "value" : "Puedes encontrar tu versión seleccionando **%1$s → Acerca de %2$s** en la barra de menús." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ne pas déplacer" + "value" : "Vous pouvez trouver votre version en sélectionnant **%1$s → À propos de %2$s** dans la barre de menu." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Non spostare" + "value" : "Puoi trovare la tua versione selezionando **%1$s → Informazioni %2$s** dalla Barra dei menu." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Niet verplaatsen" + "value" : "Je kunt je versie vinden in de menubalk naar **%1$s → Over %2$s** te gaan." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie przenoś" + "value" : "Aby znaleźć zainstalowaną wersję, wybierz **%1$s → About %2$s** na pasku menu." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Não mover" + "value" : "Podes encontrar a tua versão selecionando **%1$s → Acerca de %2$s** na barra de menus." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не переносить" + "value" : "Чтобы узнать версию своего приложения, выберите на панели меню **%1$s → Сведения о %2$s**." } } } }, - "letsmove.move.button" : { - "comment" : "Move the /Applications folder button title", + "import.passwords.indefinite.progress.text" : { + "comment" : "Operation progress info message about indefinite number of passwords being imported", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "In den Ordner „Anwendungen“ verschieben" + "value" : "Passwörter werden importiert …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Move to Applications Folder" + "value" : "Importing passwords…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mover a la carpeta Aplicaciones" + "value" : "Importando contraseñas…" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Déplacer vers le dossier Applications" + "value" : "Importation des mots de passe…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Sposta nella cartella Applicazioni" + "value" : "Importazione password in corso…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verplaatsen naar de map 'Applications'" + "value" : "Wachtwoorden importeren ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przenieś do folderu Aplikacje" + "value" : "Importowanie haseł…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mover para a pasta Aplicações" + "value" : "A importar palavras-passe…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Перенести в папку «Приложения»" + "value" : "Пароли импортируются…" } } } }, - "looking.for.bitwarden" : { - "comment" : "Setup of the integration with Bitwarden app", + "import.passwords.number.progress.text" : { + "comment" : "Operation progress info message about %d number of passwords being imported", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden nicht installiert …" + "value" : "Passwörter werden importiert (%d) …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Bitwarden not installed…" + "value" : "Importing passwords (%d)…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden no instalado..." + "value" : "Importando contraseñas (%d)..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden non installée…" + "value" : "Importation des mots de passe (%d)…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden non installata…" + "value" : "Importazione password (%d) in corso…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden niet geïnstalleerd ..." + "value" : "Wachtwoorden importeren (%d) …" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie zainstalowano aplikacji Bitwarden…" + "value" : "Importowanie haseł (%d)…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden não instalado…" + "value" : "A importar palavras-passe (%d)…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Приложение Bitwarden не установлено..." + "value" : "Пароли импортируются (%d)…" } } } }, - "macOS version" : { - "comment" : "Data import failure Report dialog description of a report field providing user‘s macOS version", + "import.shortcuts.bookmarks.subtitle" : { + "comment" : "Description for the setting to enable the bookmarks bar", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "macOS-Version" + "value" : "Halte deine Lieblingslesezeichen griffbereit" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Put your favorite bookmarks in easy reach" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Versión de macOS" + "value" : "Pon tus marcadores favoritos a tu alcance" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Version de macOS" + "value" : "Placez vos signets préférés à portée de main" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Versione macOS" + "value" : "Tieni i tuoi segnalibri preferiti a portata di mano" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "macOS-versie" + "value" : "Breng je favoriete bladwijzers binnen handbereik" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wersja systemu macOS" + "value" : "Umieść ulubione zakładki w łatwo dostępnym miejscu" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Versão do macOS" + "value" : "Coloca os teus marcadores favoritos num local de fácil alcance" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Версия macOS" + "value" : "Любимые закладки всегда под рукой" } } } }, - "main-menu.app.check-for-updates" : { - "comment" : "Main Menu DuckDuckGo item", + "import.shortcuts.bookmarks.title" : { + "comment" : "Title for the setting to enable the bookmarks bar", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Auf Updates prüfen …" + "value" : "Lesezeichenleiste anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Check for Updates…" + "value" : "Show Bookmarks Bar" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Buscar actualizaciones..." + "value" : "Mostrar barra de marcadores" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Rechercher des mises à jour…" + "value" : "Afficher la barre des signets" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Controlla gli aggiornamenti…" + "value" : "Mostra la barra dei segnalibri" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Controleer op updates ..." + "value" : "Bladwijzerbalk tonen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Sprawdź dostępność aktualizacji…" + "value" : "Pokaż pasek zakładek" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Procurar atualizações…" + "value" : "Mostrar barra de marcadores" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Проверить наличие обновлений..." + "value" : "Показывать панель закладок" } } } }, - "main-menu.app.hide-duck-duck-go" : { - "comment" : "Main Menu DuckDuckGo item", + "import.shortcuts.passwords.subtitle" : { + "comment" : "Description for the setting to enable the passwords shortcut", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo ausblenden" + "value" : "Passwörter in der Adressleiste aufbewahren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Hide DuckDuckGo" + "value" : "Keep passwords nearby in the address bar" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar DuckDuckGo" + "value" : "Mantén las contraseñas a mano en la barra de direcciones" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Masquer DuckDuckGo" + "value" : "Gardez vos mots de passe à portée de main dans la barre d'adresse" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nascondi DuckDuckGo" + "value" : "Tieni le password a portata di mano nella barra degli indirizzi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo verbergen" + "value" : "Houd wachtwoorden bij via de adresbalk" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ukryj DuckDuckGo" + "value" : "Trzymaj hasła w pobliżu, na pasku adresu" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar o DuckDuckGo" + "value" : "Mantém as palavras-passe por perto na barra de endereços" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скрыть DuckDuckGo" + "value" : "Удобное хранение паролей в адресной строке" } } } }, - "main-menu.app.hide-others" : { - "comment" : "Main Menu DuckDuckGo item", + "import.shortcuts.passwords.title" : { + "comment" : "Title for the setting to enable the passwords shortcut", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Andere ausblenden" + "value" : "Passwort-Shortcut anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Hide Others" + "value" : "Show Passwords Shortcut" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar otros" + "value" : "Mostrar acceso directo a contraseñas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Masquer les autres" + "value" : "Afficher le raccourci des mots de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nascondi altri" + "value" : "Mostra scorciatoia per le password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Andere verbergen" + "value" : "Snelkoppeling voor wachtwoorden weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ukryj inne" + "value" : "Pokaż skrót do haseł" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar outras aplicações" + "value" : "Mostrar atalho de palavras-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скрыть остальные" + "value" : "Показывать ярлык для паролей" } } } }, - "main-menu.app.preferences" : { - "comment" : "Main Menu DuckDuckGo item", + "invite.dialog.get.started.button" : { + "comment" : "Get Started button on an invite dialog", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einstellungen …" + "value" : "Los geht's!" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Preferences…" + "value" : "Get Started" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Preferencias..." + "value" : "Empezar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Préférences…" + "value" : "Commencer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Preferenze…" + "value" : "Iniziamo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Voorkeuren …" + "value" : "Aan de slag" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Preferencje…" + "value" : "Rozpocznij" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Preferências…" + "value" : "Comece" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Настройки…" + "value" : "Приступим!" } } } }, - "main-menu.app.quit-duck-duck-go" : { - "comment" : "Main Menu DuckDuckGo item", + "invite.dialog.unrecognized.code.message" : { + "comment" : "Message to show after user enters an unrecognized invite code", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo beenden" + "value" : "Wir haben diesen Einladungscode nicht erkannt." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Quit DuckDuckGo" + "value" : "We didn’t recognize this Invite Code." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Salir de DuckDuckGo" + "value" : "No hemos reconocido este código de invitación." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Quitter DuckDuckGo" + "value" : "Nous n'avons pas reconnu ce code d'invitation." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Esci da DuckDuckGo" + "value" : "Non abbiamo riconosciuto questo codice di invito." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo afsluiten" + "value" : "Deze uitnodigingscode wordt niet herkend." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyjdź z DuckDuckGo" + "value" : "Nie rozpoznaliśmy tego kodu zaproszenia." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Sair do DuckDuckGo" + "value" : "Não reconhecemos este código de convite." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Завершить DuckDuckGo" + "value" : "Не удалось распознать код приглашения." } } } }, - "main-menu.app.services" : { - "comment" : "Main Menu DuckDuckGo item", - "extractionState" : "extracted_with_value", + "JavaScript Console" : { + "comment" : "Main Menu View-Developer item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Dienste" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Services" + "value" : "JavaScript-Konsole" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Servicios" + "value" : "Consola de JavaScript" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Services" + "value" : "Console JavaScript" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Servizi" + "value" : "Console JavaScript" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Diensten" + "value" : "JavaScript-console" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usługi" + "value" : "Konsola JavaScript" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Serviços" + "value" : "Consola de JavaScript" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Службы" + "value" : "Консоль JavaScript" } } } }, - "main-menu.app.show-all" : { - "comment" : "Main Menu DuckDuckGo item", + "learnmore.link" : { + "comment" : "Learn More link", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alle anzeigen" + "value" : "Mehr erfahren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show All" + "value" : "Learn More" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar todo" + "value" : "Más información" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Tout afficher" + "value" : "En savoir plus" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra tutto" + "value" : "Ulteriori informazioni" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alles weergeven" + "value" : "Meer informatie" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż wszystko" + "value" : "Dowiedz się więcej" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar tudo" + "value" : "Saiba mais" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать все" + "value" : "Узнать больше" } } } }, - "main-menu.edit" : { - "comment" : "Main Menu Edit", - "extractionState" : "extracted_with_value", + "Let’s try doing it manually. It won’t take long." : { + "comment" : "Suggestion to switch to a Manual File Data Import when data import fails.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bearbeiten" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Edit" + "value" : "Versuchen wir, es manuell zu machen. Es wird nicht lange dauern." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Editar" + "value" : "Vamos a intentar hacerlo de forma manual. No tardará mucho." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Modifier" + "value" : "Essayons de le faire manuellement. Cela ne prendra pas longtemps." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Modifica" + "value" : "Proviamo a farlo manualmente. Non ci vorrà molto." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bewerken" + "value" : "Laten we het handmatig proberen. Het duurt niet lang." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Edytuj" + "value" : "Spróbujmy zrobić to ręcznie. To nie potrwa długo." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Editar" + "value" : "Vamos tentar fazer a importação manualmente. Não vai demorar muito." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Правка" + "value" : "Давайте попробуем вручную. Это не займет много времени." } } } }, - "main-menu.edit.copy" : { - "comment" : "Main Menu Edit item", + "letsmove.alert.message" : { + "comment" : "Message of the alert shown if the app is launched not from the /Applications folder – suggesting to move it there", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kopieren" + "value" : "Die DuckDuckGo-App muss sich im Ordner Anwendungen befinden, damit einige Funktionen richtig funktionieren." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Copy" + "value" : "The DuckDuckGo app needs to be in the Applications folder for some features to work properly." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar" + "value" : "La aplicación DuckDuckGo debe estar en la carpeta Aplicaciones para que algunas características funcionen correctamente." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Copier" + "value" : "L'application DuckDuckGo doit se trouver dans le dossier Applications pour que certaines fonctionnalités marchent correctement." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Copia" + "value" : "Affinché alcune funzionalità funzionino correttamente, l'app DuckDuckGo deve essere nella cartella Applicazioni." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiëren" + "value" : "Sommige functies in de DuckDuckGo-app werken alleen goed als de app in de map 'Applications' staat." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiuj" + "value" : "Aplikacja DuckDuckGo musi znajdować się w folderze Aplikacje, aby zapewnić prawidłowe działanie określonych funkcji." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar" + "value" : "A aplicação DuckDuckGo tem de estar na pasta Aplicações para que algumas funcionalidades funcionem corretamente." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Копировать" + "value" : "Для нормальной работы некоторых функций нужно, чтобы приложение DuckDuckGo находилось в папке «Приложения»." } } } }, - "main-menu.edit.cut" : { - "comment" : "Main Menu Edit item", + "letsmove.alert.title" : { + "comment" : "Title of the alert shown if the app is launched not from the /Applications folder – suggesting to move it there", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Schneiden" + "value" : "Du hast es fast geschafft" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Cut" + "value" : "You‘re almost there" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cortar" + "value" : "Ya casi estamos." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Couper" + "value" : "Vous y êtes presque !" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Taglia" + "value" : "Ci sei quasi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Knippen" + "value" : "Je bent er bijna" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wytnij" + "value" : "Prawie gotowe" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Cortar" + "value" : "Estás quase lá" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вырезать" + "value" : "Почти готово" } } } }, - "main-menu.edit.delete" : { - "comment" : "Main Menu Edit item", + "letsmove.could.not.move" : { + "comment" : "Error message when moving the app to the /Applications folder failed", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Löschen" + "value" : "Konnte nicht in den Ordner „Anwendungen“ verschoben werden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Delete" + "value" : "Could not move to Applications folder" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar" + "value" : "No se ha podido mover a la carpeta Aplicaciones" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Supprimer" + "value" : "Impossible de déplacer vers le dossier Applications" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cancella" + "value" : "Impossibile passare alla cartella Applicazioni" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verwijderen" + "value" : "Kan de app niet verplaatsen naar de map 'Applications'" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usuń" + "value" : "Nie można przenieść do folderu Aplikacje" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar" + "value" : "Não foi possível mover para a pasta Aplicações" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удалить" + "value" : "Не удалось перенести DuckDuckGo в папку «Приложения»" } } } }, - "main-menu.edit.edit-spelling-and-grammar" : { - "comment" : "Main Menu Edit item", + "letsmove.dont.move.button" : { + "comment" : "Do Not Move to the /Applications folder button title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Rechtschreibung und Grammatik" + "value" : "Nicht bewegen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Spelling and Grammar" + "value" : "Do Not Move" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ortografía y gramática" + "value" : "No mover" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Orthographe et grammaire" + "value" : "Ne pas déplacer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ortografia e grammatica" + "value" : "Non spostare" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Spelling en grammatica" + "value" : "Niet verplaatsen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pisownia i gramatyka" + "value" : "Nie przenoś" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ortografia e gramática" + "value" : "Não mover" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Орфография и грамматика" + "value" : "Не переносить" } } } }, - "main-menu.edit.find" : { - "comment" : "Main Menu Edit item", + "letsmove.move.button" : { + "comment" : "Move the /Applications folder button title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Finden" + "value" : "In den Ordner „Anwendungen“ verschieben" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Find" + "value" : "Move to Applications Folder" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Buscar" + "value" : "Mover a la carpeta Aplicaciones" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Trouver" + "value" : "Déplacer vers le dossier Applications" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Trova" + "value" : "Sposta nella cartella Applicazioni" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Zoeken" + "value" : "Verplaatsen naar de map 'Applications'" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Znajdź" + "value" : "Przenieś do folderu Aplikacje" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Procurar" + "value" : "Mover para a pasta Aplicações" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Найти" + "value" : "Перенести в папку «Приложения»" } } } }, - "main-menu.edit.find.find-next" : { - "comment" : "Main Menu Edit-Find item", + "looking.for.bitwarden" : { + "comment" : "Setup of the integration with Bitwarden app", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nächstes Ergebnis" + "value" : "Bitwarden nicht installiert …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Find Next" + "value" : "Bitwarden not installed…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Buscar siguiente" + "value" : "Bitwarden no instalado..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Rechercher le suivant" + "value" : "Bitwarden non installée…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Trova successivo" + "value" : "Bitwarden non installata…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Volgende zoeken" + "value" : "Bitwarden niet geïnstalleerd ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Znajdź następny element" + "value" : "Nie zainstalowano aplikacji Bitwarden…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Encontrar seguinte" + "value" : "Bitwarden não instalado…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Найти следующий" + "value" : "Приложение Bitwarden не установлено..." } } } }, - "main-menu.edit.find.find-previous" : { - "comment" : "Main Menu Edit-Find item", - "extractionState" : "extracted_with_value", + "macOS version" : { + "comment" : "Data import failure Report dialog description of a report field providing user‘s macOS version", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Vorheriges Ergebnis" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Find Previous" + "value" : "macOS-Version" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Buscar anterior" + "value" : "Versión de macOS" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Rechercher le précédent" + "value" : "Version de macOS" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Trova precedente" + "value" : "Versione macOS" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Vorige zoeken" + "value" : "macOS-versie" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Znajdź poprzedni element" + "value" : "Wersja systemu macOS" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Encontrar anterior" + "value" : "Versão do macOS" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Найти предыдущий" + "value" : "Версия macOS" } } } }, - "main-menu.edit.find.hide-find" : { - "comment" : "Main Menu Edit-Find item", + "main-menu.app.check-for-updates" : { + "comment" : "Main Menu DuckDuckGo item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verstecken Finden" + "value" : "Auf Updates prüfen …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Hide Find" + "value" : "Check for Updates…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar búsqueda" + "value" : "Buscar actualizaciones..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Masquer la recherche" + "value" : "Rechercher des mises à jour…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nascondi Trova" + "value" : "Controlla gli aggiornamenti…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Zoeken verbergen" + "value" : "Controleer op updates ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ukryj znajdowanie" + "value" : "Sprawdź dostępność aktualizacji…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar a faixa de procura" + "value" : "Procurar atualizações…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скрыть панель поиска по странице" + "value" : "Проверить наличие обновлений..." } } } }, - "main-menu.edit.paste" : { - "comment" : "Main Menu Edit item", + "main-menu.app.hide-duck-duck-go" : { + "comment" : "Main Menu DuckDuckGo item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einfügen" + "value" : "DuckDuckGo ausblenden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Paste" + "value" : "Hide DuckDuckGo" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Pegar" + "value" : "Ocultar DuckDuckGo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Coller" + "value" : "Masquer DuckDuckGo" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Incolla" + "value" : "Nascondi DuckDuckGo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Plakken" + "value" : "DuckDuckGo verbergen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wklej" + "value" : "Ukryj DuckDuckGo" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Colar" + "value" : "Ocultar o DuckDuckGo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вставить" + "value" : "Скрыть DuckDuckGo" } } } }, - "main-menu.edit.paste-and-match-style" : { - "comment" : "Main Menu Edit item - Action that allows the user to paste copy into a target document and the target document's style will be retained (instead of the source style)", + "main-menu.app.hide-others" : { + "comment" : "Main Menu DuckDuckGo item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Stil einfügen und anpassen" + "value" : "Andere ausblenden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Paste and Match Style" + "value" : "Hide Others" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Pegar y combinar estilo" + "value" : "Ocultar otros" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Coller et faire correspondre le style" + "value" : "Masquer les autres" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Incolla e adegua lo stile" + "value" : "Nascondi altri" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Plakken en opmaak doeltekst behouden" + "value" : "Andere verbergen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wklej i dopasuj styl" + "value" : "Ukryj inne" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Colar e manter estilo" + "value" : "Ocultar outras aplicações" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вставить и согласовать стиль" + "value" : "Скрыть остальные" } } } }, - "main-menu.edit.redo" : { - "comment" : "Main Menu Edit item", + "main-menu.app.preferences" : { + "comment" : "Main Menu DuckDuckGo item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Erneuert ausführen" + "value" : "Einstellungen …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Redo" + "value" : "Preferences…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Rehacer" + "value" : "Preferencias..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Refaire" + "value" : "Préférences…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rifai" + "value" : "Preferenze…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Opnieuw uitvoeren" + "value" : "Voorkeuren …" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Powtórz" + "value" : "Preferencje…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Refazer" + "value" : "Preferências…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Повторить" + "value" : "Настройки…" } } } }, - "main-menu.edit.select-all" : { - "comment" : "Main Menu Edit item", + "main-menu.app.quit-duck-duck-go" : { + "comment" : "Main Menu DuckDuckGo item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alle auswählen" + "value" : "DuckDuckGo beenden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Select All" + "value" : "Quit DuckDuckGo" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Seleccionar todo" + "value" : "Salir de DuckDuckGo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Tout sélectionner" + "value" : "Quitter DuckDuckGo" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Seleziona tutto" + "value" : "Esci da DuckDuckGo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alles selecteren" + "value" : "DuckDuckGo afsluiten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wybierz wszystko" + "value" : "Wyjdź z DuckDuckGo" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Selecionar tudo" + "value" : "Sair do DuckDuckGo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выбрать все" + "value" : "Завершить DuckDuckGo" } } } }, - "main-menu.edit.spelling-and.check-document-now" : { - "comment" : "Main Menu Edit-Spellingand item", + "main-menu.app.services" : { + "comment" : "Main Menu DuckDuckGo item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Dokument jetzt prüfen" + "value" : "Dienste" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Check Document Now" + "value" : "Services" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Comprobar documento ahora" + "value" : "Servicios" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Vérifier le document maintenant" + "value" : "Services" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Verifica il documento ora" + "value" : "Servizi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Document nu controleren" + "value" : "Diensten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Sprawdź dokument teraz" + "value" : "Usługi" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Corrigir documento agora" + "value" : "Serviços" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Проверить документ" + "value" : "Службы" } } } }, - "main-menu.edit.spelling-and.check-grammar-with-spelling" : { - "comment" : "Main Menu Edit-Spellingand item", + "main-menu.app.show-all" : { + "comment" : "Main Menu DuckDuckGo item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Grammatik mit Rechtschreibung prüfen" + "value" : "Alle anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Check Grammar With Spelling" + "value" : "Show All" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Revisar gramática con la ortografía" + "value" : "Mostrar todo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Vérifier la grammaire et l'orthographe" + "value" : "Tout afficher" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Verifica grammatica e ortografia" + "value" : "Mostra tutto" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Grammatica en spelling controleren" + "value" : "Alles weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Sprawdź gramatykę z pisownią" + "value" : "Pokaż wszystko" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Corrigir gramática e ortografia" + "value" : "Mostrar tudo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Проверять грамматику и орфографию" + "value" : "Показать все" } } } }, - "main-menu.edit.spelling-and.check-spelling-while-typing" : { - "comment" : "Main Menu Edit-Spellingand item", + "main-menu.edit" : { + "comment" : "Main Menu Edit", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Rechtschreibung beim Tippen prüfen" + "value" : "Bearbeiten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Check Spelling While Typing" + "value" : "Edit" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Revisar la ortografía mientras escribes" + "value" : "Editar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Vérifier l'orthographe pendant la saisie" + "value" : "Modifier" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Verifica ortografia durante la digitazione" + "value" : "Modifica" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Spelling controleren tijdens het typen" + "value" : "Bewerken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Sprawdź pisownię podczas pisania" + "value" : "Edytuj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Corrigir ortografia ao escrever" + "value" : "Editar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Проверять орфографию при наборе текста" + "value" : "Правка" } } } }, - "main-menu.edit.spelling-and.correct-spelling-automatically" : { - "comment" : "Main Menu Edit-Spellingand item", + "main-menu.edit.copy" : { + "comment" : "Main Menu Edit item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Rechtschreibung automatisch korrigieren" + "value" : "Kopieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Correct Spelling Automatically" + "value" : "Copy" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Corregir la ortografía automáticamente" + "value" : "Copiar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Corriger l'orthographe automatiquement" + "value" : "Copier" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Correggi automaticamente l'ortografia" + "value" : "Copia" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Spelling automatisch corrigeren" + "value" : "Kopiëren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Automatyczne popraw pisownię" + "value" : "Kopiuj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Corrigir ortografia automaticamente" + "value" : "Copiar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Автоматически исправлять орфографию" + "value" : "Копировать" } } } }, - "main-menu.edit.spelling-and.show-spelling-and-grammar" : { - "comment" : "Main Menu Edit-Spellingand item", + "main-menu.edit.cut" : { + "comment" : "Main Menu Edit item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Rechtschreibung und Grammatik anzeigen" + "value" : "Schneiden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Spelling and Grammar" + "value" : "Cut" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar ortografía y gramática" + "value" : "Cortar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher l'orthographe et la grammaire" + "value" : "Couper" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra ortografia e grammatica" + "value" : "Taglia" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Spelling en grammatica weergeven" + "value" : "Knippen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż pisownię i gramatykę" + "value" : "Wytnij" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar ortografia e gramática" + "value" : "Cortar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показывать элементы проверки грамматики и орфографии" + "value" : "Вырезать" } } } }, - "main-menu.edit.subsitutions" : { + "main-menu.edit.delete" : { "comment" : "Main Menu Edit item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ersetzungen" + "value" : "Löschen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Substitutions" + "value" : "Delete" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Sustituciones" + "value" : "Eliminar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Substitutions" + "value" : "Supprimer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Sostituzioni" + "value" : "Cancella" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Vervangen" + "value" : "Verwijderen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Podstawienia" + "value" : "Usuń" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Substituições" + "value" : "Eliminar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Замены" + "value" : "Удалить" } } } }, - "main-menu.edit.undo" : { + "main-menu.edit.edit-spelling-and-grammar" : { "comment" : "Main Menu Edit item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Rückgängig machen" + "value" : "Rechtschreibung und Grammatik" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Undo" + "value" : "Spelling and Grammar" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Deshacer" + "value" : "Ortografía y gramática" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Annuler" + "value" : "Orthographe et grammaire" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Annulla" + "value" : "Ortografia e grammatica" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ongedaan maken" + "value" : "Spelling en grammatica" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Cofnij" + "value" : "Pisownia i gramatyka" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Anular" + "value" : "Ortografia e gramática" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отменить" + "value" : "Орфография и грамматика" } } } }, - "main-menu.file" : { - "comment" : "Main Menu File", + "main-menu.edit.find" : { + "comment" : "Main Menu Edit item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Datei" + "value" : "Finden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "File" + "value" : "Find" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Archivo" + "value" : "Buscar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fichier" + "value" : "Trouver" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "File" + "value" : "Trova" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bestand" + "value" : "Zoeken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Plik" + "value" : "Znajdź" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ficheiro" + "value" : "Procurar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Файл" + "value" : "Найти" } } } }, - "main-menu.file.close-all-windows" : { - "comment" : "Main Menu File item", + "main-menu.edit.find.find-next" : { + "comment" : "Main Menu Edit-Find item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alle Fenster schließen" + "value" : "Nächstes Ergebnis" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Close All Windows" + "value" : "Find Next" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cerrar todas las ventanas" + "value" : "Buscar siguiente" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fermer toutes les fenêtres" + "value" : "Rechercher le suivant" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Chiudi tutte le finestre" + "value" : "Trova successivo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alle vensters sluiten" + "value" : "Volgende zoeken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zamknij wszystkie okna" + "value" : "Znajdź następny element" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Fechar todas as janelas" + "value" : "Encontrar seguinte" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Закрыть все окна" + "value" : "Найти следующий" } } } }, - "main-menu.file.close-window" : { - "comment" : "Main Menu File item", + "main-menu.edit.find.find-previous" : { + "comment" : "Main Menu Edit-Find item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fenster schließen" + "value" : "Vorheriges Ergebnis" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Close Window" + "value" : "Find Previous" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cerrar ventana" + "value" : "Buscar anterior" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fermer la fenêtre" + "value" : "Rechercher le précédent" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Chiudi finestra" + "value" : "Trova precedente" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Venster sluiten" + "value" : "Vorige zoeken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zamknij okno" + "value" : "Znajdź poprzedni element" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Fechar janela" + "value" : "Encontrar anterior" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Закрыть окно" + "value" : "Найти предыдущий" } } } }, - "main-menu.file.export" : { - "comment" : "Main Menu File item", + "main-menu.edit.find.hide-find" : { + "comment" : "Main Menu Edit-Find item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Exportieren" + "value" : "Verstecken Finden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Export" + "value" : "Hide Find" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Exportar" + "value" : "Ocultar búsqueda" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Exporter" + "value" : "Masquer la recherche" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Esporta" + "value" : "Nascondi Trova" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Exporteren" + "value" : "Zoeken verbergen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Eksport" + "value" : "Ukryj znajdowanie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Exportar" + "value" : "Ocultar a faixa de procura" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Экспорт" + "value" : "Скрыть панель поиска по странице" } } } }, - "main-menu.file.export-bookmarks" : { - "comment" : "Main Menu File-Export item", + "main-menu.edit.paste" : { + "comment" : "Main Menu Edit item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen …" + "value" : "Einfügen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Bookmarks…" + "value" : "Paste" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Marcadores..." + "value" : "Pegar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Signets…" + "value" : "Coller" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Segnalibri…" + "value" : "Incolla" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bladwijzers …" + "value" : "Plakken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zakładki..." + "value" : "Wklej" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Marcadores…" + "value" : "Colar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Закладки..." + "value" : "Вставить" } } } }, - "main-menu.file.export-passwords" : { - "comment" : "Main Menu File-Export item", + "main-menu.edit.paste-and-match-style" : { + "comment" : "Main Menu Edit item - Action that allows the user to paste copy into a target document and the target document's style will be retained (instead of the source style)", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter …" + "value" : "Stil einfügen und anpassen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Passwords…" + "value" : "Paste and Match Style" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Contraseñas..." + "value" : "Pegar y combinar estilo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mots de passe…" + "value" : "Coller et faire correspondre le style" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Password…" + "value" : "Incolla e adegua lo stile" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoorden …" + "value" : "Plakken en opmaak doeltekst behouden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Hasła…" + "value" : "Wklej i dopasuj styl" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Palavras-passe…" + "value" : "Colar e manter estilo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пароли..." + "value" : "Вставить и согласовать стиль" } } } }, - "main-menu.file.import-bookmarks-and-passwords" : { - "comment" : "Main Menu File item", + "main-menu.edit.redo" : { + "comment" : "Main Menu Edit item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen und Passwörter importieren …" + "value" : "Erneuert ausführen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Import Bookmarks and Passwords…" + "value" : "Redo" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importar marcadores y contraseñas..." + "value" : "Rehacer" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importer les signets et les mots de passe…" + "value" : "Refaire" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importa segnalibri e password…" + "value" : "Rifai" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bladwijzers en wachtwoorden importeren ..." + "value" : "Opnieuw uitvoeren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importuj zakładki i hasła…" + "value" : "Powtórz" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importar marcadores e palavras-passe…" + "value" : "Refazer" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Импортировать закладки и пароли..." + "value" : "Повторить" } } } }, - "main-menu.file.new-tab" : { - "comment" : "Main Menu File item", + "main-menu.edit.select-all" : { + "comment" : "Main Menu Edit item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neuer Tab" + "value" : "Alle auswählen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "New Tab" + "value" : "Select All" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nueva pestaña" + "value" : "Seleccionar todo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvel onglet" + "value" : "Tout sélectionner" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nuova scheda" + "value" : "Seleziona tutto" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuw tabblad" + "value" : "Alles selecteren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nowa karta" + "value" : "Wybierz wszystko" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Novo separador" + "value" : "Selecionar tudo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Новая вкладка" + "value" : "Выбрать все" } } } }, - "main-menu.file.open-location" : { - "comment" : "Main Menu File item- Menu option that allows the user to connect to an address (type an address) on click the address bar of the browser is selected and the user can type.", + "main-menu.edit.spelling-and.check-document-now" : { + "comment" : "Main Menu Edit-Spellingand item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Standort öffnen …" + "value" : "Dokument jetzt prüfen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open Location…" + "value" : "Check Document Now" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir ubicación…" + "value" : "Comprobar documento ahora" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir l'emplacement…" + "value" : "Vérifier le document maintenant" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Connetti all'indirizzo..." + "value" : "Verifica il documento ora" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Locatie openen ..." + "value" : "Document nu controleren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz lokalizację…" + "value" : "Sprawdź dokument teraz" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir localização…" + "value" : "Corrigir documento agora" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть адрес…" + "value" : "Проверить документ" } } } }, - "main-menu.file.save-as" : { - "comment" : "Main Menu File item", + "main-menu.edit.spelling-and.check-grammar-with-spelling" : { + "comment" : "Main Menu Edit-Spellingand item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Speichern als …" + "value" : "Grammatik mit Rechtschreibung prüfen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Save As…" + "value" : "Check Grammar With Spelling" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar como..." + "value" : "Revisar gramática con la ortografía" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Enregistrer sous…" + "value" : "Vérifier la grammaire et l'orthographe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Salva come…" + "value" : "Verifica grammatica e ortografia" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Opslaan als ..." + "value" : "Grammatica en spelling controleren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zapisz jako…" + "value" : "Sprawdź gramatykę z pisownią" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar como…" + "value" : "Corrigir gramática e ortografia" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сохранить как…" + "value" : "Проверять грамматику и орфографию" } } } }, - "main.menu.close.downloads" : { - "comment" : "Hide Downloads Popover", + "main-menu.edit.spelling-and.check-spelling-while-typing" : { + "comment" : "Main Menu Edit-Spellingand item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Downloads ausblenden" + "value" : "Rechtschreibung beim Tippen prüfen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Hide Downloads" + "value" : "Check Spelling While Typing" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar descargas" + "value" : "Revisar la ortografía mientras escribes" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Masquer les téléchargements" + "value" : "Vérifier l'orthographe pendant la saisie" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nascondi download" + "value" : "Verifica ortografia durante la digitazione" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Downloads verbergen" + "value" : "Spelling controleren tijdens het typen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ukryj pobrane" + "value" : "Sprawdź pisownię podczas pisania" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar transferências" + "value" : "Corrigir ortografia ao escrever" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скрыть загрузки" + "value" : "Проверять орфографию при наборе текста" } } } }, - "main.menu.close.inspector" : { - "comment" : "Hide Web Inspector/Close Developer Tools", + "main-menu.edit.spelling-and.correct-spelling-automatically" : { + "comment" : "Main Menu Edit-Spellingand item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Entwicklertools schließen" + "value" : "Rechtschreibung automatisch korrigieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Close Developer Tools" + "value" : "Correct Spelling Automatically" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cerrar Herramientas para desarrolladores" + "value" : "Corregir la ortografía automáticamente" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fermer les outils de développement" + "value" : "Corriger l'orthographe automatiquement" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Chiudi strumenti per sviluppatori" + "value" : "Correggi automaticamente l'ortografia" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ontwikkelaarstools sluiten" + "value" : "Spelling automatisch corrigeren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zamknij narzędzia deweloperskie" + "value" : "Automatyczne popraw pisownię" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Fechar Ferramentas para programadores" + "value" : "Corrigir ortografia automaticamente" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Закрыть инструменты разработчика" + "value" : "Автоматически исправлять орфографию" } } } }, - "main.menu.home.button.mode.hide" : { - "comment" : "Main Menu > View > Home Button > None item", + "main-menu.edit.spelling-and.show-spelling-and-grammar" : { + "comment" : "Main Menu Edit-Spellingand item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ausblenden" + "value" : "Rechtschreibung und Grammatik anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Hide" + "value" : "Show Spelling and Grammar" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar" + "value" : "Mostrar ortografía y gramática" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Masquer" + "value" : "Afficher l'orthographe et la grammaire" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nascondi" + "value" : "Mostra ortografia e grammatica" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verbergen" + "value" : "Spelling en grammatica weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ukryj" + "value" : "Pokaż pisownię i gramatykę" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar" + "value" : "Mostrar ortografia e gramática" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скрыть" + "value" : "Показывать элементы проверки грамматики и орфографии" } } } }, - "main.menu.home.button.mode.left" : { - "comment" : "Main Menu > View > Home Button > left position item", + "main-menu.edit.subsitutions" : { + "comment" : "Main Menu Edit item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Links vom Zurück-Button anzeigen" + "value" : "Ersetzungen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Left of the Back Button" + "value" : "Substitutions" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar a la izquierda del botón Atrás" + "value" : "Sustituciones" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher la gauche du bouton Retour" + "value" : "Substitutions" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra a sinistra del pulsante Indietro" + "value" : "Sostituzioni" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Links van de 'Terug'-knop weergeven" + "value" : "Vervangen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż na lewo od przycisku Wstecz" + "value" : "Podstawienia" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar à esquerda do botão Retroceder" + "value" : "Substituições" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показывать слева от кнопки «Назад»" + "value" : "Замены" } } } }, - "main.menu.home.button.mode.right" : { - "comment" : "Main Menu > View > Home Button > right position item", + "main-menu.edit.undo" : { + "comment" : "Main Menu Edit item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Rechts vom Reload-Button anzeigen" + "value" : "Rückgängig machen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Right of the Reload Button" + "value" : "Undo" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar a la derecha del botón Volver a cargar" + "value" : "Deshacer" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher la droite du bouton Recharger" + "value" : "Annuler" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra a destra del pulsante Ricarica" + "value" : "Annulla" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Rechts van de knop 'Opnieuw laden' weergeven" + "value" : "Ongedaan maken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż na prawo od przycisku ponownego ładowania" + "value" : "Cofnij" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar à direita do botão Recarregar" + "value" : "Anular" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показывать справа от кнопки «Перезагрузить»" + "value" : "Отменить" } } } }, - "main.menu.show.downloads" : { - "comment" : "Show Downloads Popover", + "main-menu.file" : { + "comment" : "Main Menu File", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Downloads anzeigen" + "value" : "Datei" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Downloads" + "value" : "File" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar descargas" + "value" : "Archivo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher les téléchargements" + "value" : "Fichier" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra Download" + "value" : "File" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Downloads weergeven" + "value" : "Bestand" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż pobrane" + "value" : "Plik" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar transferências" + "value" : "Ficheiro" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать загрузки" + "value" : "Файл" } } } }, - "main.menu.show.inspector" : { - "comment" : "Show Web Inspector/Open Developer Tools", + "main-menu.file.close-all-windows" : { + "comment" : "Main Menu File item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Entwicklertools öffnen" + "value" : "Alle Fenster schließen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open Developer Tools" + "value" : "Close All Windows" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir herramientas para desarrolladores" + "value" : "Cerrar todas las ventanas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir les outils de développement" + "value" : "Fermer toutes les fenêtres" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri strumenti per sviluppatori" + "value" : "Chiudi tutte le finestre" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ontwikkelaarstools openen" + "value" : "Alle vensters sluiten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz narzędzia deweloperskie" + "value" : "Zamknij wszystkie okna" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir Ferramentas para programadores" + "value" : "Fechar todas as janelas" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть инструменты разработчика" + "value" : "Закрыть все окна" } } } }, - "Make Lower Case" : { - "comment" : "Main Menu Edit-Transformations item", + "main-menu.file.close-window" : { + "comment" : "Main Menu File item", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kleinschreibung vornehmen" + "value" : "Fenster schließen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Close Window" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cambiar a minúsculas" + "value" : "Cerrar ventana" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mettre en minuscule" + "value" : "Fermer la fenêtre" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Converti in lettere minuscole" + "value" : "Chiudi finestra" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "In kleine letters zetten" + "value" : "Venster sluiten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przekształć na małe litery" + "value" : "Zamknij okno" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "tudo em minúsculas" + "value" : "Fechar janela" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Строчные" + "value" : "Закрыть окно" } } } }, - "Make Upper Case" : { - "comment" : "Main Menu Edit-Transformations item", + "main-menu.file.export" : { + "comment" : "Main Menu File item", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Großschreibung vornehmen" + "value" : "Exportieren" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Export" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cambiar a mayúsculas" + "value" : "Exportar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mettre en majuscule" + "value" : "Exporter" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Converti in lettere maiuscole" + "value" : "Esporta" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "In hoofdletters zetten" + "value" : "Exporteren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przekształć na wielkie litery" + "value" : "Eksport" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "TUDO EM MAIÚSCULAS" + "value" : "Exportar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Прописные" + "value" : "Экспорт" } } } }, - "Manage Bookmarks" : { - "comment" : "Main Menu History item", + "main-menu.file.export-bookmarks" : { + "comment" : "Main Menu File-Export item", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen verwalten" + "value" : "Lesezeichen …" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Bookmarks…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Gestionar marcadores" + "value" : "Marcadores..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Gérer les signets" + "value" : "Signets…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Gestisci segnalibri" + "value" : "Segnalibri…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bladwijzers beheren" + "value" : "Bladwijzers …" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zarządzaj zakładkami" + "value" : "Zakładki..." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Gerir marcadores" + "value" : "Marcadores…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Управление закладками" + "value" : "Закладки..." } } } }, - "menu.add.folder" : { - "comment" : "Menu item to add a folder", + "main-menu.file.export-passwords" : { + "comment" : "Main Menu File-Export item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ordner hinzufügen …" + "value" : "Passwörter …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Add Folder…" + "value" : "Passwords…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Añadir carpeta..." + "value" : "Contraseñas..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ajouter le dossier…" + "value" : "Mots de passe…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aggiungi cartella…" + "value" : "Password…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Map toevoegen …" + "value" : "Wachtwoorden …" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dodaj folder..." + "value" : "Hasła…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Adicionar pasta…" + "value" : "Palavras-passe…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Добавить папку..." + "value" : "Пароли..." } } } }, - "menu.bookmarks.edit" : { - "comment" : "Menu item to edit a bookmark or a folder", + "main-menu.file.import-bookmarks-and-passwords" : { + "comment" : "Main Menu File item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bearbeiten…" + "value" : "Lesezeichen und Passwörter importieren …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Edit…" + "value" : "Import Bookmarks and Passwords…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Editar..." + "value" : "Importar marcadores y contraseñas..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Modifier…" + "value" : "Importer les signets et les mots de passe…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Modifica…" + "value" : "Importa segnalibri e password…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bewerken …" + "value" : "Bladwijzers en wachtwoorden importeren ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Edytuj..." + "value" : "Importuj zakładki i hasła…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Editar…" + "value" : "Importar marcadores e palavras-passe…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Изменить..." + "value" : "Импортировать закладки и пароли..." } } } }, - "menu.item.new.tab" : { - "comment" : "Context menu item", + "main-menu.file.new-tab" : { + "comment" : "Main Menu File item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { @@ -31440,22824 +31359,22941 @@ } } }, - "menu.show.in.folder" : { - "comment" : "Menu item to show where a bookmark is located", + "main-menu.file.open-location" : { + "comment" : "Main Menu File item- Menu option that allows the user to connect to an address (type an address) on click the address bar of the browser is selected and the user can type.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "In Ordner anzeigen" + "value" : "Standort öffnen …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show in Folder" + "value" : "Open Location…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar en la carpeta" + "value" : "Abrir ubicación…" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher dans le dossier" + "value" : "Ouvrir l'emplacement…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra nella cartella" + "value" : "Connetti all'indirizzo..." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Weergeven in map" + "value" : "Locatie openen ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż w folderze" + "value" : "Otwórz lokalizację…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar na pasta" + "value" : "Abrir localização…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать в папке" + "value" : "Открыть адрес…" } } } }, - "Merge All Windows" : { - "comment" : "Main Menu Window item", + "main-menu.file.save-as" : { + "comment" : "Main Menu File item", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alle Fenster zusammenführen" + "value" : "Speichern als …" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Save As…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Fusionar todas las ventanas" + "value" : "Guardar como..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fusionner toutes les fenêtres" + "value" : "Enregistrer sous…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Unisci tutte le finestre" + "value" : "Salva come…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alle vensters samenvoegen" + "value" : "Opslaan als ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Scal wszystkie okna" + "value" : "Zapisz jako…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Agrupar todas as janelas" + "value" : "Guardar como…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Объединить все окна" + "value" : "Сохранить как…" } } } }, - "Minimize" : { - "comment" : "Main Menu Window item", + "main.menu.close.downloads" : { + "comment" : "Hide Downloads Popover", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Minimieren" + "value" : "Downloads ausblenden" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Hide Downloads" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Minimizar" + "value" : "Ocultar descargas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Réduire" + "value" : "Masquer les téléchargements" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riduci" + "value" : "Nascondi download" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Minimaliseren" + "value" : "Downloads verbergen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Minimalizuj" + "value" : "Ukryj pobrane" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Minimizar" + "value" : "Ocultar transferências" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Свернуть" + "value" : "Скрыть загрузки" } } } }, - "more-options.zoom.default-zoom-page" : { - "comment" : "Default page zoom picker title", + "main.menu.close.inspector" : { + "comment" : "Hide Web Inspector/Close Developer Tools", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Standard-Seitenzoom ändern …" + "value" : "Entwicklertools schließen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Change Default Page Zoom…" + "value" : "Close Developer Tools" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cambiar el zoom de página predeterminado..." + "value" : "Cerrar Herramientas para desarrolladores" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Modifier le zoom par défaut de la page" + "value" : "Fermer les outils de développement" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cambia lo zoom predefinito della pagina..." + "value" : "Chiudi strumenti per sviluppatori" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Standaard zoomniveau van de pagina wijzigen ..." + "value" : "Ontwikkelaarstools sluiten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zmień domyślne powiększenie strony…" + "value" : "Zamknij narzędzia deweloperskie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Alterar zoom predefinido da página…" + "value" : "Fechar Ferramentas para programadores" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Изменить масштаб страницы по умолчанию..." + "value" : "Закрыть инструменты разработчика" } } } }, - "more.or.less.collapse" : { - "comment" : "For collapsing views to show less.", + "main.menu.home.button.mode.hide" : { + "comment" : "Main Menu > View > Home Button > None item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Weniger anzeigen" + "value" : "Ausblenden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Less" + "value" : "Hide" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar menos" + "value" : "Ocultar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Réduire" + "value" : "Masquer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra meno" + "value" : "Nascondi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Minder weergeven" + "value" : "Verbergen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż mniej" + "value" : "Ukryj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar menos" + "value" : "Ocultar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать меньше результатов" + "value" : "Скрыть" } } } }, - "more.or.less.expand" : { - "comment" : "For expanding views to show more.", + "main.menu.home.button.mode.left" : { + "comment" : "Main Menu > View > Home Button > left position item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Mehr anzeigen" + "value" : "Links vom Zurück-Button anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show More" + "value" : "Show Left of the Back Button" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar más" + "value" : "Mostrar a la izquierda del botón Atrás" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher plus" + "value" : "Afficher la gauche du bouton Retour" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra altro" + "value" : "Mostra a sinistra del pulsante Indietro" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Meer weergeven" + "value" : "Links van de 'Terug'-knop weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż więcej" + "value" : "Pokaż na lewo od przycisku Wstecz" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar mais" + "value" : "Mostrar à esquerda do botão Retroceder" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать больше" + "value" : "Показывать слева от кнопки «Назад»" } } } }, - "mute.tab" : { - "comment" : "Menu item. Mute tab", + "main.menu.home.button.mode.right" : { + "comment" : "Main Menu > View > Home Button > right position item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Tab stummschalten" + "value" : "Rechts vom Reload-Button anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Mute Tab" + "value" : "Show Right of the Reload Button" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Silenciar pestaña" + "value" : "Mostrar a la derecha del botón Volver a cargar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Couper le son de l'onglet" + "value" : "Afficher la droite du bouton Recharger" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Silenzia scheda" + "value" : "Mostra a destra del pulsante Ricarica" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Tabblad dempen" + "value" : "Rechts van de knop 'Opnieuw laden' weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zakładka wyciszenia" + "value" : "Pokaż na prawo od przycisku ponownego ładowania" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Silenciar Separador" + "value" : "Mostrar à direita do botão Recarregar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отключить звук на вкладке" + "value" : "Показывать справа от кнопки «Перезагрузить»" } } } }, - "n.more.tabs" : { - "comment" : "String in Recently Closed menu item for recently closed browser window and number of tabs contained in the closed window", + "main.menu.show.downloads" : { + "comment" : "Show Downloads Popover", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fenster mit mehreren Tabs (%d)" + "value" : "Downloads anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Window with multiple tabs (%d)" + "value" : "Show Downloads" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ventana con varias pestañas (%d)" + "value" : "Mostrar descargas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fenêtre avec plusieurs onglets (%d)" + "value" : "Afficher les téléchargements" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Finestra con più schede (%d)" + "value" : "Mostra Download" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Venster met meerdere tabbladen (%d)" + "value" : "Downloads weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Okno z wieloma kartami (%d)" + "value" : "Pokaż pobrane" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Janela com vários separadores (%d)" + "value" : "Mostrar transferências" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Окно с несколькими вкладками (%d)" + "value" : "Показать загрузки" } } } }, - "navigate.back" : { - "comment" : "Context menu item", + "main.menu.show.inspector" : { + "comment" : "Show Web Inspector/Open Developer Tools", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zurück" + "value" : "Entwicklertools öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Back" + "value" : "Open Developer Tools" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Volver" + "value" : "Abrir herramientas para desarrolladores" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Retour" + "value" : "Ouvrir les outils de développement" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Indietro" + "value" : "Apri strumenti per sviluppatori" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Terug" + "value" : "Ontwikkelaarstools openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wstecz" + "value" : "Otwórz narzędzia deweloperskie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Retroceder" + "value" : "Abrir Ferramentas para programadores" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Назад" + "value" : "Открыть инструменты разработчика" } } } }, - "navigate.forward" : { - "comment" : "Context menu item", - "extractionState" : "extracted_with_value", + "Make Lower Case" : { + "comment" : "Main Menu Edit-Transformations item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Weiter" + "value" : "Kleinschreibung vornehmen" } }, - "en" : { + "es" : { "stringUnit" : { - "state" : "new", - "value" : "Forward" + "state" : "translated", + "value" : "Cambiar a minúsculas" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mettre en minuscule" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Converti in lettere minuscole" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "In kleine letters zetten" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Przekształć na małe litery" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "tudo em minúsculas" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Строчные" + } + } + } + }, + "Make Upper Case" : { + "comment" : "Main Menu Edit-Transformations item", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Großschreibung vornehmen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Avanzar" + "value" : "Cambiar a mayúsculas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Avancer" + "value" : "Mettre en majuscule" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Avanti" + "value" : "Converti in lettere maiuscole" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Vooruit" + "value" : "In hoofdletters zetten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dalej" + "value" : "Przekształć na wielkie litery" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Avançar" + "value" : "TUDO EM MAIÚSCULAS" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вперёд" + "value" : "Прописные" } } } }, - "Never Show" : { - "comment" : "Preference for never showing the bookmarks bar on new tab", + "Manage Bookmarks" : { + "comment" : "Main Menu History item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Niemals anzeigen" + "value" : "Lesezeichen verwalten" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No mostrar nunca" + "value" : "Gestionar marcadores" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ne jamais afficher" + "value" : "Gérer les signets" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Non mostrare mai" + "value" : "Gestisci segnalibri" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nooit tonen" + "value" : "Bladwijzers beheren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nigdy nie pokazuj" + "value" : "Zarządzaj zakładkami" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nunca mostrar" + "value" : "Gerir marcadores" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не показывать" + "value" : "Управление закладками" } } } }, - "never.for.this.site" : { - "comment" : "Never ask to save login credentials for this site button", + "menu.add.folder" : { + "comment" : "Menu item to add a folder", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Für diese Website niemals fragen" + "value" : "Ordner hinzufügen …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Never Ask for This Site" + "value" : "Add Folder…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No preguntar nunca para esta página" + "value" : "Añadir carpeta..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ne jamais demander pour ce site" + "value" : "Ajouter le dossier…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Non chiedere mai per questo sito" + "value" : "Aggiungi cartella…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nooit vragen voor deze site" + "value" : "Map toevoegen …" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nigdy nie pytaj o tę witrynę" + "value" : "Dodaj folder..." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nunca pedir para este site" + "value" : "Adicionar pasta…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Больше не спрашивать на этом сайте" + "value" : "Добавить папку..." } } } }, - "new.burner.window.menu.item" : { - "comment" : "Menu item title", + "menu.bookmarks.edit" : { + "comment" : "Menu item to edit a bookmark or a folder", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neues Fire Window" + "value" : "Bearbeiten…" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "New Fire Window" + "value" : "Edit…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nueva Fire Window" + "value" : "Editar..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle Fire Window" + "value" : "Modifier…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nuova Fire Window" + "value" : "Modifica…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuw Fire Window" + "value" : "Bewerken …" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nowe okno Fire Window" + "value" : "Edytuj..." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nova Fire Window" + "value" : "Editar…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Новое окно Fire Window" + "value" : "Изменить..." } } } }, - "new.window.menu.item" : { - "comment" : "Menu item title", + "menu.item.new.tab" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neues Fenster" + "value" : "Neuer Tab" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "New Window" + "value" : "New Tab" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ventana nueva" + "value" : "Nueva pestaña" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle fenêtre" + "value" : "Nouvel onglet" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nuova finestra" + "value" : "Nuova scheda" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuw venster" + "value" : "Nieuw tabblad" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nowe okno" + "value" : "Nowa karta" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nova janela" + "value" : "Novo separador" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Новое окно" + "value" : "Новая вкладка" } } } }, - "newTab.bottom.popover.title" : { - "comment" : "Title of the popover that appears when pressing the bottom right button", + "menu.show.in.folder" : { + "comment" : "Menu item to show where a bookmark is located", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neue Tab-Seite" + "value" : "In Ordner anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "New Tab Page" + "value" : "Show in Folder" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Página de nueva pestaña" + "value" : "Mostrar en la carpeta" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle page d'onglet" + "value" : "Afficher dans le dossier" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Pagina Nuova scheda" + "value" : "Mostra nella cartella" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuwe tabbladpagina" + "value" : "Weergeven in map" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Strona nowej karty" + "value" : "Pokaż w folderze" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nova página de separador" + "value" : "Mostrar na pasta" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Страница новой вкладки" + "value" : "Показать в папке" } } } }, - "newTab.favorites.section.title" : { - "comment" : "Title of the Favorites section in the home page", - "extractionState" : "extracted_with_value", + "Merge All Windows" : { + "comment" : "Main Menu Window item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Favoriten" + "value" : "Alle Fenster zusammenführen" } }, - "en" : { + "es" : { "stringUnit" : { - "state" : "new", - "value" : "Favorites" + "state" : "translated", + "value" : "Fusionar todas las ventanas" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fusionner toutes les fenêtres" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unisci tutte le finestre" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle vensters samenvoegen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scal wszystkie okna" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agrupar todas as janelas" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Объединить все окна" + } + } + } + }, + "Minimize" : { + "comment" : "Main Menu Window item", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Minimieren" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Favoritos" + "value" : "Minimizar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Favoris" + "value" : "Réduire" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Preferiti" + "value" : "Riduci" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Favorieten" + "value" : "Minimaliseren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ulubione" + "value" : "Minimalizuj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Favoritos" + "value" : "Minimizar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Избранное" + "value" : "Свернуть" } } } }, - "newTab.menu.item.show.continue.setup" : { - "comment" : "Title of the menu item in the home page to show/hide continue setup section", + "more-options.zoom.default-zoom-page" : { + "comment" : "Default page zoom picker title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nächste Schritte anzeigen" + "value" : "Standard-Seitenzoom ändern …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Next Steps" + "value" : "Change Default Page Zoom…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar pasos siguientes" + "value" : "Cambiar el zoom de página predeterminado..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher les étapes suivantes" + "value" : "Modifier le zoom par défaut de la page" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra i passaggi successivi" + "value" : "Cambia lo zoom predefinito della pagina..." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Volgende stappen weergeven" + "value" : "Standaard zoomniveau van de pagina wijzigen ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż dalsze kroki" + "value" : "Zmień domyślne powiększenie strony…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar passos seguintes" + "value" : "Alterar zoom predefinido da página…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать дальнейшие шаги" + "value" : "Изменить масштаб страницы по умолчанию..." } } } }, - "newTab.menu.item.show.favorite" : { - "comment" : "Title of the menu item in the home page to show/hide favorite section", + "more.or.less.collapse" : { + "comment" : "For collapsing views to show less.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Favoriten anzeigen" + "value" : "Weniger anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Favorites" + "value" : "Show Less" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar favoritos" + "value" : "Mostrar menos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher les favoris" + "value" : "Réduire" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra Preferiti" + "value" : "Mostra meno" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Favorieten weergeven" + "value" : "Minder weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż ulubione" + "value" : "Pokaż mniej" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar favoritos" + "value" : "Mostrar menos" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показывать избранное" + "value" : "Показать меньше результатов" } } } }, - "newTab.menu.item.show.recent.activity" : { - "comment" : "Title of the menu item in the home page to show/hide recent activity section", + "more.or.less.expand" : { + "comment" : "For expanding views to show more.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Aktuelle Aktivität anzeigen" + "value" : "Mehr anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Recent Activity" + "value" : "Show More" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar actividad reciente" + "value" : "Mostrar más" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher l'activité récente" + "value" : "Afficher plus" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra attività recente" + "value" : "Mostra altro" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Recente activiteit weergeven" + "value" : "Meer weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż ostatnią aktywność" + "value" : "Pokaż więcej" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar atividade recente" + "value" : "Mostrar mais" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать недавнюю активность" + "value" : "Показать больше" } } } }, - "newTab.recent.activity.section.title" : { - "comment" : "Title of the RecentActivity section in the home page", + "mute.tab" : { + "comment" : "Menu item. Mute tab", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Aktuelle Aktivitäten" + "value" : "Tab stummschalten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Recent Activity" + "value" : "Mute Tab" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Actividad reciente" + "value" : "Silenciar pestaña" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Activité récente" + "value" : "Couper le son de l'onglet" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Attività recente" + "value" : "Silenzia scheda" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Recente activiteit" + "value" : "Tabblad dempen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ostatnia aktywność" + "value" : "Zakładka wyciszenia" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Atividade recente" + "value" : "Silenciar Separador" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Недавняя активность" + "value" : "Отключить звук на вкладке" } } } }, - "newTab.setup.default.browser.action" : { - "comment" : "Action title on the action menu of the Default Browser card", + "n.more.tabs" : { + "comment" : "String in Recently Closed menu item for recently closed browser window and number of tabs contained in the closed window", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Standardbrowser erstellen" + "value" : "Fenster mit mehreren Tabs (%d)" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Make Default Browser" + "value" : "Window with multiple tabs (%d)" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Convertir en navegador predeterminado" + "value" : "Ventana con varias pestañas (%d)" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Définir comme navigateur par défaut" + "value" : "Fenêtre avec plusieurs onglets (%d)" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rendi il browser predefinito" + "value" : "Finestra con più schede (%d)" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Instellen als standaardbrowser" + "value" : "Venster met meerdere tabbladen (%d)" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustaw jako domyślną przeglądarkę" + "value" : "Okno z wieloma kartami (%d)" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Tornar navegador predefinido" + "value" : "Janela com vários separadores (%d)" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Назначить браузером по умолчанию" + "value" : "Окно с несколькими вкладками (%d)" } } } }, - "newTab.setup.default.browser.summary" : { - "comment" : "Summary of the Default Browser card", + "navigate.back" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wir blockieren automatisch Tracker, während du browst. Das ist Datenschutz, vereinfacht." + "value" : "Zurück" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "We automatically block trackers as you browse. It's privacy, simplified." + "value" : "Back" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Bloqueamos automáticamente los rastreadores mientras navegas. Es tan solo privacidad simplificada." + "value" : "Volver" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nous bloquons automatiquement les traqueurs lorsque vous naviguez. C'est la confidentialité, simplifiée." + "value" : "Retour" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Blocchiamo automaticamente i sistemi di tracciamento mentre navighi. La privacy, semplificata." + "value" : "Indietro" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "We blokkeren automatisch trackers wanneer je surft. Dat is privacy, vereenvoudigd." + "value" : "Terug" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Podczas przeglądania automatycznie blokujemy mechanizmy śledzące. To prywatność – jeszcze prostsza." + "value" : "Wstecz" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Bloqueamos automaticamente os rastreadores enquanto navegas. É privacidade, simplificada." + "value" : "Retroceder" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пока вы ходите по сайтам, мы автоматически блокируем трекеры. Максимум защиты, минимум усилий." + "value" : "Назад" } } } }, - "newTab.setup.default.browser.title" : { - "comment" : "Title of the Default Browser card of the Set Up section in the home page", + "navigate.forward" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Standardmäßig auf Datenschutz einstellen" + "value" : "Weiter" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Default to Privacy" + "value" : "Forward" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Predeterminado a Privacidad" + "value" : "Avanzar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "La confidentialité par défaut" + "value" : "Avancer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "La privacy prima di tutto" + "value" : "Avanti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Standaard ingesteld op Privacy" + "value" : "Vooruit" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Domyślnie ustaw prywatność" + "value" : "Dalej" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Predefinir a privacidade" + "value" : "Avançar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Конфиденциальность по умолчанию" + "value" : "Вперёд" } } } }, - "newTab.setup.dock.action" : { - "comment" : "Action title on the action menu of the 'Add App to the Dock' card", - "extractionState" : "extracted_with_value", + "Never Show" : { + "comment" : "Preference for never showing the bookmarks bar on new tab", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Im Dock behalten" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Keep In Dock" + "value" : "Niemals anzeigen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mantener en Dock" + "value" : "No mostrar nunca" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Garder dans le Dock" + "value" : "Ne jamais afficher" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Resta nel dock" + "value" : "Non mostrare mai" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "In het Dock houden" + "value" : "Nooit tonen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Trzymaj w Docku" + "value" : "Nigdy nie pokazuj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Manter na Dock" + "value" : "Nunca mostrar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Ярлык для док-панели" + "value" : "Не показывать" } } } }, - "newTab.setup.dock.confirmation" : { - "comment" : "Confirmation title after user clicks on 'Add to Dock' card", + "never.for.this.site" : { + "comment" : "Never ask to save login credentials for this site button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zum Dock hinzugefügt!" + "value" : "Für diese Website niemals fragen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Added to Dock!" + "value" : "Never Ask for This Site" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¡Añadido al Dock!" + "value" : "No preguntar nunca para esta página" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ajouté au Dock !" + "value" : "Ne jamais demander pour ce site" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aggiunto al dock!" + "value" : "Non chiedere mai per questo sito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Toegevoegd aan Dock!" + "value" : "Nooit vragen voor deze site" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dodano do Docka!" + "value" : "Nigdy nie pytaj o tę witrynę" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Adicionado à Dock!" + "value" : "Nunca pedir para este site" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Ярлык добавлен на док-панель." + "value" : "Больше не спрашивать на этом сайте" } } } }, - "newTab.setup.dock.summary" : { - "comment" : "Summary of the 'Add App to the Dock' card", + "new.burner.window.menu.item" : { + "comment" : "Menu item title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Du kannst DuckDuckGo schneller erreichen, indem du es zu deinem Dock hinzufügst." + "value" : "Neues Fire Window" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Get to DuckDuckGo faster by adding it to your Dock." + "value" : "New Fire Window" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Accede a DuckDuckGo más rápido añadiéndolo a tu Dock." + "value" : "Nueva Fire Window" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Accédez plus rapidement à DuckDuckGo en l'ajoutant à votre Dock." + "value" : "Nouvelle Fire Window" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Accedi più velocemente a DuckDuckGo aggiungendolo al tuo dock." + "value" : "Nuova Fire Window" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ga sneller naar DuckDuckGo door het aan je Dock toe te voegen." + "value" : "Nieuw Fire Window" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Uzyskuj szybszy dostęp do przeglądarki DuckDuckGo dzięki jej dodaniu do Docka." + "value" : "Nowe okno Fire Window" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Acede ao DuckDuckGo mais rapidamente adicionando-o à tua Dock." + "value" : "Nova Fire Window" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Добавьте DuckDuckGo на док-панель для быстрого запуска." + "value" : "Новое окно Fire Window" } } } }, - "newTab.setup.dock.title" : { - "comment" : "Title of the new tab page card for adding application to the Dock", + "new.window.menu.item" : { + "comment" : "Menu item title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "In deinem Dock behalten" + "value" : "Neues Fenster" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Keep in Your Dock" + "value" : "New Window" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mantener en tu Dock" + "value" : "Ventana nueva" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Garder dans votre Dock" + "value" : "Nouvelle fenêtre" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Tieni nel tuo dock" + "value" : "Nuova finestra" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bewaar in je dock" + "value" : "Nieuw venster" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Trzymaj w Docku" + "value" : "Nowe okno" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Manter na tua Dock" + "value" : "Nova janela" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Ярлык для док-панели" + "value" : "Новое окно" } } } }, - "newTab.setup.duck.player.action" : { - "comment" : "Action title on the action menu of the Duck Player card of the Set Up section in the home page", + "newTab.bottom.popover.title" : { + "comment" : "Title of the popover that appears when pressing the bottom right button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player testen" + "value" : "Neue Tab-Seite" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Try Duck Player" + "value" : "New Tab Page" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Prueba Duck Player" + "value" : "Página de nueva pestaña" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Essayer Duck Player" + "value" : "Nouvelle page d'onglet" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Prova Duck Player" + "value" : "Pagina Nuova scheda" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Probeer Duck Player" + "value" : "Nieuwe tabbladpagina" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wypróbuj Duck Player" + "value" : "Strona nowej karty" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Experimente o Duck Player" + "value" : "Nova página de separador" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Попробовать Duck Player" + "value" : "Страница новой вкладки" } } } }, - "newTab.setup.duck.player.summary" : { - "comment" : "Summary of the Duck Player card of the Set Up section in the home page", + "newTab.favorites.section.title" : { + "comment" : "Title of the Favorites section in the home page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Genieße ein sauberes Seherlebnis ohne personalisierte Werbung." + "value" : "Favoriten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Enjoy a clean viewing experience without personalized ads." + "value" : "Favorites" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Disfruta de una experiencia de visualización limpia sin anuncios personalizados." + "value" : "Favoritos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Profitez d'une expérience de visionnage épurée, sans publicités personnalisées." + "value" : "Favoris" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Usufruisci di un'esperienza di visione lineare, senza annunci personalizzati." + "value" : "Preferiti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Beleef ongeëvenaard kijkplezier zonder gepersonaliseerde advertenties." + "value" : "Favorieten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Korzystaj z czystego środowiska oglądania bez spersonalizowanych reklam." + "value" : "Ulubione" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Desfruta de uma experiência de visualização limpa sem anúncios personalizados." + "value" : "Favoritos" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "«Чистый» просмотр без персонализированной рекламы." + "value" : "Избранное" } } } }, - "newTab.setup.duck.player.title" : { - "comment" : "Title of the Duck Player card of the Set Up section in the home page", + "newTab.menu.item.show.continue.setup" : { + "comment" : "Title of the menu item in the home page to show/hide continue setup section", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "YouTube aufräumen" + "value" : "Nächste Schritte anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Clean Up YouTube" + "value" : "Show Next Steps" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Limpiar YouTube" + "value" : "Mostrar pasos siguientes" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Place nette sur YouTube" + "value" : "Afficher les étapes suivantes" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ripulisci YouTube" + "value" : "Mostra i passaggi successivi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "YouTube opruimen" + "value" : "Volgende stappen weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Oczyść YouTube" + "value" : "Pokaż dalsze kroki" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Limpar YouTube" + "value" : "Mostrar passos seguintes" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "YouTube в очищенном варианте" + "value" : "Показать дальнейшие шаги" } } } }, - "newTab.setup.email.protection.action" : { - "comment" : "Action title on the action menu of the Email Protection card of the Set Up section in the home page", + "newTab.menu.item.show.favorite" : { + "comment" : "Title of the menu item in the home page to show/hide favorite section", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hol dir eine Duck Address" + "value" : "Favoriten anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Get a Duck Address" + "value" : "Show Favorites" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Consigue una Duck Address" + "value" : "Mostrar favoritos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Se procurer une Duck Address" + "value" : "Afficher les favoris" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ottieni un Duck Address" + "value" : "Mostra Preferiti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Een Duck Address aanmaken" + "value" : "Favorieten weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Uzyskaj Duck Address" + "value" : "Pokaż ulubione" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Obtém um Duck Address" + "value" : "Mostrar favoritos" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Создать адрес Duck Address" + "value" : "Показывать избранное" } } } }, - "newTab.setup.email.protection.summary" : { - "comment" : "Summary of the Email Protection card of the Set Up section in the home page", + "newTab.menu.item.show.recent.activity" : { + "comment" : "Title of the menu item in the home page to show/hide recent activity section", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Erstelle eigene @duck.com-Adressen, die Tracker aus eingehenden E-Mails entfernen." + "value" : "Aktuelle Aktivität anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Generate custom @duck.com addresses that clean trackers from incoming email." + "value" : "Show Recent Activity" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Genera direcciones personalizadas de @duck.com que limpien los rastreadores del correo electrónico entrante." + "value" : "Mostrar actividad reciente" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Générez des adresses @duck.com personnalisées qui débarrassent les e-mails entrants des traqueurs." + "value" : "Afficher l'activité récente" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Genera indirizzi @duck.com personalizzati che eliminano i sistemi di tracciamento della posta in arrivo." + "value" : "Mostra attività recente" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Aangepaste @duck.com-adressen genereren om trackers van binnenkomende e-mails te wissen." + "value" : "Recente activiteit weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Generuj adresy w domenie @duck.com, które usuwają skrypty śledzące z przychodzących e-maili." + "value" : "Pokaż ostatnią aktywność" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Gera endereços @duck.com personalizados que limpam os rastreadores dos e-mails recebidos." + "value" : "Mostrar atividade recente" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Уникальные почтовые адреса на домене @duck.com для очистки входящей почты от трекеров." + "value" : "Показать недавнюю активность" } } } }, - "newTab.setup.email.protection.title" : { - "comment" : "Title of the Email Protection card of the Set Up section in the home page", + "newTab.recent.activity.section.title" : { + "comment" : "Title of the RecentActivity section in the home page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Schütze deinen Posteingang" + "value" : "Aktuelle Aktivitäten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Protect Your Inbox" + "value" : "Recent Activity" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Protege tu bandeja de entrada" + "value" : "Actividad reciente" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Protégez votre boîte de réception" + "value" : "Activité récente" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Proteggi la tua casella di posta" + "value" : "Attività recente" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bescherm je inbox" + "value" : "Recente activiteit" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Chroń swoją skrzynkę odbiorczą" + "value" : "Ostatnia aktywność" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Protege a tua caixa de entrada" + "value" : "Atividade recente" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Защита для вашей почты" + "value" : "Недавняя активность" } } } }, - "newTab.setup.Import.action" : { - "comment" : "Action title on the action menu of the Import card of the Set Up section in the home page", + "newTab.setup.default.browser.action" : { + "comment" : "Action title on the action menu of the Default Browser card", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Jetzt importieren" + "value" : "Standardbrowser erstellen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Import Now" + "value" : "Make Default Browser" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importar ahora" + "value" : "Convertir en navegador predeterminado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importer dès maintenant" + "value" : "Définir comme navigateur par défaut" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importa ora" + "value" : "Rendi il browser predefinito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nu importeren" + "value" : "Instellen als standaardbrowser" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importuj teraz" + "value" : "Ustaw jako domyślną przeglądarkę" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importar agora" + "value" : "Tornar navegador predefinido" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Импортировать" + "value" : "Назначить браузером по умолчанию" } } } }, - "newTab.setup.import.summary" : { - "comment" : "Summary of the Import card of the Set Up section in the home page", + "newTab.setup.default.browser.summary" : { + "comment" : "Summary of the Default Browser card", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Importiere Lesezeichen, Favoriten und Passwörter aus deinem alten Browser." + "value" : "Wir blockieren automatisch Tracker, während du browst. Das ist Datenschutz, vereinfacht." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Import bookmarks, favorites, and passwords from your old browser." + "value" : "We automatically block trackers as you browse. It's privacy, simplified." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importa marcadores, favoritos y contraseñas desde tu navegador antiguo." + "value" : "Bloqueamos automáticamente los rastreadores mientras navegas. Es tan solo privacidad simplificada." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importez des signets, des favoris et des mots de passe depuis votre ancien navigateur." + "value" : "Nous bloquons automatiquement les traqueurs lorsque vous naviguez. C'est la confidentialité, simplifiée." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importa segnalibri, preferiti e password dal tuo vecchio browser." + "value" : "Blocchiamo automaticamente i sistemi di tracciamento mentre navighi. La privacy, semplificata." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bladwijzers, favorieten en wachtwoorden vanuit je vorige browser importeren." + "value" : "We blokkeren automatisch trackers wanneer je surft. Dat is privacy, vereenvoudigd." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zaimportuj zakładki, elementy ulubione i hasła ze starej przeglądarki." + "value" : "Podczas przeglądania automatycznie blokujemy mechanizmy śledzące. To prywatność – jeszcze prostsza." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importa marcadores, favoritos e palavras-passe do teu navegador antigo." + "value" : "Bloqueamos automaticamente os rastreadores enquanto navegas. É privacidade, simplificada." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Закладки, избранное и пароли можно импортировать из старого браузера." + "value" : "Пока вы ходите по сайтам, мы автоматически блокируем трекеры. Максимум защиты, минимум усилий." } } } }, - "newTab.setup.import.title" : { - "comment" : "Title of the Import card of the Set Up section in the home page", + "newTab.setup.default.browser.title" : { + "comment" : "Title of the Default Browser card of the Set Up section in the home page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bring deine Sachen" + "value" : "Standardmäßig auf Datenschutz einstellen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Bring Your Stuff" + "value" : "Default to Privacy" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Trae tus cosas" + "value" : "Predeterminado a Privacidad" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Emportez tout avec vous" + "value" : "La confidentialité par défaut" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importa i tuoi file" + "value" : "La privacy prima di tutto" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Neem je favorieten mee" + "value" : "Standaard ingesteld op Privacy" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przenieś swoje rzeczy" + "value" : "Domyślnie ustaw prywatność" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Traz as tuas coisas" + "value" : "Predefinir a privacidade" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Все свое с собой" + "value" : "Конфиденциальность по умолчанию" } } } }, - "newTab.setup.remove.item" : { - "comment" : "Action title on the action menu of the set up cards card of the SetUp section in the home page to remove the item", + "newTab.setup.dock.action" : { + "comment" : "Action title on the action menu of the 'Add App to the Dock' card", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verwerfen" + "value" : "Im Dock behalten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Dismiss" + "value" : "Keep In Dock" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Descartar" + "value" : "Mantener en Dock" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ignorer" + "value" : "Garder dans le Dock" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ignora" + "value" : "Resta nel dock" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Negeren" + "value" : "In het Dock houden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Odrzuć" + "value" : "Trzymaj w Docku" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ignorar" + "value" : "Manter na Dock" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отклонить" + "value" : "Ярлык для док-панели" } } } }, - "newTab.setup.section.title" : { - "comment" : "Title of the setup section in the home page", + "newTab.setup.dock.confirmation" : { + "comment" : "Confirmation title after user clicks on 'Add to Dock' card", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nächste Schritte" + "value" : "Zum Dock hinzugefügt!" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Next Steps" + "value" : "Added to Dock!" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Próximos pasos" + "value" : "¡Añadido al Dock!" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Étapes suivantes" + "value" : "Ajouté au Dock !" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Passaggi successivi" + "value" : "Aggiunto al dock!" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Volgende stappen" + "value" : "Toegevoegd aan Dock!" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dalsze kroki" + "value" : "Dodano do Docka!" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Passos seguintes" + "value" : "Adicionado à Dock!" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Дальнейшие шаги" + "value" : "Ярлык добавлен на док-панель." } } } }, - "next" : { - "comment" : "Next button", + "newTab.setup.dock.summary" : { + "comment" : "Summary of the 'Add App to the Dock' card", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Weiter" + "value" : "Du kannst DuckDuckGo schneller erreichen, indem du es zu deinem Dock hinzufügst." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Next" + "value" : "Get to DuckDuckGo faster by adding it to your Dock." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Siguiente" + "value" : "Accede a DuckDuckGo más rápido añadiéndolo a tu Dock." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Suivant" + "value" : "Accédez plus rapidement à DuckDuckGo en l'ajoutant à votre Dock." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Successivo" + "value" : "Accedi più velocemente a DuckDuckGo aggiungendolo al tuo dock." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Volgende" + "value" : "Ga sneller naar DuckDuckGo door het aan je Dock toe te voegen." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dalej" + "value" : "Uzyskuj szybszy dostęp do przeglądarki DuckDuckGo dzięki jej dodaniu do Docka." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Seguinte" + "value" : "Acede ao DuckDuckGo mais rapidamente adicionando-o à tua Dock." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Далее" + "value" : "Добавьте DuckDuckGo на док-панель для быстрого запуска." } } } }, - "no.access.to.downloads.folder.header" : { - "comment" : "Header of the alert dialog warning the user they need to give the browser permission to access the Downloads folder", + "newTab.setup.dock.title" : { + "comment" : "Title of the new tab page card for adding application to the Dock", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo benötigt die Erlaubnis, auf deinen Downloads-Ordner zuzugreifen" + "value" : "In deinem Dock behalten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo needs permission to access your Downloads folder" + "value" : "Keep in Your Dock" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo necesita permiso para acceder a tu carpeta de descargas" + "value" : "Mantener en tu Dock" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo a besoin d'une autorisation pour accéder à votre dossier Téléchargements" + "value" : "Garder dans votre Dock" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo ha bisogno dell'autorizzazione per accedere alla tua cartella Download" + "value" : "Tieni nel tuo dock" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo heeft toestemming nodig om toegang te krijgen tot je downloadsmap" + "value" : "Bewaar in je dock" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo potrzebuje pozwolenia na dostęp do folderu Pobrane" + "value" : "Trzymaj w Docku" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O DuckDuckGo precisa de permissão para aceder à tua pasta Transferências" + "value" : "Manter na tua Dock" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo запрашивает доступ к папке «Загрузки»" + "value" : "Ярлык для док-панели" } } } }, - "no.access.to.downloads.folder.legacy" : { - "comment" : "Alert presented to user if the app doesn't have rights to access Downloads folder. This is used for macOS version 12 and below", + "newTab.setup.duck.player.action" : { + "comment" : "Action title on the action menu of the Duck Player card of the Set Up section in the home page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Erlaube den Zugriff in den Einstellungen für Sicherheit und Datenschutz in den Systemeinstellungen." + "value" : "Duck Player testen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Grant access in Security & Privacy preferences in System Settings." + "value" : "Try Duck Player" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Concede acceso en las preferencias de Seguridad y privacidad en Configuración del sistema." + "value" : "Prueba Duck Player" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Accordez l'accès dans les préférences de sécurité et de confidentialité des paramètres système." + "value" : "Essayer Duck Player" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Concedi l'accesso alle preferenze di Sicurezza e Privacy nelle Impostazioni di sistema." + "value" : "Prova Duck Player" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geef toegang in Beveiligings- en privacyvoorkeuren in Systeeminstellingen." + "value" : "Probeer Duck Player" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przyznaj dostęp w preferencjach dotyczących bezpieczeństwa i prywatności w ustawieniach systemowych." + "value" : "Wypróbuj Duck Player" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Concede acesso nas preferências de segurança e privacidade em Definições do sistema." + "value" : "Experimente o Duck Player" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Предоставьте доступ в разделе «Безопасность и конфиденциальность» в настройках системы." + "value" : "Попробовать Duck Player" } } } }, - "no.access.to.downloads.folder.modern" : { - "comment" : "Alert presented to user if the app doesn't have rights to access Downloads folder. This is used for macOS version 13 and above", + "newTab.setup.duck.player.summary" : { + "comment" : "Summary of the Duck Player card of the Set Up section in the home page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Erlaube den Zugriff in den Einstellungen für Datenschutz und Sicherheit in den Systemeinstellungen." + "value" : "Genieße ein sauberes Seherlebnis ohne personalisierte Werbung." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Grant access in Privacy & Security preferences in System Settings." + "value" : "Enjoy a clean viewing experience without personalized ads." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Concede acceso en las preferencias de Seguridad y privacidad en Configuración del sistema." + "value" : "Disfruta de una experiencia de visualización limpia sin anuncios personalizados." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Accordez l'accès dans les préférences de confidentialité et de sécurité des paramètres système." + "value" : "Profitez d'une expérience de visionnage épurée, sans publicités personnalisées." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Concedi l'accesso alle preferenze di Privacy e Sicurezza nelle Impostazioni di sistema." + "value" : "Usufruisci di un'esperienza di visione lineare, senza annunci personalizzati." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geef toegang in Privacy en beveiligingsvoorkeuren in Systeeminstellingen." + "value" : "Beleef ongeëvenaard kijkplezier zonder gepersonaliseerde advertenties." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przyznaj dostęp w preferencjach dotyczących prywatności i bezpieczeństwa w ustawieniach systemowych." + "value" : "Korzystaj z czystego środowiska oglądania bez spersonalizowanych reklam." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Conceda acesso às preferências de privacidade e segurança em Definições do sistema." + "value" : "Desfruta de uma experiência de visualização limpa sem anúncios personalizados." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Предоставьте доступ в разделе «Конфиденциальность и безопасность» в настройках системы." + "value" : "«Чистый» просмотр без персонализированной рекламы." } } } }, - "notification.badge.cookiesmanaged" : { - "comment" : "Notification that appears when browser automatically handle cookies", + "newTab.setup.duck.player.title" : { + "comment" : "Title of the Duck Player card of the Set Up section in the home page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Cookies verwaltet" + "value" : "YouTube aufräumen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Cookies Managed" + "value" : "Clean Up YouTube" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cookies gestionadas" + "value" : "Limpiar YouTube" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Cookies gérés" + "value" : "Place nette sur YouTube" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cookie gestiti" + "value" : "Ripulisci YouTube" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Beheerde cookies" + "value" : "YouTube opruimen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zarządzane pliki cookie" + "value" : "Oczyść YouTube" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Cookies geridos" + "value" : "Limpar YouTube" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Kуки-файлы выбраны" + "value" : "YouTube в очищенном варианте" } } } }, - "notification.badge.popuphidden" : { - "comment" : "Notification that appears when browser cosmetically hides a cookie popup", + "newTab.setup.email.protection.action" : { + "comment" : "Action title on the action menu of the Email Protection card of the Set Up section in the home page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Pop-up ausgeblendet" + "value" : "Hol dir eine Duck Address" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Pop-up Hidden" + "value" : "Get a Duck Address" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ventana emergente oculta" + "value" : "Consigue una Duck Address" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fenêtre contextuelle masquée" + "value" : "Se procurer une Duck Address" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Popup nascosto" + "value" : "Ottieni un Duck Address" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Pop-up verborgen" + "value" : "Een Duck Address aanmaken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ukryte wyskakujące okienka" + "value" : "Uzyskaj Duck Address" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Pop-up ocultado" + "value" : "Obtém um Duck Address" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Всплывающее окно скрыто" + "value" : "Создать адрес Duck Address" } } } }, - "notification.browser.downgraded" : { - "comment" : "Notification informing user the app has been downgraded", + "newTab.setup.email.protection.summary" : { + "comment" : "Summary of the Email Protection card of the Set Up section in the home page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Browser herabgestuft" + "value" : "Erstelle eigene @duck.com-Adressen, die Tracker aus eingehenden E-Mails entfernen." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Browser Downgraded" + "value" : "Generate custom @duck.com addresses that clean trackers from incoming email." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Navegador degradado" + "value" : "Genera direcciones personalizadas de @duck.com que limpien los rastreadores del correo electrónico entrante." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Version antérieure du navigateur activée" + "value" : "Générez des adresses @duck.com personnalisées qui débarrassent les e-mails entrants des traqueurs." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Browser declassato" + "value" : "Genera indirizzi @duck.com personalizzati che eliminano i sistemi di tracciamento della posta in arrivo." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Browser gedegradeerd" + "value" : "Aangepaste @duck.com-adressen genereren om trackers van binnenkomende e-mails te wissen." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zmieniono wersję przeglądarki na niższą" + "value" : "Generuj adresy w domenie @duck.com, które usuwają skrypty śledzące z przychodzących e-maili." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Navegador com versão anterior" + "value" : "Gera endereços @duck.com personalizados que limpam os rastreadores dos e-mails recebidos." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Версия браузера понижена" + "value" : "Уникальные почтовые адреса на домене @duck.com для очистки входящей почты от трекеров." } } } }, - "notification.browser.updated" : { - "comment" : "Notification informing user the app has been updated", + "newTab.setup.email.protection.title" : { + "comment" : "Title of the Email Protection card of the Set Up section in the home page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Browser aktualisiert" + "value" : "Schütze deinen Posteingang" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Browser Updated" + "value" : "Protect Your Inbox" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Navegador actualizado" + "value" : "Protege tu bandeja de entrada" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Navigateur mis à jour" + "value" : "Protégez votre boîte de réception" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Browser aggiornato" + "value" : "Proteggi la tua casella di posta" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Browser bijgewerkt" + "value" : "Bescherm je inbox" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zaktualizowano przeglądarkę" + "value" : "Chroń swoją skrzynkę odbiorczą" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Navegador atualizado" + "value" : "Protege a tua caixa de entrada" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Браузер обновлен" + "value" : "Защита для вашей почты" } } } }, - "notification.critical.update" : { - "comment" : "Notification informing user a critical update is required.", + "newTab.setup.Import.action" : { + "comment" : "Action title on the action menu of the Import card of the Set Up section in the home page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kritisches Update erforderlich. Zum Aktualisieren neu starten." + "value" : "Jetzt importieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Critical update required. Restart to update." + "value" : "Import Now" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Se requiere una actualización crítica. Reinicia para actualizar." + "value" : "Importar ahora" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Une mise à jour critique est requise. Redémarrer pour mettre à jour." + "value" : "Importer dès maintenant" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aggiornamento critico obbligatorio. Riavvia per aggiornare." + "value" : "Importa ora" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Kritieke update vereist. Start opnieuw om bij te werken." + "value" : "Nu importeren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wymagana krytyczna aktualizacja. Uruchom ponownie, aby zaktualizować." + "value" : "Importuj teraz" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Atualização crítica obrigatória. Reiniciar para atualizar." + "value" : "Importar agora" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Требуется критическое обновление. Перезапустите приложение." + "value" : "Импортировать" } } } }, - "notification.update.available" : { - "comment" : "Notification informing user the a version of app is available", + "newTab.setup.import.summary" : { + "comment" : "Summary of the Import card of the Set Up section in the home page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neue Version verfügbar. Zum Aktualisieren neu starten." + "value" : "Importiere Lesezeichen, Favoriten und Passwörter aus deinem alten Browser." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "New version available. Restart to update." + "value" : "Import bookmarks, favorites, and passwords from your old browser." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nueva versión disponible. Reinicia para actualizar." + "value" : "Importa marcadores, favoritos y contraseñas desde tu navegador antiguo." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Une nouvelle version est disponible. Redémarrer pour mettre à jour." + "value" : "Importez des signets, des favoris et des mots de passe depuis votre ancien navigateur." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nuova versione disponibile. Riavvia per aggiornare." + "value" : "Importa segnalibri, preferiti e password dal tuo vecchio browser." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuwe versie beschikbaar. Start opnieuw om bij te werken." + "value" : "Bladwijzers, favorieten en wachtwoorden vanuit je vorige browser importeren." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dostępna nowa wersja. Uruchom ponownie, aby zaktualizować." + "value" : "Zaimportuj zakładki, elementy ulubione i hasła ze starej przeglądarki." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nova versão disponível. Reiniciar para atualizar." + "value" : "Importa marcadores, favoritos e palavras-passe do teu navegador antigo." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Доступна новая версия. Перезапустите приложение." + "value" : "Закладки, избранное и пароли можно импортировать из старого браузера." } } } }, - "notnow" : { - "comment" : "Not Now button", + "newTab.setup.import.title" : { + "comment" : "Title of the Import card of the Set Up section in the home page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Jetzt nicht" + "value" : "Bring deine Sachen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Not Now" + "value" : "Bring Your Stuff" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ahora no" + "value" : "Trae tus cosas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Pas maintenant" + "value" : "Emportez tout avec vous" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Non adesso" + "value" : "Importa i tuoi file" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Niet nu" + "value" : "Neem je favorieten mee" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie teraz" + "value" : "Przenieś swoje rzeczy" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Agora não" + "value" : "Traz as tuas coisas" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не сейчас" + "value" : "Все свое с собой" } } } }, - "ok" : { - "comment" : "OK button", + "newTab.setup.remove.item" : { + "comment" : "Action title on the action menu of the set up cards card of the SetUp section in the home page to remove the item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Verwerfen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "OK" + "value" : "Dismiss" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "De acuerdo" + "value" : "Descartar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ok" + "value" : "Ignorer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Ignora" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Negeren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Odrzuć" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Ignorar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Хорошо" + "value" : "Отклонить" } } } }, - "onboarding.addtodock.button" : { - "comment" : "Button label to add application to the macOS system dock", + "newTab.setup.section.title" : { + "comment" : "Title of the setup section in the home page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Im Dock behalten" + "value" : "Nächste Schritte" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Keep in Dock" + "value" : "Next Steps" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mantener en Dock" + "value" : "Próximos pasos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Garder dans le Dock" + "value" : "Étapes suivantes" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Tieni nel Dock" + "value" : "Passaggi successivi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "In Dock bewaren" + "value" : "Volgende stappen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Trzymaj w Docku" + "value" : "Dalsze kroki" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Manter na Dock" + "value" : "Passos seguintes" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Держать на док-панели" + "value" : "Дальнейшие шаги" } } } }, - "onboarding.addtodock.text" : { - "comment" : "Call to action to add the DuckDuckGo app icon to the macOS system dock", + "next" : { + "comment" : "Next button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Eine letzte Sache. Du möchtest DuckDuckGo in deinem Dock haben, damit der Browser immer in Reichweite ist?" + "value" : "Weiter" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "One last thing. Want to keep DuckDuckGo in your Dock so the browser's always within reach?" + "value" : "Next" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Una última cosa. ¿Quieres tener DuckDuckGo en tu Dock para que el navegador esté siempre al alcance de la mano?" + "value" : "Siguiente" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Une dernière chose. Vous voulez garder DuckDuckGo dans votre Dock pour que le navigateur reste à portée de main ?" + "value" : "Suivant" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Un'ultima cosa. Vuoi tenere DuckDuckGo nel tuo dock in modo che il browser sia sempre disponibile?" + "value" : "Successivo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nog een laatste ding. Wil je DuckDuckGo in je Dock houden zodat de browser altijd binnen handbereik is?" + "value" : "Volgende" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ostatnia sprawa. Czy chcesz trzymać przeglądarkę DuckDuckGo w Docku, aby zawsze ją mieć pod ręką?" + "value" : "Dalej" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Só mais uma coisa. Queres ter o navegador DuckDuckGo na tua Dock estar sempre à mão?" + "value" : "Seguinte" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "И кое-что еще... Хотите сохранить DuckDuckGo на док-панели, чтобы наш браузер всегда был под рукой?" + "value" : "Далее" } } } }, - "onboarding.importdata.button" : { - "comment" : "Launch the import data UI", + "no.access.to.downloads.folder.header" : { + "comment" : "Header of the alert dialog warning the user they need to give the browser permission to access the Downloads folder", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Importieren" + "value" : "DuckDuckGo benötigt die Erlaubnis, auf deinen Downloads-Ordner zuzugreifen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Import" + "value" : "DuckDuckGo needs permission to access your Downloads folder" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importar" + "value" : "DuckDuckGo necesita permiso para acceder a tu carpeta de descargas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importer" + "value" : "DuckDuckGo a besoin d'une autorisation pour accéder à votre dossier Téléchargements" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importa" + "value" : "DuckDuckGo ha bisogno dell'autorizzazione per accedere alla tua cartella Download" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Importeren" + "value" : "DuckDuckGo heeft toestemming nodig om toegang te krijgen tot je downloadsmap" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Import" + "value" : "DuckDuckGo potrzebuje pozwolenia na dostęp do folderu Pobrane" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importar" + "value" : "O DuckDuckGo precisa de permissão para aceder à tua pasta Transferências" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Импорт" + "value" : "DuckDuckGo запрашивает доступ к папке «Загрузки»" } } } }, - "onboarding.importdata.text" : { - "comment" : "Call to action to import data from other browsers", + "no.access.to.downloads.folder.legacy" : { + "comment" : "Alert presented to user if the app doesn't have rights to access Downloads folder. This is used for macOS version 12 and below", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zuerst möchte ich dir helfen, deine Lesezeichen 📖 und Passwörter 🔑 aus den weniger privaten Browsern zu importieren." + "value" : "Erlaube den Zugriff in den Einstellungen für Sicherheit und Datenschutz in den Systemeinstellungen." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "First, let me help you import your bookmarks 📖 and passwords 🔑 from those less private browsers." + "value" : "Grant access in Security & Privacy preferences in System Settings." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Primero, déjame ayudarte a importar tus marcadores 📖 y contraseñas 🔑 desde esos navegadores no tan privados." + "value" : "Concede acceso en las preferencias de Seguridad y privacidad en Configuración del sistema." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Tout d'abord, laissez-moi vous aider à importer vos signets 📖 et vos mots de passe 🔑 depuis ces navigateurs moins privés." + "value" : "Accordez l'accès dans les préférences de sécurité et de confidentialité des paramètres système." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Innanzitutto, ti aiuterò a importare segnalibri 📖 e password 🔑 dai browser con meno privacy." + "value" : "Concedi l'accesso alle preferenze di Sicurezza e Privacy nelle Impostazioni di sistema." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Laat me je eerst helpen bij het importeren van je bladwijzers 📖 en wachtwoorden 🔑 vanuit de minder private browsers." + "value" : "Geef toegang in Beveiligings- en privacyvoorkeuren in Systeeminstellingen." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Na początek pozwól, że pomogę Ci zaimportować zakładki 📖 i hasła 🔑 z tych przeglądarek cechujących się mniejszą prywatnością." + "value" : "Przyznaj dostęp w preferencjach dotyczących bezpieczeństwa i prywatności w ustawieniach systemowych." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Primeiro, deixa-me ajudar-te a importar os teus marcadores 📖 e palavras-passe 🔑 desses navegadores menos privados." + "value" : "Concede acesso nas preferências de segurança e privacidade em Definições do sistema." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Прежде всего, давайте импортируем закладки 📖 и пароли 🔑 из браузеров с более низким уровнем защиты данных." + "value" : "Предоставьте доступ в разделе «Безопасность и конфиденциальность» в настройках системы." } } } }, - "onboarding.notnow.button" : { - "comment" : "Skip a step of the onboarding flow", + "no.access.to.downloads.folder.modern" : { + "comment" : "Alert presented to user if the app doesn't have rights to access Downloads folder. This is used for macOS version 13 and above", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Vielleicht später" + "value" : "Erlaube den Zugriff in den Einstellungen für Datenschutz und Sicherheit in den Systemeinstellungen." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Maybe Later" + "value" : "Grant access in Privacy & Security preferences in System Settings." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Quizá más tarde" + "value" : "Concede acceso en las preferencias de Seguridad y privacidad en Configuración del sistema." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Peut-être plus tard" + "value" : "Accordez l'accès dans les préférences de confidentialité et de sécurité des paramètres système." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Forse più tardi" + "value" : "Concedi l'accesso alle preferenze di Privacy e Sicurezza nelle Impostazioni di sistema." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Later misschien" + "value" : "Geef toegang in Privacy en beveiligingsvoorkeuren in Systeeminstellingen." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Może później" + "value" : "Przyznaj dostęp w preferencjach dotyczących prywatności i bezpieczeństwa w ustawieniach systemowych." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Talvez mais tarde" + "value" : "Conceda acesso às preferências de privacidade e segurança em Definições do sistema." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Позже" + "value" : "Предоставьте доступ в разделе «Конфиденциальность и безопасность» в настройках системы." } } } }, - "onboarding.setdefault.button" : { - "comment" : "Launch the set default UI", + "notification.badge.cookiesmanaged" : { + "comment" : "Notification that appears when browser automatically handle cookies", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fangen wir an!" + "value" : "Cookies verwaltet" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Let's Do It!" + "value" : "Cookies Managed" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¡De acuerdo!" + "value" : "Cookies gestionadas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Allons-y !" + "value" : "Cookies gérés" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Facciamolo!" + "value" : "Cookie gestiti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Laten we het doen!" + "value" : "Beheerde cookies" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zróbmy to!" + "value" : "Zarządzane pliki cookie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Vamos lá!" + "value" : "Cookies geridos" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Поехали!" + "value" : "Kуки-файлы выбраны" } } } }, - "onboarding.setdefault.text" : { - "comment" : "Call to action to set the browser as default", + "notification.badge.popuphidden" : { + "comment" : "Notification that appears when browser cosmetically hides a cookie popup", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Als Nächstes solltest du DuckDuckGo als deinen Standardbrowser festlegen, damit du Links immer mit ruhigem Gewissen öffnen kannst." + "value" : "Pop-up ausgeblendet" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Next, try setting DuckDuckGo as your default️ browser, so you can open links with peace of mind, every time." + "value" : "Pop-up Hidden" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "A continuación, prueba a configurar DuckDuckGo como tu navegador predeterminado️, para que puedas abrir los enlaces con tranquilidad en todo momento." + "value" : "Ventana emergente oculta" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Puis essayez de définir DuckDuckGo comme navigateur par défaut afin de pouvoir ouvrir des liens l'esprit tranquille à chaque fois." + "value" : "Fenêtre contextuelle masquée" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Successivamente, prova a impostare DuckDuckGo come browser predefinito️, così potrai aprire i link in tutta tranquillità, ogni volta." + "value" : "Popup nascosto" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Probeer vervolgens DuckDuckGo in te stellen als je standaard️browser, zodat je elke keer met een gerust hart links kunt openen." + "value" : "Pop-up verborgen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Następnie spróbuj ustawić DuckDuckGo jako przeglądarkę domyślną, aby móc spokojnie otwierać każde łącze." + "value" : "Ukryte wyskakujące okienka" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Em seguida, experimenta definir o DuckDuckGo como o teu navegador predefinido️, para que possas abrir links com tranquilidade, sempre." + "value" : "Pop-up ocultado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Затем задайте DuckDuckGo как браузер по умолчанию, чтобы открывать ссылки без каких-либо опасений." + "value" : "Всплывающее окно скрыто" } } } }, - "onboarding.startbrowsing.added-to-dock.text" : { - "comment" : "Call to action to start using the app as a browser", + "notification.browser.downgraded" : { + "comment" : "Notification informing user the app has been downgraded", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Du bist bereit! Du kannst mich jederzeit im Dock antreffen.\nMöchtest du sehen, wie ich dich beschütze? Versuche, eine deiner Lieblingsseiten zu besuchen 👆\n\nBehalte die Adressleiste im Auge. Ich werde Tracker blockieren und die Sicherheit deiner Verbindung verbessern, wenn möglichu{00A0}🔒" + "value" : "Browser herabgestuft" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "You’re all set! You can find me hanging out in the Dock anytime.\n\nWant to see how I protect you? Try visiting one of your favorite sites 👆\n\nKeep watching the address bar as you go. I’ll be blocking trackers and upgrading the security of your connection when possibleu{00A0}🔒" + "value" : "Browser Downgraded" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¡Ya está todo listo! Puedes encontrarme en el Dock en cualquier momento.\n¿Quieres ver cómo te protejo? Prueba a visitar uno de tus sitios favoritos 👆\n\nNo pierdas de vista la barra de direcciones al navegar. Bloquearé los rastreadores y mejoraré la seguridad de tu conexión cuando sea posible{00A0}🔒" + "value" : "Navegador degradado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Tout est prêt ! Vous pouvez me trouver sur le Dock à tout moment.\nVous voulez voir comment je vous protège ? Essayez de visiter l'un de vos sites préférés 👆\n\nContinuez à regarder la barre d'adresse au fur et à mesure. Je bloquerai les traqueurs et mettrai à niveau la sécurité de votre connexion si possible 🔒" + "value" : "Version antérieure du navigateur activée" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Tutto pronto! Puoi trovarmi nel dock in qualsiasi momento.\nVuoi vedere come ti proteggo? Prova a visitare uno dei tuoi siti preferiti 👆\n\nContinua a controllare la barra degli indirizzi mentre esplori. Bloccherò i sistemi di tracciamento e aggiornerò la sicurezza della tua connessione quando possibile{00A0} 🔒" + "value" : "Browser declassato" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Je bent helemaal klaar! Je kunt me altijd vinden in het Dock.\nWil je zien hoe ik je bescherm? Ga eens naar een van je favoriete websites 👆\n\nKijk tijdens het surfen goed naar de adresbalk. Ik blokkeer trackers en werk de beveiliging van je verbinding bij wanneer mogelijk 🔒" + "value" : "Browser gedegradeerd" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wszystko gotowe! W każdej chwili możesz mnie znaleźć w Docku.\nChcesz zobaczyć, jak Cię chronię? Spróbuj odwiedzić jedną z ulubionych stron 👆\n\nW międzyczasie obserwuj pasek adresu. Będę blokować mechanizmy śledzące i w miarę możliwości poprawiać bezpieczeństwo połączenia 🔒" + "value" : "Zmieniono wersję przeglądarki na niższą" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Está tudo pronto! Podes encontrar-me na Dock em qualquer altura.\nQueres ver como te protejo? Experimenta visitar um dos teus sites favoritos 👆\n\nContinua a observar a barra de endereço à medida que vais avançando. Vou bloquear os rastreadores e melhorar a segurança da tua ligação sempre que possível 🔒" + "value" : "Navegador com versão anterior" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Готово! Теперь меня всегда можно найти на док-панели.\nВам интересно, как я защищаю вашу конфиденциальность? Зайдите на свой любимый сайт...👆\n\nИ следите за адресной строкой. По возможности я заблокирую все трекеры и сделаю соединение более безопасным {00A0}🔒" + "value" : "Версия браузера понижена" } } } }, - "onboarding.startbrowsing.text" : { - "comment" : "Call to action to start using the app as a browser", + "notification.browser.updated" : { + "comment" : "Notification informing user the app has been updated", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Du bist bereit!\n\nMöchtest du sehen, wie ich dich beschütze? Versuche, eine deiner Lieblingsseiten zu besuchen 👆\n\nBehalte die Adressleiste im Auge. Ich werde Tracker blockieren und die Sicherheit deiner Verbindung verbessern, wenn möglich 🔒" + "value" : "Browser aktualisiert" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "You’re all set!\n\nWant to see how I protect you? Try visiting one of your favorite sites 👆\n\nKeep watching the address bar as you go. I’ll be blocking trackers and upgrading the security of your connection when possibleu{00A0}🔒" + "value" : "Browser Updated" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¡Ya está todo listo!\n\n¿Quieres ver cómo te protejo? Prueba a visitar uno de tus sitios favoritos 👆\n\nSigue viendo la barra de direcciones sobre la marcha. Bloquearé los rastreadores y mejoraré la seguridad de tu conexión cuando sea posible 🔒" + "value" : "Navegador actualizado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Tout est prêt !\n\nVous voulez voir comment je vous protège ? Essayez de visiter l'un de vos sites préférés 👆\n\n Continuez à regarder la barre d'adresse au fur et à mesure. Je bloquerai les traqueurs et mettrai à niveau la sécurité de votre connexion si possible 🔒" + "value" : "Navigateur mis à jour" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Tutto pronto.\n\nVuoi vedere come ti proteggo? Prova a visitare uno dei tuoi siti preferiti 👆Continua a controllare la barra degli indirizzi mentre esplori. Bloccherò i sistemi di tracciamento e aggiornerò la sicurezza della tua connessione quando possibile 🔒" + "value" : "Browser aggiornato" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Je bent helemaal klaar!\n\nWil je zien hoe ik je bescherm? Ga eens naar een van je favoriete websites 👆 \n\n Kijk tijdens het surfen goed naar de adresbalk. Ik blokkeer trackers en werk de beveiliging van je verbinding bij wanneer mogelijk 🔒" + "value" : "Browser bijgewerkt" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wszystko gotowe!\n\nChcesz zobaczyć, jak Cię chronię? Spróbuj odwiedzić jedną z ulubionych stron 👆\n\nW międzyczasie obserwuj pasek adresu. Będę blokować mechanizmy śledzące i w miarę możliwości poprawiać bezpieczeństwo połączenia 🔒" + "value" : "Zaktualizowano przeglądarkę" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Estás tudo pronto!\n\nQueres ver como te protejo? Experimenta visitar um dos teus sites favoritos 👆\n\nContinua a observar a barra de endereço à medida que vais avançando. Vou bloquear os rastreadores e melhorar a segurança da tua ligação sempre que possível 🔒" + "value" : "Navegador atualizado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Все готово!\n\nХотите увидеть, как я вас защищаю? Зайдите на один из любимых сайтов 👆\n\nСледите за адресной строкой. Я по возможности буду блокировать трекеры и обеспечивать вам более безопасное соединение 🔒" + "value" : "Браузер обновлен" } } } }, - "onboarding.welcome.button" : { - "comment" : "Start the onboarding flow", + "notification.critical.update" : { + "comment" : "Notification informing user a critical update is required.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Los geht's!" + "value" : "Kritisches Update erforderlich. Zum Aktualisieren neu starten." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Get Started" + "value" : "Critical update required. Restart to update." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Empezar" + "value" : "Se requiere una actualización crítica. Reinicia para actualizar." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Commencer" + "value" : "Une mise à jour critique est requise. Redémarrer pour mettre à jour." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Iniziamo" + "value" : "Aggiornamento critico obbligatorio. Riavvia per aggiornare." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Aan de slag" + "value" : "Kritieke update vereist. Start opnieuw om bij te werken." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Rozpocznij" + "value" : "Wymagana krytyczna aktualizacja. Uruchom ponownie, aby zaktualizować." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Comece" + "value" : "Atualização crítica obrigatória. Reiniciar para atualizar." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Приступим!" + "value" : "Требуется критическое обновление. Перезапустите приложение." } } } }, - "onboarding.welcome.text" : { - "comment" : "Detailed welcome to the app text", + "notification.update.available" : { + "comment" : "Notification informing user the a version of app is available", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Du bist es leid, online getrackt zu werden? Dann bist du hier genau richtig 👍\n\nIch helfe dir, deine Privatsphäre zu wahren, während du im Internet suchst und surfst. Weg mit den Trackern!" + "value" : "Neue Version verfügbar. Zum Aktualisieren neu starten." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Tired of being tracked online? You've come to the right place 👍\n\nI'll help you stay private️ as you search and browse the web. Trackers be gone!" + "value" : "New version available. Restart to update." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Cansado de que te rastreen en línea? Has venido al lugar adecuado 👍\n\nTe ayudaré a mantener tu privacidad mientras buscas y navegas por la web. Rastreadores, ¡fuera!" + "value" : "Nueva versión disponible. Reinicia para actualizar." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Vous en avez assez d'être suivi(e) en ligne ? Alors vous êtes au bon endroit 👍\n\nJe vous aiderai à préserver la confidentialité de vos recherches et de votre navigation sur le Web. Adieu les traqueurs !" + "value" : "Une nouvelle version est disponible. Redémarrer pour mettre à jour." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Non vuoi che le tue attività online vengano tracciate? Abbiamo la soluzione 👍\n\nTi aiuterò a rendere private le tue ricerche e la navigazione sul web. Sistemi di tracciamento, addio!" + "value" : "Nuova versione disponibile. Riavvia per aggiornare." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ben je het zat om online gevolgd te worden? Je bent bij ons aan het juiste adres 👍\n\n Ik help je privacy te beschermen terwijl je op het internet zoekt en surft. Trackers zijn voorgoed verleden tijd!" + "value" : "Nieuwe versie beschikbaar. Start opnieuw om bij te werken." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Masz dość śledzenia w Internecie? Jesteś we właściwym miejscu 👍\n\nPomogę Ci w zachowaniu prywatności podczas wyszukiwania i przeglądania stron internetowych. Precz z mechanizmami śledzącymi!" + "value" : "Dostępna nowa wersja. Uruchom ponownie, aby zaktualizować." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Cansado de seres rastreado online? Vieste ao sítio certo 👍\n\n Vou ajudar-te a manteres a tua privacidade enquanto fazes pesquisas e navegas na Internet. Até à vista rastreadores!" + "value" : "Nova versão disponível. Reiniciar para atualizar." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Надоели рекламные трекеры? Тогда вы обратились по адресу! 👍\n\n Я помогу вам скрыть свои данные от посторонних глаз во время поиска и посещения сайтов. Прощайте, трекеры!" + "value" : "Доступна новая версия. Перезапустите приложение." } } } }, - "onboarding.welcome.title" : { - "comment" : "General welcome to the app title", + "notnow" : { + "comment" : "Not Now button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Willkommen bei DuckDuckGo!" + "value" : "Jetzt nicht" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Welcome to DuckDuckGo!" + "value" : "Not Now" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¡Bienvenido a DuckDuckGo!" + "value" : "Ahora no" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Bienvenue sur DuckDuckGo !" + "value" : "Pas maintenant" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo ti dà il benvenuto!" + "value" : "Non adesso" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Welkom bij DuckDuckGo!" + "value" : "Niet nu" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Witamy w DuckDuckGo!" + "value" : "Nie teraz" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Damos-lhe as boas-vindas ao DuckDuckGo!" + "value" : "Agora não" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Добро пожаловать в DuckDuckGo!" + "value" : "Не сейчас" } } } }, - "Only Show on New Tab" : { - "comment" : "Preference for only showing the bookmarks bar on new tab", + "ok" : { + "comment" : "OK button", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nur auf neuem Tab anzeigen" + "value" : "OK" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "OK" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar solo en una pestaña nueva" + "value" : "De acuerdo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher uniquement sur un nouvel onglet" + "value" : "Ok" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra solo la nuova scheda" + "value" : "OK" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alleen tonen op nieuw tabblad" + "value" : "OK" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż tylko na nowej karcie" + "value" : "OK" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar apenas no novo separador" + "value" : "OK" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показывать только в новой вкладке" + "value" : "Хорошо" } } } }, - "open" : { - "comment" : "Open button", + "onboarding.addtodock.button" : { + "comment" : "Button label to add application to the macOS system dock", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Öffnen" + "value" : "Im Dock behalten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open" + "value" : "Keep in Dock" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir" + "value" : "Mantener en Dock" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir" + "value" : "Garder dans le Dock" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri" + "value" : "Tieni nel Dock" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Openen" + "value" : "In Dock bewaren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz" + "value" : "Trzymaj w Docku" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir" + "value" : "Manter na Dock" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть" + "value" : "Держать на док-панели" } } } }, - "open.all.in.new.tabs" : { - "comment" : "Menu item that opens all the bookmarks in a folder to new tabs", + "onboarding.addtodock.text" : { + "comment" : "Call to action to add the DuckDuckGo app icon to the macOS system dock", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alle in neuen Tabs öffnen" + "value" : "Eine letzte Sache. Du möchtest DuckDuckGo in deinem Dock haben, damit der Browser immer in Reichweite ist?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open All in New Tabs" + "value" : "One last thing. Want to keep DuckDuckGo in your Dock so the browser's always within reach?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir todo en pestañas nuevas" + "value" : "Una última cosa. ¿Quieres tener DuckDuckGo en tu Dock para que el navegador esté siempre al alcance de la mano?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Tout ouvrir dans de nouveaux onglets" + "value" : "Une dernière chose. Vous voulez garder DuckDuckGo dans votre Dock pour que le navigateur reste à portée de main ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri tutto in nuove schede" + "value" : "Un'ultima cosa. Vuoi tenere DuckDuckGo nel tuo dock in modo che il browser sia sempre disponibile?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alles openen in nieuwe tabbladen" + "value" : "Nog een laatste ding. Wil je DuckDuckGo in je Dock houden zodat de browser altijd binnen handbereik is?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz wszystko w nowych kartach" + "value" : "Ostatnia sprawa. Czy chcesz trzymać przeglądarkę DuckDuckGo w Docku, aby zawsze ją mieć pod ręką?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir tudo em novos separadores" + "value" : "Só mais uma coisa. Queres ter o navegador DuckDuckGo na tua Dock estar sempre à mão?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть все в новых вкладках" + "value" : "И кое-что еще... Хотите сохранить DuckDuckGo на док-панели, чтобы наш браузер всегда был под рукой?" } } } }, - "open.all.tabs.in.new.window" : { - "comment" : "Menu item that opens all the bookmarks in a folder in a new window", + "onboarding.importdata.button" : { + "comment" : "Launch the import data UI", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alle in neuem Fenster öffnen" + "value" : "Importieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open All in New Window" + "value" : "Import" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir todo en una ventana nueva" + "value" : "Importar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Tout ouvrir dans une nouvelle fenêtre" + "value" : "Importer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri tutto in una nuova finestra" + "value" : "Importa" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alles openen in een nieuw venster" + "value" : "Importeren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz wszystko w nowym oknie" + "value" : "Import" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir tudo numa nova janela" + "value" : "Importar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть все в новом окне" + "value" : "Импорт" } } } }, - "open.bitwarden" : { - "comment" : "Button to open Bitwarden app", + "onboarding.importdata.text" : { + "comment" : "Call to action to import data from other browsers", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden öffnen" + "value" : "Zuerst möchte ich dir helfen, deine Lesezeichen 📖 und Passwörter 🔑 aus den weniger privaten Browsern zu importieren." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open Bitwarden" + "value" : "First, let me help you import your bookmarks 📖 and passwords 🔑 from those less private browsers." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir bitwarden" + "value" : "Primero, déjame ayudarte a importar tus marcadores 📖 y contraseñas 🔑 desde esos navegadores no tan privados." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir Bitwarden" + "value" : "Tout d'abord, laissez-moi vous aider à importer vos signets 📖 et vos mots de passe 🔑 depuis ces navigateurs moins privés." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri Bitwarden" + "value" : "Innanzitutto, ti aiuterò a importare segnalibri 📖 e password 🔑 dai browser con meno privacy." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden openen" + "value" : "Laat me je eerst helpen bij het importeren van je bladwijzers 📖 en wachtwoorden 🔑 vanuit de minder private browsers." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz Bitwarden" + "value" : "Na początek pozwól, że pomogę Ci zaimportować zakładki 📖 i hasła 🔑 z tych przeglądarek cechujących się mniejszą prywatnością." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir o Bitwarden" + "value" : "Primeiro, deixa-me ajudar-te a importar os teus marcadores 📖 e palavras-passe 🔑 desses navegadores menos privados." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть Bitwarden" + "value" : "Прежде всего, давайте импортируем закладки 📖 и пароли 🔑 из браузеров с более низким уровнем защиты данных." } } } }, - "open.bitwarden.and.log.in.or.unlock" : { - "comment" : "Setup of the integration with Bitwarden app", + "onboarding.notnow.button" : { + "comment" : "Skip a step of the onboarding flow", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Öffne Bitwarden und logge dich ein oder entsperre deinen Tresor." + "value" : "Vielleicht später" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open Bitwarden and Log in or Unlock your vault." + "value" : "Maybe Later" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abre Bitwarden e inicia sesión o desbloquea tu caja fuerte." + "value" : "Quizá más tarde" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrez Bitwarden et connectez-vous ou déverrouillez votre coffre-fort." + "value" : "Peut-être plus tard" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri Bitwarden e accedi o sblocca la cassaforte." + "value" : "Forse più tardi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Open Bitwarden en log in of ontgrendel je kluis." + "value" : "Later misschien" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz aplikację Bitwarden i zaloguj się lub odblokuj swój sejf." + "value" : "Może później" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abre o Bitwarden e inicia sessão ou desbloqueia o teu cofre." + "value" : "Talvez mais tarde" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Откройте приложение Bitwarden и войдите в систему либо разблокируйте свое хранилище." + "value" : "Позже" } } } }, - "open.externally.failed" : { - "comment" : "’Link’ is link on a website, it couldn't be opened due to the required app not being found", + "onboarding.setdefault.button" : { + "comment" : "Launch the set default UI", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Die zum Öffnen dieses Links erforderliche App wurde nicht gefunden" + "value" : "Fangen wir an!" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "The app required to open that link can’t be found" + "value" : "Let's Do It!" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No se puede encontrar la aplicación necesaria para abrir ese enlace" + "value" : "¡De acuerdo!" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "L'application nécessaire pour ouvrir ce lien est introuvable" + "value" : "Allons-y !" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Impossibile trovare l'app necessaria per aprire il link" + "value" : "Facciamolo!" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "De app die nodig is om die link te openen, is niet gevonden" + "value" : "Laten we het doen!" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie można znaleźć aplikacji wymaganej do otwarcia tego linku" + "value" : "Zróbmy to!" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Não encontramos a aplicação necessária para abrir esse link" + "value" : "Vamos lá!" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Приложение для открытия этой ссылки не найдено" + "value" : "Поехали!" } } } }, - "open.image.in.new.burner.tab" : { - "comment" : "Context menu item", + "onboarding.setdefault.text" : { + "comment" : "Call to action to set the browser as default", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bild in neuem Fire Tab öffnen" + "value" : "Als Nächstes solltest du DuckDuckGo als deinen Standardbrowser festlegen, damit du Links immer mit ruhigem Gewissen öffnen kannst." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open Image in New Fire Tab" + "value" : "Next, try setting DuckDuckGo as your default️ browser, so you can open links with peace of mind, every time." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir imagen en una nueva Fire tab" + "value" : "A continuación, prueba a configurar DuckDuckGo como tu navegador predeterminado️, para que puedas abrir los enlaces con tranquilidad en todo momento." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir l'image dans le nouveau Fire Tab" + "value" : "Puis essayez de définir DuckDuckGo comme navigateur par défaut afin de pouvoir ouvrir des liens l'esprit tranquille à chaque fois." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri l'immagine in una nuova scheda Fire" + "value" : "Successivamente, prova a impostare DuckDuckGo come browser predefinito️, così potrai aprire i link in tutta tranquillità, ogni volta." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Afbeelding openen in nieuw Fire-tabblad" + "value" : "Probeer vervolgens DuckDuckGo in te stellen als je standaard️browser, zodat je elke keer met een gerust hart links kunt openen." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz obraz w nowej karcie Fire" + "value" : "Następnie spróbuj ustawić DuckDuckGo jako przeglądarkę domyślną, aby móc spokojnie otwierać każde łącze." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir imagem num novo separador Fire" + "value" : "Em seguida, experimenta definir o DuckDuckGo como o teu navegador predefinido️, para que possas abrir links com tranquilidade, sempre." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть изображение в новой Fire-вкладке" + "value" : "Затем задайте DuckDuckGo как браузер по умолчанию, чтобы открывать ссылки без каких-либо опасений." } } } }, - "open.image.in.new.tab" : { - "comment" : "Context menu item", + "onboarding.startbrowsing.added-to-dock.text" : { + "comment" : "Call to action to start using the app as a browser", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bild in neuem Tab öffnen" + "value" : "Du bist bereit! Du kannst mich jederzeit im Dock antreffen.\nMöchtest du sehen, wie ich dich beschütze? Versuche, eine deiner Lieblingsseiten zu besuchen 👆\n\nBehalte die Adressleiste im Auge. Ich werde Tracker blockieren und die Sicherheit deiner Verbindung verbessern, wenn möglichu{00A0}🔒" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open Image in New Tab" + "value" : "You’re all set! You can find me hanging out in the Dock anytime.\n\nWant to see how I protect you? Try visiting one of your favorite sites 👆\n\nKeep watching the address bar as you go. I’ll be blocking trackers and upgrading the security of your connection when possibleu{00A0}🔒" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir imagen en una nueva pestaña" + "value" : "¡Ya está todo listo! Puedes encontrarme en el Dock en cualquier momento.\n¿Quieres ver cómo te protejo? Prueba a visitar uno de tus sitios favoritos 👆\n\nNo pierdas de vista la barra de direcciones al navegar. Bloquearé los rastreadores y mejoraré la seguridad de tu conexión cuando sea posible{00A0}🔒" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir l'image dans un nouvel onglet" + "value" : "Tout est prêt ! Vous pouvez me trouver sur le Dock à tout moment.\nVous voulez voir comment je vous protège ? Essayez de visiter l'un de vos sites préférés 👆\n\nContinuez à regarder la barre d'adresse au fur et à mesure. Je bloquerai les traqueurs et mettrai à niveau la sécurité de votre connexion si possible 🔒" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri l'immagine in una nuova scheda" + "value" : "Tutto pronto! Puoi trovarmi nel dock in qualsiasi momento.\nVuoi vedere come ti proteggo? Prova a visitare uno dei tuoi siti preferiti 👆\n\nContinua a controllare la barra degli indirizzi mentre esplori. Bloccherò i sistemi di tracciamento e aggiornerò la sicurezza della tua connessione quando possibile{00A0} 🔒" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Afbeelding openen in nieuw tabblad" + "value" : "Je bent helemaal klaar! Je kunt me altijd vinden in het Dock.\nWil je zien hoe ik je bescherm? Ga eens naar een van je favoriete websites 👆\n\nKijk tijdens het surfen goed naar de adresbalk. Ik blokkeer trackers en werk de beveiliging van je verbinding bij wanneer mogelijk 🔒" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz obraz w nowej karcie" + "value" : "Wszystko gotowe! W każdej chwili możesz mnie znaleźć w Docku.\nChcesz zobaczyć, jak Cię chronię? Spróbuj odwiedzić jedną z ulubionych stron 👆\n\nW międzyczasie obserwuj pasek adresu. Będę blokować mechanizmy śledzące i w miarę możliwości poprawiać bezpieczeństwo połączenia 🔒" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir imagem num novo separador" + "value" : "Está tudo pronto! Podes encontrar-me na Dock em qualquer altura.\nQueres ver como te protejo? Experimenta visitar um dos teus sites favoritos 👆\n\nContinua a observar a barra de endereço à medida que vais avançando. Vou bloquear os rastreadores e melhorar a segurança da tua ligação sempre que possível 🔒" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть изображение в новой вкладке" + "value" : "Готово! Теперь меня всегда можно найти на док-панели.\nВам интересно, как я защищаю вашу конфиденциальность? Зайдите на свой любимый сайт...👆\n\nИ следите за адресной строкой. По возможности я заблокирую все трекеры и сделаю соединение более безопасным {00A0}🔒" } } } }, - "open.in" : { - "comment" : "Opening an entity in other application", + "onboarding.startbrowsing.text" : { + "comment" : "Call to action to start using the app as a browser", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "In %@ öffnen" + "value" : "Du bist bereit!\n\nMöchtest du sehen, wie ich dich beschütze? Versuche, eine deiner Lieblingsseiten zu besuchen 👆\n\nBehalte die Adressleiste im Auge. Ich werde Tracker blockieren und die Sicherheit deiner Verbindung verbessern, wenn möglich 🔒" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open in %@" + "value" : "You’re all set!\n\nWant to see how I protect you? Try visiting one of your favorite sites 👆\n\nKeep watching the address bar as you go. I’ll be blocking trackers and upgrading the security of your connection when possibleu{00A0}🔒" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir en %@" + "value" : "¡Ya está todo listo!\n\n¿Quieres ver cómo te protejo? Prueba a visitar uno de tus sitios favoritos 👆\n\nSigue viendo la barra de direcciones sobre la marcha. Bloquearé los rastreadores y mejoraré la seguridad de tu conexión cuando sea posible 🔒" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir dans % @" + "value" : "Tout est prêt !\n\nVous voulez voir comment je vous protège ? Essayez de visiter l'un de vos sites préférés 👆\n\n Continuez à regarder la barre d'adresse au fur et à mesure. Je bloquerai les traqueurs et mettrai à niveau la sécurité de votre connexion si possible 🔒" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri in % @" + "value" : "Tutto pronto.\n\nVuoi vedere come ti proteggo? Prova a visitare uno dei tuoi siti preferiti 👆Continua a controllare la barra degli indirizzi mentre esplori. Bloccherò i sistemi di tracciamento e aggiornerò la sicurezza della tua connessione quando possibile 🔒" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Openen in %@" + "value" : "Je bent helemaal klaar!\n\nWil je zien hoe ik je bescherm? Ga eens naar een van je favoriete websites 👆 \n\n Kijk tijdens het surfen goed naar de adresbalk. Ik blokkeer trackers en werk de beveiliging van je verbinding bij wanneer mogelijk 🔒" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz w aplikacji %@" + "value" : "Wszystko gotowe!\n\nChcesz zobaczyć, jak Cię chronię? Spróbuj odwiedzić jedną z ulubionych stron 👆\n\nW międzyczasie obserwuj pasek adresu. Będę blokować mechanizmy śledzące i w miarę możliwości poprawiać bezpieczeństwo połączenia 🔒" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir em %@" + "value" : "Estás tudo pronto!\n\nQueres ver como te protejo? Experimenta visitar um dos teus sites favoritos 👆\n\nContinua a observar a barra de endereço à medida que vais avançando. Vou bloquear os rastreadores e melhorar a segurança da tua ligação sempre que possível 🔒" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть в %@" + "value" : "Все готово!\n\nХотите увидеть, как я вас защищаю? Зайдите на один из любимых сайтов 👆\n\nСледите за адресной строкой. Я по возможности буду блокировать трекеры и обеспечивать вам более безопасное соединение 🔒" } } } }, - "open.in.new.tab" : { - "comment" : "Menu item that opens the link in a new tab", + "onboarding.welcome.button" : { + "comment" : "Start the onboarding flow", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "In neuem Tab öffnen" + "value" : "Los geht's!" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open in New Tab" + "value" : "Get Started" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir en una nueva pestaña" + "value" : "Empezar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir dans un nouvel onglet" + "value" : "Commencer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri in una nuova scheda" + "value" : "Iniziamo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Openen in nieuw tabblad" + "value" : "Aan de slag" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz w nowej karcie" + "value" : "Rozpocznij" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir em novo separador" + "value" : "Comece" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть в новой вкладке" + "value" : "Приступим!" } } } }, - "open.in.new.window" : { - "comment" : "Menu item that opens the link in a new window", + "onboarding.welcome.text" : { + "comment" : "Detailed welcome to the app text", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "In neuem Fenster öffnen" + "value" : "Du bist es leid, online getrackt zu werden? Dann bist du hier genau richtig 👍\n\nIch helfe dir, deine Privatsphäre zu wahren, während du im Internet suchst und surfst. Weg mit den Trackern!" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open in New Window" + "value" : "Tired of being tracked online? You've come to the right place 👍\n\nI'll help you stay private️ as you search and browse the web. Trackers be gone!" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir en Nueva ventana" + "value" : "¿Cansado de que te rastreen en línea? Has venido al lugar adecuado 👍\n\nTe ayudaré a mantener tu privacidad mientras buscas y navegas por la web. Rastreadores, ¡fuera!" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir dans une nouvelle fenêtre" + "value" : "Vous en avez assez d'être suivi(e) en ligne ? Alors vous êtes au bon endroit 👍\n\nJe vous aiderai à préserver la confidentialité de vos recherches et de votre navigation sur le Web. Adieu les traqueurs !" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri in una nuova finestra" + "value" : "Non vuoi che le tue attività online vengano tracciate? Abbiamo la soluzione 👍\n\nTi aiuterò a rendere private le tue ricerche e la navigazione sul web. Sistemi di tracciamento, addio!" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Openen in nieuw venster" + "value" : "Ben je het zat om online gevolgd te worden? Je bent bij ons aan het juiste adres 👍\n\n Ik help je privacy te beschermen terwijl je op het internet zoekt en surft. Trackers zijn voorgoed verleden tijd!" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz w nowym oknie" + "value" : "Masz dość śledzenia w Internecie? Jesteś we właściwym miejscu 👍\n\nPomogę Ci w zachowaniu prywatności podczas wyszukiwania i przeglądania stron internetowych. Precz z mechanizmami śledzącymi!" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir numa nova janela" + "value" : "Cansado de seres rastreado online? Vieste ao sítio certo 👍\n\n Vou ajudar-te a manteres a tua privacidade enquanto fazes pesquisas e navegas na Internet. Até à vista rastreadores!" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть в новом окне" + "value" : "Надоели рекламные трекеры? Тогда вы обратились по адресу! 👍\n\n Я помогу вам скрыть свои данные от посторонних глаз во время поиска и посещения сайтов. Прощайте, трекеры!" } } } }, - "open.link.in.new.burner.tab" : { - "comment" : "Context menu item", + "onboarding.welcome.title" : { + "comment" : "General welcome to the app title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Link in neuem Fire Tab öffnen" + "value" : "Willkommen bei DuckDuckGo!" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open Link in New Fire Tab" + "value" : "Welcome to DuckDuckGo!" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir enlace en una nueva Fire tab" + "value" : "¡Bienvenido a DuckDuckGo!" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir le lien dans le nouveau Fire Tab" + "value" : "Bienvenue sur DuckDuckGo !" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri il link in una nuova scheda Fire" + "value" : "DuckDuckGo ti dà il benvenuto!" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Link in nieuw Fire-tabblad openen" + "value" : "Welkom bij DuckDuckGo!" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz łącze w nowej karcie Fire" + "value" : "Witamy w DuckDuckGo!" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir link num novo separador Fire" + "value" : "Damos-lhe as boas-vindas ao DuckDuckGo!" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть ссылку в новой Fire-вкладке" + "value" : "Добро пожаловать в DuckDuckGo!" } } } }, - "open.link.in.new.tab" : { - "comment" : "Context menu item", - "extractionState" : "extracted_with_value", + "Only Show on New Tab" : { + "comment" : "Preference for only showing the bookmarks bar on new tab", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Link in neuem Tab öffnen" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Open Link in New Tab" + "value" : "Nur auf neuem Tab anzeigen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir enlace en nueva pestaña" + "value" : "Mostrar solo en una pestaña nueva" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir le lien dans un nouvel onglet" + "value" : "Afficher uniquement sur un nouvel onglet" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri link in una nuova scheda" + "value" : "Mostra solo la nuova scheda" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Link openen in een nieuw tabblad" + "value" : "Alleen tonen op nieuw tabblad" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz łącze w nowej karcie" + "value" : "Pokaż tylko na nowej karcie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir ligação num separador novo" + "value" : "Mostrar apenas no novo separador" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть ссылку в новой вкладке" + "value" : "Показывать только в новой вкладке" } } } }, - "open.preferences" : { - "comment" : "Open System Preferences (to re-enable permission for the App) (up to and including macOS 12", + "open" : { + "comment" : "Open button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Systemeinstellungen öffnen" + "value" : "Öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open System Preferences" + "value" : "Open" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir preferencias del sistema" + "value" : "Abrir" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir les préférences système" + "value" : "Ouvrir" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri Preferenze di Sistema" + "value" : "Apri" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Systeemvoorkeuren openen" + "value" : "Openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz preferencje systemowe" + "value" : "Otwórz" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir Preferências do sistema" + "value" : "Abrir" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть настройки системы" + "value" : "Открыть" } } } }, - "open.settings" : { - "comment" : "This string represents a prompt or button label prompting the user to open system settings", + "open.all.in.new.tabs" : { + "comment" : "Menu item that opens all the bookmarks in a folder to new tabs", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Systemeinstellungen öffnen …" + "value" : "Alle in neuen Tabs öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open System Settings…" + "value" : "Open All in New Tabs" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir configuración del sistema" + "value" : "Abrir todo en pestañas nuevas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir les paramètres système…" + "value" : "Tout ouvrir dans de nouveaux onglets" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri Impostazioni di Sistema..." + "value" : "Apri tutto in nuove schede" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Systeeminstellingen openen ..." + "value" : "Alles openen in nieuwe tabbladen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz ustawienia systemowe…" + "value" : "Otwórz wszystko w nowych kartach" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir Definições do sistema…" + "value" : "Abrir tudo em novos separadores" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть настройки системы…" + "value" : "Открыть все в новых вкладках" } } } }, - "options.menu.fireproof-site" : { - "comment" : "Context menu item", + "open.all.tabs.in.new.window" : { + "comment" : "Menu item that opens all the bookmarks in a folder in a new window", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Diese Website feuerfest machen" + "value" : "Alle in neuem Fenster öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Fireproof This Site" + "value" : "Open All in New Window" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Activar a prueba de fuego para este sitio" + "value" : "Abrir todo en una ventana nueva" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Activer le mode coupe-feu pour ce site" + "value" : "Tout ouvrir dans une nouvelle fenêtre" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Proteggi questo sito dal fuoco" + "value" : "Apri tutto in una nuova finestra" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Deze website brandveilig maken" + "value" : "Alles openen in een nieuw venster" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zabezpiecz tę witrynę" + "value" : "Otwórz wszystko w nowym oknie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Coloque uma barreira de segurança neste site" + "value" : "Abrir tudo numa nova janela" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сделать огнеупорным" + "value" : "Открыть все в новом окне" } } } }, - "options.menu.move.tab.to.new.window" : { - "comment" : "Context menu item", + "open.bitwarden" : { + "comment" : "Button to open Bitwarden app", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Tab in neues Fenster verschieben" + "value" : "Bitwarden öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Move Tab to New Window" + "value" : "Open Bitwarden" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mover pestaña a una ventana nueva" + "value" : "Abrir bitwarden" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Déplacer l'onglet vers une nouvelle fenêtre" + "value" : "Ouvrir Bitwarden" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Sposta scheda in una nuova finestra" + "value" : "Apri Bitwarden" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Tabblad naar nieuw venster verplaatsen" + "value" : "Bitwarden openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przenieś kartę do nowego okna" + "value" : "Otwórz Bitwarden" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mover separador para nova janela" + "value" : "Abrir o Bitwarden" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Перенести вкладку в новое окно" + "value" : "Открыть Bitwarden" } } } }, - "options.menu.remove-fireproofing" : { - "comment" : "Context menu item", + "open.bitwarden.and.log.in.or.unlock" : { + "comment" : "Setup of the integration with Bitwarden app", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Feuerfest-Einstellung deaktivieren" + "value" : "Öffne Bitwarden und logge dich ein oder entsperre deinen Tresor." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Remove Fireproofing" + "value" : "Open Bitwarden and Log in or Unlock your vault." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Desactivar a prueba de fuego" + "value" : "Abre Bitwarden e inicia sesión o desbloquea tu caja fuerte." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Désactiver le mode coupe-feu" + "value" : "Ouvrez Bitwarden et connectez-vous ou déverrouillez votre coffre-fort." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rimuovi protezione dal fuoco" + "value" : "Apri Bitwarden e accedi o sblocca la cassaforte." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Brandveiligheid uitschakelen" + "value" : "Open Bitwarden en log in of ontgrendel je kluis." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usuń zabezpieczenie" + "value" : "Otwórz aplikację Bitwarden i zaloguj się lub odblokuj swój sejf." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Remover barreira de segurança" + "value" : "Abre o Bitwarden e inicia sessão ou desbloqueia o teu cofre." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Снять огнеупорность" + "value" : "Откройте приложение Bitwarden и войдите в систему либо разблокируйте свое хранилище." } } } }, - "page.crash.header" : { - "comment" : "Error page heading text shown when a Web Page process had crashed", + "open.externally.failed" : { + "comment" : "’Link’ is link on a website, it couldn't be opened due to the required app not being found", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Diese Webseite ist abgestürzt." + "value" : "Die zum Öffnen dieses Links erforderliche App wurde nicht gefunden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "This webpage has crashed." + "value" : "The app required to open that link can’t be found" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Esta página web se ha caído." + "value" : "No se puede encontrar la aplicación necesaria para abrir ese enlace" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Cette page Web a planté." + "value" : "L'application nécessaire pour ouvrir ce lien est introuvable" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Questa pagina web si è bloccata." + "value" : "Impossibile trovare l'app necessaria per aprire il link" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Deze webpagina is gecrasht." + "value" : "De app die nodig is om die link te openen, is niet gevonden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ta strona uległa awarii." + "value" : "Nie można znaleźć aplikacji wymaganej do otwarcia tego linku" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Esta página falhou." + "value" : "Não encontramos a aplicação necessária para abrir esse link" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "На этой странице произошел сбой." + "value" : "Приложение для открытия этой ссылки не найдено" } } } }, - "page.crash.message" : { - "comment" : "Error page message text shown when a Web Page process had crashed", + "open.image.in.new.burner.tab" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Versuche, die Seite neu zu laden, oder versuche es später erneut." + "value" : "Bild in neuem Fire Tab öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Try reloading the page or come back later." + "value" : "Open Image in New Fire Tab" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Intenta volver a cargar la página o vuelve más tarde." + "value" : "Abrir imagen en una nueva Fire tab" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Essayez de recharger la page ou revenez plus tard." + "value" : "Ouvrir l'image dans le nouveau Fire Tab" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Prova a ricaricare la pagina o torna più tardi." + "value" : "Apri l'immagine in una nuova scheda Fire" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Probeer de pagina opnieuw te laden of kom later terug." + "value" : "Afbeelding openen in nieuw Fire-tabblad" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Spróbuj ponownie załadować stronę lub wróć później." + "value" : "Otwórz obraz w nowej karcie Fire" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Experimenta recarregar a página ou voltar mais tarde." + "value" : "Abrir imagem num novo separador Fire" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Перезагрузите ее или попробуйте вернуться позже." + "value" : "Открыть изображение в новой Fire-вкладке" } } } }, - "page.error.header" : { - "comment" : "Error page heading text", + "open.image.in.new.tab" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo kann diese Seite nicht laden." + "value" : "Bild in neuem Tab öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo can’t load this page." + "value" : "Open Image in New Tab" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo no puede cargar esta página." + "value" : "Abrir imagen en una nueva pestaña" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo ne peut pas charger cette page." + "value" : "Ouvrir l'image dans un nouvel onglet" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo non riesce a caricare questa pagina." + "value" : "Apri l'immagine in una nuova scheda" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo kan deze pagina niet laden." + "value" : "Afbeelding openen in nieuw tabblad" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo nie może wczytać tej strony." + "value" : "Otwórz obraz w nowej karcie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O DuckDuckGo não consegue carregar esta página." + "value" : "Abrir imagem num novo separador" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo не удалось загрузить эту страницу." + "value" : "Открыть изображение в новой вкладке" } } } }, - "passsword.management" : { - "comment" : "Used as title for password management user interface", - "extractionState" : "stale", + "open.in" : { + "comment" : "Opening an entity in other application", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Autovervollständigen" + "value" : "In %@ öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Autofill" + "value" : "Open in %@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Autocompletar" + "value" : "Abrir en %@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Saisie automatique" + "value" : "Ouvrir dans % @" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Compilazione automatica" + "value" : "Apri in % @" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Automatisch invullen" + "value" : "Openen in %@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Autouzupełnianie" + "value" : "Otwórz w aplikacji %@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Preenchimento automático" + "value" : "Abrir em %@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Автозаполнение" + "value" : "Открыть в %@" } } } }, - "passsword.management.all-items" : { - "comment" : "Used as title for the Autofill All Items option", + "open.in.new.tab" : { + "comment" : "Menu item that opens the link in a new tab", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alle Elemente" + "value" : "In neuem Tab öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "All Items" + "value" : "Open in New Tab" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Todos los elementos" + "value" : "Abrir en una nueva pestaña" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Tous les éléments" + "value" : "Ouvrir dans un nouvel onglet" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Tutti gli elementi" + "value" : "Apri in una nuova scheda" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alle artikelen" + "value" : "Openen in nieuw tabblad" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wszystkie elementy" + "value" : "Otwórz w nowej karcie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Todos os elementos" + "value" : "Abrir em novo separador" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Все типы данных" + "value" : "Открыть в новой вкладке" } } } }, - "passsword.management.credit-cards" : { - "comment" : "Used as title for the Autofill Credit Cards option", + "open.in.new.window" : { + "comment" : "Menu item that opens the link in a new window", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kreditkarten" + "value" : "In neuem Fenster öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Credit Cards" + "value" : "Open in New Window" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Tarjetas de crédito" + "value" : "Abrir en Nueva ventana" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Cartes bancaires" + "value" : "Ouvrir dans une nouvelle fenêtre" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Carte di credito" + "value" : "Apri in una nuova finestra" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Creditcards" + "value" : "Openen in nieuw venster" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Karty kredytowe" + "value" : "Otwórz w nowym oknie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Cartões de crédito" + "value" : "Abrir numa nova janela" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Платежные карты" + "value" : "Открыть в новом окне" } } } }, - "passsword.management.identities" : { - "comment" : "Used as title for the Autofill Identities option", + "open.link.in.new.burner.tab" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Identitäten" + "value" : "Link in neuem Fire Tab öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Identities" + "value" : "Open Link in New Fire Tab" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Identidades" + "value" : "Abrir enlace en una nueva Fire tab" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Identités" + "value" : "Ouvrir le lien dans le nouveau Fire Tab" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Identità" + "value" : "Apri il link in una nuova scheda Fire" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Identiteiten" + "value" : "Link in nieuw Fire-tabblad openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Tożsamości" + "value" : "Otwórz łącze w nowej karcie Fire" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Identidades" + "value" : "Abrir link num novo separador Fire" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Учетные данные" + "value" : "Открыть ссылку в новой Fire-вкладке" } } } }, - "passsword.management.lock" : { - "comment" : "Lock Logins Vault menu", + "open.link.in.new.tab" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sperren" + "value" : "Link in neuem Tab öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Lock" + "value" : "Open Link in New Tab" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Bloquear" + "value" : "Abrir enlace en nueva pestaña" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Verrouiller" + "value" : "Ouvrir le lien dans un nouvel onglet" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Blocca" + "value" : "Apri link in una nuova scheda" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Vergrendelen" + "value" : "Link openen in een nieuw tabblad" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zablokuj" + "value" : "Otwórz łącze w nowej karcie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Bloquear" + "value" : "Abrir ligação num separador novo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Заблокировать" + "value" : "Открыть ссылку в новой вкладке" } } } }, - "passsword.management.logins" : { - "comment" : "Used as title for the Autofill Logins option", + "open.preferences" : { + "comment" : "Open System Preferences (to re-enable permission for the App) (up to and including macOS 12", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter" + "value" : "Systemeinstellungen öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Passwords" + "value" : "Open System Preferences" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Contraseñas" + "value" : "Abrir preferencias del sistema" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mots de passe" + "value" : "Ouvrir les préférences système" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Password" + "value" : "Apri Preferenze di Sistema" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoorden" + "value" : "Systeemvoorkeuren openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Hasła" + "value" : "Otwórz preferencje systemowe" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Palavras-passe" + "value" : "Abrir Preferências do sistema" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пароли" + "value" : "Открыть настройки системы" } } } }, - "passsword.management.notes" : { - "comment" : "Used as title for the Autofill Notes option", + "open.settings" : { + "comment" : "This string represents a prompt or button label prompting the user to open system settings", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Notizen" + "value" : "Systemeinstellungen öffnen …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Notes" + "value" : "Open System Settings…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Notas" + "value" : "Abrir configuración del sistema" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Notes" + "value" : "Ouvrir les paramètres système…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Note" + "value" : "Apri Impostazioni di Sistema..." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Opmerkingen" + "value" : "Systeeminstellingen openen ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Uwagi" + "value" : "Otwórz ustawienia systemowe…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Notas" + "value" : "Abrir Definições do sistema…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Примечания" + "value" : "Открыть настройки системы…" } } } }, - "passsword.management.save.address" : { - "comment" : "Title of dialog that allows the user to save an address method", + "options.menu.fireproof-site" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Adresse speichern?" + "value" : "Diese Website feuerfest machen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Save Address?" + "value" : "Fireproof This Site" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Guardar dirección?" + "value" : "Activar a prueba de fuego para este sitio" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Enregistrer l'adresse ?" + "value" : "Activer le mode coupe-feu pour ce site" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Salvare l'indirizzo?" + "value" : "Proteggi questo sito dal fuoco" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Adres opslaan?" + "value" : "Deze website brandveilig maken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zapisać adres?" + "value" : "Zabezpiecz tę witrynę" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar endereço?" + "value" : "Coloque uma barreira de segurança neste site" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сохранить адрес?" + "value" : "Сделать огнеупорным" } } } }, - "passsword.management.save.credentials.account.label" : { - "comment" : "In the password manager dialog, label that specifies the password manager vault we are connected with", + "options.menu.move.tab.to.new.window" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verbunden mit %@" + "value" : "Tab in neues Fenster verschieben" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Connected to %@" + "value" : "Move Tab to New Window" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Conectado a %@" + "value" : "Mover pestaña a una ventana nueva" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Connecté à %@" + "value" : "Déplacer l'onglet vers une nouvelle fenêtre" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Connesso a %@" + "value" : "Sposta scheda in una nuova finestra" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verbonden met %@" + "value" : "Tabblad naar nieuw venster verplaatsen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Połączono z sejfem %@" + "value" : "Przenieś kartę do nowego okna" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ligado a %@" + "value" : "Mover separador para nova janela" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Привязан к %@" + "value" : "Перенести вкладку в новое окно" } } } }, - "passsword.management.save.credentials.fireproof.checkbox.description" : { - "comment" : "In the password manager dialog, description of the section that allows the user to fireproof a website via a checkbox", + "options.menu.remove-fireproofing" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Behält deine Anmeldung bei, nachdem du den Fire Button benutzt hast" + "value" : "Feuerfest-Einstellung deaktivieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Keeps you signed in after using the Fire Button" + "value" : "Remove Fireproofing" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mantiene la sesión iniciada después de usar el Fire Button" + "value" : "Desactivar a prueba de fuego" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Permet de maintenir votre connexion après l'utilisation du Fire Button" + "value" : "Désactiver le mode coupe-feu" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mantiene la connessione dopo aver utilizzato il Fire Button" + "value" : "Rimuovi protezione dal fuoco" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Je blijft ingelogd nadat je de Fire Button hebt gebruikt" + "value" : "Brandveiligheid uitschakelen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zachowuje stan Twojego zalogowania po użyciu przycisku Fire Button" + "value" : "Usuń zabezpieczenie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mantém a tua sessão iniciada depois de utilizares o Fire Button" + "value" : "Remover barreira de segurança" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Позволяет оставаться в учетной записи даже после применения кнопки Fire Button." + "value" : "Снять огнеупорность" } } } }, - "passsword.management.save.credentials.fireproof.checkbox.title" : { - "comment" : "In the password manager dialog, title of the section that allows the user to fireproof a website via a checkbox", + "page.crash.header" : { + "comment" : "Error page heading text shown when a Web Page process had crashed", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "„Fireproof“ diese Website?" + "value" : "Diese Webseite ist abgestürzt." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Fireproof this website" + "value" : "This webpage has crashed." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Marcar el sitio web como Fireproof" + "value" : "Esta página web se ha caído." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Placer ce site Web en mode coupe-feu" + "value" : "Cette page Web a planté." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Fireproof questo sito" + "value" : "Questa pagina web si è bloccata." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Deze website fireproof maken" + "value" : "Deze webpagina is gecrasht." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zabezpiecz tę witrynę funkcją Fireproof" + "value" : "Ta strona uległa awarii." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Faz o fireproof deste site" + "value" : "Esta página falhou." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Fireproof-защита для сайта" + "value" : "На этой странице произошел сбой." } } } }, - "passsword.management.save.credentials.password.manager.title" : { - "comment" : "Title of the passwored manager section of dialog that allows the user to save credentials", + "page.crash.message" : { + "comment" : "Error page message text shown when a Web Page process had crashed", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwort bei Bitwarden speichern?" + "value" : "Versuche, die Seite neu zu laden, oder versuche es später erneut." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Save password to Bitwarden?" + "value" : "Try reloading the page or come back later." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Guardar contraseña en Bitwarden?" + "value" : "Intenta volver a cargar la página o vuelve más tarde." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Enregistrer le mot de passe dans Bitwarden ?" + "value" : "Essayez de recharger la page ou revenez plus tard." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Salvare la password su Bitwarden?" + "value" : "Prova a ricaricare la pagina o torna più tardi." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoord opslaan in Bitwarden?" + "value" : "Probeer de pagina opnieuw te laden of kom later terug." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zapisać hasło w aplikacji Bitwarden?" + "value" : "Spróbuj ponownie załadować stronę lub wróć później." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar palavra-passe no Bitwarden?" + "value" : "Experimenta recarregar a página ou voltar mais tarde." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сохранить пароль в Bitwarden?" + "value" : "Перезагрузите ее или попробуйте вернуться позже." } } } }, - "passsword.management.save.credentials.unlock.password.manager" : { - "comment" : "In the password manager dialog, alerts the user that they need to unlock Bitworden before being able to save the credential", + "page.error.header" : { + "comment" : "Error page heading text", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden zum Speichern freischalten" + "value" : "DuckDuckGo kann diese Seite nicht laden." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Unlock Bitwarden to Save" + "value" : "DuckDuckGo can’t load this page." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Desbloquea Bitwarden para guardar" + "value" : "DuckDuckGo no puede cargar esta página." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Déverrouiller Bitwarden pour enregistrer" + "value" : "DuckDuckGo ne peut pas charger cette page." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Sblocca Bitwarden per salvare" + "value" : "DuckDuckGo non riesce a caricare questa pagina." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ontgrendel Bitwarden om op te slaan" + "value" : "DuckDuckGo kan deze pagina niet laden." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Odblokuj aplikację Bitwarden, aby zapisać" + "value" : "DuckDuckGo nie może wczytać tej strony." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Desbloquear o Bitwarden para guardar" + "value" : "O DuckDuckGo não consegue carregar esta página." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Для сохранения данных нужно разблокировать Bitwarden." + "value" : "DuckDuckGo не удалось загрузить эту страницу." } } } }, - "passsword.management.save.payment" : { - "comment" : "Title of dialog that allows the user to save a payment method", + "passsword.management.all-items" : { + "comment" : "Used as title for the Autofill All Items option", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zahlungsmethode speichern?" + "value" : "Alle Elemente" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Save Payment Method?" + "value" : "All Items" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Guardar método de pago?" + "value" : "Todos los elementos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Enregistrer le mode de paiement ?" + "value" : "Tous les éléments" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Salva il metodo di pagamento?" + "value" : "Tutti gli elementi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Betaalmethode opslaan?" + "value" : "Alle artikelen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zapisać metodę płatności?" + "value" : "Wszystkie elementy" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar método de pagamento?" + "value" : "Todos os elementos" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сохранить способ оплаты?" + "value" : "Все типы данных" } } } }, - "passsword.management.unlock" : { - "comment" : "Unlock Logins Vault menu", + "passsword.management.credit-cards" : { + "comment" : "Used as title for the Autofill Credit Cards option", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Freischalten" + "value" : "Kreditkarten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Unlock" + "value" : "Credit Cards" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Desbloquear" + "value" : "Tarjetas de crédito" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Déverrouiller" + "value" : "Cartes bancaires" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Sblocca" + "value" : "Carte di credito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ontgrendelen" + "value" : "Creditcards" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Odblokuj" + "value" : "Karty kredytowe" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Desbloquear" + "value" : "Cartões de crédito" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Разблокировать" + "value" : "Платежные карты" } } } }, - "passsword.manager.alert.delete" : { - "comment" : "Button of the alert that asks the user to confirm they want to delete an password, login or credential to actually delete", + "passsword.management.identities" : { + "comment" : "Used as title for the Autofill Identities option", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Löschen" + "value" : "Identitäten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Delete" + "value" : "Identities" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar" + "value" : "Identidades" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Supprimer" + "value" : "Identités" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cancella" + "value" : "Identità" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verwijderen" + "value" : "Identiteiten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usuń" + "value" : "Tożsamości" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar" + "value" : "Identidades" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удалить" + "value" : "Учетные данные" } } } }, - "passsword.manager.alert.duplicate.password" : { - "comment" : "Title of the alert that the password inserted already exists", + "passsword.management.lock" : { + "comment" : "Lock Logins Vault menu", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwort duplizieren" + "value" : "Sperren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Duplicate Password" + "value" : "Lock" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Duplicar contraseña" + "value" : "Bloquear" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mot de passe dupliqué" + "value" : "Verrouiller" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Password duplicata" + "value" : "Blocca" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoord dupliceren" + "value" : "Vergrendelen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zduplikowane hasło" + "value" : "Zablokuj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Palavra-passe duplicada" + "value" : "Bloquear" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Дубликат пароля" + "value" : "Заблокировать" } } } }, - "passsword.manager.alert.duplicate.password.description" : { - "comment" : "Text of the alert that explains the password inserted already exists for a given website", + "passsword.management.logins" : { + "comment" : "Used as title for the Autofill Logins option", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Du hast bereits ein Passwort für diesen Benutzernamen und diese Website gespeichert." + "value" : "Passwörter" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "You already have a password saved for this username and website." + "value" : "Passwords" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ya tienes una contraseña guardada para este nombre de usuario y sitio web." + "value" : "Contraseñas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Vous avez déjà enregistré un mot de passe pour ce nom d'utilisateur et ce site Web." + "value" : "Mots de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Hai già salvato una password per questo nome utente e questo sito web." + "value" : "Password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Je hebt al een wachtwoord opgeslagen voor deze gebruikersnaam en website." + "value" : "Wachtwoorden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Masz już zapisane hasło powiązane z tą nazwą użytkownika i witryną." + "value" : "Hasła" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Já tens uma palavra-passe guardada para este nome de utilizador e site." + "value" : "Palavras-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "У вас уже есть сохраненный пароль для этого сайта и имени пользователя." + "value" : "Пароли" } } } }, - "passsword.manager.alert.remove-card.confirmation" : { - "comment" : "Text of the alert that asks the user to confirm they want to delete a credit card", + "passsword.management.notes" : { + "comment" : "Used as title for the Autofill Notes option", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Möchtest du diese gespeicherte Kreditkarte wirklich löschen?" + "value" : "Notizen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Are you sure you want to delete this saved credit card?" + "value" : "Notes" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Seguro que quieres eliminar esta tarjeta de crédito guardada?" + "value" : "Notas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Voulez-vous vraiment supprimer cette carte bancaire enregistrée ?" + "value" : "Notes" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminare questa carta di credito salvata?" + "value" : "Note" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Weet je zeker dat je deze opgeslagen creditcard wilt verwijderen?" + "value" : "Opmerkingen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Czy na pewno chcesz usunąć tę zapisaną kartę kredytową?" + "value" : "Uwagi" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Tens a certeza de que pretendes eliminar este cartão de crédito guardado?" + "value" : "Notas" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вы точно хотите удалить эту сохраненную платежную карту?" + "value" : "Примечания" } } } }, - "passsword.manager.alert.remove-identity.confirmation" : { - "comment" : "Text of the alert that asks the user to confirm they want to delete an identity", + "passsword.management.save.address" : { + "comment" : "Title of dialog that allows the user to save an address method", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Möchtest du die gespeicherten Informationen zum Autovervollständigen wirklich löschen?" + "value" : "Adresse speichern?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Are you sure you want to delete this saved autofill info?" + "value" : "Save Address?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Seguro que quieres eliminar esta información de autocompletar guardada?" + "value" : "¿Guardar dirección?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Voulez-vous vraiment supprimer ces informations de saisie automatique enregistrées ?" + "value" : "Enregistrer l'adresse ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminare le info compilate automaticamente salvate?" + "value" : "Salvare l'indirizzo?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Weet je zeker dat je deze opgeslagen automatisch ingevulde gegevens wilt verwijderen?" + "value" : "Adres opslaan?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Czy na pewno chcesz usunąć te zapisane informacje autouzupełniania?" + "value" : "Zapisać adres?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Tens a certeza de que pretendes eliminar estas informações de preenchimento automático guardadas?" + "value" : "Guardar endereço?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вы точно хотите удалить эти сохраненные учетные данные?" + "value" : "Сохранить адрес?" } } } }, - "passsword.manager.alert.remove-note.confirmation" : { - "comment" : "Text of the alert that asks the user to confirm they want to delete a note", + "passsword.management.save.credentials.account.label" : { + "comment" : "In the password manager dialog, label that specifies the password manager vault we are connected with", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Möchtest du diese Notiz wirklich löschen?" + "value" : "Verbunden mit %@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Are you sure you want to delete this note?" + "value" : "Connected to %@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Seguro que quieres eliminar esta nota?" + "value" : "Conectado a %@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Voulez-vous vraiment supprimer cette note ?" + "value" : "Connecté à %@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminare questa nota?" + "value" : "Connesso a %@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Weet je zeker dat je deze notitie wilt verwijderen?" + "value" : "Verbonden met %@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Czy na pewno chcesz usunąć tę notatkę?" + "value" : "Połączono z sejfem %@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Tens a certeza de que pretendes eliminar esta nota?" + "value" : "Ligado a %@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вы точно хотите удалить это примечание?" + "value" : "Привязан к %@" } } } }, - "passsword.manager.alert.remove-password.confirmation" : { - "comment" : "Text of the alert that asks the user to confirm they want to delete a password", + "passsword.management.save.credentials.fireproof.checkbox.description" : { + "comment" : "In the password manager dialog, description of the section that allows the user to fireproof a website via a checkbox", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Möchtest du dieses gespeicherte Passwort wirklich löschen?" + "value" : "Behält deine Anmeldung bei, nachdem du den Fire Button benutzt hast" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Are you sure you want to delete this saved password" + "value" : "Keeps you signed in after using the Fire Button" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Seguro que quieres eliminar esta contraseña guardada" + "value" : "Mantiene la sesión iniciada después de usar el Fire Button" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Voulez-vous vraiment supprimer ce mot de passe enregistré ?" + "value" : "Permet de maintenir votre connexion après l'utilisation du Fire Button" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminare questa password salvata?" + "value" : "Mantiene la connessione dopo aver utilizzato il Fire Button" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Weet je zeker dat je dit opgeslagen wachtwoord wilt verwijderen?" + "value" : "Je blijft ingelogd nadat je de Fire Button hebt gebruikt" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Czy na pewno chcesz usunąć to zapisane hasło?" + "value" : "Zachowuje stan Twojego zalogowania po użyciu przycisku Fire Button" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Tens a certeza de que pretendes eliminar esta palavra-passe guardada?" + "value" : "Mantém a tua sessão iniciada depois de utilizares o Fire Button" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вы точно хотите удалить этот сохраненный пароль?" + "value" : "Позволяет оставаться в учетной записи даже после применения кнопки Fire Button." } } } }, - "passsword.manager.alert.save-changes" : { - "comment" : "Text of the alert that asks the user if the want to save the changes made", + "passsword.management.save.credentials.fireproof.checkbox.title" : { + "comment" : "In the password manager dialog, title of the section that allows the user to fireproof a website via a checkbox", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Die vorgenommenen Änderungen speichern?" + "value" : "„Fireproof“ diese Website?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Save the changes you made?" + "value" : "Fireproof this website" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Guardar los cambios realizados?" + "value" : "Marcar el sitio web como Fireproof" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Enregistrer les modifications que vous avez apportées ?" + "value" : "Placer ce site Web en mode coupe-feu" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Salvare le modifiche apportate?" + "value" : "Fireproof questo sito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "De aangebrachte wijzigingen opslaan?" + "value" : "Deze website fireproof maken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zapisać wprowadzone zmiany?" + "value" : "Zabezpiecz tę witrynę funkcją Fireproof" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar as alterações que fizeste?" + "value" : "Faz o fireproof deste site" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сохранить изменения?" + "value" : "Fireproof-защита для сайта" } } } }, - "passsword.manager.empty.state.message" : { - "comment" : "In the password manager message when there are no items", + "passsword.management.save.credentials.password.manager.title" : { + "comment" : "Title of the passwored manager section of dialog that allows the user to save credentials", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wenn deine Logins in einem anderen Browser gespeichert sind,\nkannst du sie in DuckDuckGo importieren." + "value" : "Passwort bei Bitwarden speichern?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "If your logins are saved in another browser, you can import them into DuckDuckGo." + "value" : "Save password to Bitwarden?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Si tus inicios de sesión se guardan en otro navegador, puedes importarlos a DuckDuckGo." + "value" : "¿Guardar contraseña en Bitwarden?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Si vos identifiants sont enregistrés dans un autre navigateur, vous pouvez les importer dans DuckDuckGo." + "value" : "Enregistrer le mot de passe dans Bitwarden ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Se gli accessi vengono salvati in un altro browser, è possibile importarli in DuckDuckGo." + "value" : "Salvare la password su Bitwarden?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Als je inloggegevens in een andere browser zijn opgeslagen, kun je ze in DuckDuckGo importeren." + "value" : "Wachtwoord opslaan in Bitwarden?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Jeśli Twoje dane logowania są zapisane w innej przeglądarce, możesz je zaimportować do DuckDuckGo." + "value" : "Zapisać hasło w aplikacji Bitwarden?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Se os teus inícios de sessão estão guardados noutro navegador, podes importá-los para o DuckDuckGo." + "value" : "Guardar palavra-passe no Bitwarden?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Если вы уже храните учетные данные в другом браузере, вы можете импортировать их в DuckDuckGo." + "value" : "Сохранить пароль в Bitwarden?" } } } }, - "passsword.manager.empty.state.title" : { - "comment" : "In the password manager title when there are no items", + "passsword.management.save.credentials.unlock.password.manager" : { + "comment" : "In the password manager dialog, alerts the user that they need to unlock Bitworden before being able to save the credential", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Noch keine Logins oder Kreditkartendaten" + "value" : "Bitwarden zum Speichern freischalten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "No logins or credit card info yet" + "value" : "Unlock Bitwarden to Save" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Aún no hay ingresos ni información de tarjeta de crédito" + "value" : "Desbloquea Bitwarden para guardar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aucun identifiant ni aucune information de carte bancaire pour l'instant" + "value" : "Déverrouiller Bitwarden pour enregistrer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ancora nessun accesso o dati della carta di credito" + "value" : "Sblocca Bitwarden per salvare" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nog geen inloggegevens of creditcardgegevens" + "value" : "Ontgrendel Bitwarden om op te slaan" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Jeszcze nie ma danych logowania ani danych kart kredytowych" + "value" : "Odblokuj aplikację Bitwarden, aby zapisać" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ainda sem inícios de sessão ou informações de cartão de crédito" + "value" : "Desbloquear o Bitwarden para guardar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Учетных данных и реквизитов карт пока нет" + "value" : "Для сохранения данных нужно разблокировать Bitwarden." } } } }, - "passsword.manager.unlock.autofill" : { - "comment" : "In the password manager text of button to unlock autofill info", + "passsword.management.save.payment" : { + "comment" : "Title of dialog that allows the user to save a payment method", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Schalte deine Autovervollständigungs-Infos frei" + "value" : "Zahlungsmethode speichern?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Unlock your Autofill info" + "value" : "Save Payment Method?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Desbloquea tu información de autocompletar" + "value" : "¿Guardar método de pago?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Déverrouiller vos informations de saisie automatique" + "value" : "Enregistrer le mode de paiement ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Sblocca la compilazione automatica delle informazioni" + "value" : "Salva il metodo di pagamento?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ontgrendel uw gegevens voor automatisch aanvullen" + "value" : "Betaalmethode opslaan?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Odblokuj informacje autouzupełniania" + "value" : "Zapisać metodę płatności?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Desbloquear as informações de preenchimento automático" + "value" : "Guardar método de pagamento?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Разблокировать данные для автозаполнения" + "value" : "Сохранить способ оплаты?" } } } }, - "Password import complete. You can now delete the saved passwords file." : { - "comment" : "message about Passwords Data Import completion", + "passsword.management.unlock" : { + "comment" : "Unlock Logins Vault menu", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwortimport abgeschlossen. Du kannst jetzt die gespeicherte Passwortdatei löschen." + "value" : "Freischalten" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Unlock" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importación de contraseña completada. Ya puedes eliminar el archivo de contraseñas guardadas." + "value" : "Desbloquear" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "L'importation des mots de passe est terminée. Vous pouvez maintenant supprimer le fichier de mots de passe sauvegardé." + "value" : "Déverrouiller" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importazione della password completata. Ora puoi eliminare il file delle password salvate." + "value" : "Sblocca" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Importeren wachtwoord voltooid. Je kunt het opgeslagen wachtwoordbestand nu verwijderen." + "value" : "Ontgrendelen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importowanie haseł zostało ukończone. Teraz możesz usunąć zapisany plik haseł." + "value" : "Odblokuj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importação de palavra-passe concluída. Já podes eliminar o ficheiro de palavras-passe guardadas." + "value" : "Desbloquear" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пароли импортированы. Можете удалить сохраненный файл с паролями." + "value" : "Разблокировать" } } } }, - "Password import failed: " : { - "comment" : "Data import summary format of how many passwords (%lld) failed to import.", + "passsword.manager.alert.delete" : { + "comment" : "Button of the alert that asks the user to confirm they want to delete an password, login or credential to actually delete", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwortimport fehlgeschlagen: " + "value" : "Löschen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Delete" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Error al importar contraseña: " + "value" : "Eliminar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Échec de l'importation des mots de passe : " + "value" : "Supprimer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importazione password non riuscita: " + "value" : "Cancella" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Importeren wachtwoord mislukt: " + "value" : "Verwijderen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importowanie haseł nie powiodło się: " + "value" : "Usuń" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Falha na importação da palavra-passe: " + "value" : "Eliminar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не удалось импортировать пароли: " + "value" : "Удалить" } } } }, - "Password import failed." : { - "comment" : "Data import summary message of failed passwords import.", + "passsword.manager.alert.duplicate.password" : { + "comment" : "Title of the alert that the password inserted already exists", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Der Passwortimport ist fehlgeschlagen." + "value" : "Passwort duplizieren" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Duplicate Password" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Se ha producido un error al importar la contraseña." + "value" : "Duplicar contraseña" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "L'importation des mots de passe a échoué." + "value" : "Mot de passe dupliqué" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importazione password non riuscita." + "value" : "Password duplicata" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Importeren wachtwoord mislukt." + "value" : "Wachtwoord dupliceren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importowanie haseł nie powiodło się." + "value" : "Zduplikowane hasło" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Falha na importação da palavra-passe." + "value" : "Palavra-passe duplicada" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не удалось импортировать пароли." + "value" : "Дубликат пароля" } } } }, - "password.management.title" : { - "comment" : "Used as the title for menu item and related Settings page", + "passsword.manager.alert.duplicate.password.description" : { + "comment" : "Text of the alert that explains the password inserted already exists for a given website", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter und Autovervollständigen" + "value" : "Du hast bereits ein Passwort für diesen Benutzernamen und diese Website gespeichert." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Passwords & Autofill" + "value" : "You already have a password saved for this username and website." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Contraseñas y autocompletar" + "value" : "Ya tienes una contraseña guardada para este nombre de usuario y sitio web." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mots de passe et saisie automatique" + "value" : "Vous avez déjà enregistré un mot de passe pour ce nom d'utilisateur et ce site Web." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Password e compilazione automatica" + "value" : "Hai già salvato una password per questo nome utente e questo sito web." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoorden en automatisch invullen" + "value" : "Je hebt al een wachtwoord opgeslagen voor deze gebruikersnaam en website." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Hasła i autouzupełnianie" + "value" : "Masz już zapisane hasło powiązane z tą nazwą użytkownika i witryną." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Palavras-passe e preenchimento automático" + "value" : "Já tens uma palavra-passe guardada para este nome de utilizador e site." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пароли и автозаполнение" + "value" : "У вас уже есть сохраненный пароль для этого сайта и имени пользователя." } } } }, - "password.manager" : { - "comment" : "Section header", + "passsword.manager.alert.remove-card.confirmation" : { + "comment" : "Text of the alert that asks the user to confirm they want to delete a credit card", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwort-Manager" + "value" : "Möchtest du diese gespeicherte Kreditkarte wirklich löschen?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Password Manager" + "value" : "Are you sure you want to delete this saved credit card?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Administrador de contraseñas" + "value" : "¿Seguro que quieres eliminar esta tarjeta de crédito guardada?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Gestionnaire de mots de passe" + "value" : "Voulez-vous vraiment supprimer cette carte bancaire enregistrée ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Gestore di password" + "value" : "Eliminare questa carta di credito salvata?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoordbeheerder" + "value" : "Weet je zeker dat je deze opgeslagen creditcard wilt verwijderen?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Menedżer haseł" + "value" : "Czy na pewno chcesz usunąć tę zapisaną kartę kredytową?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Gestor de palavras-passe" + "value" : "Tens a certeza de que pretendes eliminar este cartão de crédito guardado?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Менеджер паролей" + "value" : "Вы точно хотите удалить эту сохраненную платежную карту?" } } } }, - "Passwords:" : { - "comment" : "Data import summary format of how many passwords were successfully imported.", + "passsword.manager.alert.remove-identity.confirmation" : { + "comment" : "Text of the alert that asks the user to confirm they want to delete an identity", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter:" + "value" : "Möchtest du die gespeicherten Informationen zum Autovervollständigen wirklich löschen?" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Are you sure you want to delete this saved autofill info?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Contraseñas:" + "value" : "¿Seguro que quieres eliminar esta información de autocompletar guardada?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mots de passe :" + "value" : "Voulez-vous vraiment supprimer ces informations de saisie automatique enregistrées ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Password:" + "value" : "Eliminare le info compilate automaticamente salvate?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoorden:" + "value" : "Weet je zeker dat je deze opgeslagen automatisch ingevulde gegevens wilt verwijderen?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Hasła:" + "value" : "Czy na pewno chcesz usunąć te zapisane informacje autouzupełniania?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Palavras-passe:" + "value" : "Tens a certeza de que pretendes eliminar estas informações de preenchimento automático guardadas?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пароли:" + "value" : "Вы точно хотите удалить эти сохраненные учетные данные?" } } } }, - "paste-from-clipboard" : { - "comment" : "Paste button", + "passsword.manager.alert.remove-note.confirmation" : { + "comment" : "Text of the alert that asks the user to confirm they want to delete a note", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Vom Clipboard einfügen" + "value" : "Möchtest du diese Notiz wirklich löschen?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Paste from Clipboard" + "value" : "Are you sure you want to delete this note?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Pegar desde el portapapeles" + "value" : "¿Seguro que quieres eliminar esta nota?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Coller depuis le presse-papier" + "value" : "Voulez-vous vraiment supprimer cette note ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Incolla dagli appunti" + "value" : "Eliminare questa nota?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Plakken vanaf klembord" + "value" : "Weet je zeker dat je deze notitie wilt verwijderen?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wklej ze schowka" + "value" : "Czy na pewno chcesz usunąć tę notatkę?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Colar da área de transferência" + "value" : "Tens a certeza de que pretendes eliminar esta nota?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вставить из буфера обмена" + "value" : "Вы точно хотите удалить это примечание?" } } } }, - "paste.and.go" : { - "comment" : "Paste & Go button", + "passsword.manager.alert.remove-password.confirmation" : { + "comment" : "Text of the alert that asks the user to confirm they want to delete a password", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einfügen & los" + "value" : "Möchtest du dieses gespeicherte Passwort wirklich löschen?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Paste & Go" + "value" : "Are you sure you want to delete this saved password" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Pegar e ir" + "value" : "Seguro que quieres eliminar esta contraseña guardada" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Coller et valider" + "value" : "Voulez-vous vraiment supprimer ce mot de passe enregistré ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Incolla e continua" + "value" : "Eliminare questa password salvata?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Plakken en gaan" + "value" : "Weet je zeker dat je dit opgeslagen wachtwoord wilt verwijderen?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wklej i przejdź dalej" + "value" : "Czy na pewno chcesz usunąć to zapisane hasło?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Colar e ir" + "value" : "Tens a certeza de que pretendes eliminar esta palavra-passe guardada?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вставить и перейти" + "value" : "Вы точно хотите удалить этот сохраненный пароль?" } } } }, - "paste.and.search" : { - "comment" : "Paste & Search button", + "passsword.manager.alert.save-changes" : { + "comment" : "Text of the alert that asks the user if the want to save the changes made", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einfügen & suchen" + "value" : "Die vorgenommenen Änderungen speichern?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Paste & Search" + "value" : "Save the changes you made?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Pegar y buscar" + "value" : "¿Guardar los cambios realizados?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Coller et rechercher" + "value" : "Enregistrer les modifications que vous avez apportées ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Incolla e cerca" + "value" : "Salvare le modifiche apportate?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Plakken en zoeken" + "value" : "De aangebrachte wijzigingen opslaan?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wklej i wyszukaj" + "value" : "Zapisać wprowadzone zmiany?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Colar e pesquisar" + "value" : "Guardar as alterações que fizeste?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вставить и найти" + "value" : "Сохранить изменения?" } } } }, - "permission.allow.externalScheme.format" : { - "comment" : "Allow to open External Link (%@ 2) to open on current domain (%@ 1)", + "passsword.manager.empty.state.message" : { + "comment" : "In the password manager message when there are no items", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zulassen, dass „%1$@“ %2$@ öffnet" + "value" : "Wenn deine Logins in einem anderen Browser gespeichert sind,\nkannst du sie in DuckDuckGo importieren." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Allow “%1$@“ to open %2$@" + "value" : "If your logins are saved in another browser, you can import them into DuckDuckGo." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Permitir que \"%1$@\" abra %2$@" + "value" : "Si tus inicios de sesión se guardan en otro navegador, puedes importarlos a DuckDuckGo." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Autoriser « %1$@ » à ouvrir %2$@" + "value" : "Si vos identifiants sont enregistrés dans un autre navigateur, vous pouvez les importer dans DuckDuckGo." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Consenti a \"%1$@\" di aprire %2$@" + "value" : "Se gli accessi vengono salvati in un altro browser, è possibile importarli in DuckDuckGo." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "\"%1$@\" toestaan om %2$@ te openen" + "value" : "Als je inloggegevens in een andere browser zijn opgeslagen, kun je ze in DuckDuckGo importeren." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zezwól domenie „%1$@” na otwarcie %2$@" + "value" : "Jeśli Twoje dane logowania są zapisane w innej przeglądarce, możesz je zaimportować do DuckDuckGo." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Permitir que \"%1$@\" abra %2$@" + "value" : "Se os teus inícios de sessão estão guardados noutro navegador, podes importá-los para o DuckDuckGo." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Разрешить сайту %1$@ открывать %2$@" + "value" : "Если вы уже храните учетные данные в другом браузере, вы можете импортировать их в DuckDuckGo." } } } }, - "permission.allow.externalScheme.menu-header" : { - "comment" : "Allow the App Name(%@ 1) to open “URL Scheme”(%@ 2) links", + "passsword.manager.empty.state.title" : { + "comment" : "In the password manager title when there are no items", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Erlaube dem %1$@, „%2$@“ Links zu öffnen" + "value" : "Noch keine Logins oder Kreditkartendaten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Allow the %1$@ to open “%2$@” links" + "value" : "No logins or credit card info yet" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Permitir que %1$@ abra los enlaces \"%2$@\"" + "value" : "Aún no hay ingresos ni información de tarjeta de crédito" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Autoriser l'application %1$@ à ouvrir les liens « %2$@ »" + "value" : "Aucun identifiant ni aucune information de carte bancaire pour l'instant" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Consenti a %1$@ di aprire i link \"%2$@\"" + "value" : "Ancora nessun accesso o dati della carta di credito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%1$@ toestaan om “%2$@”-links te openen" + "value" : "Nog geen inloggegevens of creditcardgegevens" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zezwól aplikacji %1$@ na otwieranie łączy „%2$@”" + "value" : "Jeszcze nie ma danych logowania ani danych kart kredytowych" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Permitir que a aplicação %1$@ abra os links do \"%2$@\"" + "value" : "Ainda sem inícios de sessão ou informações de cartão de crédito" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Разрешить %1$@ открывать ссылки «%2$@»" + "value" : "Учетных данных и реквизитов карт пока нет" } } } }, - "permission.authorization.externalScheme.empty.format" : { - "comment" : "Popover asking to open link in External App (%@)", + "passsword.manager.unlock.autofill" : { + "comment" : "In the password manager text of button to unlock autofill info", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Diesen Link in %@ öffnen?" + "value" : "Schalte deine Autovervollständigungs-Infos frei" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open this link in %@?" + "value" : "Unlock your Autofill info" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Abrir este enlace en %@?" + "value" : "Desbloquea tu información de autocompletar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir ce lien dans %@ ?" + "value" : "Déverrouiller vos informations de saisie automatique" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri questo link in %@?" + "value" : "Sblocca la compilazione automatica delle informazioni" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Deze link openen in %@?" + "value" : "Ontgrendel uw gegevens voor automatisch aanvullen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otworzyć to łącze w aplikacji %@?" + "value" : "Odblokuj informacje autouzupełniania" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir este link na aplicação %@?" + "value" : "Desbloquear as informações de preenchimento automático" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть ссылку в %@?" + "value" : "Разблокировать данные для автозаполнения" } } } }, - "permission.authorization.externalScheme.format" : { - "comment" : "Popover asking for domain %@ to open link in External App (%@)", - "extractionState" : "extracted_with_value", + "Password import complete. You can now delete the saved passwords file." : { + "comment" : "message about Passwords Data Import completion", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "„%1$@“ möchte diesen Link in %2$@ öffnen" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "“%1$@” would like to open this link in %2$@" + "value" : "Passwortimport abgeschlossen. Du kannst jetzt die gespeicherte Passwortdatei löschen." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "\"%1$@\" desea abrir este enlace en %2$@" + "value" : "Importación de contraseña completada. Ya puedes eliminar el archivo de contraseñas guardadas." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "« %1$@ » souhaiterait ouvrir ce lien dans %2$@" + "value" : "L'importation des mots de passe est terminée. Vous pouvez maintenant supprimer le fichier de mots de passe sauvegardé." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "\"%1$@\" vorrebbe aprire questo link in %2$@" + "value" : "Importazione della password completata. Ora puoi eliminare il file delle password salvate." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "\"%1$@\" wil deze link openen in %2$@" + "value" : "Importeren wachtwoord voltooid. Je kunt het opgeslagen wachtwoordbestand nu verwijderen." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "„%1$@” chce otworzyć to łącze w aplikacji %2$@" + "value" : "Importowanie haseł zostało ukończone. Teraz możesz usunąć zapisany plik haseł." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "\"%1$@\" gostaria de abrir este link na aplicação %2$@" + "value" : "Importação de palavra-passe concluída. Já podes eliminar o ficheiro de palavras-passe guardadas." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сайт %1$@ хочет открыть эту ссылку в %2$@" + "value" : "Пароли импортированы. Можете удалить сохраненный файл с паролями." } } } }, - "permission.authorization.format" : { - "comment" : "Popover asking for domain %@ to use camera/mic/location (%@)", - "extractionState" : "extracted_with_value", + "Password import failed: " : { + "comment" : "Data import summary format of how many passwords (%lld) failed to import.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Erlaube „%1$@“, dein %2$@ zu benutzen?" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Allow “%1$@“ to use your %2$@?" + "value" : "Passwortimport fehlgeschlagen: " } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Permitir que \"%1$@\" use tu %2$@?" + "value" : "Error al importar contraseña: " } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Autoriser « %1$@ » à utiliser votre %2$@ ?" + "value" : "Échec de l'importation des mots de passe : " } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Consenti a \"%1$@\" di utilizzare %2$@?" + "value" : "Importazione password non riuscita: " } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "'%1$@' toestaan om je %2$@ te gebruiken?" + "value" : "Importeren wachtwoord mislukt: " } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zezwolić domenie „%1$@” na użycie Twojego urządzenia/funkcji %2$@?" + "value" : "Importowanie haseł nie powiodło się: " } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Permitir que \"%1$@\" utilize %2$@?" + "value" : "Falha na importação da palavra-passe: " } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть сайту %1$@ доступ (%2$@)?" + "value" : "Не удалось импортировать пароли: " } } } }, - "permission.authorization.popups.format" : { - "comment" : "Popover asking for domain %@ to open Popup Window", - "extractionState" : "extracted_with_value", + "Password import failed." : { + "comment" : "Data import summary message of failed passwords import.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Erlaube „%@“, ein PopUp-Fenster zu öffnen?" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Allow “%@“ to open PopUp Window?" + "value" : "Der Passwortimport ist fehlgeschlagen." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Permitir que \"%@\" abra una ventana emergente?" + "value" : "Se ha producido un error al importar la contraseña." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Autoriser « %@ » à ouvrir la fenêtre contextuelle ?" + "value" : "L'importation des mots de passe a échoué." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Consenti a \"%@\" di aprire la finestra popup?" + "value" : "Importazione password non riuscita." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Toestaan dat \"%@\" een pop-upvenster opent?" + "value" : "Importeren wachtwoord mislukt." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zezwolić domenie „%@” na otwarcie wyskakującego okienka?" + "value" : "Importowanie haseł nie powiodło się." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Permitir que \"%@\" abra a janela de pop-up?" + "value" : "Falha na importação da palavra-passe." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Разрешить сайту %@ открыть всплывающее окно?" + "value" : "Не удалось импортировать пароли." } } } }, - "permission.authorization.popups.menu-header" : { - "comment" : "Popover asking for domain %@ to open Popup Window", + "password.management.title" : { + "comment" : "Used as the title for menu item and related Settings page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Erlaube „%@“, PopUp-Fenster zu öffnen?" + "value" : "Passwörter und Autovervollständigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Allow “%@“ to open PopUp Windows?" + "value" : "Passwords & Autofill" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Permitir que \"%@\" abra ventanas emergentes?" + "value" : "Contraseñas y autocompletar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Autoriser « %@ » à ouvrir les fenêtres contextuelles ?" + "value" : "Mots de passe et saisie automatique" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Consenti a \"%@\" di aprire finestre popup?" + "value" : "Password e compilazione automatica" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Toestaan dat \"%@\" pop-upvensters opent?" + "value" : "Wachtwoorden en automatisch invullen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zezwolić domenie „%@” na otwieranie wyskakujących okienek?" + "value" : "Hasła i autouzupełnianie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Permitir que \"%@\" abra janelas de pop-up?" + "value" : "Palavras-passe e preenchimento automático" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Разрешить сайту %@ открыть всплывающие окна?" + "value" : "Пароли и автозаполнение" } } } }, - "permission.camera" : { - "comment" : "Camera input media device name", + "password.manager" : { + "comment" : "Section header", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kamera" + "value" : "Passwort-Manager" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Camera" + "value" : "Password Manager" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cámara" + "value" : "Administrador de contraseñas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Appareil photo" + "value" : "Gestionnaire de mots de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Fotocamera" + "value" : "Gestore di password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Camera" + "value" : "Wachtwoordbeheerder" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Aparat" + "value" : "Menedżer haseł" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Câmara" + "value" : "Gestor de palavras-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Камера" + "value" : "Менеджер паролей" } } } }, - "permission.cameraAndmicrophone" : { - "comment" : "camera and microphone input media devices name", - "extractionState" : "extracted_with_value", + "Passwords:" : { + "comment" : "Data import summary format of how many passwords were successfully imported.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kamera und Mikrofon" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Camera and Microphone" + "value" : "Passwörter:" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cámara y micrófono" + "value" : "Contraseñas:" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Appareil photo et micro" + "value" : "Mots de passe :" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Fotocamera e microfono" + "value" : "Password:" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Camera en microfoon" + "value" : "Wachtwoorden:" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Aparat i mikrofon" + "value" : "Hasła:" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Câmara e microfone" + "value" : "Palavras-passe:" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Камера и микрофон" + "value" : "Пароли:" } } } }, - "permission.disabled.app" : { - "comment" : "The app (DuckDuckGo: %@ 2) has no access permission to (%@ 1) media device", + "paste-from-clipboard" : { + "comment" : "Paste button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%1$@ Zugang ist deaktiviert für %2$@" + "value" : "Vom Clipboard einfügen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$@ access is disabled for %2$@" + "value" : "Paste from Clipboard" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Acceso a %1$@ desactivado para %2$@" + "value" : "Pegar desde el portapapeles" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "L'accès à %1$@ est désactivé pour %2$@" + "value" : "Coller depuis le presse-papier" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "L'accesso a %1$@ è disabilitato per %2$@" + "value" : "Incolla dagli appunti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Toegang tot %1$@ is uitgeschakeld voor %2$@" + "value" : "Plakken vanaf klembord" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%2$@ ma wyłączony dostęp do urządzenia %1$@" + "value" : "Wklej ze schowka" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O acesso do %2$@ a %1$@ está desativado" + "value" : "Colar da área de transferência" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%2$@: у %1$@ нет доступа" + "value" : "Вставить из буфера обмена" } } } }, - "permission.disabled.system" : { - "comment" : "Geolocation Services are disabled in System Preferences", + "paste.and.go" : { + "comment" : "Paste & Go button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Die System-Standortbestimmung ist deaktiviert" + "value" : "Einfügen & los" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "System location services are disabled" + "value" : "Paste & Go" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Los servicios de ubicación del sistema están deshabilitados" + "value" : "Pegar e ir" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Les services de localisation du système sont désactivés" + "value" : "Coller et valider" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "I servizi di localizzazione del sistema sono disabilitati" + "value" : "Incolla e continua" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "De locatiediensten van het systeem zijn uitgeschakeld" + "value" : "Plakken en gaan" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Systemowe usługi lokalizacji są wyłączone" + "value" : "Wklej i przejdź dalej" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Os serviços de localização do sistema estão desativados" + "value" : "Colar e ir" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Системные службы геолокации отключены" + "value" : "Вставить и перейти" } } } }, - "permission.externalScheme.open.format" : { - "comment" : "Open %@ App Name", + "paste.and.search" : { + "comment" : "Paste & Search button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%@ öffnen" + "value" : "Einfügen & suchen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open %@" + "value" : "Paste & Search" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir %@" + "value" : "Pegar y buscar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir %@" + "value" : "Coller et rechercher" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri %@" + "value" : "Incolla e cerca" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%@ openen" + "value" : "Plakken en zoeken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz %@" + "value" : "Wklej i wyszukaj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir %@" + "value" : "Colar e pesquisar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть %@" + "value" : "Вставить и найти" } } } }, - "permission.geolocation" : { - "comment" : "User's Geolocation permission access name", + "permission.allow.externalScheme.format" : { + "comment" : "Allow to open External Link (%@ 2) to open on current domain (%@ 1)", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Standort" + "value" : "Zulassen, dass „%1$@“ %2$@ öffnet" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Location" + "value" : "Allow “%1$@“ to open %2$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ubicación" + "value" : "Permitir que \"%1$@\" abra %2$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Localisation" + "value" : "Autoriser « %1$@ » à ouvrir %2$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Posizione" + "value" : "Consenti a \"%1$@\" di aprire %2$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Locatie" + "value" : "\"%1$@\" toestaan om %2$@ te openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Lokalizacja" + "value" : "Zezwól domenie „%1$@” na otwarcie %2$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Localização" + "value" : "Permitir que \"%1$@\" abra %2$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Местоположение" + "value" : "Разрешить сайту %1$@ открывать %2$@" } } } }, - "permission.microphone" : { - "comment" : "Microphone input media device name", + "permission.allow.externalScheme.menu-header" : { + "comment" : "Allow the App Name(%@ 1) to open “URL Scheme”(%@ 2) links", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Mikrofon" + "value" : "Erlaube dem %1$@, „%2$@“ Links zu öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Microphone" + "value" : "Allow the %1$@ to open “%2$@” links" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Micrófono" + "value" : "Permitir que %1$@ abra los enlaces \"%2$@\"" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Micro" + "value" : "Autoriser l'application %1$@ à ouvrir les liens « %2$@ »" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Microfono" + "value" : "Consenti a %1$@ di aprire i link \"%2$@\"" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Microfoon" + "value" : "%1$@ toestaan om “%2$@”-links te openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Mikrofon" + "value" : "Zezwól aplikacji %1$@ na otwieranie łączy „%2$@”" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Microfone" + "value" : "Permitir que a aplicação %1$@ abra os links do \"%2$@\"" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Микрофон" + "value" : "Разрешить %1$@ открывать ссылки «%2$@»" } } } }, - "permission.mute" : { - "comment" : "Temporarily pause input media device %@ access for %@2 website", + "permission.authorization.externalScheme.empty.format" : { + "comment" : "Popover asking to open link in External App (%@)", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verwendung von %1$@ für „%2$@“ pausieren" + "value" : "Diesen Link in %@ öffnen?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Pause %1$@ use on “%2$@”" + "value" : "Open this link in %@?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Pausar el uso de %1$@ en \"%2$@\"" + "value" : "¿Abrir este enlace en %@?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Suspendre l'utilisation de %1$@ sur « %2$@ »" + "value" : "Ouvrir ce lien dans %@ ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Metti in pausa l'utilizzo di %1$@ su \"%2$@\"" + "value" : "Apri questo link in %@?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Toegang van %1$@ tot “%2$@\" pauzeren" + "value" : "Deze link openen in %@?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wstrzymaj korzystanie z urządzenia %1$@ w witrynie „%2$@”" + "value" : "Otworzyć to łącze w aplikacji %@?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Pausar a utilização de %1$@ em \"%2$@\"" + "value" : "Abrir este link na aplicação %@?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$@: приостановить доступ на сайте %2$@" + "value" : "Открыть ссылку в %@?" } } } }, - "permission.open.settings" : { - "comment" : "Open System Settings (to re-enable permission for the App) (macOS 13 and above)", + "permission.authorization.externalScheme.format" : { + "comment" : "Popover asking for domain %@ to open link in External App (%@)", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Systemeinstellungen öffnen" + "value" : "„%1$@“ möchte diesen Link in %2$@ öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open System Settings" + "value" : "“%1$@” would like to open this link in %2$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir Configuración del Sistema" + "value" : "\"%1$@\" desea abrir este enlace en %2$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir les paramètres système" + "value" : "« %1$@ » souhaiterait ouvrir ce lien dans %2$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri Impostazioni di Sistema" + "value" : "\"%1$@\" vorrebbe aprire questo link in %2$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Systeeminstellingen openen" + "value" : "\"%1$@\" wil deze link openen in %2$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz ustawienia systemowe" + "value" : "„%1$@” chce otworzyć to łącze w aplikacji %2$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir Definições do sistema" + "value" : "\"%1$@\" gostaria de abrir este link na aplicação %2$@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть настройки системы" + "value" : "Сайт %1$@ хочет открыть эту ссылку в %2$@" } } } }, - "permission.popover.deny" : { - "comment" : "Permission Popover: Deny Website input media device access", + "permission.authorization.format" : { + "comment" : "Popover asking for domain %@ to use camera/mic/location (%@)", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ablehnen" + "value" : "Erlaube „%1$@“, dein %2$@ zu benutzen?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Deny" + "value" : "Allow “%1$@“ to use your %2$@?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Rechazar" + "value" : "¿Permitir que \"%1$@\" use tu %2$@?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Refuser" + "value" : "Autoriser « %1$@ » à utiliser votre %2$@ ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rifiuta" + "value" : "Consenti a \"%1$@\" di utilizzare %2$@?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Weigeren" + "value" : "'%1$@' toestaan om je %2$@ te gebruiken?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Odmów" + "value" : "Zezwolić domenie „%1$@” na użycie Twojego urządzenia/funkcji %2$@?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Recusar" + "value" : "Permitir que \"%1$@\" utilize %2$@?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отказать" + "value" : "Открыть сайту %1$@ доступ (%2$@)?" } } } }, - "permission.popup.allow.button" : { - "comment" : "Button that the user can use to authorise a web site to for, for example access location or camera and microphone etc.", + "permission.authorization.popups.format" : { + "comment" : "Popover asking for domain %@ to open Popup Window", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zulassen" + "value" : "Erlaube „%@“, ein PopUp-Fenster zu öffnen?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Allow" + "value" : "Allow “%@“ to open PopUp Window?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Permitir" + "value" : "¿Permitir que \"%@\" abra una ventana emergente?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Autoriser" + "value" : "Autoriser « %@ » à ouvrir la fenêtre contextuelle ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Consenti" + "value" : "Consenti a \"%@\" di aprire la finestra popup?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Toestaan" + "value" : "Toestaan dat \"%@\" een pop-upvenster opent?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zezwól" + "value" : "Zezwolić domenie „%@” na otwarcie wyskakującego okienka?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Permitir" + "value" : "Permitir que \"%@\" abra a janela de pop-up?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Разрешить" + "value" : "Разрешить сайту %@ открыть всплывающее окно?" } } } }, - "permission.popup.blocked.popover" : { - "comment" : "Text of popver warning the user that the a pop-up as been blocked", + "permission.authorization.popups.menu-header" : { + "comment" : "Popover asking for domain %@ to open Popup Window", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Pop-up blockiert" + "value" : "Erlaube „%@“, PopUp-Fenster zu öffnen?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Pop-up Blocked" + "value" : "Allow “%@“ to open PopUp Windows?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ventana emergente bloqueada" + "value" : "¿Permitir que \"%@\" abra ventanas emergentes?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fenêtre contextuelle bloquée" + "value" : "Autoriser « %@ » à ouvrir les fenêtres contextuelles ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Popup bloccato" + "value" : "Consenti a \"%@\" di aprire finestre popup?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Pop-up geblokkeerd" + "value" : "Toestaan dat \"%@\" pop-upvensters opent?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zablokowano wyskakujące okienko" + "value" : "Zezwolić domenie „%@” na otwieranie wyskakujących okienek?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Pop-up bloqueado" + "value" : "Permitir que \"%@\" abra janelas de pop-up?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Всплывающее окно заблокировано" + "value" : "Разрешить сайту %@ открыть всплывающие окна?" } } } }, - "permission.popup.learn-more.link" : { - "comment" : "Text of link that leads to web page with more informations about location services.", + "permission.camera" : { + "comment" : "Camera input media device name", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Mehr über Standortbestimmung erfahren" + "value" : "Kamera" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Learn more about location services" + "value" : "Camera" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Más información sobre los servicios de ubicación" + "value" : "Cámara" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "En savoir plus sur les services de localisation" + "value" : "Appareil photo" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Scopri di più sui servizi di localizzazione" + "value" : "Fotocamera" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Meer informatie over locatieservices" + "value" : "Camera" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dowiedz się więcej o usługach lokalizacji" + "value" : "Aparat" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Sabe mais sobre os serviços de localização" + "value" : "Câmara" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Подробнее о службах геолокации" + "value" : "Камера" } } } }, - "permission.popup.open.format" : { - "comment" : "Open %@ URL Pop-up", + "permission.cameraAndmicrophone" : { + "comment" : "camera and microphone input media devices name", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%@" + "value" : "Kamera und Mikrofon" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%@" + "value" : "Camera and Microphone" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%@" + "value" : "Cámara y micrófono" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%@" + "value" : "Appareil photo et micro" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%@" + "value" : "Fotocamera e microfono" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%@" + "value" : "Camera en microfoon" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%@" + "value" : "Aparat i mikrofon" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%@" + "value" : "Câmara e microfone" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%@" + "value" : "Камера и микрофон" } } } }, - "permission.popup.title" : { - "comment" : "Title of a popup that has a list of blocked popups", + "permission.disabled.app" : { + "comment" : "The app (DuckDuckGo: %@ 2) has no access permission to (%@ 1) media device", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Blockierte Pop-ups" + "value" : "%1$@ Zugang ist deaktiviert für %2$@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Blocked Pop-ups" + "value" : "%1$@ access is disabled for %2$@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ventanas emergentes bloqueadas" + "value" : "Acceso a %1$@ desactivado para %2$@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fenêtres contextuelles bloquées" + "value" : "L'accès à %1$@ est désactivé pour %2$@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Popup bloccati" + "value" : "L'accesso a %1$@ è disabilitato per %2$@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geblokkeerde pop-ups" + "value" : "Toegang tot %1$@ is uitgeschakeld voor %2$@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zablokowane wyskakujące okienka" + "value" : "%2$@ ma wyłączony dostęp do urządzenia %1$@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Pop-ups bloqueados" + "value" : "O acesso do %2$@ a %1$@ está desativado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Заблокированные всплывающие окна" + "value" : "%2$@: у %1$@ нет доступа" } } } }, - "permission.popups" : { - "comment" : "Open Pop Up Windows permission access name", + "permission.disabled.system" : { + "comment" : "Geolocation Services are disabled in System Preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Pop-ups" + "value" : "Die System-Standortbestimmung ist deaktiviert" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Pop-ups" + "value" : "System location services are disabled" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ventanas emergentes" + "value" : "Los servicios de ubicación del sistema están deshabilitados" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fenêtres contextuelles" + "value" : "Les services de localisation du système sont désactivés" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Popup" + "value" : "I servizi di localizzazione del sistema sono disabilitati" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Pop-ups" + "value" : "De locatiediensten van het systeem zijn uitgeschakeld" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyskakujące okienka" + "value" : "Systemowe usługi lokalizacji są wyłączone" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Pop-ups" + "value" : "Os serviços de localização do sistema estão desativados" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Всплывающие окна" + "value" : "Системные службы геолокации отключены" } } } }, - "permission.reloadPage" : { - "comment" : "Reload webpage to ask for input media device access permission again", + "permission.externalScheme.open.format" : { + "comment" : "Open %@ App Name", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neu laden, um erneut um Erlaubnis zu fragen" + "value" : "%@ öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Reload to ask permission again" + "value" : "Open %@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Volver a cargar para solicitar permiso de nuevo" + "value" : "Abrir %@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Recharger pour demander à nouveau l'autorisation" + "value" : "Ouvrir %@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ricarica per chiedere nuovamente l'autorizzazione" + "value" : "Apri %@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Opnieuw laden om opnieuw toestemming te vragen" + "value" : "%@ openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Załaduj ponownie, aby ponownie poprosić o pozwolenie" + "value" : "Otwórz %@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Recarregar para pedir permissão novamente" + "value" : "Abrir %@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Перезагрузите страницу, чтобы снова увидеть запрос" + "value" : "Открыть %@" } } } }, - "permission.unmute" : { - "comment" : "Resume input media device %@ access for %@ website", + "permission.geolocation" : { + "comment" : "User's Geolocation permission access name", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verwendung von %1$@ am „%2$@“ wieder aufnehmen" + "value" : "Standort" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Resume %1$@ use on “%2$@”" + "value" : "Location" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Reanudar el uso de %1$@ en \"%2$@\"" + "value" : "Ubicación" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Reprendre l'utilisation de %1$@ sur « %2$@ »" + "value" : "Localisation" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riprendi l'uso di %1$@ su \"%2$@\"" + "value" : "Posizione" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Toegang van %1$@ tot “%2$@” hervatten" + "value" : "Locatie" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wznów korzystanie z urządzenia %1$@ w witrynie „%2$@”" + "value" : "Lokalizacja" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Retomar a utilização de %1$@ em \"%2$@\"" + "value" : "Localização" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%1$@: возобновить доступ на сайте %2$@" + "value" : "Местоположение" } } } }, - "pin.tab" : { - "comment" : "Menu item. Pin as a verb", + "permission.microphone" : { + "comment" : "Microphone input media device name", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Tab anpinnen" + "value" : "Mikrofon" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Pin Tab" + "value" : "Microphone" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Fijar pestaña" + "value" : "Micrófono" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Épingler l'onglet" + "value" : "Micro" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Fissa scheda" + "value" : "Microfono" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Tabblad vastzetten" + "value" : "Microfoon" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przypnij kartę" + "value" : "Mikrofon" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Fixar separador" + "value" : "Microfone" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Закрепить вкладку" + "value" : "Микрофон" } } } }, - "pinning.hide-autofill-shortcut" : { - "comment" : "Menu item for hiding the passwords shortcut", + "permission.mute" : { + "comment" : "Temporarily pause input media device %@ access for %@2 website", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwort-Shortcut ausblenden" + "value" : "Verwendung von %1$@ für „%2$@“ pausieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Hide Passwords Shortcut" + "value" : "Pause %1$@ use on “%2$@”" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar acceso directo a contraseñas" + "value" : "Pausar el uso de %1$@ en \"%2$@\"" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Masquer le raccourci des mots de passe" + "value" : "Suspendre l'utilisation de %1$@ sur « %2$@ »" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nascondi scorciatoia per le password" + "value" : "Metti in pausa l'utilizzo di %1$@ su \"%2$@\"" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Snelkoppeling voor wachtwoorden verbergen" + "value" : "Toegang van %1$@ tot “%2$@\" pauzeren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ukryj skrót do haseł" + "value" : "Wstrzymaj korzystanie z urządzenia %1$@ w witrynie „%2$@”" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar atalho de palavras-passe" + "value" : "Pausar a utilização de %1$@ em \"%2$@\"" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скрыть ярлык для паролей" + "value" : "%1$@: приостановить доступ на сайте %2$@" } } } }, - "pinning.hide-bookmarks-shortcut" : { - "comment" : "Menu item for hiding the bookmarks shortcut", + "permission.open.settings" : { + "comment" : "Open System Settings (to re-enable permission for the App) (macOS 13 and above)", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verknüpfung Lesezeichen ausblenden" + "value" : "Systemeinstellungen öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Hide Bookmarks Shortcut" + "value" : "Open System Settings" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar acceso directo a marcadores" + "value" : "Abrir Configuración del Sistema" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Masquer le raccourci des signets" + "value" : "Ouvrir les paramètres système" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nascondi scorciatoia segnalibri" + "value" : "Apri Impostazioni di Sistema" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Snelkoppeling voor bladwijzers verbergen" + "value" : "Systeeminstellingen openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ukryj skrót do zakładek" + "value" : "Otwórz ustawienia systemowe" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar atalho de marcadores" + "value" : "Abrir Definições do sistema" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скрыть ярлык для закладок" + "value" : "Открыть настройки системы" } } } }, - "pinning.hide-downloads-shortcut" : { - "comment" : "Menu item for hiding the downloads shortcut", + "permission.popover.deny" : { + "comment" : "Permission Popover: Deny Website input media device access", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Download-Verknüpfung ausblenden" + "value" : "Ablehnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Hide Downloads Shortcut" + "value" : "Deny" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar acceso directo a descargas" + "value" : "Rechazar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Masquer le raccourci des téléchargements" + "value" : "Refuser" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nascondi scorciatoia download" + "value" : "Rifiuta" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Snelkoppeling voor downloads verbergen" + "value" : "Weigeren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ukryj skrót do pobranych" + "value" : "Odmów" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar atalho de transferências" + "value" : "Recusar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скрыть ярлык для загрузок" + "value" : "Отказать" } } } }, - "pinning.hide-netp-shortcut" : { - "comment" : "Menu item for hiding the NetP shortcut", + "permission.popup.allow.button" : { + "comment" : "Button that the user can use to authorise a web site to for, for example access location or camera and microphone etc.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "VPN-Verknüpfung ausblenden" + "value" : "Zulassen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Hide VPN Shortcut" + "value" : "Allow" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar acceso directo a VPN" + "value" : "Permitir" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Masquer le raccourci VPN" + "value" : "Autoriser" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nascondi scorciatoia VPN" + "value" : "Consenti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Snelkoppeling voor VPN verbergen" + "value" : "Toestaan" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ukryj skrót do VPN" + "value" : "Zezwól" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar atalho de VPN" + "value" : "Permitir" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скрыть ярлык для VPN" + "value" : "Разрешить" } } } }, - "pinning.show-autofill-shortcut" : { - "comment" : "Menu item for showing the passwords shortcut", + "permission.popup.blocked.popover" : { + "comment" : "Text of popver warning the user that the a pop-up as been blocked", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwort-Shortcut anzeigen" + "value" : "Pop-up blockiert" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Passwords Shortcut" + "value" : "Pop-up Blocked" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar acceso directo a contraseñas" + "value" : "Ventana emergente bloqueada" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher le raccourci des mots de passe" + "value" : "Fenêtre contextuelle bloquée" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra scorciatoia per le password" + "value" : "Popup bloccato" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Snelkoppeling voor wachtwoorden weergeven" + "value" : "Pop-up geblokkeerd" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż skrót do haseł" + "value" : "Zablokowano wyskakujące okienko" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar atalho de palavras-passe" + "value" : "Pop-up bloqueado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показывать ярлык для паролей" + "value" : "Всплывающее окно заблокировано" } } } }, - "pinning.show-bookmarks-shortcut" : { - "comment" : "Menu item for showing the bookmarks shortcut", + "permission.popup.learn-more.link" : { + "comment" : "Text of link that leads to web page with more informations about location services.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen-Verknüpfung anzeigen" + "value" : "Mehr über Standortbestimmung erfahren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Bookmarks Shortcut" + "value" : "Learn more about location services" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar acceso directo a Favoritos" + "value" : "Más información sobre los servicios de ubicación" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher le raccourci des signets" + "value" : "En savoir plus sur les services de localisation" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra scorciatoia segnalibri" + "value" : "Scopri di più sui servizi di localizzazione" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Snelkoppeling voor bladwijzers weergeven" + "value" : "Meer informatie over locatieservices" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż skrót do zakładek" + "value" : "Dowiedz się więcej o usługach lokalizacji" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar atalho de marcadores" + "value" : "Sabe mais sobre os serviços de localização" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показывать ярлык для закладок" + "value" : "Подробнее о службах геолокации" } } } }, - "pinning.show-downloads-shortcut" : { - "comment" : "Menu item for showing the downloads shortcut", + "permission.popup.open.format" : { + "comment" : "Open %@ URL Pop-up", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Download-Verknüpfung anzeigen" + "value" : "%@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Downloads Shortcut" + "value" : "%@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar acceso directo a Descargas" + "value" : "%@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher le raccourci des téléchargements" + "value" : "%@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra scorciatoia download" + "value" : "%@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Snelkoppeling voor downloads weergeven" + "value" : "%@" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż skrót do pobranych" + "value" : "%@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar atalho de transferências" + "value" : "%@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать ярлык для загрузок" + "value" : "%@" } } } }, - "pinning.show-netp-shortcut" : { - "comment" : "Menu item for showing the NetP shortcut", + "permission.popup.title" : { + "comment" : "Title of a popup that has a list of blocked popups", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "VPN-Verknüpfung anzeigen" + "value" : "Blockierte Pop-ups" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show VPN Shortcut" + "value" : "Blocked Pop-ups" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar acceso directo a VPN" + "value" : "Ventanas emergentes bloqueadas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher le raccourci VPN" + "value" : "Fenêtres contextuelles bloquées" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra scorciatoia VPN" + "value" : "Popup bloccati" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Snelkoppeing voor VPN weergeven" + "value" : "Geblokkeerde pop-ups" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż skrót do VPN" + "value" : "Zablokowane wyskakujące okienka" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar atalho de VPN" + "value" : "Pop-ups bloqueados" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показывать ярлык VPN" + "value" : "Заблокированные всплывающие окна" } } } }, - "Please submit a report to help us fix the issue." : { - "comment" : "Data import failure Report dialog title.", + "permission.popups" : { + "comment" : "Open Pop Up Windows permission access name", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitte reiche einen Bericht ein, damit wir das Problem beheben können." + "value" : "Pop-ups" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Pop-ups" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Envía un informe para ayudarnos a solucionar el problema." + "value" : "Ventanas emergentes" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Veuillez soumettre un rapport pour nous aider à résoudre le problème." + "value" : "Fenêtres contextuelles" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Invia una segnalazione per aiutarci a risolvere il problema." + "value" : "Popup" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Dien een melding in om ons te helpen het probleem op te lossen." + "value" : "Pop-ups" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Prześlij raport, aby pomóc nam w rozwiązaniu problemu." + "value" : "Wyskakujące okienka" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Envia um relatório para nos ajudar a corrigir o problema." + "value" : "Pop-ups" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отправьте отчет, чтобы мы могли устранить ошибку." + "value" : "Всплывающие окна" } } } }, - "pm.activate" : { - "comment" : "Activate button", + "permission.reloadPage" : { + "comment" : "Reload webpage to ask for input media device access permission again", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Reaktivieren" + "value" : "Neu laden, um erneut um Erlaubnis zu fragen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Reactivate" + "value" : "Reload to ask permission again" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Reactivar" + "value" : "Volver a cargar para solicitar permiso de nuevo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Réactiver" + "value" : "Recharger pour demander à nouveau l'autorisation" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riattiva" + "value" : "Ricarica per chiedere nuovamente l'autorizzazione" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Opnieuw activeren" + "value" : "Opnieuw laden om opnieuw toestemming te vragen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ponownie aktywuj" + "value" : "Załaduj ponownie, aby ponownie poprosić o pozwolenie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Reativar" + "value" : "Recarregar para pedir permissão novamente" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Активировать" + "value" : "Перезагрузите страницу, чтобы снова увидеть запрос" } } } }, - "pm.activate.private.email" : { - "comment" : "Activate private email address button", + "permission.unmute" : { + "comment" : "Resume input media device %@ access for %@ website", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address reaktivieren" + "value" : "Verwendung von %1$@ am „%2$@“ wieder aufnehmen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Reactivate Duck Address" + "value" : "Resume %1$@ use on “%2$@”" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Reactivar Duck Address" + "value" : "Reanudar el uso de %1$@ en \"%2$@\"" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Réactiver la Duck Address" + "value" : "Reprendre l'utilisation de %1$@ sur « %2$@ »" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riattiva Duck Address" + "value" : "Riprendi l'uso di %1$@ su \"%2$@\"" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address reactiveren" + "value" : "Toegang van %1$@ tot “%2$@” hervatten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Aktywuj ponownie Duck Address" + "value" : "Wznów korzystanie z urządzenia %1$@ w witrynie „%2$@”" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Reativar Duck Address" + "value" : "Retomar a utilização de %1$@ em \"%2$@\"" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Снова активировать Duck Address" + "value" : "%1$@: возобновить доступ на сайте %2$@" } } } }, - "pm.add.card" : { - "comment" : "Add New Credit Card button", + "pin.tab" : { + "comment" : "Menu item. Pin as a verb", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kreditkarte hinzufügen" + "value" : "Tab anpinnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Add Credit Card" + "value" : "Pin Tab" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Añadir tarjeta de crédito" + "value" : "Fijar pestaña" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ajouter une carte bancaire" + "value" : "Épingler l'onglet" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aggiungi carta di credito" + "value" : "Fissa scheda" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Creditcard toevoegen" + "value" : "Tabblad vastzetten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dodaj kartę kredytową" + "value" : "Przypnij kartę" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Adicionar cartão de crédito" + "value" : "Fixar separador" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Добавить карту" + "value" : "Закрепить вкладку" } } } }, - "pm.add.identity" : { - "comment" : "Add New Identity button", + "pinning.hide-autofill-shortcut" : { + "comment" : "Menu item for hiding the passwords shortcut", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Identität hinzufügen" + "value" : "Passwort-Shortcut ausblenden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Add Identity" + "value" : "Hide Passwords Shortcut" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Añadir identidad" + "value" : "Ocultar acceso directo a contraseñas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ajouter une identité" + "value" : "Masquer le raccourci des mots de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aggiungi identità" + "value" : "Nascondi scorciatoia per le password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Identiteit toevoegen" + "value" : "Snelkoppeling voor wachtwoorden verbergen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dodaj tożsamość" + "value" : "Ukryj skrót do haseł" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Adicionar identidade" + "value" : "Ocultar atalho de palavras-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Добавить учетные данные" + "value" : "Скрыть ярлык для паролей" } } } }, - "pm.add.login" : { - "comment" : "Add New Login button", + "pinning.hide-bookmarks-shortcut" : { + "comment" : "Menu item for hiding the bookmarks shortcut", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwort hinzufügen" + "value" : "Verknüpfung Lesezeichen ausblenden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Add Password" + "value" : "Hide Bookmarks Shortcut" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Añadir contraseña" + "value" : "Ocultar acceso directo a marcadores" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ajouter un mot de passe" + "value" : "Masquer le raccourci des signets" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aggiungi password" + "value" : "Nascondi scorciatoia segnalibri" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoord toevoegen" + "value" : "Snelkoppeling voor bladwijzers verbergen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dodaj hasło" + "value" : "Ukryj skrót do zakładek" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Adicionar palavra-passe" + "value" : "Ocultar atalho de marcadores" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Добавление пароля" + "value" : "Скрыть ярлык для закладок" } } } }, - "pm.add.new" : { - "comment" : "Add New item button", + "pinning.hide-downloads-shortcut" : { + "comment" : "Menu item for hiding the downloads shortcut", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neue hinzufügen" + "value" : "Download-Verknüpfung ausblenden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Add New" + "value" : "Hide Downloads Shortcut" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Añadir nuevo" + "value" : "Ocultar acceso directo a descargas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ajouter nouveau" + "value" : "Masquer le raccourci des téléchargements" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aggiungi nuovo" + "value" : "Nascondi scorciatoia download" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuw toevoegen" + "value" : "Snelkoppeling voor downloads verbergen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dodaj nowy" + "value" : "Ukryj skrót do pobranych" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Adicionar novo" + "value" : "Ocultar atalho de transferências" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Добавить" + "value" : "Скрыть ярлык для загрузок" } } } }, - "pm.added" : { - "comment" : "Label for login added data", + "pinning.hide-netp-shortcut" : { + "comment" : "Menu item for hiding the NetP shortcut", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hinzugefügt" + "value" : "VPN-Verknüpfung ausblenden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Added" + "value" : "Hide VPN Shortcut" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Añadido" + "value" : "Ocultar acceso directo a VPN" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ajouté" + "value" : "Masquer le raccourci VPN" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aggiunto" + "value" : "Nascondi scorciatoia VPN" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Toegevoegd" + "value" : "Snelkoppeling voor VPN verbergen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dodano" + "value" : "Ukryj skrót do VPN" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Adicionado" + "value" : "Ocultar atalho de VPN" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Добавлено" + "value" : "Скрыть ярлык для VPN" } } } }, - "pm.address.address1" : { - "comment" : "Label for address 1 title", + "pinning.show-autofill-shortcut" : { + "comment" : "Menu item for showing the passwords shortcut", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Adresse 1" + "value" : "Passwort-Shortcut anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Address 1" + "value" : "Show Passwords Shortcut" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Dirección 1" + "value" : "Mostrar acceso directo a contraseñas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Adresse 1" + "value" : "Afficher le raccourci des mots de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Indirizzo 1" + "value" : "Mostra scorciatoia per le password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Adres 1" + "value" : "Snelkoppeling voor wachtwoorden weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Adres 1" + "value" : "Pokaż skrót do haseł" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Morada 1" + "value" : "Mostrar atalho de palavras-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Адрес (строка 1)" + "value" : "Показывать ярлык для паролей" } } } }, - "pm.address.address2" : { - "comment" : "Label for address 2 title", + "pinning.show-bookmarks-shortcut" : { + "comment" : "Menu item for showing the bookmarks shortcut", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Adresse 2" + "value" : "Lesezeichen-Verknüpfung anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Address 2" + "value" : "Show Bookmarks Shortcut" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Dirección 2" + "value" : "Mostrar acceso directo a Favoritos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Adresse 2" + "value" : "Afficher le raccourci des signets" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Indirizzo 2" + "value" : "Mostra scorciatoia segnalibri" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Adres 2" + "value" : "Snelkoppeling voor bladwijzers weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Adres 2" + "value" : "Pokaż skrót do zakładek" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Morada 2" + "value" : "Mostrar atalho de marcadores" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Адрес (строка 2)" + "value" : "Показывать ярлык для закладок" } } } }, - "pm.address.city" : { - "comment" : "Label for city title", + "pinning.show-downloads-shortcut" : { + "comment" : "Menu item for showing the downloads shortcut", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ort" + "value" : "Download-Verknüpfung anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "City" + "value" : "Show Downloads Shortcut" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ciudad" + "value" : "Mostrar acceso directo a Descargas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ville" + "value" : "Afficher le raccourci des téléchargements" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Città" + "value" : "Mostra scorciatoia download" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Plaats" + "value" : "Snelkoppeling voor downloads weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Miasto" + "value" : "Pokaż skrót do pobranych" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Cidade" + "value" : "Mostrar atalho de transferências" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Город" + "value" : "Показать ярлык для загрузок" } } } }, - "pm.address.postal-code" : { - "comment" : "Label for postal code title", + "pinning.show-netp-shortcut" : { + "comment" : "Menu item for showing the NetP shortcut", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Postleitzahl" + "value" : "VPN-Verknüpfung anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Postal Code" + "value" : "Show VPN Shortcut" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Código postal" + "value" : "Mostrar acceso directo a VPN" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Code postal" + "value" : "Afficher le raccourci VPN" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Codice Postale" + "value" : "Mostra scorciatoia VPN" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Postcode" + "value" : "Snelkoppeing voor VPN weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Kod pocztowy" + "value" : "Pokaż skrót do VPN" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Código postal" + "value" : "Mostrar atalho de VPN" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Почтовый индекс" + "value" : "Показывать ярлык VPN" } } } }, - "pm.address.state-province" : { - "comment" : "Label for state/province title", - "extractionState" : "extracted_with_value", + "Please submit a report to help us fix the issue." : { + "comment" : "Data import failure Report dialog title.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Staat/Provinz" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "State/Province" + "value" : "Bitte reiche einen Bericht ein, damit wir das Problem beheben können." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Estado/Provincia" + "value" : "Envía un informe para ayudarnos a solucionar el problema." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Pays/Région" + "value" : "Veuillez soumettre un rapport pour nous aider à résoudre le problème." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Stato/Provincia" + "value" : "Invia una segnalazione per aiutarci a risolvere il problema." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Staat/Provincie" + "value" : "Dien een melding in om ons te helpen het probleem op te lossen." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Stan/prowincja/region" + "value" : "Prześlij raport, aby pomóc nam w rozwiązaniu problemu." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Distrito/província" + "value" : "Envia um relatório para nos ajudar a corrigir o problema." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Штат/регион" + "value" : "Отправьте отчет, чтобы мы могли устранить ошибку." } } } }, - "pm.cancel" : { - "comment" : "Cancel button", + "pm.activate" : { + "comment" : "Activate button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Abbrechen" + "value" : "Reaktivieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Cancel" + "value" : "Reactivate" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cancelar" + "value" : "Reactivar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Annuler" + "value" : "Réactiver" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Annulla" + "value" : "Riattiva" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Annuleren" + "value" : "Opnieuw activeren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Anuluj" + "value" : "Ponownie aktywuj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Cancelar" + "value" : "Reativar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отменить" + "value" : "Активировать" } } } }, - "pm.card.cardholder-name" : { - "comment" : "Label for cardholder name title", + "pm.activate.private.email" : { + "comment" : "Activate private email address button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Name des Karteninhabers" + "value" : "Duck Address reaktivieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Cardholder Name" + "value" : "Reactivate Duck Address" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nombre del titular de la tarjeta" + "value" : "Reactivar Duck Address" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nom du titulaire de la carte" + "value" : "Réactiver la Duck Address" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nome del titolare della carta" + "value" : "Riattiva Duck Address" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Naam kaarthouder" + "value" : "Duck Address reactiveren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nazwisko właściciela karty" + "value" : "Aktywuj ponownie Duck Address" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nome do titular do cartão" + "value" : "Reativar Duck Address" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Имя держателя карты" + "value" : "Снова активировать Duck Address" } } } }, - "pm.card.cvv" : { - "comment" : "Label for CVV title", + "pm.add.card" : { + "comment" : "Add New Credit Card button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "CVV" + "value" : "Kreditkarte hinzufügen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "CVV" + "value" : "Add Credit Card" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "CVV" + "value" : "Añadir tarjeta de crédito" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "CVV" + "value" : "Ajouter une carte bancaire" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "CVV" + "value" : "Aggiungi carta di credito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "CVV" + "value" : "Creditcard toevoegen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "CVV" + "value" : "Dodaj kartę kredytową" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "CVV" + "value" : "Adicionar cartão de crédito" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "CVV-код" + "value" : "Добавить карту" } } } }, - "pm.card.expiration-date" : { - "comment" : "Label for expiration date title", + "pm.add.identity" : { + "comment" : "Add New Identity button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verfallsdatum" + "value" : "Identität hinzufügen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Expiration Date" + "value" : "Add Identity" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Fecha de caducidad" + "value" : "Añadir identidad" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Date d'expiration" + "value" : "Ajouter une identité" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Data di scadenza" + "value" : "Aggiungi identità" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Vervaldatum" + "value" : "Identiteit toevoegen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Data ważności" + "value" : "Dodaj tożsamość" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Data de validade" + "value" : "Adicionar identidade" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Срок действия" + "value" : "Добавить учетные данные" } } } }, - "pm.card.number" : { - "comment" : "Label for card number title", + "pm.add.login" : { + "comment" : "Add New Login button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nummer der Karte" + "value" : "Passwort hinzufügen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Card Number" + "value" : "Add Password" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Número de tarjeta" + "value" : "Añadir contraseña" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Numéro de carte" + "value" : "Ajouter un mot de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Numero della carta" + "value" : "Aggiungi password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Kaartnummer" + "value" : "Wachtwoord toevoegen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Numer karty" + "value" : "Dodaj hasło" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Número do cartão" + "value" : "Adicionar palavra-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Номер карты" + "value" : "Добавление пароля" } } } }, - "pm.day" : { - "comment" : "Label for Day title", + "pm.add.new" : { + "comment" : "Add New item button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Tag" + "value" : "Neue hinzufügen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Day" + "value" : "Add New" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Día" + "value" : "Añadir nuevo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Jour" + "value" : "Ajouter nouveau" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Giorno" + "value" : "Aggiungi nuovo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Dag" + "value" : "Nieuw toevoegen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dzień" + "value" : "Dodaj nowy" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Dia" + "value" : "Adicionar novo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "День" + "value" : "Добавить" } } } }, - "pm.deactivate" : { - "comment" : "Deactivate button", + "pm.added" : { + "comment" : "Label for login added data", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Deaktivieren" + "value" : "Hinzugefügt" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Deactivate" + "value" : "Added" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Desactivar" + "value" : "Añadido" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Désactiver" + "value" : "Ajouté" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Disattiva" + "value" : "Aggiunto" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Deactiveren" + "value" : "Toegevoegd" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dezaktywuj" + "value" : "Dodano" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Desativar" + "value" : "Adicionado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Деактивировать" + "value" : "Добавлено" } } } }, - "pm.deactivate.private.email" : { - "comment" : "Deactivate private email address button", + "pm.address.address1" : { + "comment" : "Label for address 1 title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address deaktivieren" + "value" : "Adresse 1" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Deactivate Duck Address" + "value" : "Address 1" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Desactivar Duck Address" + "value" : "Dirección 1" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Désactiver la Duck Address" + "value" : "Adresse 1" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Disattiva Duck Address" + "value" : "Indirizzo 1" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address deactiveren" + "value" : "Adres 1" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dezaktywuj Duck Address" + "value" : "Adres 1" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Desativar Duck Address" + "value" : "Morada 1" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Деактивировать Duck Address" + "value" : "Адрес (строка 1)" } } } }, - "pm.delete" : { - "comment" : "Delete button", + "pm.address.address2" : { + "comment" : "Label for address 2 title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Löschen" + "value" : "Adresse 2" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Delete" + "value" : "Address 2" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar" + "value" : "Dirección 2" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Supprimer" + "value" : "Adresse 2" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cancella" + "value" : "Indirizzo 2" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verwijderen" + "value" : "Adres 2" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usuń" + "value" : "Adres 2" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar" + "value" : "Morada 2" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удалить" + "value" : "Адрес (строка 2)" } } } }, - "pm.edit" : { - "comment" : "Edit button", + "pm.address.city" : { + "comment" : "Label for city title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bearbeiten" + "value" : "Ort" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Edit" + "value" : "City" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Editar" + "value" : "Ciudad" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Modifier" + "value" : "Ville" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Modifica" + "value" : "Città" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bewerken" + "value" : "Plaats" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Edytuj" + "value" : "Miasto" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Editar" + "value" : "Cidade" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Редактировать" + "value" : "Город" } } } }, - "pm.email-address" : { - "comment" : "Label for email address title", + "pm.address.postal-code" : { + "comment" : "Label for postal code title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "E-Mail-Adresse" + "value" : "Postleitzahl" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Email Address" + "value" : "Postal Code" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Dirección de correo electrónico" + "value" : "Código postal" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Adresse e-mail" + "value" : "Code postal" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Indirizzo e-mail" + "value" : "Codice Postale" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "E-mailadres" + "value" : "Postcode" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Adres e-mail" + "value" : "Kod pocztowy" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Endereço de e-mail" + "value" : "Código postal" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Адрес эл. почты" + "value" : "Почтовый индекс" } } } }, - "pm.empty.cards.title" : { - "comment" : "Label for cards empty state title", + "pm.address.state-province" : { + "comment" : "Label for state/province title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine Karten" + "value" : "Staat/Provinz" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "No Cards" + "value" : "State/Province" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Sin tarjetas" + "value" : "Estado/Provincia" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aucune carte" + "value" : "Pays/Région" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nessuna carta" + "value" : "Stato/Provincia" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geen kaarten" + "value" : "Staat/Provincie" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Brak kart" + "value" : "Stan/prowincja/region" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Sem cartões" + "value" : "Distrito/província" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Нет карт" + "value" : "Штат/регион" } } } }, - "pm.empty.default.button.title" : { - "comment" : "Import passwords button title for default empty state", + "pm.cancel" : { + "comment" : "Cancel button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter importieren" + "value" : "Abbrechen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Import Passwords" + "value" : "Cancel" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Importar contraseñas" + "value" : "Cancelar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Importer les mots de passe" + "value" : "Annuler" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Importa password" + "value" : "Annulla" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoorden importeren" + "value" : "Annuleren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Importuj hasła" + "value" : "Anuluj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Importar palavras-passe" + "value" : "Cancelar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Импорт паролей" + "value" : "Отменить" } } } }, - "pm.empty.default.description" : { - "comment" : "Label for default empty state description", + "pm.card.cardholder-name" : { + "comment" : "Label for cardholder name title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wenn deine Passwörter in einem anderen Browser gespeichert sind, kannst du sie in DuckDuckGo importieren." + "value" : "Name des Karteninhabers" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "If your passwords are saved in another browser, you can import them into DuckDuckGo." + "value" : "Cardholder Name" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Si tienes contraseñas guardadas en otro navegador, puedes importarlas a DuckDuckGo." + "value" : "Nombre del titular de la tarjeta" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Si vos mots de passe sont enregistrés dans un autre navigateur, vous pouvez les importer dans DuckDuckGo." + "value" : "Nom du titulaire de la carte" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Se le password vengono salvate in un altro browser, è possibile importarle in DuckDuckGo." + "value" : "Nome del titolare della carta" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Als je wachtwoorden in een andere browser zijn opgeslagen, kun je ze in DuckDuckGo importeren." + "value" : "Naam kaarthouder" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Jeśli Twoje hasła są zapisane w innej przeglądarce, możesz je zaimportować do DuckDuckGo." + "value" : "Nazwisko właściciela karty" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Se as tuas palavras-passe estão guardadas noutro navegador, podes importá-las para o DuckDuckGo." + "value" : "Nome do titular do cartão" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Если вы уже храните пароли в другом браузере, вы можете импортировать их в DuckDuckGo." + "value" : "Имя держателя карты" } } } }, - "pm.empty.default.title" : { - "comment" : "Label for default empty state title", + "pm.card.cvv" : { + "comment" : "Label for CVV title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Noch keine Passwörter oder Kreditkarten gespeichert" + "value" : "CVV" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "No passwords or credit cards saved yet" + "value" : "CVV" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Aún no hay contraseñas ni tarjetas de crédito guardadas" + "value" : "CVV" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aucun mot de passe ni aucune carte bancaire n'a encore été enregistré(e)" + "value" : "CVV" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nessuna password o carta di credito salvata" + "value" : "CVV" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nog geen wachtwoorden of creditcards opgeslagen" + "value" : "CVV" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Jeszcze nie zapisano haseł ani kart kredytowych" + "value" : "CVV" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ainda não há palavras-passe ou cartões de crédito guardados" + "value" : "CVV" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сохраненных паролей или платежных карт пока нет" + "value" : "CVV-код" } } } }, - "pm.empty.identities.title" : { - "comment" : "Label for identities empty state title", + "pm.card.expiration-date" : { + "comment" : "Label for expiration date title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine Identitäten" + "value" : "Verfallsdatum" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "No Identities" + "value" : "Expiration Date" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Sin identidades" + "value" : "Fecha de caducidad" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aucune identité" + "value" : "Date d'expiration" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nessuna identità" + "value" : "Data di scadenza" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geen identiteiten" + "value" : "Vervaldatum" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Brak tożsamości" + "value" : "Data ważności" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Sem identidades" + "value" : "Data de validade" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Нет учетных данных" + "value" : "Срок действия" } } } }, - "pm.empty.logins.title" : { - "comment" : "Label for logins empty state title", + "pm.card.number" : { + "comment" : "Label for card number title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Noch keine Passwörter gespeichert" + "value" : "Nummer der Karte" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "No passwords saved yet" + "value" : "Card Number" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Aún no hay contraseñas guardadas" + "value" : "Número de tarjeta" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aucun mot de passe n'a été enregistré" + "value" : "Numéro de carte" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nessuna password ancora salvata" + "value" : "Numero della carta" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nog geen wachtwoorden opgeslagen" + "value" : "Kaartnummer" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie zapisano jeszcze żadnych haseł" + "value" : "Numer karty" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ainda não há palavras-passe guardadas" + "value" : "Número do cartão" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сохраненных паролей пока нет" + "value" : "Номер карты" } } } }, - "pm.empty.notes.title" : { - "comment" : "Label for notes empty state title", + "pm.day" : { + "comment" : "Label for Day title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine Notizen" + "value" : "Tag" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "No Notes" + "value" : "Day" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Sin notas" + "value" : "Día" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aucune note" + "value" : "Jour" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nessuna nota" + "value" : "Giorno" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geen notities" + "value" : "Dag" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Brak notatek" + "value" : "Dzień" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Sem notas" + "value" : "Dia" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Нет примечаний" + "value" : "День" } } } }, - "pm.enable.email.protection" : { - "comment" : "Text link to email protection website", + "pm.deactivate" : { + "comment" : "Deactivate button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Email Protection aktivieren" + "value" : "Deaktivieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Enable Email Protection" + "value" : "Deactivate" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Activar Email Protection" + "value" : "Desactivar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Activez Email Protection" + "value" : "Désactiver" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Abilita Email Protection" + "value" : "Disattiva" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "E-mailbeveiliging inschakelen" + "value" : "Deactiveren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Włącz ochronę poczty Email Protection" + "value" : "Dezaktywuj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ativa a Email Protection" + "value" : "Desativar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Включите защиту электронной почты" + "value" : "Деактивировать" } } } }, - "pm.identification" : { - "comment" : "Label for identification title", + "pm.deactivate.private.email" : { + "comment" : "Deactivate private email address button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Identifikation" + "value" : "Duck Address deaktivieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Identification" + "value" : "Deactivate Duck Address" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Identificación" + "value" : "Desactivar Duck Address" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Identification" + "value" : "Désactiver la Duck Address" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Identificazione" + "value" : "Disattiva Duck Address" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Identificatie" + "value" : "Duck Address deactiveren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Identyfikacja" + "value" : "Dezaktywuj Duck Address" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Identificação" + "value" : "Desativar Duck Address" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Учетные данные" + "value" : "Деактивировать Duck Address" } } } }, - "pm.identity.autofill.title.default" : { - "comment" : "Default title for Addresses/Identities", + "pm.delete" : { + "comment" : "Delete button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Adresse" + "value" : "Löschen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Address" + "value" : "Delete" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Dirección" + "value" : "Eliminar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Adresse" + "value" : "Supprimer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Indirizzo" + "value" : "Cancella" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Adres" + "value" : "Verwijderen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Adres" + "value" : "Usuń" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Morada" + "value" : "Eliminar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Адрес" + "value" : "Удалить" } } } }, - "pm.last.updated" : { - "comment" : "Label for last updated edit field", + "pm.edit" : { + "comment" : "Edit button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zuletzt aktualisiert" + "value" : "Bearbeiten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Last Updated" + "value" : "Edit" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Última actualización" + "value" : "Editar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Dernière mise à jour" + "value" : "Modifier" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ultimo aggiornamento" + "value" : "Modifica" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Laatst bijgewerkt" + "value" : "Bewerken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ostatnia aktualizacja" + "value" : "Edytuj" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Última atualização" + "value" : "Editar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Последнее обновление" + "value" : "Редактировать" } } } }, - "pm.lock-screen.duration" : { - "comment" : "Message about the duration for which autofill information remains unlocked on the lock screen.", + "pm.email-address" : { + "comment" : "Label for email address title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Deine Autovervollständigungsdaten bleiben freigeschaltet, bis dein Computer für %@ inaktiv ist." + "value" : "E-Mail-Adresse" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Your autofill info will remain unlocked until your computer is idle for %@." + "value" : "Email Address" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Tu información de autocompletar permanecerá desbloqueada hasta que tu ordenador esté inactivo durante %@." + "value" : "Dirección de correo electrónico" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Vos informations de saisie automatique resteront déverrouillées jusqu'à ce que votre ordinateur soit inactif pendant %@." + "value" : "Adresse e-mail" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "La compilazione automatica delle informazioni rimarrà sbloccata per %@ fino a quando il computer non si disattiverà." + "value" : "Indirizzo e-mail" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Je automatisch ingevulde gegevens blijven ontgrendeld totdat je computer %@ inactief is." + "value" : "E-mailadres" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Informacje autouzupełniania pozostaną odblokowane do czasu, aż komputer będzie bezczynny przez %@." + "value" : "Adres e-mail" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "As tuas informações de preenchimento automático permanecerão desbloqueadas durante %@ até o teu computador ficar inativo." + "value" : "Endereço de e-mail" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Ваши автозаполняемые данные останутся в разблокированном состоянии в течение %@, пока компьютер не перейдет в режим простоя." + "value" : "Адрес эл. почты" } } } }, - "pm.lock-screen.preferences.label" : { - "comment" : "Label used for a button that opens preferences", + "pm.empty.cards.title" : { + "comment" : "Label for cards empty state title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Änderung in" + "value" : "Keine Karten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Change in" + "value" : "No Cards" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cambiar en" + "value" : "Sin tarjetas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Modification dans" + "value" : "Aucune carte" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cambiamento di" + "value" : "Nessuna carta" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Veranderen in" + "value" : "Geen kaarten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zmień w obszarze" + "value" : "Brak kart" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Alterar nas" + "value" : "Sem cartões" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Изменить в разделе" + "value" : "Нет карт" } } } }, - "pm.lock-screen.preferences.link" : { - "comment" : "Label used for a button that opens preferences", + "pm.empty.default.button.title" : { + "comment" : "Import passwords button title for default empty state", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einstellungen" + "value" : "Passwörter importieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Settings" + "value" : "Import Passwords" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes" + "value" : "Importar contraseñas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Paramètres" + "value" : "Importer les mots de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Impostazioni" + "value" : "Importa password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Instellingen" + "value" : "Wachtwoorden importeren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustawienia" + "value" : "Importuj hasła" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Definições" + "value" : "Importar palavras-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Настройки" + "value" : "Импорт паролей" } } } }, - "pm.lock-screen.prompt.autofill" : { - "comment" : "Label presented when autofilling credit card information", + "pm.empty.default.description" : { + "comment" : "Label for default empty state description", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zugang zu deinen Autovervollständigungs-Infos freischalten" + "value" : "Wenn deine Passwörter in einem anderen Browser gespeichert sind, kannst du sie in DuckDuckGo importieren." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "unlock access to your autofill info" + "value" : "If your passwords are saved in another browser, you can import them into DuckDuckGo." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "desbloquear el acceso a tu información de autocompletar" + "value" : "Si tienes contraseñas guardadas en otro navegador, puedes importarlas a DuckDuckGo." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "déverrouiller l'accès à vos informations de saisie automatique" + "value" : "Si vos mots de passe sont enregistrés dans un autre navigateur, vous pouvez les importer dans DuckDuckGo." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "sblocca l'accesso alla compilazione automatica delle informazioni" + "value" : "Se le password vengono salvate in un altro browser, è possibile importarle in DuckDuckGo." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "toegang tot automatisch ingevulde gegevens ontgrendelen" + "value" : "Als je wachtwoorden in een andere browser zijn opgeslagen, kun je ze in DuckDuckGo importeren." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "odblokuj dostęp do informacji autouzupełniania" + "value" : "Jeśli Twoje hasła są zapisane w innej przeglądarce, możesz je zaimportować do DuckDuckGo." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "desbloquear o acesso às tuas informações de preenchimento automático" + "value" : "Se as tuas palavras-passe estão guardadas noutro navegador, podes importá-las para o DuckDuckGo." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Разблокировать доступ к автозаполняемым данным" + "value" : "Если вы уже храните пароли в другом браузере, вы можете импортировать их в DuckDuckGo." } } } }, - "pm.lock-screen.prompt.change-settings" : { - "comment" : "Label presented when changing Auto-Lock settings", + "pm.empty.default.title" : { + "comment" : "Label for default empty state title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einstellungen für das automatische Ausfüllen von Informationen ändern" + "value" : "Noch keine Passwörter oder Kreditkarten gespeichert" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "change your autofill info access settings" + "value" : "No passwords or credit cards saved yet" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "cambiar la configuración de acceso a la información de autocompletar" + "value" : "Aún no hay contraseñas ni tarjetas de crédito guardadas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "modifier les paramètres d'accès à vos informations de saisie automatique" + "value" : "Aucun mot de passe ni aucune carte bancaire n'a encore été enregistré(e)" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "modifica le impostazioni di accesso alla compilazione automatica delle informazioni" + "value" : "Nessuna password o carta di credito salvata" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "toegangsinstellingen voor automatisch ingevulde gegevens wijzigen" + "value" : "Nog geen wachtwoorden of creditcards opgeslagen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "zmień ustawienia dostępu do informacji autouzupełniania" + "value" : "Jeszcze nie zapisano haseł ani kart kredytowych" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "alterar as tuas definições de acesso às informações de preenchimento automático" + "value" : "Ainda não há palavras-passe ou cartões de crédito guardados" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Изменить настройки доступа к автозаполняемым данным" + "value" : "Сохраненных паролей или платежных карт пока нет" } } } }, - "pm.lock-screen.prompt.export-logins" : { - "comment" : "Label presented when exporting logins", + "pm.empty.identities.title" : { + "comment" : "Label for identities empty state title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Deine Benutzernamen und Passwörter exportieren" + "value" : "Keine Identitäten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "export your usernames and passwords" + "value" : "No Identities" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "exporta tus nombres de usuario y contraseñas" + "value" : "Sin identidades" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "exporter vos noms d'utilisateur et mots de passe" + "value" : "Aucune identité" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "esporta i tuoi nomi utente e le tue password" + "value" : "Nessuna identità" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "je gebruikersnamen en wachtwoorden exporteren" + "value" : "Geen identiteiten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "eksportuj swoje nazwy użytkownika i hasła" + "value" : "Brak tożsamości" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "exportar os nomes de utilizador e as palavras-passe" + "value" : "Sem identidades" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Экспортировать имена пользователей и пароли" + "value" : "Нет учетных данных" } } } }, - "pm.lock-screen.prompt.unlock-logins" : { - "comment" : "Label presented when unlocking Autofill", + "pm.empty.logins.title" : { + "comment" : "Label for logins empty state title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "deine Passwörter freischalten und Informationen autovervollständigen" + "value" : "Noch keine Passwörter gespeichert" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "unlock your passwords and autofill info for you" + "value" : "No passwords saved yet" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "desbloquea tus contraseñas y completa automáticamente tu información" + "value" : "Aún no hay contraseñas guardadas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "débloque vos mots de passe et saisit automatiquement les informations pour vous" + "value" : "Aucun mot de passe n'a été enregistré" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "sblocca le tue password e compila automaticamente le informazioni" + "value" : "Nessuna password ancora salvata" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "ontgrendel je wachtwoorden en vul gegevens automatisch in" + "value" : "Nog geen wachtwoorden opgeslagen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "odblokuj hasła i automatycznie uzupełniaj informacje" + "value" : "Nie zapisano jeszcze żadnych haseł" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "desbloquear as palavras-passe e preencher automaticamente informações por ti" + "value" : "Ainda não há palavras-passe guardadas" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Разблокирует ваши пароли и автоматически заполнит информацию" + "value" : "Сохраненных паролей пока нет" } } } }, - "pm.lock-screen.threshold.1-hour" : { - "comment" : "Label used when selecting the Auto-Lock threshold", + "pm.empty.notes.title" : { + "comment" : "Label for notes empty state title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "1 Stunde" + "value" : "Keine Notizen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "1 hour" + "value" : "No Notes" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "1 hora" + "value" : "Sin notas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "1 heure" + "value" : "Aucune note" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "1 ora" + "value" : "Nessuna nota" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "1 uur" + "value" : "Geen notities" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "1 godzina" + "value" : "Brak notatek" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "1 hora" + "value" : "Sem notas" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "1 ч." + "value" : "Нет примечаний" } } } }, - "pm.lock-screen.threshold.1-minute" : { - "comment" : "Label used when selecting the Auto-Lock threshold", + "pm.enable.email.protection" : { + "comment" : "Text link to email protection website", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "1 Minute" + "value" : "Email Protection aktivieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "1 minute" + "value" : "Enable Email Protection" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "1 minuto" + "value" : "Activar Email Protection" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "1 minute" + "value" : "Activez Email Protection" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "1 minuto" + "value" : "Abilita Email Protection" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "1 minuut" + "value" : "E-mailbeveiliging inschakelen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "1 minuta" + "value" : "Włącz ochronę poczty Email Protection" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "1 minuto" + "value" : "Ativa a Email Protection" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "1 мин." + "value" : "Включите защиту электронной почты" } } } }, - "pm.lock-screen.threshold.5-minutes" : { - "comment" : "Label used when selecting the Auto-Lock threshold", + "pm.identification" : { + "comment" : "Label for identification title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "5 Minuten" + "value" : "Identifikation" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "5 minutes" + "value" : "Identification" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "5 minutos" + "value" : "Identificación" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "5 minutes" + "value" : "Identification" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "5 minuti" + "value" : "Identificazione" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "5 minuten" + "value" : "Identificatie" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "5 minut" + "value" : "Identyfikacja" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "5 minutos" + "value" : "Identificação" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "5 мин." + "value" : "Учетные данные" } } } }, - "pm.lock-screen.threshold.12-hours" : { - "comment" : "Label used when selecting the Auto-Lock threshold", + "pm.identity.autofill.title.default" : { + "comment" : "Default title for Addresses/Identities", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "12 Stunden" + "value" : "Adresse" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "12 hours" + "value" : "Address" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "12 horas" + "value" : "Dirección" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "12 heures" + "value" : "Adresse" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "12 ore" + "value" : "Indirizzo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "12 uur" + "value" : "Adres" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "12 godzin" + "value" : "Adres" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "12 horas" + "value" : "Morada" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "12 ч." + "value" : "Адрес" } } } }, - "pm.lock-screen.threshold.15-minutes" : { - "comment" : "Label used when selecting the Auto-Lock threshold", + "pm.last.updated" : { + "comment" : "Label for last updated edit field", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "15 Minuten" + "value" : "Zuletzt aktualisiert" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "15 minutes" + "value" : "Last Updated" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "15 minutos" + "value" : "Última actualización" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "15 minutes" + "value" : "Dernière mise à jour" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "15 minuti" + "value" : "Ultimo aggiornamento" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "15 minuten" + "value" : "Laatst bijgewerkt" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "15 minut" + "value" : "Ostatnia aktualizacja" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "15 minutos" + "value" : "Última atualização" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "15 мин." + "value" : "Последнее обновление" } } } }, - "pm.lock-screen.threshold.30-minutes" : { - "comment" : "Label used when selecting the Auto-Lock threshold", + "pm.lock-screen.duration" : { + "comment" : "Message about the duration for which autofill information remains unlocked on the lock screen.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "30 Minuten" + "value" : "Deine Autovervollständigungsdaten bleiben freigeschaltet, bis dein Computer für %@ inaktiv ist." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "30 minutes" + "value" : "Your autofill info will remain unlocked until your computer is idle for %@." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "30 minutos" + "value" : "Tu información de autocompletar permanecerá desbloqueada hasta que tu ordenador esté inactivo durante %@." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "30 minutes" + "value" : "Vos informations de saisie automatique resteront déverrouillées jusqu'à ce que votre ordinateur soit inactif pendant %@." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "30 minuti" + "value" : "La compilazione automatica delle informazioni rimarrà sbloccata per %@ fino a quando il computer non si disattiverà." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "30 minuten" + "value" : "Je automatisch ingevulde gegevens blijven ontgrendeld totdat je computer %@ inactief is." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "30 minut" + "value" : "Informacje autouzupełniania pozostaną odblokowane do czasu, aż komputer będzie bezczynny przez %@." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "30 minutos" + "value" : "As tuas informações de preenchimento automático permanecerão desbloqueadas durante %@ até o teu computador ficar inativo." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "30 мин." + "value" : "Ваши автозаполняемые данные останутся в разблокированном состоянии в течение %@, пока компьютер не перейдет в режим простоя." } } } }, - "pm.month" : { - "comment" : "Label for Month title", + "pm.lock-screen.preferences.label" : { + "comment" : "Label used for a button that opens preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Monat" + "value" : "Änderung in" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Month" + "value" : "Change in" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mes" + "value" : "Cambiar en" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mois" + "value" : "Modification dans" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mese" + "value" : "Cambiamento di" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Maand" + "value" : "Veranderen in" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Miesiąc" + "value" : "Zmień w obszarze" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mês" + "value" : "Alterar nas" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Месяц" + "value" : "Изменить в разделе" } } } }, - "pm.name.first" : { - "comment" : "Label for first name title", + "pm.lock-screen.preferences.link" : { + "comment" : "Label used for a button that opens preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Vorname" + "value" : "Einstellungen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "First Name" + "value" : "Settings" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nombre" + "value" : "Ajustes" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Prénom" + "value" : "Paramètres" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nome" + "value" : "Impostazioni" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Voornaam" + "value" : "Instellingen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Imię" + "value" : "Ustawienia" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nome próprio" + "value" : "Definições" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Имя" + "value" : "Настройки" } } } }, - "pm.name.last" : { - "comment" : "Label for last name title", + "pm.lock-screen.prompt.autofill" : { + "comment" : "Label presented when autofilling credit card information", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nachname" + "value" : "Zugang zu deinen Autovervollständigungs-Infos freischalten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Last Name" + "value" : "unlock access to your autofill info" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Apellidos" + "value" : "desbloquear el acceso a tu información de autocompletar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nom de famille" + "value" : "déverrouiller l'accès à vos informations de saisie automatique" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cognome" + "value" : "sblocca l'accesso alla compilazione automatica delle informazioni" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Achternaam" + "value" : "toegang tot automatisch ingevulde gegevens ontgrendelen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nazwisko" + "value" : "odblokuj dostęp do informacji autouzupełniania" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Apelido" + "value" : "desbloquear o acesso às tuas informações de preenchimento automático" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Фамилия" + "value" : "Разблокировать доступ к автозаполняемым данным" } } } }, - "pm.name.middle" : { - "comment" : "Label for middle name title", + "pm.lock-screen.prompt.change-settings" : { + "comment" : "Label presented when changing Auto-Lock settings", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Mittlerer Name" + "value" : "Einstellungen für das automatische Ausfüllen von Informationen ändern" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Middle Name" + "value" : "change your autofill info access settings" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Segundo nombre" + "value" : "cambiar la configuración de acceso a la información de autocompletar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Deuxième prénom" + "value" : "modifier les paramètres d'accès à vos informations de saisie automatique" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Secondo nome" + "value" : "modifica le impostazioni di accesso alla compilazione automatica delle informazioni" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Middelste naam" + "value" : "toegangsinstellingen voor automatisch ingevulde gegevens wijzigen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Drugie imię" + "value" : "zmień ustawienia dostępu do informacji autouzupełniania" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nome do meio" + "value" : "alterar as tuas definições de acesso às informações de preenchimento automático" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Второе имя" + "value" : "Изменить настройки доступа к автозаполняемым данным" } } } }, - "pm.new.card" : { - "comment" : "Label for new card title", + "pm.lock-screen.prompt.export-logins" : { + "comment" : "Label presented when exporting logins", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kreditkarte" + "value" : "Deine Benutzernamen und Passwörter exportieren" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Credit Card" + "value" : "export your usernames and passwords" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Tarjeta de crédito" + "value" : "exporta tus nombres de usuario y contraseñas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Carte bancaire" + "value" : "exporter vos noms d'utilisateur et mots de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Carta di credito" + "value" : "esporta i tuoi nomi utente e le tue password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Creditcard" + "value" : "je gebruikersnamen en wachtwoorden exporteren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Karta kredytowa" + "value" : "eksportuj swoje nazwy użytkownika i hasła" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Cartão de crédito" + "value" : "exportar os nomes de utilizador e as palavras-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Платежная карта" + "value" : "Экспортировать имена пользователей и пароли" } } } }, - "pm.new.identity" : { - "comment" : "Label for new identity title", + "pm.lock-screen.prompt.unlock-logins" : { + "comment" : "Label presented when unlocking Autofill", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Identität" + "value" : "deine Passwörter freischalten und Informationen autovervollständigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Identity" + "value" : "unlock your passwords and autofill info for you" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Identidad" + "value" : "desbloquea tus contraseñas y completa automáticamente tu información" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Identité" + "value" : "débloque vos mots de passe et saisit automatiquement les informations pour vous" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Identità" + "value" : "sblocca le tue password e compila automaticamente le informazioni" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Identiteit" + "value" : "ontgrendel je wachtwoorden en vul gegevens automatisch in" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Tożsamość" + "value" : "odblokuj hasła i automatycznie uzupełniaj informacje" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Identidade" + "value" : "desbloquear as palavras-passe e preencher automaticamente informações por ti" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Учетные данные" + "value" : "Разблокирует ваши пароли и автоматически заполнит информацию" } } } }, - "pm.new.login" : { - "comment" : "Label for new login title", + "pm.lock-screen.threshold.1-hour" : { + "comment" : "Label used when selecting the Auto-Lock threshold", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwort" + "value" : "1 Stunde" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Password" + "value" : "1 hour" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Contraseña" + "value" : "1 hora" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mot de passe" + "value" : "1 heure" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Password" + "value" : "1 ora" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoord" + "value" : "1 uur" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Hasło" + "value" : "1 godzina" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Palavra-passe" + "value" : "1 hora" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пароль" + "value" : "1 ч." } } } }, - "pm.new.note" : { - "comment" : "Label for new note title", + "pm.lock-screen.threshold.1-minute" : { + "comment" : "Label used when selecting the Auto-Lock threshold", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hinweis" + "value" : "1 Minute" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Note" + "value" : "1 minute" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nota" + "value" : "1 minuto" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Note" + "value" : "1 minute" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nota" + "value" : "1 minuto" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Notitie" + "value" : "1 minuut" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Notatka" + "value" : "1 minuta" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nota" + "value" : "1 minuto" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Примечание" + "value" : "1 мин." } } } }, - "pm.note" : { - "comment" : "Label for note title", + "pm.lock-screen.threshold.5-minutes" : { + "comment" : "Label used when selecting the Auto-Lock threshold", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hinweis" + "value" : "5 Minuten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Note" + "value" : "5 minutes" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nota" + "value" : "5 minutos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Note" + "value" : "5 minutes" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nota" + "value" : "5 minuti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Opmerking" + "value" : "5 minuten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Notatka" + "value" : "5 minut" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nota" + "value" : "5 minutos" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Примечание" + "value" : "5 мин." } } } }, - "pm.note.empty" : { - "comment" : "Label for empty note title", + "pm.lock-screen.threshold.12-hours" : { + "comment" : "Label used when selecting the Auto-Lock threshold", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Leerer Hinweis" + "value" : "12 Stunden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Empty note" + "value" : "12 hours" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nota vacía" + "value" : "12 horas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Note vide" + "value" : "12 heures" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nota vuota" + "value" : "12 ore" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Lege notitie" + "value" : "12 uur" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pusta notatka" + "value" : "12 godzin" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nota vazia" + "value" : "12 horas" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пустое примечание" + "value" : "12 ч." } } } }, - "pm.notes" : { - "comment" : "Label for notes edit field", + "pm.lock-screen.threshold.15-minutes" : { + "comment" : "Label used when selecting the Auto-Lock threshold", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Notizen" + "value" : "15 Minuten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Notes" + "value" : "15 minutes" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Notas" + "value" : "15 minutos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Notes" + "value" : "15 minutes" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Note" + "value" : "15 minuti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Opmerkingen" + "value" : "15 minuten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Uwagi" + "value" : "15 minut" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Notas" + "value" : "15 minutos" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Примечания" + "value" : "15 мин." } } } }, - "pm.password" : { - "comment" : "Label for password edit field", + "pm.lock-screen.threshold.30-minutes" : { + "comment" : "Label used when selecting the Auto-Lock threshold", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwort" + "value" : "30 Minuten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Password" + "value" : "30 minutes" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Contraseña" + "value" : "30 minutos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mot de passe" + "value" : "30 minutes" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Password" + "value" : "30 minuti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoord" + "value" : "30 minuten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Hasło" + "value" : "30 minut" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Palavra-passe" + "value" : "30 minutos" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пароль" + "value" : "30 мин." } } } }, - "pm.phone-number" : { - "comment" : "Label for phone number title", + "pm.month" : { + "comment" : "Label for Month title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Telefonnummer" + "value" : "Monat" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Phone Number" + "value" : "Month" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Número de teléfono" + "value" : "Mes" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Numéro de téléphone" + "value" : "Mois" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Numero di telefono" + "value" : "Mese" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Telefoonnummer" + "value" : "Maand" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Numer telefonu" + "value" : "Miesiąc" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Número de telefone" + "value" : "Mês" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Номер телефона" + "value" : "Месяц" } } } }, - "pm.private.email.mesage.activate.confirm.content" : { - "comment" : "Text for the confirmation message displayed when a user tries activate a Private Email Address", + "pm.name.first" : { + "comment" : "Label for first name title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "E-Mails, die an %@ gesendet werden, werden wieder an deinen Posteingang weitergeleitet." + "value" : "Vorname" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Emails sent to %@ will again be forwarded to your inbox." + "value" : "First Name" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Los correos electrónicos enviados a %@ volverán a reenviarse a tu bandeja de entrada." + "value" : "Nombre" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Les e-mails envoyés à %@ seront à nouveau transférés vers votre boîte de réception." + "value" : "Prénom" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Le e-mail inviate a %@ saranno nuovamente inoltrate alla tua casella di posta." + "value" : "Nome" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "E-mails die naar %@ worden gestuurd, worden opnieuw doorgestuurd naar je inbox." + "value" : "Voornaam" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wiadomości e-mail wysłane do %@ będą ponownie przekazywane do Twojej skrzynki odbiorczej." + "value" : "Imię" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Os e-mails enviados para %@ serão encaminhados para a tua caixa de entrada." + "value" : "Nome próprio" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Электронные письма, отправленные на адрес %@, снова будут пересылаться на ваш почтовый ящик." + "value" : "Имя" } } } }, - "pm.private.email.mesage.activate.confirm.title" : { - "comment" : "Title for the confirmation message displayed when a user tries activate a Private Email Address", + "pm.name.last" : { + "comment" : "Label for last name title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Private Duck Address reaktivieren?" + "value" : "Nachname" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Reactivate Private Duck Address?" + "value" : "Last Name" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Reactivar la Duck Address privada?" + "value" : "Apellidos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Réactiver la Duck Address privée ?" + "value" : "Nom de famille" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riattivare Duck Address privato?" + "value" : "Cognome" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Privé-Duck Address opnieuw activeren?" + "value" : "Achternaam" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ponownie aktywować prywatny adres Duck Address?" + "value" : "Nazwisko" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Reativar Duck Address privado?" + "value" : "Apelido" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вновь активировать приватный адрес Duck Address?" + "value" : "Фамилия" } } } }, - "pm.private.email.mesage.active" : { - "comment" : "Mesasage displayed when a private email address is active", + "pm.name.middle" : { + "comment" : "Label for middle name title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address aktiv" + "value" : "Mittlerer Name" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Duck Address Active" + "value" : "Middle Name" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address activa" + "value" : "Segundo nombre" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address active" + "value" : "Deuxième prénom" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address attivo" + "value" : "Secondo nome" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Actief Duck Address" + "value" : "Middelste naam" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address aktywny" + "value" : "Drugie imię" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address ativo" + "value" : "Nome do meio" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Адрес Duck Address активирован" + "value" : "Второе имя" } } } }, - "pm.private.email.mesage.deactivate.confirm.content" : { - "comment" : "Text for the confirmation message displayed when a user tries deactivate a Private Email Address", + "pm.new.card" : { + "comment" : "Label for new card title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "E-Mails, die an %@ gesendet werden, werden nicht mehr an deinen Posteingang weitergeleitet." + "value" : "Kreditkarte" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Emails sent to %@ will no longer be forwarded to your inbox." + "value" : "Credit Card" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Los correos electrónicos electrónicos enviados a %@ ya no se se reenviarán a tu bandeja de entrada." + "value" : "Tarjeta de crédito" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Les e-mails envoyés à %@ ne seront plus transférés vers votre boîte de réception." + "value" : "Carte bancaire" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Le e-mail inviate a %@ non saranno più inoltrate alla tua casella di posta in arrivo." + "value" : "Carta di credito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "E-mails die naar %@ worden gestuurd, worden niet meer doorgestuurd naar je inbox." + "value" : "Creditcard" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wiadomości e-mail wysłane do %@ nie będą już przekazywane do Twojej skrzynki odbiorczej." + "value" : "Karta kredytowa" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Os e-mails enviados para %@ já não serão encaminhados para a tua caixa de entrada." + "value" : "Cartão de crédito" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Электронные письма, отправленные на адрес %@, не будут пересылаться на ваш почтовый ящик." + "value" : "Платежная карта" } } } }, - "pm.private.email.mesage.deactivate.confirm.title" : { - "comment" : "Title for the confirmation message displayed when a user tries deactivate a Private Email Address", + "pm.new.identity" : { + "comment" : "Label for new identity title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Private Duck Address deaktivieren?" + "value" : "Identität" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Deactivate Private Duck Address?" + "value" : "Identity" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Desactivar la Duck Address privada?" + "value" : "Identidad" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Désactiver la Duck Address privée ?" + "value" : "Identité" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Disattivare Duck Address privato?" + "value" : "Identità" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Privé-Duck Address deactiveren?" + "value" : "Identiteit" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dezaktywować prywatny adres Duck Address?" + "value" : "Tożsamość" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Desativar Duck Address privado?" + "value" : "Identidade" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Деактивировать приватный адрес Duck Address?" + "value" : "Учетные данные" } } } }, - "pm.private.email.mesage.error" : { - "comment" : "Mesasage displayed when a user tries to manage a private email address but the service is not available, returns an error or network is down", + "pm.new.login" : { + "comment" : "Label for new login title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Die Verwaltung dieser Adresse ist vorübergehend nicht möglich." + "value" : "Passwort" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Management of this address is temporarily unavailable." + "value" : "Password" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "La gestión de esta dirección no está disponible temporalmente." + "value" : "Contraseña" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "La gestion de cette adresse est temporairement indisponible." + "value" : "Mot de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "La gestione di questo indirizzo non è al momento disponibile." + "value" : "Password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Beheren van dit adres is tijdelijk niet beschikbaar." + "value" : "Wachtwoord" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zarządzanie tym adresem jest chwilowo niedostępne." + "value" : "Hasło" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "A gestão deste endereço está temporariamente indisponível." + "value" : "Palavra-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Управление этим адресом временно недоступно." + "value" : "Пароль" } } } }, - "pm.private.email.mesage.inactive" : { - "comment" : "Mesasage displayed when a private email address is inactive", + "pm.new.note" : { + "comment" : "Label for new note title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address deaktiviert" + "value" : "Hinweis" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Duck Address Deactivated" + "value" : "Note" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address desactivada" + "value" : "Nota" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address désactivée" + "value" : "Note" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address disattivato" + "value" : "Nota" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Gedeactiveerd Duck Address" + "value" : "Notitie" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address dezaktywowany" + "value" : "Notatka" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Address desativado" + "value" : "Nota" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Адрес Duck Address деактивирован" + "value" : "Примечание" } } } }, - "pm.removed.duck.address.button" : { - "comment" : "Button text for the alert dialog telling the user an updated username is no longer a private email address", + "pm.note" : { + "comment" : "Label for note title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verstanden" + "value" : "Hinweis" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Got it" + "value" : "Note" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Entendido" + "value" : "Nota" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "J'ai compris" + "value" : "Note" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ho capito" + "value" : "Nota" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ik snap het" + "value" : "Opmerking" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Rozumiem" + "value" : "Notatka" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Entendi" + "value" : "Nota" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Понятно" + "value" : "Примечание" } } } }, - "pm.removed.duck.address.content" : { - "comment" : "Content for the alert dialog telling the user an updated username is no longer a private email address", + "pm.note.empty" : { + "comment" : "Label for empty note title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Du kannst diese Duck Address weiterhin über die E-Mails in deinem persönlichen Posteingang verwalten." + "value" : "Leerer Hinweis" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "You can still manage this Duck Address from emails received from it in your personal inbox." + "value" : "Empty note" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Puedes gestionar esta Duck Address desde los correos electrónicos recibidos en tu bandeja de entrada personal." + "value" : "Nota vacía" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Vous pouvez toujours gérer cette Duck Address à partir des e-mails reçus de sa part dans votre boîte de réception personnelle." + "value" : "Note vide" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Puoi comunque gestire questo Duck Address dalle e-mail da esso ricevute nella tua casella di posta personale." + "value" : "Nota vuota" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Je kunt dit Duck Address nog steeds beheren via e-mails die je ervan ontvangt in je persoonlijke inbox." + "value" : "Lege notitie" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nadal możesz zarządzać adresem Duck Address z wiadomości e-mail otrzymanych z tego adresu w swojej osobistej skrzynce odbiorczej." + "value" : "Pusta notatka" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ainda podes gerir este Duck Address a partir dos e-mails recebidos deste endereço na tua caixa de entrada pessoal." + "value" : "Nota vazia" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вы по-прежнему можете управлять этим адресом Duck Address через поступившие на него письма в своем почтовом ящике." + "value" : "Пустое примечание" } } } }, - "pm.removed.duck.address.title" : { - "comment" : "Title for the alert dialog telling the user an updated username is no longer a private email address", + "pm.notes" : { + "comment" : "Label for notes edit field", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Benutzername von Private Duck Address wurde entfernt" + "value" : "Notizen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Private Duck Address username was removed" + "value" : "Notes" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Se ha eliminado el nombre de usuario de la Duck Address privada" + "value" : "Notas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Le nom d'utilisateur de la Duck Address privée a été supprimé" + "value" : "Notes" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Il nome utente Private Duck Address è stato rimosso" + "value" : "Note" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "De persoonlijke gebruikersnaam voor het Duck Address is verwijderd" + "value" : "Opmerkingen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nazwa użytkownika prywatnego adresu Duck Address została usunięta" + "value" : "Uwagi" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O nome de utilizador privado do Duck Address foi removido" + "value" : "Notas" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Приватное имя пользователя Duck Address удалено" + "value" : "Примечания" } } } }, - "pm.save" : { - "comment" : "Save button", + "pm.password" : { + "comment" : "Label for password edit field", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Speichern" + "value" : "Passwort" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Save" + "value" : "Password" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar" + "value" : "Contraseña" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Enregistrer" + "value" : "Mot de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Salva" + "value" : "Password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Opslaan" + "value" : "Wachtwoord" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zapisz" + "value" : "Hasło" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar" + "value" : "Palavra-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сохранить" + "value" : "Пароль" } } } }, - "pm.save-credentials.editable.title" : { - "comment" : "Title for the editable Save Credentials popover", + "pm.phone-number" : { + "comment" : "Label for phone number title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwort in DuckDuckGo speichern?" + "value" : "Telefonnummer" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Save password in DuckDuckGo?" + "value" : "Phone Number" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Guardar contraseña en DuckDuckGo?" + "value" : "Número de teléfono" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Enregistrer le mot de passe dans DuckDuckGo ?" + "value" : "Numéro de téléphone" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Salvare la password in DuckDuckGo?" + "value" : "Numero di telefono" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoord opslaan in DuckDuckGo?" + "value" : "Telefoonnummer" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zapisać hasło w DuckDuckGo?" + "value" : "Numer telefonu" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar palavra-passe no DuckDuckGo?" + "value" : "Número de telefone" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сохранить пароль в DuckDuckGo?" + "value" : "Номер телефона" } } } }, - "pm.save-credentials.non-editable.title" : { - "comment" : "Title for the non-editable Save Credentials popover", + "pm.private.email.mesage.activate.confirm.content" : { + "comment" : "Text for the confirmation message displayed when a user tries activate a Private Email Address", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neues Passwort gespeichert" + "value" : "E-Mails, die an %@ gesendet werden, werden wieder an deinen Posteingang weitergeleitet." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "New password saved" + "value" : "Emails sent to %@ will again be forwarded to your inbox." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nueva contraseña guardada" + "value" : "Los correos electrónicos enviados a %@ volverán a reenviarse a tu bandeja de entrada." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nouveau mot de passe enregistré" + "value" : "Les e-mails envoyés à %@ seront à nouveau transférés vers votre boîte de réception." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nuova password salvata" + "value" : "Le e-mail inviate a %@ saranno nuovamente inoltrate alla tua casella di posta." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuw wachtwoord opgeslagen" + "value" : "E-mails die naar %@ worden gestuurd, worden opnieuw doorgestuurd naar je inbox." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zapisano nowe hasło" + "value" : "Wiadomości e-mail wysłane do %@ będą ponownie przekazywane do Twojej skrzynki odbiorczej." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nova palavra-passe guardada" + "value" : "Os e-mails enviados para %@ serão encaminhados para a tua caixa de entrada." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Новый пароль сохранен" + "value" : "Электронные письма, отправленные на адрес %@, снова будут пересылаться на ваш почтовый ящик." } } } }, - "pm.signin.to.manage" : { - "comment" : "Message displayed to the user when they are logged out of Email protection.", + "pm.private.email.mesage.activate.confirm.title" : { + "comment" : "Title for the confirmation message displayed when a user tries activate a Private Email Address", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "%@, um deine Duck Addresses auf diesem Gerät zu verwalten." + "value" : "Private Duck Address reaktivieren?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "%@ to manage your Duck Addresses on this device." + "value" : "Reactivate Private Duck Address?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "%@ para gestionar tus Duck Addresses en este dispositivo." + "value" : "¿Reactivar la Duck Address privada?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "%@ pour gérer vos Duck Addresses sur cet appareil." + "value" : "Réactiver la Duck Address privée ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "%@ per gestire i tuoi Duck Address su questo dispositivo." + "value" : "Riattivare Duck Address privato?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "%@ om je Duck-adressen op dit apparaat te beheren." + "value" : "Privé-Duck Address opnieuw activeren?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "%@, aby zarządzać adresami Duck Address na tym urządzeniu." + "value" : "Ponownie aktywować prywatny adres Duck Address?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "%@ para gerires os teus Duck Addresses neste dispositivo." + "value" : "Reativar Duck Address privado?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "%@ для управления адресами Duck Address с этого устройства." + "value" : "Вновь активировать приватный адрес Duck Address?" } } } }, - "pm.sort.date.ascending" : { - "comment" : "Label for Ascending date sort order", + "pm.private.email.mesage.active" : { + "comment" : "Mesasage displayed when a private email address is active", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neueste zuerst" + "value" : "Duck Address aktiv" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Newest First" + "value" : "Duck Address Active" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Más reciente primero" + "value" : "Duck Address activa" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Du plus récent au plus ancien" + "value" : "Duck Address active" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Prima il più recente" + "value" : "Duck Address attivo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuwste eerst" + "value" : "Actief Duck Address" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Najpierw najnowsze" + "value" : "Duck Address aktywny" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mais recente primeiro" + "value" : "Duck Address ativo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "От новых к старым" + "value" : "Адрес Duck Address активирован" } } } }, - "pm.sort.date.descending" : { - "comment" : "Label for Descending date sort order", + "pm.private.email.mesage.deactivate.confirm.content" : { + "comment" : "Text for the confirmation message displayed when a user tries deactivate a Private Email Address", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Älteste zuerst" + "value" : "E-Mails, die an %@ gesendet werden, werden nicht mehr an deinen Posteingang weitergeleitet." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Oldest First" + "value" : "Emails sent to %@ will no longer be forwarded to your inbox." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Más antiguo primero" + "value" : "Los correos electrónicos electrónicos enviados a %@ ya no se se reenviarán a tu bandeja de entrada." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Du plus ancien au plus récent" + "value" : "Les e-mails envoyés à %@ ne seront plus transférés vers votre boîte de réception." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Prima il meno recente" + "value" : "Le e-mail inviate a %@ non saranno più inoltrate alla tua casella di posta in arrivo." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Oudste eerst" + "value" : "E-mails die naar %@ worden gestuurd, worden niet meer doorgestuurd naar je inbox." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Najpierw najstarsze" + "value" : "Wiadomości e-mail wysłane do %@ nie będą już przekazywane do Twojej skrzynki odbiorczej." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mais antigo primeiro" + "value" : "Os e-mails enviados para %@ já não serão encaminhados para a tua caixa de entrada." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "От старых к новым" + "value" : "Электронные письма, отправленные на адрес %@, не будут пересылаться на ваш почтовый ящик." } } } }, - "pm.sort.parameter.date-created" : { - "comment" : "Label for Date Created sort parameter", + "pm.private.email.mesage.deactivate.confirm.title" : { + "comment" : "Title for the confirmation message displayed when a user tries deactivate a Private Email Address", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Erstellungsdatum" + "value" : "Private Duck Address deaktivieren?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Date Created" + "value" : "Deactivate Private Duck Address?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Fecha de creación" + "value" : "¿Desactivar la Duck Address privada?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Date de création" + "value" : "Désactiver la Duck Address privée ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Data di creazione" + "value" : "Disattivare Duck Address privato?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Datum aangemaakt" + "value" : "Privé-Duck Address deactiveren?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Data utworzenia" + "value" : "Dezaktywować prywatny adres Duck Address?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Data de criação" + "value" : "Desativar Duck Address privado?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "По дате создания" + "value" : "Деактивировать приватный адрес Duck Address?" } } } }, - "pm.sort.parameter.date-modified" : { - "comment" : "Label for Date Modified sort parameter", + "pm.private.email.mesage.error" : { + "comment" : "Mesasage displayed when a user tries to manage a private email address but the service is not available, returns an error or network is down", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Änderungsdatum" + "value" : "Die Verwaltung dieser Adresse ist vorübergehend nicht möglich." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Date Modified" + "value" : "Management of this address is temporarily unavailable." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Fecha de modificación" + "value" : "La gestión de esta dirección no está disponible temporalmente." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Date de modification" + "value" : "La gestion de cette adresse est temporairement indisponible." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Data modificata" + "value" : "La gestione di questo indirizzo non è al momento disponibile." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Datum gewijzigd" + "value" : "Beheren van dit adres is tijdelijk niet beschikbaar." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Data modyfikacji" + "value" : "Zarządzanie tym adresem jest chwilowo niedostępne." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Data de modificação" + "value" : "A gestão deste endereço está temporariamente indisponível." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "По дате изменения" + "value" : "Управление этим адресом временно недоступно." } } } }, - "pm.sort.parameter.title" : { - "comment" : "Label for Title sort parameter", + "pm.private.email.mesage.inactive" : { + "comment" : "Mesasage displayed when a private email address is inactive", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Titel" + "value" : "Duck Address deaktiviert" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Title" + "value" : "Duck Address Deactivated" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Título" + "value" : "Duck Address desactivada" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Titre" + "value" : "Duck Address désactivée" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Titolo" + "value" : "Duck Address disattivato" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Titel" + "value" : "Gedeactiveerd Duck Address" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Tytuł" + "value" : "Duck Address dezaktywowany" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Título" + "value" : "Duck Address desativado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Название" + "value" : "Адрес Duck Address деактивирован" } } } }, - "pm.sort.string.ascending" : { - "comment" : "Label for Ascending string sort order", + "pm.removed.duck.address.button" : { + "comment" : "Button text for the alert dialog telling the user an updated username is no longer a private email address", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alphabetisch" + "value" : "Verstanden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Alphabetically" + "value" : "Got it" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "En orden alfabético" + "value" : "Entendido" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Par ordre alphabétique" + "value" : "J'ai compris" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Alfabeticamente" + "value" : "Ho capito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alfabetisch" + "value" : "Ik snap het" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Alfabetycznie" + "value" : "Rozumiem" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Por ordem alfabética" + "value" : "Entendi" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "В алфавитном порядке" + "value" : "Понятно" } } } }, - "pm.sort.string.descending" : { - "comment" : "Label for Descending string sort order", + "pm.removed.duck.address.content" : { + "comment" : "Content for the alert dialog telling the user an updated username is no longer a private email address", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alphabetisch rückwärts" + "value" : "Du kannst diese Duck Address weiterhin über die E-Mails in deinem persönlichen Posteingang verwalten." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Reverse Alphabetically" + "value" : "You can still manage this Duck Address from emails received from it in your personal inbox." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "En orden alfabético inverso" + "value" : "Puedes gestionar esta Duck Address desde los correos electrónicos recibidos en tu bandeja de entrada personal." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Dans le sens inverse de l'alphabet" + "value" : "Vous pouvez toujours gérer cette Duck Address à partir des e-mails reçus de sa part dans votre boîte de réception personnelle." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Inverti l'ordine alfabetico" + "value" : "Puoi comunque gestire questo Duck Address dalle e-mail da esso ricevute nella tua casella di posta personale." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alfabetisch omdraaien" + "value" : "Je kunt dit Duck Address nog steeds beheren via e-mails die je ervan ontvangt in je persoonlijke inbox." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Alfabetycznie od końca" + "value" : "Nadal możesz zarządzać adresem Duck Address z wiadomości e-mail otrzymanych z tego adresu w swojej osobistej skrzynce odbiorczej." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Inverter a ordem alfabética" + "value" : "Ainda podes gerir este Duck Address a partir dos e-mails recebidos deste endereço na tua caixa de entrada pessoal." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "В обратном алфавитном порядке" + "value" : "Вы по-прежнему можете управлять этим адресом Duck Address через поступившие на него письма в своем почтовом ящике." } } } }, - "pm.update-credentials.title" : { - "comment" : "Title for the Update Credentials popover", + "pm.removed.duck.address.title" : { + "comment" : "Title for the alert dialog telling the user an updated username is no longer a private email address", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwort aktualisieren?" + "value" : "Benutzername von Private Duck Address wurde entfernt" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Update password?" + "value" : "Private Duck Address username was removed" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Actualizar contraseña?" + "value" : "Se ha eliminado el nombre de usuario de la Duck Address privada" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Modifier le mot de passe ?" + "value" : "Le nom d'utilisateur de la Duck Address privée a été supprimé" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aggiornare password?" + "value" : "Il nome utente Private Duck Address è stato rimosso" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoord bijwerken?" + "value" : "De persoonlijke gebruikersnaam voor het Duck Address is verwijderd" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zaktualizować hasło?" + "value" : "Nazwa użytkownika prywatnego adresu Duck Address została usunięta" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Atualizar palavra-passe?" + "value" : "O nome de utilizador privado do Duck Address foi removido" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Обновить пароль?" + "value" : "Приватное имя пользователя Duck Address удалено" } } } }, - "pm.username" : { - "comment" : "Label for username edit field", + "pm.save" : { + "comment" : "Save button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Benutzername" + "value" : "Speichern" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Username" + "value" : "Save" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nombre de usuario" + "value" : "Guardar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nom d'utilisateur" + "value" : "Enregistrer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nome utente" + "value" : "Salva" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Gebruikersnaam" + "value" : "Opslaan" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nazwa użytkownika" + "value" : "Zapisz" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nome de utilizador" + "value" : "Guardar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Имя пользователя" + "value" : "Сохранить" } } } }, - "pm.website" : { - "comment" : "Label for website edit field", + "pm.save-credentials.editable.title" : { + "comment" : "Title for the editable Save Credentials popover", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "URL der Website" + "value" : "Passwort in DuckDuckGo speichern?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Website URL" + "value" : "Save password in DuckDuckGo?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "URL del sitio web" + "value" : "¿Guardar contraseña en DuckDuckGo?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "URL du site Web" + "value" : "Enregistrer le mot de passe dans DuckDuckGo ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "URL del sito Web" + "value" : "Salvare la password in DuckDuckGo?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "URL van de website" + "value" : "Wachtwoord opslaan in DuckDuckGo?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Adres URL witryny" + "value" : "Zapisać hasło w DuckDuckGo?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "URL do site" + "value" : "Guardar palavra-passe no DuckDuckGo?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Адрес сайта" + "value" : "Сохранить пароль в DuckDuckGo?" } } } }, - "pm.year" : { - "comment" : "Label for Year title", + "pm.save-credentials.non-editable.title" : { + "comment" : "Title for the non-editable Save Credentials popover", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Jahr" + "value" : "Neues Passwort gespeichert" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Year" + "value" : "New password saved" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Año" + "value" : "Nueva contraseña guardada" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Année" + "value" : "Nouveau mot de passe enregistré" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Anno" + "value" : "Nuova password salvata" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Jaar" + "value" : "Nieuw wachtwoord opgeslagen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Rok" + "value" : "Zapisano nowe hasło" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ano" + "value" : "Nova palavra-passe guardada" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Год" + "value" : "Новый пароль сохранен" } } } }, - "preferences-homepage-address" : { - "comment" : "Homepage address field label", + "pm.signin.to.manage" : { + "comment" : "Message displayed to the user when they are logged out of Email protection.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Adresse:" + "value" : "%@, um deine Duck Addresses auf diesem Gerät zu verwalten." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Address:" + "value" : "%@ to manage your Duck Addresses on this device." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Dirección:" + "value" : "%@ para gestionar tus Duck Addresses en este dispositivo." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Adresse :" + "value" : "%@ pour gérer vos Duck Addresses sur cet appareil." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Indirizzo:" + "value" : "%@ per gestire i tuoi Duck Address su questo dispositivo." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Adres:" + "value" : "%@ om je Duck-adressen op dit apparaat te beheren." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Adres:" + "value" : "%@, aby zarządzać adresami Duck Address na tym urządzeniu." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Endereço:" + "value" : "%@ para gerires os teus Duck Addresses neste dispositivo." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Адрес:" + "value" : "%@ для управления адресами Duck Address с этого устройства." } } } }, - "preferences-homepage-customPage" : { - "comment" : "Option to control Specific Home Page", + "pm.sort.date.ascending" : { + "comment" : "Label for Ascending date sort order", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bestimmte Seite" + "value" : "Neueste zuerst" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Specific page" + "value" : "Newest First" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Página específica" + "value" : "Más reciente primero" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Page spécifique" + "value" : "Du plus récent au plus ancien" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Pagina specifica" + "value" : "Prima il più recente" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Specifieke pagina" + "value" : "Nieuwste eerst" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Określona strona" + "value" : "Najpierw najnowsze" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Página específica" + "value" : "Mais recente primeiro" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Конкретная страница" + "value" : "От новых к старым" } } } }, - "preferences-homepage-newTab" : { - "comment" : "Option to open a new tab", + "pm.sort.date.descending" : { + "comment" : "Label for Descending date sort order", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neue Tab-Seite" + "value" : "Älteste zuerst" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "New Tab page" + "value" : "Oldest First" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Página de nueva pestaña" + "value" : "Más antiguo primero" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle page d'onglet" + "value" : "Du plus ancien au plus récent" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Pagina della nuova scheda" + "value" : "Prima il meno recente" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuwe tabbladpagina" + "value" : "Oudste eerst" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Strona nowej karty" + "value" : "Najpierw najstarsze" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Nova página de separador" + "value" : "Mais antigo primeiro" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Страница новой вкладки" + "value" : "От старых к новым" } } } }, - "preferences-homepage-set-homePage" : { - "comment" : "Set Homepage dialog title", + "pm.sort.parameter.date-created" : { + "comment" : "Label for Date Created sort parameter", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Startseite festlegen" + "value" : "Erstellungsdatum" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Set Homepage" + "value" : "Date Created" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Configurar página de inicio" + "value" : "Fecha de creación" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Définir la page d'accueil" + "value" : "Date de création" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Imposta la homepage" + "value" : "Data di creazione" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Startpagina instellen" + "value" : "Datum aangemaakt" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustaw stronę główną" + "value" : "Data utworzenia" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Definir página inicial" + "value" : "Data de criação" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выбор стартовой страницы" + "value" : "По дате создания" } } } }, - "preferences-homepage-set-page" : { - "comment" : "Option to control the Specific Page", + "pm.sort.parameter.date-modified" : { + "comment" : "Label for Date Modified sort parameter", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Seite festlegen …" + "value" : "Änderungsdatum" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Set Page…" + "value" : "Date Modified" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Configurar página…" + "value" : "Fecha de modificación" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Définir la page…" + "value" : "Date de modification" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Imposta pagina…" + "value" : "Data modificata" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Pagina instellen ..." + "value" : "Datum gewijzigd" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustaw stronę…" + "value" : "Data modyfikacji" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Definir página…" + "value" : "Data de modificação" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Назначить страницу..." + "value" : "По дате изменения" } } } }, - "preferences-homepage.description" : { - "comment" : "Homepage behavior description", + "pm.sort.parameter.title" : { + "comment" : "Label for Title sort parameter", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wenn du nach Hause navigierst oder neue Fenster öffnest." + "value" : "Titel" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "When navigating home or opening new windows." + "value" : "Title" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Al navegar por la página de inicio o abrir nuevas ventanas." + "value" : "Título" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Lors du retour à l'accueil ou lors de l'ouverture de nouvelles fenêtres." + "value" : "Titre" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Quando navighi nella home o apri nuove finestre." + "value" : "Titolo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wanneer je naar de startpagina surft of nieuwe vensters opent." + "value" : "Titel" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Podczas przechodzenia na stronę główną lub otwierania nowych okien." + "value" : "Tytuł" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ao navegar na página inicial ou ao abrir novas janelas." + "value" : "Título" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "При переходе на стартовую страницу или открытии нового окна." + "value" : "Название" } } } }, - "preferences-homepage.title" : { - "comment" : "Title for Homepage section in settings", + "pm.sort.string.ascending" : { + "comment" : "Label for Ascending string sort order", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Startseite" + "value" : "Alphabetisch" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Homepage" + "value" : "Alphabetically" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Página de inicio" + "value" : "En orden alfabético" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Page d'accueil" + "value" : "Par ordre alphabétique" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Homepage" + "value" : "Alfabeticamente" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Startpagina" + "value" : "Alfabetisch" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Strona główna" + "value" : "Alfabetycznie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Página inicial" + "value" : "Por ordem alfabética" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Стартовая страница" + "value" : "В алфавитном порядке" } } } }, - "preferences-tabs.new.tab.position.title" : { - "comment" : "Title for new tab positioning", + "pm.sort.string.descending" : { + "comment" : "Label for Descending string sort order", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Beim Erstellen eines neuen Tabs" + "value" : "Alphabetisch rückwärts" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "When creating a new tab" + "value" : "Reverse Alphabetically" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Al crear una nueva pestaña" + "value" : "En orden alfabético inverso" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Lors de la création d'un nouvel onglet" + "value" : "Dans le sens inverse de l'alphabet" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Quando si crea una nuova scheda" + "value" : "Inverti l'ordine alfabetico" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bij het maken van een nieuw tabblad" + "value" : "Alfabetisch omdraaien" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Podczas tworzenia nowej karty" + "value" : "Alfabetycznie od końca" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ao criar um novo separador" + "value" : "Inverter a ordem alfabética" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "При создании новой вкладки" + "value" : "В обратном алфавитном порядке" } } } }, - "preferences-tabs.prefer.new.tabs.to.windows" : { - "comment" : "Option to prefer opening new tabs instead of windows when opening links", + "pm.update-credentials.title" : { + "comment" : "Title for the Update Credentials popover", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Links nach Möglichkeit in neuen Tabs statt in neuen Fenstern öffnen" + "value" : "Passwort aktualisieren?" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open links in new tabs instead of new windows whenever possible" + "value" : "Update password?" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir enlaces en nuevas pestañas en lugar de nuevas ventanas siempre que sea posible" + "value" : "¿Actualizar contraseña?" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir les liens dans de nouveaux onglets plutôt que dans de nouvelles fenêtres si possible" + "value" : "Modifier le mot de passe ?" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri i collegamenti in nuove schede anziché in nuove finestre quando possibile" + "value" : "Aggiornare password?" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Links waar mogelijk openen in nieuwe tabbladen in plaats van in nieuwe vensters" + "value" : "Wachtwoord bijwerken?" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwieraj linki w nowych kartach zamiast w nowych oknach, gdy tylko jest to możliwe" + "value" : "Zaktualizować hasło?" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir links em separadores novos em vez de novas janelas sempre que possível" + "value" : "Atualizar palavra-passe?" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "По возможности открывать ссылки в новых вкладках, а не окнах" + "value" : "Обновить пароль?" } } } }, - "preferences-tabs.switch.tab.when.opened" : { - "comment" : "Option to switch to a new tab/window when it is opened", + "pm.username" : { + "comment" : "Label for username edit field", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Beim Öffnen von Links sofort zu einem neuen Tab oder Fenster wechseln" + "value" : "Benutzername" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "When opening links, switch to the new tab or window immediately" + "value" : "Username" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Al abrir enlaces, cambiar a la nueva pestaña o ventana inmediatamente" + "value" : "Nombre de usuario" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "À l'ouverture des liens, basculer immédiatement sur le nouvel onglet ou la nouvelle fenêtre" + "value" : "Nom d'utilisateur" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Quando apri i collegamenti, passa immediatamente alla nuova scheda o finestra" + "value" : "Nome utente" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Meteen overschakelen naar het nieuwe tabblad of venster bij het openen van links" + "value" : "Gebruikersnaam" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwierając linki, natychmiast przełącz na nową kartę lub nowe okno" + "value" : "Nazwa użytkownika" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ao abrir links, mudar imediatamente para o novo separador ou janela" + "value" : "Nome de utilizador" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "При открытии ссылки сразу переключаться на ее вкладку или окно" + "value" : "Имя пользователя" } } } }, - "preferences-tabs.title" : { - "comment" : "Title for tabs section in settings", + "pm.website" : { + "comment" : "Label for website edit field", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Tabs" + "value" : "URL der Website" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Tabs" + "value" : "Website URL" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Pestañas" + "value" : "URL del sitio web" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Onglets" + "value" : "URL du site Web" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Schede" + "value" : "URL del sito Web" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Tabbladen" + "value" : "URL van de website" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Karty" + "value" : "Adres URL witryny" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Separadores" + "value" : "URL do site" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вкладки" + "value" : "Адрес сайта" } } } }, - "preferences.about" : { - "comment" : "Title of the option to show the About screen", + "pm.year" : { + "comment" : "Label for Year title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Über" + "value" : "Jahr" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "About" + "value" : "Year" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Acerca de" + "value" : "Año" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "À propos" + "value" : "Année" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Informazioni" + "value" : "Anno" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Over" + "value" : "Jaar" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Informacje" + "value" : "Rok" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Acerca de" + "value" : "Ano" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "О нас" + "value" : "Год" } } } }, - "preferences.about.about-duckduckgo" : { - "comment" : "About screen", + "preferences-homepage-address" : { + "comment" : "Homepage address field label", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Über DuckDuckGo" + "value" : "Adresse:" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "About DuckDuckGo" + "value" : "Address:" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Acerca de DuckDuckGo" + "value" : "Dirección:" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "À propos de DuckDuckGo" + "value" : "Adresse :" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Info su DuckDuckGo" + "value" : "Indirizzo:" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Over DuckDuckGo" + "value" : "Adres:" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "O DuckDuckGo" + "value" : "Adres:" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Sobre o DuckDuckGo" + "value" : "Endereço:" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "О приложении DuckDuckGo" + "value" : "Адрес:" } } } }, - "preferences.about.more-at" : { - "comment" : "Link to the about page", + "preferences-homepage-customPage" : { + "comment" : "Option to control Specific Home Page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Mehr bei %@" + "value" : "Bestimmte Seite" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "More at %@" + "value" : "Specific page" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Más en %@" + "value" : "Página específica" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Plus sur %@" + "value" : "Page spécifique" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ulteriori informazioni su %@" + "value" : "Pagina specifica" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Meer op % @" + "value" : "Specifieke pagina" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Więcej na %@" + "value" : "Określona strona" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mais em %@" + "value" : "Página específica" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Подробности: %@" + "value" : "Конкретная страница" } } } }, - "preferences.about.privacy-policy" : { - "comment" : "Link to privacy policy page", + "preferences-homepage-newTab" : { + "comment" : "Option to open a new tab", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Datenschutzrichtlinie" + "value" : "Neue Tab-Seite" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Privacy Policy" + "value" : "New Tab page" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Política de privacidad" + "value" : "Página de nueva pestaña" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Politique de confidentialité" + "value" : "Nouvelle page d'onglet" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Privacy policy" + "value" : "Pagina della nuova scheda" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Privacybeleid" + "value" : "Nieuwe tabbladpagina" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Polityka prywatności" + "value" : "Strona nowej karty" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Política de Privacidade" + "value" : "Nova página de separador" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Политика конфиденциальности" + "value" : "Страница новой вкладки" } } } }, - "preferences.about.privacy-simplified" : { - "comment" : "About screen", + "preferences-homepage-set-homePage" : { + "comment" : "Set Homepage dialog title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Datenschutz leicht gemacht." + "value" : "Startseite festlegen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Privacy, simplified." + "value" : "Set Homepage" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Privacidad, simplificada." + "value" : "Configurar página de inicio" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "La confidentialité, simplifiée." + "value" : "Définir la page d'accueil" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "La privacy, semplificata." + "value" : "Imposta la homepage" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Privacy, vereenvoudigd." + "value" : "Startpagina instellen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Prywatność — jeszcze prostsza." + "value" : "Ustaw stronę główną" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Privacidade, simplificada." + "value" : "Definir página inicial" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Максимум конфиденциальности, минимум усилий." + "value" : "Выбор стартовой страницы" } } } }, - "preferences.about.send-feedback" : { - "comment" : "Feedback button in the about preferences page", + "preferences-homepage-set-page" : { + "comment" : "Option to control the Specific Page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Rückmeldung senden" + "value" : "Seite festlegen …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Send Feedback" + "value" : "Set Page…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar comentarios" + "value" : "Configurar página…" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Envoyer vos remarques" + "value" : "Définir la page…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Invia feedback" + "value" : "Imposta pagina…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verstuur feedback" + "value" : "Pagina instellen ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyślij opinię" + "value" : "Ustaw stronę…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar comentário" + "value" : "Definir página…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отправить отзыв" + "value" : "Назначить страницу..." } } } }, - "preferences.about.unsupported-device-info1" : { - "comment" : "This string represents a message informing the user that DuckDuckGo is no longer providing browser updates for their version of macOS", + "preferences-homepage.description" : { + "comment" : "Homepage behavior description", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo bietet keine Browser-Updates mehr für deine Version von macOS an." + "value" : "Wenn du nach Hause navigierst oder neue Fenster öffnest." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo is no longer providing browser updates for your version of macOS." + "value" : "When navigating home or opening new windows." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo ya no proporciona actualizaciones del navegador para tu versión de macOS." + "value" : "Al navegar por la página de inicio o abrir nuevas ventanas." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo ne propose plus de mises à jour de navigateur pour votre version de macOS." + "value" : "Lors du retour à l'accueil ou lors de l'ouverture de nouvelles fenêtres." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo non fornisce più aggiornamenti del browser per la tua versione di macOS." + "value" : "Quando navighi nella home o apri nuove finestre." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo biedt geen browserupdates meer voor je versie van macOS." + "value" : "Wanneer je naar de startpagina surft of nieuwe vensters opent." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo już nie udostępnia aktualizacji przeglądarki dla Twojej wersji systemu macOS." + "value" : "Podczas przechodzenia na stronę główną lub otwierania nowych okien." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O DuckDuckGo já não fornece atualizações para a tua versão do macOS." + "value" : "Ao navegar na página inicial ou ao abrir novas janelas." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сервис DuckDuckGo перестал выпускать обновления браузера для вашей версии macOS." + "value" : "При переходе на стартовую страницу или открытии нового окна." } } } }, - "preferences.about.unsupported-device-info2" : { - "comment" : "Copy in section that tells the user to update their macOS version since their current version is unsupported", + "preferences-homepage.title" : { + "comment" : "Title for Homepage section in settings", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitte aktualisiere auf macOS %@ oder höher, um die neueste Version von DuckDuckGo zu nutzen. Du kannst auch deine aktuelle Version des Browsers weiter benutzen, aber sie wird keine weiteren Updates erhalten." + "value" : "Startseite" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Please update to macOS %@ or later to use the most recent version of DuckDuckGo. You can also keep using your current version of the browser, but it will not receive further updates." + "value" : "Homepage" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Actualiza a macOS %@ o posterior para usar la versión más reciente de DuckDuckGo. También puedes seguir utilizando tu versión actual del navegador, pero no recibirás más actualizaciones." + "value" : "Página de inicio" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Veuillez installer macOS %@ ou une version ultérieure pour utiliser la version la plus récente de DuckDuckGo. Vous pouvez également continuer à utiliser votre version actuelle du navigateur, mais elle ne recevra plus de mises à jour." + "value" : "Page d'accueil" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aggiorna a macOS %@ o a una versione successiva per utilizzare la versione più recente di DuckDuckGo. Puoi anche continuare a utilizzare la versione attuale del browser, ma non riceverai ulteriori aggiornamenti." + "value" : "Homepage" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Update naar macOS %@ of hoger om de meest recente versie van DuckDuckGo te gebruiken. Je kunt ook de huidige versie van de browser blijven gebruiken, maar die wordt niet verder bijgewerkt." + "value" : "Startpagina" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zaktualizuj system macOS do wersji %@ lub nowszej, aby móc korzystać z najnowszej wersji DuckDuckGo. Możesz też nadal korzystać z obecnej wersji przeglądarki, ale nie będziesz otrzymywać dalszych aktualizacji." + "value" : "Strona główna" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Atualiza para o macOS %@ ou posterior para utilizares a versão mais recente do DuckDuckGo. Também podes continuar a utilizar a tua versão atual do navegador, mas esta não receberá mais atualizações." + "value" : "Página inicial" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Чтобы пользоваться новейшей версией DuckDuckGo, обновите операционную систему до macOS %@ или более поздней версии. Вы также можете продолжить пользоваться текущей версией браузера – просто она больше не будет обновляться." + "value" : "Стартовая страница" } } } }, - "preferences.accessibility" : { - "comment" : "Title of the option to show the Accessibility browser preferences", + "preferences-tabs.new.tab.position.title" : { + "comment" : "Title for new tab positioning", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Barrierefreiheit" + "value" : "Beim Erstellen eines neuen Tabs" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Accessibility" + "value" : "When creating a new tab" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Accesibilidad" + "value" : "Al crear una nueva pestaña" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Accessibilité" + "value" : "Lors de la création d'un nouvel onglet" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Accessibilità" + "value" : "Quando si crea una nuova scheda" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Toegankelijkheid" + "value" : "Bij het maken van een nieuw tabblad" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dostępność" + "value" : "Podczas tworzenia nowej karty" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Acessibilidade" + "value" : "Ao criar um novo separador" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Универсальный доступ" + "value" : "При создании новой вкладки" } } } }, - "preferences.add-to-dock" : { - "comment" : "Action button to add the app to the Dock", + "preferences-tabs.prefer.new.tabs.to.windows" : { + "comment" : "Option to prefer opening new tabs instead of windows when opening links", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zum Dock hinzufügen …" + "value" : "Links nach Möglichkeit in neuen Tabs statt in neuen Fenstern öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Add to Dock…" + "value" : "Open links in new tabs instead of new windows whenever possible" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Añadir al Dock…" + "value" : "Abrir enlaces en nuevas pestañas en lugar de nuevas ventanas siempre que sea posible" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ajouter au Dock…" + "value" : "Ouvrir les liens dans de nouveaux onglets plutôt que dans de nouvelles fenêtres si possible" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aggiungi al dock…" + "value" : "Apri i collegamenti in nuove schede anziché in nuove finestre quando possibile" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "App toevoegen aan je dock…" + "value" : "Links waar mogelijk openen in nieuwe tabbladen in plaats van in nieuwe vensters" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dodaj do Docka…" + "value" : "Otwieraj linki w nowych kartach zamiast w nowych oknach, gdy tylko jest to możliwe" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Adicionar à Dock…" + "value" : "Abrir links em separadores novos em vez de novas janelas sempre que possível" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Добавить на док-панель…" + "value" : "По возможности открывать ссылки в новых вкладках, а не окнах" } } } }, - "preferences.always-on" : { - "comment" : "Status indicator of a browser privacy protection feature.", + "preferences-tabs.switch.tab.when.opened" : { + "comment" : "Option to switch to a new tab/window when it is opened", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Immer aktiviert" + "value" : "Beim Öffnen von Links sofort zu einem neuen Tab oder Fenster wechseln" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Always On" + "value" : "When opening links, switch to the new tab or window immediately" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Siempre activado" + "value" : "Al abrir enlaces, cambiar a la nueva pestaña o ventana inmediatamente" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Toujours activé" + "value" : "À l'ouverture des liens, basculer immédiatement sur le nouvel onglet ou la nouvelle fenêtre" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Sempre attiva" + "value" : "Quando apri i collegamenti, passa immediatamente alla nuova scheda o finestra" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Altijd aan" + "value" : "Meteen overschakelen naar het nieuwe tabblad of venster bij het openen van links" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zawsze włączone" + "value" : "Otwierając linki, natychmiast przełącz na nową kartę lub nowe okno" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Sempre ligada" + "value" : "Ao abrir links, mudar imediatamente para o novo separador ou janela" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Всегда включено" + "value" : "При открытии ссылки сразу переключаться на ее вкладку или окно" } } } }, - "preferences.appearance" : { - "comment" : "Title of the option to show the Appearance preferences", + "preferences-tabs.title" : { + "comment" : "Title for tabs section in settings", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Aussehen" + "value" : "Tabs" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Appearance" + "value" : "Tabs" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Apariencia" + "value" : "Pestañas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Apparence" + "value" : "Onglets" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aspetto" + "value" : "Schede" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Uiterlijk" + "value" : "Tabbladen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wygląd" + "value" : "Karty" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Aparência" + "value" : "Separadores" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Внешний вид" + "value" : "Вкладки" } } } }, - "preferences.appearance.address-bar" : { - "comment" : "Theme preferences", + "preferences.about" : { + "comment" : "Title of the option to show the About screen", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Adresszeile" + "value" : "Über" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Address Bar" + "value" : "About" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Barra de direcciones" + "value" : "Acerca de" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Barre d'adresse" + "value" : "À propos" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Barra degli indirizzi" + "value" : "Informazioni" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Adresbalk" + "value" : "Over" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pasek adresu" + "value" : "Informacje" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Barra de endereço" + "value" : "Acerca de" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Адресная строка" + "value" : "О нас" } } } }, - "preferences.appearance.show-autocomplete-suggestions" : { - "comment" : "Option to show autocomplete suggestions in the address bar", + "preferences.about.about-duckduckgo" : { + "comment" : "About screen", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Vorschläge für die Autovervollständigung" + "value" : "Über DuckDuckGo" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Autocomplete suggestions" + "value" : "About DuckDuckGo" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Sugerencias de autocompletado" + "value" : "Acerca de DuckDuckGo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher les suggestions de saisie semi-automatique" + "value" : "À propos de DuckDuckGo" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra suggerimenti di completamento automatico" + "value" : "Info su DuckDuckGo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Suggesties voor automatisch aanvullen weergeven" + "value" : "Over DuckDuckGo" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż sugestie autouzupełniania" + "value" : "O DuckDuckGo" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar sugestões de preenchimento automático" + "value" : "Sobre o DuckDuckGo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать предложения автозаполнения" + "value" : "О приложении DuckDuckGo" } } } }, - "preferences.appearance.show-full-url" : { - "comment" : "Option to show full URL in the address bar", + "preferences.about.more-at" : { + "comment" : "Link to the about page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Vollständige Website-Adresse" + "value" : "Mehr bei %@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Full website address" + "value" : "More at %@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Dirección completa del sitio web" + "value" : "Más en %@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Adresse complète du site Web" + "value" : "Plus sur %@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Indirizzo completo del sito web" + "value" : "Ulteriori informazioni su %@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Volledig websiteadres" + "value" : "Meer op % @" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pełny adres witryny internetowej" + "value" : "Więcej na %@" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Endereço completo do site" + "value" : "Mais em %@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Полный адрес сайта" + "value" : "Подробности: %@" } } } }, - "preferences.appearance.theme" : { - "comment" : "Theme preferences", + "preferences.about.privacy-policy" : { + "comment" : "Link to privacy policy page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Design" + "value" : "Datenschutzrichtlinie" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Theme" + "value" : "Privacy Policy" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Tema" + "value" : "Política de privacidad" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Thème" + "value" : "Politique de confidentialité" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Tema" + "value" : "Privacy policy" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Thema" + "value" : "Privacybeleid" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Motyw" + "value" : "Polityka prywatności" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Tema" + "value" : "Política de Privacidade" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Тема" + "value" : "Политика конфиденциальности" } } } }, - "preferences.appearance.theme.dark" : { - "comment" : "In the preferences for themes, the option to select for activating dark mode in the app.", + "preferences.about.privacy-simplified" : { + "comment" : "About screen", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Dunkel" + "value" : "Datenschutz leicht gemacht." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Dark" + "value" : "Privacy, simplified." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Oscuro" + "value" : "Privacidad, simplificada." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Sombre" + "value" : "La confidentialité, simplifiée." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Scuro" + "value" : "La privacy, semplificata." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Donker" + "value" : "Privacy, vereenvoudigd." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ciemny" + "value" : "Prywatność — jeszcze prostsza." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Escuro" + "value" : "Privacidade, simplificada." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Тёмная" + "value" : "Максимум конфиденциальности, минимум усилий." } } } }, - "preferences.appearance.theme.light" : { - "comment" : "In the preferences for themes, the option to select for activating light mode in the app.", + "preferences.about.send-feedback" : { + "comment" : "Feedback button in the about preferences page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hell" + "value" : "Rückmeldung senden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Light" + "value" : "Send Feedback" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Claro" + "value" : "Enviar comentarios" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Clair" + "value" : "Envoyer vos remarques" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Chiaro" + "value" : "Invia feedback" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Licht" + "value" : "Verstuur feedback" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Jasny" + "value" : "Wyślij opinię" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Claro" + "value" : "Enviar comentário" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Светлая" + "value" : "Отправить отзыв" } } } }, - "preferences.appearance.theme.system" : { - "comment" : "In the preferences for themes, the option to select for use the change the mode based on the system preferences.", + "preferences.about.unsupported-device-info1" : { + "comment" : "This string represents a message informing the user that DuckDuckGo is no longer providing browser updates for their version of macOS", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Standard" + "value" : "DuckDuckGo bietet keine Browser-Updates mehr für deine Version von macOS an." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "System" + "value" : "DuckDuckGo is no longer providing browser updates for your version of macOS." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Sistema" + "value" : "DuckDuckGo ya no proporciona actualizaciones del navegador para tu versión de macOS." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Système" + "value" : "DuckDuckGo ne propose plus de mises à jour de navigateur pour votre version de macOS." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Sistema" + "value" : "DuckDuckGo non fornisce più aggiornamenti del browser per la tua versione di macOS." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Systeem" + "value" : "DuckDuckGo biedt geen browserupdates meer voor je versie van macOS." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Systemowy" + "value" : "DuckDuckGo już nie udostępnia aktualizacji przeglądarki dla Twojej wersji systemu macOS." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Sistema" + "value" : "O DuckDuckGo já não fornece atualizações para a tua versão do macOS." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Тема системы" + "value" : "Сервис DuckDuckGo перестал выпускать обновления браузера для вашей версии macOS." } } } }, - "preferences.appearance.zoom-picker" : { - "comment" : "Default page zoom picker title", + "preferences.about.unsupported-device-info2" : { + "comment" : "Copy in section that tells the user to update their macOS version since their current version is unsupported", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Standardmäßiger Seitenzoom" + "value" : "Bitte aktualisiere auf macOS %@ oder höher, um die neueste Version von DuckDuckGo zu nutzen. Du kannst auch deine aktuelle Version des Browsers weiter benutzen, aber sie wird keine weiteren Updates erhalten." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Default page zoom" + "value" : "Please update to macOS %@ or later to use the most recent version of DuckDuckGo. You can also keep using your current version of the browser, but it will not receive further updates." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Zoom de página predeterminado" + "value" : "Actualiza a macOS %@ o posterior para usar la versión más reciente de DuckDuckGo. También puedes seguir utilizando tu versión actual del navegador, pero no recibirás más actualizaciones." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Zoom par défaut de la page" + "value" : "Veuillez installer macOS %@ ou une version ultérieure pour utiliser la version la plus récente de DuckDuckGo. Vous pouvez également continuer à utiliser votre version actuelle du navigateur, mais elle ne recevra plus de mises à jour." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Zoom pagina predefinito" + "value" : "Aggiorna a macOS %@ o a una versione successiva per utilizzare la versione più recente di DuckDuckGo. Puoi anche continuare a utilizzare la versione attuale del browser, ma non riceverai ulteriori aggiornamenti." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Standaard zoomniveau van de pagina" + "value" : "Update naar macOS %@ of hoger om de meest recente versie van DuckDuckGo te gebruiken. Je kunt ook de huidige versie van de browser blijven gebruiken, maar die wordt niet verder bijgewerkt." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Domyślne powiększenie strony" + "value" : "Zaktualizuj system macOS do wersji %@ lub nowszej, aby móc korzystać z najnowszej wersji DuckDuckGo. Możesz też nadal korzystać z obecnej wersji przeglądarki, ale nie będziesz otrzymywać dalszych aktualizacji." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Zoom da página predefinida" + "value" : "Atualiza para o macOS %@ ou posterior para utilizares a versão mais recente do DuckDuckGo. Também podes continuar a utilizar a tua versão atual do navegador, mas esta não receberá mais atualizações." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Масштаб страницы по умолчанию" + "value" : "Чтобы пользоваться новейшей версией DuckDuckGo, обновите операционную систему до macOS %@ или более поздней версии. Вы также можете продолжить пользоваться текущей версией браузера – просто она больше не будет обновляться." } } } }, - "preferences.autofill" : { - "comment" : "Show Autofill preferences", + "preferences.accessibility" : { + "comment" : "Title of the option to show the Accessibility browser preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter" + "value" : "Barrierefreiheit" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Passwords" + "value" : "Accessibility" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Contraseñas" + "value" : "Accesibilidad" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mots de passe" + "value" : "Accessibilité" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Password" + "value" : "Accessibilità" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoorden" + "value" : "Toegankelijkheid" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Hasła" + "value" : "Dostępność" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Palavras-passe" + "value" : "Acessibilidade" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пароли" + "value" : "Универсальный доступ" } } } }, - "preferences.autofill-enabled-for" : { - "comment" : "Label presented before the email account in email protection preferences", + "preferences.add-to-dock" : { + "comment" : "Action button to add the app to the Dock", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Autovervollständigen in diesem Browser aktiviert für:" + "value" : "Zum Dock hinzufügen …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Autofill enabled in this browser for:" + "value" : "Add to Dock…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Autocompletar habilitado en este navegador para:" + "value" : "Añadir al Dock…" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "La saisie automatique est activée dans ce navigateur pour :" + "value" : "Ajouter au Dock…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Compilazione automatica abilitata in questo browser per:" + "value" : "Aggiungi al dock…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "'Automatisch aanvullen' is ingeschakeld in deze browser voor:" + "value" : "App toevoegen aan je dock…" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Autouzupełnianie włączone w tej przeglądarce dla:" + "value" : "Dodaj do Docka…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Preenchimento automático ativado neste navegador para:" + "value" : "Adicionar à Dock…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "В этом браузере включено автозаполнение для:" + "value" : "Добавить на док-панель…" } } } }, - "preferences.cookie-pop-up-protection" : { - "comment" : "Title of the option to show the Cookie Pop-Up Protection preferences", + "preferences.always-on" : { + "comment" : "Status indicator of a browser privacy protection feature.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Cookie-Pop-up-Schutz" + "value" : "Immer aktiviert" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Cookie Pop-Up Protection" + "value" : "Always On" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Protección contra ventanas emergentes de cookies" + "value" : "Siempre activado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Protection contre les fenêtres contextuelles des cookies" + "value" : "Toujours activé" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Protezione pop-up dei cookie" + "value" : "Sempre attiva" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bescherming tegen cookiepop-ups" + "value" : "Altijd aan" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ochrona przed wyskakującymi okienkami dotyczącymi plików cookie" + "value" : "Zawsze włączone" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Proteção contra pop-ups de cookies" + "value" : "Sempre ligada" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Защита от всплывающих окон куки" + "value" : "Всегда включено" } } } }, - "preferences.data-clearing" : { - "comment" : "Title of the option to show the Data Clearing preferences", + "preferences.appearance" : { + "comment" : "Title of the option to show the Appearance preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Datenlöschung" + "value" : "Aussehen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Data Clearing" + "value" : "Appearance" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminación de datos" + "value" : "Apariencia" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Effacement des données" + "value" : "Apparence" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cancellazione dati" + "value" : "Aspetto" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Gegevens wissen" + "value" : "Uiterlijk" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Czyszczenie danych" + "value" : "Wygląd" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Limpeza de Dados" + "value" : "Aparência" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Очистка данных" + "value" : "Внешний вид" } } } }, - "preferences.default-browser" : { - "comment" : "Title of the option to show the Default Browser Preferences", + "preferences.appearance.address-bar" : { + "comment" : "Theme preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Standard-Browser" + "value" : "Adresszeile" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Default Browser" + "value" : "Address Bar" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Navegador predeterminado" + "value" : "Barra de direcciones" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Navigateur par défaut" + "value" : "Barre d'adresse" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Browser predefinito" + "value" : "Barra degli indirizzi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Standaardbrowser" + "value" : "Adresbalk" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Domyślna przeglądarka" + "value" : "Pasek adresu" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Navegador predefinido" + "value" : "Barra de endereço" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Браузер по умолчанию" + "value" : "Адресная строка" } } } }, - "preferences.default-browser.active" : { - "comment" : "Indicate that the browser is the default", + "preferences.appearance.show-autocomplete-suggestions" : { + "comment" : "Option to show autocomplete suggestions in the address bar", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo ist dein Standard-Browser" + "value" : "Vorschläge für die Autovervollständigung" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo is your default browser" + "value" : "Autocomplete suggestions" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo es tu navegador predeterminado" + "value" : "Sugerencias de autocompletado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo est votre navigateur par défaut" + "value" : "Afficher les suggestions de saisie semi-automatique" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo è il tuo browser predefinito" + "value" : "Mostra suggerimenti di completamento automatico" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo is je standaardbrowser" + "value" : "Suggesties voor automatisch aanvullen weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo to Twoja domyślna przeglądarka" + "value" : "Pokaż sugestie autouzupełniania" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo é o teu navegador predefinido" + "value" : "Mostrar sugestões de preenchimento automático" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo — ваш браузер по умолчанию" + "value" : "Показать предложения автозаполнения" } } } }, - "preferences.default-browser.button.make-default" : { - "comment" : "represents a prompt message asking the user to make DuckDuckGo their default browser.", + "preferences.appearance.show-full-url" : { + "comment" : "Option to show full URL in the address bar", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo zum Standard machen …" + "value" : "Vollständige Website-Adresse" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Make DuckDuckGo Default…" + "value" : "Full website address" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Establecer DuckDuckGo como predeterminado…" + "value" : "Dirección completa del sitio web" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Faire de DuckDuckGo votre navigateur par défaut…" + "value" : "Adresse complète du site Web" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rendi DuckDuckGo predefinito..." + "value" : "Indirizzo completo del sito web" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo instellen als standaardbrowser ..." + "value" : "Volledig websiteadres" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustaw przeglądarkę DuckDuckGo jako domyślną…" + "value" : "Pełny adres witryny internetowej" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Tornar o DuckDuckGo a predefinição…" + "value" : "Endereço completo do site" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Назначить DuckDuckGo браузером по умолчанию..." + "value" : "Полный адрес сайта" } } } }, - "preferences.default-browser.inactive" : { - "comment" : "Indicate that the browser is not the default", + "preferences.appearance.theme" : { + "comment" : "Theme preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo ist nicht dein Standard-Browser." + "value" : "Design" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo is not your default browser." + "value" : "Theme" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo no es tu navegador predeterminado." + "value" : "Tema" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo n'est pas votre navigateur par défaut." + "value" : "Thème" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo non è il tuo browser predefinito." + "value" : "Tema" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo is niet je standaardbrowser." + "value" : "Thema" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo nie jest Twoją domyślną przeglądarką." + "value" : "Motyw" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O DuckDuckGo não é o teu navegador predefinido." + "value" : "Tema" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo не является вашим браузером по умолчанию." + "value" : "Тема" } } } }, - "preferences.downloads" : { - "comment" : "Title of the downloads browser preferences", + "preferences.appearance.theme.dark" : { + "comment" : "In the preferences for themes, the option to select for activating dark mode in the app.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Downloads" + "value" : "Dunkel" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Downloads" + "value" : "Dark" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Descargas" + "value" : "Oscuro" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Téléchargements" + "value" : "Sombre" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Download" + "value" : "Scuro" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Downloads" + "value" : "Donker" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pobrane" + "value" : "Ciemny" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Transferências" + "value" : "Escuro" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Загрузки" + "value" : "Тёмная" } } } }, - "preferences.duck-player" : { - "comment" : "Title of the option to show the Duck Player browser preferences", + "preferences.appearance.theme.light" : { + "comment" : "In the preferences for themes, the option to select for activating light mode in the app.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player" + "value" : "Hell" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Duck Player" + "value" : "Light" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player" + "value" : "Claro" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player" + "value" : "Clair" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player" + "value" : "Chiaro" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player" + "value" : "Licht" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player" + "value" : "Jasny" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Duck Player" + "value" : "Claro" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Проигрыватель Duck Player" + "value" : "Светлая" } } } }, - "preferences.duckduckgo-on-other-platforms" : { - "comment" : "Button presented to users to navigate them to our product page which presents all other products for other platforms", + "preferences.appearance.theme.system" : { + "comment" : "In the preferences for themes, the option to select for use the change the mode based on the system preferences.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo auf anderen Plattformen" + "value" : "Standard" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo on Other Platforms" + "value" : "System" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo en otras plataformas" + "value" : "Sistema" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo sur d'autres plateformes" + "value" : "Système" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo su altre piattaforme" + "value" : "Sistema" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo op andere platforms" + "value" : "Systeem" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo na innych platformach" + "value" : "Systemowy" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo Noutras Plataformas" + "value" : "Sistema" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo для других платформ" + "value" : "Тема системы" } } } }, - "preferences.email-protection" : { - "comment" : "Title of the option to show the Email Protection preferences", + "preferences.appearance.zoom-picker" : { + "comment" : "Default page zoom picker title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "E-Mail-Schutz" + "value" : "Standardmäßiger Seitenzoom" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Email Protection" + "value" : "Default page zoom" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Protección del correo electrónico" + "value" : "Zoom de página predeterminado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Protection des e-mails" + "value" : "Zoom par défaut de la page" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Protezione email" + "value" : "Zoom pagina predefinito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "E-mailbescherming" + "value" : "Standaard zoomniveau van de pagina" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ochrona poczty e-mail" + "value" : "Domyślne powiększenie strony" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Proteção de e-mail" + "value" : "Zoom da página predefinida" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Защита электронной почты" + "value" : "Масштаб страницы по умолчанию" } } } }, - "preferences.general" : { - "comment" : "Title of the option to show the General preferences", + "preferences.autofill" : { + "comment" : "Show Autofill preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Allgemein" + "value" : "Passwörter" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "General" + "value" : "Passwords" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "General" + "value" : "Contraseñas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Général" + "value" : "Mots de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Generale" + "value" : "Password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Algemeen" + "value" : "Wachtwoorden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ogólne" + "value" : "Hasła" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Geral" + "value" : "Palavras-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Основные" + "value" : "Пароли" } } } }, - "preferences.is-added-to-dock" : { - "comment" : "Indicates that the browser is added to the macOS system Dock", + "preferences.autofill-enabled-for" : { + "comment" : "Label presented before the email account in email protection preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo wird zum Dock hinzugefügt." + "value" : "Autovervollständigen in diesem Browser aktiviert für:" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo is added to the Dock." + "value" : "Autofill enabled in this browser for:" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo se ha añadido al Dock." + "value" : "Autocompletar habilitado en este navegador para:" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo a été ajouté au Dock." + "value" : "La saisie automatique est activée dans ce navigateur pour :" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo è stato aggiunto al dock." + "value" : "Compilazione automatica abilitata in questo browser per:" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo is toegevoegd aan het Dock." + "value" : "'Automatisch aanvullen' is ingeschakeld in deze browser voor:" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przeglądarka DuckDuckGo została dodana do Docka." + "value" : "Autouzupełnianie włączone w tej przeglądarce dla:" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O DuckDuckGo foi adicionado à Dock." + "value" : "Preenchimento automático ativado neste navegador para:" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Ярлык DuckDuckGo добавлен на док-панель." + "value" : "В этом браузере включено автозаполнение для:" } } } }, - "preferences.main-settings" : { - "comment" : "Section header in Preferences for main settings", + "preferences.cookie-pop-up-protection" : { + "comment" : "Title of the option to show the Cookie Pop-Up Protection preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Haupteinstellungen" + "value" : "Cookie-Pop-up-Schutz" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Main Settings" + "value" : "Cookie Pop-Up Protection" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes principales" + "value" : "Protección contra ventanas emergentes de cookies" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Réglages principaux" + "value" : "Protection contre les fenêtres contextuelles des cookies" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Impostazioni principali" + "value" : "Protezione pop-up dei cookie" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Hoofdinstellingen" + "value" : "Bescherming tegen cookiepop-ups" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustawienia główne" + "value" : "Ochrona przed wyskakującymi okienkami dotyczącymi plików cookie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Definições Principais" + "value" : "Proteção contra pop-ups de cookies" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Основные настройки" + "value" : "Защита от всплывающих окон куки" } } } }, - "preferences.not-added-to-dock" : { - "comment" : "Indicate that the browser is not added to macOS system Dock", + "preferences.data-clearing" : { + "comment" : "Title of the option to show the Data Clearing preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo wird nicht zum Dock hinzugefügt." + "value" : "Datenlöschung" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo is not added to the Dock." + "value" : "Data Clearing" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo no se ha añadido al Dock." + "value" : "Eliminación de datos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo n'a pas été ajouté au Dock." + "value" : "Effacement des données" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo non è stato aggiunto al dock." + "value" : "Cancellazione dati" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo is niet toegevoegd aan het Dock." + "value" : "Gegevens wissen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przeglądarka DuckDuckGo nie została dodana do Docka." + "value" : "Czyszczenie danych" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O DuckDuckGo não foi adicionado à Dock." + "value" : "Limpeza de Dados" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Ярлык DuckDuckGo не добавлен на док-панель." + "value" : "Очистка данных" } } } }, - "preferences.off" : { - "comment" : "Status indicator of a browser privacy protection feature.", + "preferences.default-browser" : { + "comment" : "Title of the option to show the Default Browser Preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Aus" + "value" : "Standard-Browser" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Off" + "value" : "Default Browser" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Desactivado" + "value" : "Navegador predeterminado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Désactivé" + "value" : "Navigateur par défaut" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Disattivato" + "value" : "Browser predefinito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Uit" + "value" : "Standaardbrowser" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wył." + "value" : "Domyślna przeglądarka" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Desligado" + "value" : "Navegador predefinido" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выкл." + "value" : "Браузер по умолчанию" } } } }, - "preferences.on" : { - "comment" : "Status indicator of a browser privacy protection feature.", + "preferences.default-browser.active" : { + "comment" : "Indicate that the browser is the default", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "An" + "value" : "DuckDuckGo ist dein Standard-Browser" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "On" + "value" : "DuckDuckGo is your default browser" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Activado" + "value" : "DuckDuckGo es tu navegador predeterminado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Activé" + "value" : "DuckDuckGo est votre navigateur par défaut" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "On" + "value" : "DuckDuckGo è il tuo browser predefinito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Aan" + "value" : "DuckDuckGo is je standaardbrowser" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wł." + "value" : "DuckDuckGo to Twoja domyślna przeglądarka" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ligado" + "value" : "DuckDuckGo é o teu navegador predefinido" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Вкл." + "value" : "DuckDuckGo — ваш браузер по умолчанию" } } } }, - "preferences.on-startup" : { - "comment" : "Name of the preferences section related to app startup", + "preferences.default-browser.button.make-default" : { + "comment" : "represents a prompt message asking the user to make DuckDuckGo their default browser.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Beim Start" + "value" : "DuckDuckGo zum Standard machen …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "On Startup" + "value" : "Make DuckDuckGo Default…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Al iniciar" + "value" : "Establecer DuckDuckGo como predeterminado…" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Au démarrage" + "value" : "Faire de DuckDuckGo votre navigateur par défaut…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "All'avvio" + "value" : "Rendi DuckDuckGo predefinito..." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bij opstarten" + "value" : "DuckDuckGo instellen als standaardbrowser ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przy uruchomieniu" + "value" : "Ustaw przeglądarkę DuckDuckGo jako domyślną…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "No arranque" + "value" : "Tornar o DuckDuckGo a predefinição…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "При запуске" + "value" : "Назначить DuckDuckGo браузером по умолчанию..." } } } }, - "preferences.privacy-protections" : { - "comment" : "The section header in Preferences representing browser features related to privacy protection", + "preferences.default-browser.inactive" : { + "comment" : "Indicate that the browser is not the default", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Datenschutz" + "value" : "DuckDuckGo ist nicht dein Standard-Browser." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Privacy Protections" + "value" : "DuckDuckGo is not your default browser." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Protecciones de privacidad" + "value" : "DuckDuckGo no es tu navegador predeterminado." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Protections de la confidentialité" + "value" : "DuckDuckGo n'est pas votre navigateur par défaut." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Protezioni della Privacy" + "value" : "DuckDuckGo non è il tuo browser predefinito." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Privacybescherming" + "value" : "DuckDuckGo is niet je standaardbrowser." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Mechanizmy Ochrony Prywatności" + "value" : "DuckDuckGo nie jest Twoją domyślną przeglądarką." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Proteções de Privacidade" + "value" : "O DuckDuckGo não é o teu navegador predefinido." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Защита конфиденциальности" + "value" : "DuckDuckGo не является вашим браузером по умолчанию." } } } }, - "preferences.private-search" : { - "comment" : "Title of the option to show the Private Search preferences", + "preferences.downloads" : { + "comment" : "Title of the downloads browser preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Private Suche" + "value" : "Downloads" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Private Search" + "value" : "Downloads" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Búsqueda privada" + "value" : "Descargas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Recherche privée" + "value" : "Téléchargements" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ricerca Privata" + "value" : "Download" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Privézoekopdracht" + "value" : "Downloads" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Prywatne wyszukiwanie" + "value" : "Pobrane" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Pesquisa Privada" + "value" : "Transferências" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Частный поиск" + "value" : "Загрузки" } } } }, - "preferences.reopen-windows" : { - "comment" : "Option to control session restoration", + "preferences.duck-player" : { + "comment" : "Title of the option to show the Duck Player browser preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alle Fenster der letzten Sitzung wieder öffnen" + "value" : "Duck Player" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Reopen all windows from last session" + "value" : "Duck Player" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Volver a abrir todas las ventanas de la última sesión" + "value" : "Duck Player" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Rouvrir toutes les fenêtres de la dernière session" + "value" : "Duck Player" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riapri tutte le finestre dell'ultima sessione" + "value" : "Duck Player" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alle vensters van vorige sessie opnieuw openen" + "value" : "Duck Player" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ponownie otwórz wszystkie okna z ostatniej sesji" + "value" : "Duck Player" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Reabrir todas as janelas da última sessão" + "value" : "Duck Player" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Заново открыть все окна из последнего сеанса" + "value" : "Проигрыватель Duck Player" } } } }, - "preferences.shortcuts" : { - "comment" : "Name of the preferences section related to shortcuts", + "preferences.duckduckgo-on-other-platforms" : { + "comment" : "Button presented to users to navigate them to our product page which presents all other products for other platforms", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Shortcuts" + "value" : "DuckDuckGo auf anderen Plattformen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Shortcuts" + "value" : "DuckDuckGo on Other Platforms" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Accesos directos" + "value" : "DuckDuckGo en otras plataformas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Raccourcis" + "value" : "DuckDuckGo sur d'autres plateformes" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Scorciatoie" + "value" : "DuckDuckGo su altre piattaforme" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Sneltoetsen" + "value" : "DuckDuckGo op andere platforms" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Skróty" + "value" : "DuckDuckGo na innych platformach" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Atalhos" + "value" : "DuckDuckGo Noutras Plataformas" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Ярлыки" + "value" : "DuckDuckGo для других платформ" } } } }, - "preferences.show-home" : { - "comment" : "Option to control session startup", + "preferences.email-protection" : { + "comment" : "Title of the option to show the Email Protection preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ein neues Fenster öffnen" + "value" : "E-Mail-Schutz" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open a new window" + "value" : "Email Protection" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir una ventana nueva" + "value" : "Protección del correo electrónico" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir une nouvelle fenêtre" + "value" : "Protection des e-mails" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri una nuova finestra" + "value" : "Protezione email" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Een nieuw venster openen" + "value" : "E-mailbescherming" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz nowe okno" + "value" : "Ochrona poczty e-mail" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir uma nova janela" + "value" : "Proteção de e-mail" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть новое окно" + "value" : "Защита электронной почты" } } } }, - "preferences.support" : { - "comment" : "Open support page", + "preferences.general" : { + "comment" : "Title of the option to show the General preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Unterstützung" + "value" : "Allgemein" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Support" + "value" : "General" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ayuda" + "value" : "General" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aide" + "value" : "Général" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Supporto" + "value" : "Generale" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Support" + "value" : "Algemeen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wsparcie" + "value" : "Ogólne" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Assistência" + "value" : "Geral" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Поддержка" + "value" : "Основные" } } } }, - "preferences.sync" : { - "comment" : "Title of the option to show the Sync preferences", + "preferences.is-added-to-dock" : { + "comment" : "Indicates that the browser is added to the macOS system Dock", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Synchronisieren und sichern" + "value" : "DuckDuckGo wird zum Dock hinzugefügt." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Sync & Backup" + "value" : "DuckDuckGo is added to the Dock." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Sincronización y copia de seguridad" + "value" : "DuckDuckGo se ha añadido al Dock." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Synchronisation et sauvegarde" + "value" : "DuckDuckGo a été ajouté au Dock." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Sincronizzazione e backup" + "value" : "DuckDuckGo è stato aggiunto al dock." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Synchronisatie en back-up" + "value" : "DuckDuckGo is toegevoegd aan het Dock." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Synchronizacja i kopia zapasowa" + "value" : "Przeglądarka DuckDuckGo została dodana do Docka." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Sincronização e cópia de segurança" + "value" : "O DuckDuckGo foi adicionado à Dock." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Синхронизация и резервное копирование" + "value" : "Ярлык DuckDuckGo добавлен на док-панель." } } } }, - "preferences.sync.auto-lock-prompt" : { - "comment" : "Reason for auth when setting up Sync", + "preferences.main-settings" : { + "comment" : "Section header in Preferences for main settings", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Gerät entsperren, um Sync & Backup einzurichten" + "value" : "Haupteinstellungen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Unlock device to setup Sync & Backup" + "value" : "Main Settings" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Desbloquea el dispositivo para configurar Sincronización y copia de seguridad" + "value" : "Ajustes principales" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Déverrouiller l'appareil pour configurer Sync & Backup" + "value" : "Réglages principaux" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Sblocca il dispositivo per configurare Sync & Backup" + "value" : "Impostazioni principali" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ontgrendel het apparaat om synchronisatie en back-up in te stellen" + "value" : "Hoofdinstellingen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Odblokuj urządzenie, aby skonfigurować funkcję Sync & Backup" + "value" : "Ustawienia główne" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Desbloquear dispositivo para configurar a Sync & Backup" + "value" : "Definições Principais" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Для настройки синхронизации и резервного копирования нужно разблокировать устройство." + "value" : "Основные настройки" } } } }, - "preferences.vpn" : { - "comment" : "Title of the option to show the VPN preferences", + "preferences.not-added-to-dock" : { + "comment" : "Indicate that the browser is not added to macOS system Dock", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "VPN" + "value" : "DuckDuckGo wird nicht zum Dock hinzugefügt." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "VPN" + "value" : "DuckDuckGo is not added to the Dock." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "VPN" + "value" : "DuckDuckGo no se ha añadido al Dock." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "VPN" + "value" : "DuckDuckGo n'a pas été ajouté au Dock." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "VPN" + "value" : "DuckDuckGo non è stato aggiunto al dock." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "VPN" + "value" : "DuckDuckGo is niet toegevoegd aan het Dock." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "VPN" + "value" : "Przeglądarka DuckDuckGo nie została dodana do Docka." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "VPN" + "value" : "O DuckDuckGo não foi adicionado à Dock." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "VPN" + "value" : "Ярлык DuckDuckGo не добавлен на док-панель." } } } }, - "preferences.web-tracking-protection" : { - "comment" : "Title of the option to show the Web Tracking Protection preferences", + "preferences.off" : { + "comment" : "Status indicator of a browser privacy protection feature.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Web Tracking Protection" + "value" : "Aus" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Web Tracking Protection" + "value" : "Off" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Protección de rastreo en la web" + "value" : "Desactivado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Protection contre le pistage sur le Web" + "value" : "Désactivé" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Protezione dal tracciamento web" + "value" : "Disattivato" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bescherming tegen webtracking" + "value" : "Uit" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ochrona przed śledzeniem w sieci" + "value" : "Wył." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Proteção contra rastreamento na internet" + "value" : "Desligado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Защита от отслеживания онлайн" + "value" : "Выкл." } } } }, - "prefrences.sync.bad.request.description" : { - "comment" : "Description of incorrectly formatted data error when syncing.", + "preferences.on" : { + "comment" : "Status indicator of a browser privacy protection feature.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einige Lesezeichen sind falsch formatiert oder zu lang und wurden nicht synchronisiert." + "value" : "An" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Some bookmarks are formatted incorrectly or too long and were not synced." + "value" : "On" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Algunos marcadores tienen un formato incorrecto o demasiado largo y no se han sincronizado." + "value" : "Activado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Certains signets sont mal formatés ou trop longs et n'ont pas été synchronisés." + "value" : "Activé" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Alcuni segnalibri sono formattati in modo errato o sono troppo lunghi e non sono stati sincronizzati." + "value" : "On" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Sommige bladwijzers hebben de verkeerde indeling of zijn te lang en werden niet gesynchroniseerd." + "value" : "Aan" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Niektóre zakładki mają niepoprawny format lub są zbyt długie i nie zostały zsynchronizowane." + "value" : "Wł." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Alguns marcadores estão formatados incorretamente ou são demasiado longos e não foram sincronizados." + "value" : "Ligado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Некоторые закладки не синхронизируются из-за неверного формата или превышения ограничений по длине." + "value" : "Вкл." } } } }, - "prefrences.sync.bookmarks-limit-exceeded-action" : { - "comment" : "Button title for sync bookmarks limits exceeded warning to go to manage bookmarks", + "preferences.on-startup" : { + "comment" : "Name of the preferences section related to app startup", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen verwalten" + "value" : "Beim Start" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Manage Bookmarks" + "value" : "On Startup" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Gestionar marcadores" + "value" : "Al iniciar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Gérer les signets" + "value" : "Au démarrage" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Gestisci segnalibri" + "value" : "All'avvio" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bladwijzers beheren" + "value" : "Bij opstarten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zarządzaj zakładkami" + "value" : "Przy uruchomieniu" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Gerir marcadores" + "value" : "No arranque" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Управление закладками" + "value" : "При запуске" } } } }, - "prefrences.sync.bookmarks-limit-exceeded-description" : { - "comment" : "Description for sync bookmarks limits exceeded warning", + "preferences.privacy-protections" : { + "comment" : "The section header in Preferences representing browser features related to privacy protection", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Du hast die maximale Anzahl von Lesezeichen erreicht. Lösche Lesezeichen, um die Synchronisierung fortzusetzen." + "value" : "Datenschutz" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "You've reached the maximum number of bookmarks. Please delete some to resume sync." + "value" : "Privacy Protections" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Has alcanzado el número máximo de marcadores. Elimina algunos para reanudar la sincronización." + "value" : "Protecciones de privacidad" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Vous avez atteint le nombre maximal de signets. Veuillez en supprimer quelques-uns pour reprendre la synchronisation." + "value" : "Protections de la confidentialité" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Hai raggiunto il numero massimo di segnalibri. Eliminane alcuni per riprendere la sincronizzazione." + "value" : "Protezioni della Privacy" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Je hebt het maximumaantal bladwijzers bereikt. Verwijder er een paar om de synchronisatie te hervatten." + "value" : "Privacybescherming" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Osiągnięto maksymalną liczbę zakładek. Usuń część z nich, aby wznowić synchronizację." + "value" : "Mechanizmy Ochrony Prywatności" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Atingiste o número máximo de marcadores. Elimina algumas para retomar a sincronização." + "value" : "Proteções de Privacidade" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Достигнуто максимальное число закладок. Чтобы возобновить синхронизацию, удалите некоторые из них." + "value" : "Защита конфиденциальности" } } } }, - "prefrences.sync.bookmarks.too-many-requests" : { - "comment" : "Description of too many requests error when syncing.", + "preferences.private-search" : { + "comment" : "Title of the option to show the Private Search preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sync & Backup ist vorübergehend nicht verfügbar." + "value" : "Private Suche" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Sync & Backup is temporarily unavailable." + "value" : "Private Search" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "La sincronización y la copia de seguridad no están disponibles temporalmente." + "value" : "Búsqueda privada" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Sync & Backup est temporairement indisponible." + "value" : "Recherche privée" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Sync & Backup non è temporaneamente disponibile." + "value" : "Ricerca Privata" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "'Synchronisatie en back-up' is tijdelijk niet beschikbaar." + "value" : "Privézoekopdracht" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Funkcja Sync & Backup jest tymczasowo niedostępna." + "value" : "Prywatne wyszukiwanie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O Sync & Backup está temporariamente indisponível." + "value" : "Pesquisa Privada" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Функция «Синхронизация и резервное копирование» временно недоступна." + "value" : "Частный поиск" } } } }, - "prefrences.sync.credentials-limit-exceeded-action" : { - "comment" : "Button title for sync credentials limits exceeded warning to go to manage passwords", + "preferences.reopen-windows" : { + "comment" : "Option to control session restoration", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwörter verwalten …" + "value" : "Alle Fenster der letzten Sitzung wieder öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Manage passwords…" + "value" : "Reopen all windows from last session" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Administrar contraseñas..." + "value" : "Volver a abrir todas las ventanas de la última sesión" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Gérer les mots de passe…" + "value" : "Rouvrir toutes les fenêtres de la dernière session" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Gestisci password…" + "value" : "Riapri tutte le finestre dell'ultima sessione" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoorden beheren …" + "value" : "Alle vensters van vorige sessie opnieuw openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zarządzaj hasłami…" + "value" : "Ponownie otwórz wszystkie okna z ostatniej sesji" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Gerir palavras-passe…" + "value" : "Reabrir todas as janelas da última sessão" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Управление паролями..." + "value" : "Заново открыть все окна из последнего сеанса" } } } }, - "prefrences.sync.credentials-limit-exceeded-description" : { - "comment" : "Description for sync credentials limits exceeded warning", + "preferences.shortcuts" : { + "comment" : "Name of the preferences section related to shortcuts", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Du hast die maximale Anzahl von Passwörtern erreicht. Lösche einige, um die Synchronisierung fortzusetzen." + "value" : "Shortcuts" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "You've reached the maximum number of passwords. Please delete some to resume sync." + "value" : "Shortcuts" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Has alcanzado el número máximo de contraseñas. Elimina algunas para reanudar la sincronización." + "value" : "Accesos directos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Vous avez atteint le nombre maximal de mots de passe. Veuillez en supprimer quelques-uns pour reprendre la synchronisation." + "value" : "Raccourcis" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Hai raggiunto il numero massimo di password. Eliminane alcuni per riprendere la sincronizzazione." + "value" : "Scorciatoie" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Je hebt het maximumaantal wachtwoorden bereikt. Verwijder er een paar om de synchronisatie te hervatten." + "value" : "Sneltoetsen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Osiągnięto maksymalną liczbę haseł. Usuń część z nich, aby wznowić synchronizację." + "value" : "Skróty" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Atingiste o número máximo de palavras-passe. Elimina algumas para retomar a sincronização." + "value" : "Atalhos" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Достигнуто максимальное число паролей. Чтобы возобновить синхронизацию, удалите некоторые из них." + "value" : "Ярлыки" } } } }, - "prefrences.sync.credentials.bad.request.description" : { - "comment" : "Description of incorrectly formatted data error when syncing.", + "preferences.show-home" : { + "comment" : "Option to control session startup", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einige Passwörter sind falsch formatiert oder zu lang und wurden nicht synchronisiert." + "value" : "Ein neues Fenster öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Some passwords are formatted incorrectly or too long and were not synced." + "value" : "Open a new window" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Algunas contraseñas tienen un formato incorrecto o demasiado largo y no se han sincronizado." + "value" : "Abrir una ventana nueva" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Certains mots de passe sont mal formatés ou trop longs et n'ont pas été synchronisés." + "value" : "Ouvrir une nouvelle fenêtre" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Alcune password sono formattate in modo errato o sono troppo lunghe e non sono state sincronizzate." + "value" : "Apri una nuova finestra" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Sommige wachtwoorden hebben de verkeerde indeling of zijn te lang en werden niet gesynchroniseerd." + "value" : "Een nieuw venster openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Niektóre hasła mają niepoprawny format lub są zbyt długie i nie zostały zsynchronizowane." + "value" : "Otwórz nowe okno" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Algumas palavras-passe estão formatadas incorretamente ou são demasiado longas e não foram sincronizadas." + "value" : "Abrir uma nova janela" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Некоторые пароли не синхронизируются из-за неверного формата или превышения ограничений по длине." + "value" : "Открыть новое окно" } } } }, - "prefrences.sync.invalid-login-description" : { - "comment" : "Description invalid credentials error when syncing.", + "preferences.support" : { + "comment" : "Open support page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bei der Synchronisierung ist ein Fehler aufgetreten. Versuche, die Synchronisierung auf diesem Gerät zu deaktivieren, und stelle dann die Verbindung mit einem anderen Gerät oder deinem Wiederherstellungscode wieder her." + "value" : "Unterstützung" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Sync encountered an error. Try disabling sync on this device and then reconnect using another device or your recovery code." + "value" : "Support" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Se ha producido un error en la sincronización. Intenta desactivar la sincronización en este dispositivo y, a continuación, vuelve a conectarte con otro dispositivo o con tu código de recuperación." + "value" : "Ayuda" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "La synchronisation a rencontré une erreur. Essayez de désactiver la synchronisation sur cet appareil, puis reconnectez-vous à l’aide d’un autre appareil ou de votre code de récupération." + "value" : "Aide" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Sync ha riscontrato un errore. Prova a disattivare la sincronizzazione su questo dispositivo e riconnettiti utilizzando un altro dispositivo o il codice di ripristino." + "value" : "Supporto" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Er is een fout opgetreden bij het synchroniseren. Probeer synchronisatie op dit apparaat uit te schakelen en maak dan opnieuw verbinding met een ander apparaat of met je herstelcode." + "value" : "Support" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Podczas synchronizacji wystąpił błąd. Spróbuj wyłączyć synchronizację na tym urządzeniu, a następnie ponownie nawiąż połączenie za pomocą innego urządzenia lub kodu odzyskiwania." + "value" : "Wsparcie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Erro durante a sincronização. Experimenta desativar a sincronização neste dispositivo e, em seguida, inicia sessão novamente noutro dispositivo ou com o teu código de recuperação." + "value" : "Assistência" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Произошла ошибка синхронизации. Попробуйте отключить синхронизацию на этом устройстве, а затем выполнить повторную привязку с помощью другого устройства или кода восстановления." + "value" : "Поддержка" } } } }, - "prefrences.sync.limit-exceeded-title" : { - "comment" : "Title for sync limits exceeded warning", + "preferences.sync" : { + "comment" : "Title of the option to show the Sync preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Synchronisierung angehalten" + "value" : "Synchronisieren und sichern" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Sync Paused" + "value" : "Sync & Backup" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Sincronización pausada" + "value" : "Sincronización y copia de seguridad" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Synchronisation suspendue" + "value" : "Synchronisation et sauvegarde" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Sincronizzazione in pausa" + "value" : "Sincronizzazione e backup" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Synchronisatie onderbroken" + "value" : "Synchronisatie en back-up" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Synchronizacja wstrzymana" + "value" : "Synchronizacja i kopia zapasowa" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Sincronização em pausa" + "value" : "Sincronização e cópia de segurança" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Синхронизация на паузе" + "value" : "Синхронизация и резервное копирование" } } } }, - "print.menu.item" : { - "comment" : "Menu item title", + "preferences.sync.auto-lock-prompt" : { + "comment" : "Reason for auth when setting up Sync", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Drucken …" + "value" : "Gerät entsperren, um Sync & Backup einzurichten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Print…" + "value" : "Unlock device to setup Sync & Backup" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Imprimir…" + "value" : "Desbloquea el dispositivo para configurar Sincronización y copia de seguridad" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Imprimer…" + "value" : "Déverrouiller l'appareil pour configurer Sync & Backup" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Stampa…" + "value" : "Sblocca il dispositivo per configurare Sync & Backup" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Afdrukken ..." + "value" : "Ontgrendel het apparaat om synchronisatie en back-up in te stellen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Drukuj…" + "value" : "Odblokuj urządzenie, aby skonfigurować funkcję Sync & Backup" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Imprimir…" + "value" : "Desbloquear dispositivo para configurar a Sync & Backup" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Печать…" + "value" : "Для настройки синхронизации и резервного копирования нужно разблокировать устройство." } } } }, - "Privacy Pro" : { + "preferences.vpn" : { + "comment" : "Title of the option to show the VPN preferences", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Privacy Pro" + "value" : "VPN" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "VPN" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Privacy Pro" + "value" : "VPN" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Privacy Pro" + "value" : "VPN" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Privacy Pro" + "value" : "VPN" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Privacy Pro" + "value" : "VPN" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Privacy Pro" + "value" : "VPN" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Privacy Pro" + "value" : "VPN" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Privacy Pro" + "value" : "VPN" } } } }, - "private.search.explenation" : { - "comment" : "feature explanation in settings", + "preferences.web-tracking-protection" : { + "comment" : "Title of the option to show the Web Tracking Protection preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo Private Search ist deine Standardsuchmaschine – du kannst also das Web durchsuchen, ohne getrackt zu werden." + "value" : "Web Tracking Protection" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo Private Search is your default search engine, so you can search the web without being tracked." + "value" : "Web Tracking Protection" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "La búsqueda privada de DuckDuckGo es tu motor de búsqueda predeterminado para que puedas buscar en la web sin que te rastreen." + "value" : "Protección de rastreo en la web" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo Private Search est votre moteur de recherche par défaut, qui vous permet d'effectuer des recherches sur le Web sans être suivi(e)." + "value" : "Protection contre le pistage sur le Web" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo Private Search è il tuo motore di ricerca predefinito, quindi puoi effettuare ricerche sul Web senza essere tracciato." + "value" : "Protezione dal tracciamento web" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo Private Search is je standaardzoekmachine, zodat je op het web kunt zoeken zonder gevolgd te worden." + "value" : "Bescherming tegen webtracking" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo Private Search to domyślna wyszukiwarka, dzięki której możesz wyszukiwać treści w sieci, unikając śledzenia." + "value" : "Ochrona przed śledzeniem w sieci" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O DuckDuckGo Private Search é o teu motor de pesquisa predefinido, para que possas pesquisar a web sem seres rastreado." + "value" : "Proteção contra rastreamento na internet" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo Private Search — ваша поисковая система по умолчанию. Вы можете пользоваться поиском в интернете, не опасаясь слежки." + "value" : "Защита от отслеживания онлайн" } } } }, - "quit" : { - "comment" : "Quit button", + "prefrences.sync.bad.request.description" : { + "comment" : "Description of incorrectly formatted data error when syncing.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verlassen" + "value" : "Einige Lesezeichen sind falsch formatiert oder zu lang und wurden nicht synchronisiert." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Quit" + "value" : "Some bookmarks are formatted incorrectly or too long and were not synced." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Salir" + "value" : "Algunos marcadores tienen un formato incorrecto o demasiado largo y no se han sincronizado." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Quitter" + "value" : "Certains signets sont mal formatés ou trop longs et n'ont pas été synchronisés." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Esci" + "value" : "Alcuni segnalibri sono formattati in modo errato o sono troppo lunghi e non sono stati sincronizzati." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Afsluiten" + "value" : "Sommige bladwijzers hebben de verkeerde indeling of zijn te lang en werden niet gesynchroniseerd." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyjdź" + "value" : "Niektóre zakładki mają niepoprawny format lub są zbyt długie i nie zostały zsynchronizowane." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Sair" + "value" : "Alguns marcadores estão formatados incorretamente ou são demasiado longos e não foram sincronizados." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выйти" + "value" : "Некоторые закладки не синхронизируются из-за неверного формата или превышения ограничений по длине." } } } }, - "quit.without.clearing" : { - "comment" : "Button to quit the application without clearing data", + "prefrences.sync.bookmarks-limit-exceeded-action" : { + "comment" : "Button title for sync bookmarks limits exceeded warning to go to manage bookmarks", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Beenden ohne Löschen" + "value" : "Lesezeichen verwalten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Quit Without Clearing" + "value" : "Manage Bookmarks" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Salir sin borrar" + "value" : "Gestionar marcadores" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Quitter sans effacer" + "value" : "Gérer les signets" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Esci senza cancellare" + "value" : "Gestisci segnalibri" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Stoppen zonder wissen" + "value" : "Bladwijzers beheren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyjdź bez czyszczenia" + "value" : "Zarządzaj zakładkami" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Sair sem limpar" + "value" : "Gerir marcadores" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выйти без очистки" + "value" : "Управление закладками" } } } }, - "Recently Closed" : { - "comment" : "Main Menu History item", + "prefrences.sync.bookmarks-limit-exceeded-description" : { + "comment" : "Description for sync bookmarks limits exceeded warning", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kürzlich geschlossen" + "value" : "Du hast die maximale Anzahl von Lesezeichen erreicht. Lösche Lesezeichen, um die Synchronisierung fortzusetzen." + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "You've reached the maximum number of bookmarks. Please delete some to resume sync." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cerrado recientemente" + "value" : "Has alcanzado el número máximo de marcadores. Elimina algunos para reanudar la sincronización." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Récemment clôturé" + "value" : "Vous avez atteint le nombre maximal de signets. Veuillez en supprimer quelques-uns pour reprendre la synchronisation." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Chiusa di recente" + "value" : "Hai raggiunto il numero massimo di segnalibri. Eliminane alcuni per riprendere la sincronizzazione." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Onlangs gesloten" + "value" : "Je hebt het maximumaantal bladwijzers bereikt. Verwijder er een paar om de synchronisatie te hervatten." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Niedawno zamknięte" + "value" : "Osiągnięto maksymalną liczbę zakładek. Usuń część z nich, aby wznowić synchronizację." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Fechados recentemente" + "value" : "Atingiste o número máximo de marcadores. Elimina algumas para retomar a sincronização." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Недавно закрытые" + "value" : "Достигнуто максимальное число закладок. Чтобы возобновить синхронизацию, удалите некоторые из них." } } } }, - "release.notes.menu.item" : { - "comment" : "Title of the dialog menu item that opens release notes", + "prefrences.sync.bookmarks.too-many-requests" : { + "comment" : "Description of too many requests error when syncing.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Versionshinweise" + "value" : "Sync & Backup ist vorübergehend nicht verfügbar." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Release Notes" + "value" : "Sync & Backup is temporarily unavailable." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Notas de la versión" + "value" : "La sincronización y la copia de seguridad no están disponibles temporalmente." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Notes de mise à jour" + "value" : "Sync & Backup est temporairement indisponible." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Note di rilascio" + "value" : "Sync & Backup non è temporaneamente disponibile." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Release-opmerkingen" + "value" : "'Synchronisatie en back-up' is tijdelijk niet beschikbaar." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Informacje o wersji" + "value" : "Funkcja Sync & Backup jest tymczasowo niedostępna." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Notas de lançamento" + "value" : "O Sync & Backup está temporariamente indisponível." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Примечания к релизу" + "value" : "Функция «Синхронизация и резервное копирование» временно недоступна." } } } }, - "Reload Page" : { - "comment" : "Main Menu View item", + "prefrences.sync.credentials-limit-exceeded-action" : { + "comment" : "Button title for sync credentials limits exceeded warning to go to manage passwords", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Seite neu laden" + "value" : "Passwörter verwalten …" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Manage passwords…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Volver a cargar página" + "value" : "Administrar contraseñas..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Recharger la page" + "value" : "Gérer les mots de passe…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ricarica pagina" + "value" : "Gestisci password…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Pagina opnieuw laden" + "value" : "Wachtwoorden beheren …" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Załaduj ponownie stronę" + "value" : "Zarządzaj hasłami…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Recarregar página" + "value" : "Gerir palavras-passe…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Перезагрузить страницу" + "value" : "Управление паролями..." } } } }, - "reload.page" : { - "comment" : "Context menu item", + "prefrences.sync.credentials-limit-exceeded-description" : { + "comment" : "Description for sync credentials limits exceeded warning", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Seite neu laden" + "value" : "Du hast die maximale Anzahl von Passwörtern erreicht. Lösche einige, um die Synchronisierung fortzusetzen." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Reload Page" + "value" : "You've reached the maximum number of passwords. Please delete some to resume sync." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Volver a cargar página" + "value" : "Has alcanzado el número máximo de contraseñas. Elimina algunas para reanudar la sincronización." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Recharger la page" + "value" : "Vous avez atteint le nombre maximal de mots de passe. Veuillez en supprimer quelques-uns pour reprendre la synchronisation." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ricarica pagina" + "value" : "Hai raggiunto il numero massimo di password. Eliminane alcuni per riprendere la sincronizzazione." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Pagina opnieuw laden" + "value" : "Je hebt het maximumaantal wachtwoorden bereikt. Verwijder er een paar om de synchronisatie te hervatten." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Załaduj ponownie stronę" + "value" : "Osiągnięto maksymalną liczbę haseł. Usuń część z nich, aby wznowić synchronizację." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Recarregar página" + "value" : "Atingiste o número máximo de palavras-passe. Elimina algumas para retomar a sincronização." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Перезагрузить страницу" + "value" : "Достигнуто максимальное число паролей. Чтобы возобновить синхронизацию, удалите некоторые из них." } } } }, - "remove-favorite" : { - "comment" : "Remove Favorite button", + "prefrences.sync.credentials.bad.request.description" : { + "comment" : "Description of incorrectly formatted data error when syncing.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Favorit entfernen" + "value" : "Einige Passwörter sind falsch formatiert oder zu lang und wurden nicht synchronisiert." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Remove Favorite" + "value" : "Some passwords are formatted incorrectly or too long and were not synced." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar favorito" + "value" : "Algunas contraseñas tienen un formato incorrecto o demasiado largo y no se han sincronizado." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Supprimer le favori" + "value" : "Certains mots de passe sont mal formatés ou trop longs et n'ont pas été synchronisés." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rimuovi Preferito" + "value" : "Alcune password sono formattate in modo errato o sono troppo lunghe e non sono state sincronizzate." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Favoriet verwijderen" + "value" : "Sommige wachtwoorden hebben de verkeerde indeling of zijn te lang en werden niet gesynchroniseerd." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usuń ulubione" + "value" : "Niektóre hasła mają niepoprawny format lub są zbyt długie i nie zostały zsynchronizowane." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Remover favorito" + "value" : "Algumas palavras-passe estão formatadas incorretamente ou são demasiado longas e não foram sincronizadas." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удалить из избранного" + "value" : "Некоторые пароли не синхронизируются из-за неверного формата или превышения ограничений по длине." } } } }, - "remove.from.favorites" : { - "comment" : "Button for removing bookmarks from favorites", + "prefrences.sync.invalid-login-description" : { + "comment" : "Description invalid credentials error when syncing.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Aus Favoriten entfernen" + "value" : "Bei der Synchronisierung ist ein Fehler aufgetreten. Versuche, die Synchronisierung auf diesem Gerät zu deaktivieren, und stelle dann die Verbindung mit einem anderen Gerät oder deinem Wiederherstellungscode wieder her." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Remove from Favorites" + "value" : "Sync encountered an error. Try disabling sync on this device and then reconnect using another device or your recovery code." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar de favoritos" + "value" : "Se ha producido un error en la sincronización. Intenta desactivar la sincronización en este dispositivo y, a continuación, vuelve a conectarte con otro dispositivo o con tu código de recuperación." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Supprimer des favoris" + "value" : "La synchronisation a rencontré une erreur. Essayez de désactiver la synchronisation sur cet appareil, puis reconnectez-vous à l’aide d’un autre appareil ou de votre code de récupération." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rimuovi dai preferiti" + "value" : "Sync ha riscontrato un errore. Prova a disattivare la sincronizzazione su questo dispositivo e riconnettiti utilizzando un altro dispositivo o il codice di ripristino." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verwijderen uit favorieten" + "value" : "Er is een fout opgetreden bij het synchroniseren. Probeer synchronisatie op dit apparaat uit te schakelen en maak dan opnieuw verbinding met een ander apparaat of met je herstelcode." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usuń z ulubionych" + "value" : "Podczas synchronizacji wystąpił błąd. Spróbuj wyłączyć synchronizację na tym urządzeniu, a następnie ponownie nawiąż połączenie za pomocą innego urządzenia lub kodu odzyskiwania." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Remover dos favoritos" + "value" : "Erro durante a sincronização. Experimenta desativar a sincronização neste dispositivo e, em seguida, inicia sessão novamente noutro dispositivo ou com o teu código de recuperação." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удалить из избранного" + "value" : "Произошла ошибка синхронизации. Попробуйте отключить синхронизацию на этом устройстве, а затем выполнить повторную привязку с помощью другого устройства или кода восстановления." } } } }, - "Reopen All Windows from Last Session" : { - "comment" : "Main Menu History item", + "prefrences.sync.limit-exceeded-title" : { + "comment" : "Title for sync limits exceeded warning", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alle Fenster der letzten Sitzung wieder öffnen" + "value" : "Synchronisierung angehalten" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Sync Paused" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Volver a abrir todas las ventanas de la última sesión" + "value" : "Sincronización pausada" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Rouvrir toutes les fenêtres de la dernière session" + "value" : "Synchronisation suspendue" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riapri tutte le finestre dell'ultima sessione" + "value" : "Sincronizzazione in pausa" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Alle vensters van de laatste sessie opnieuw openen" + "value" : "Synchronisatie onderbroken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz ponownie wszystkie okna z ostatniej sesji" + "value" : "Synchronizacja wstrzymana" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Reabrir todas as janelas da última sessão" + "value" : "Sincronização em pausa" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Заново открыть все окна из последнего сеанса" + "value" : "Синхронизация на паузе" } } } }, - "reopen.last.closed.tab" : { - "comment" : "This string represents an action to reopen the last closed tab in the browser", + "print.menu.item" : { + "comment" : "Menu item title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Letzten geschlossenen Tab erneut öffnen" + "value" : "Drucken …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Reopen Last Closed Tab" + "value" : "Print…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Volver a abrir la última pestaña cerrada" + "value" : "Imprimir…" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Rouvrir le dernier onglet fermé" + "value" : "Imprimer…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riapri l'ultima scheda chiusa" + "value" : "Stampa…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Laatste gesloten tabblad opnieuw openen" + "value" : "Afdrukken ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz ponownie ostatnio zamkniętą kartę" + "value" : "Drukuj…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Reabrir o último separador fechado" + "value" : "Imprimir…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Заново открыть последнюю закрытую вкладку" + "value" : "Печать…" } } } }, - "reopen.last.closed.window" : { - "comment" : "This string represents an action to reopen the last closed window in the browser", - "extractionState" : "extracted_with_value", + "Privacy Pro" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Letztes geschlossenes Fenster erneut öffnen" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Reopen Last Closed Window" + "value" : "Privacy Pro" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Volver a abrir la última ventana cerrada" + "value" : "Privacy Pro" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Rouvrir la dernière fenêtre fermée" + "value" : "Privacy Pro" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riapri l'ultima finestra chiusa" + "value" : "Privacy Pro" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Laatste gesloten venster opnieuw openen" + "value" : "Privacy Pro" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz ponownie ostatnio zamknięte okno" + "value" : "Privacy Pro" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Reabrir a última janela fechada" + "value" : "Privacy Pro" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Заново открыть последнее закрытое окно" + "value" : "Privacy Pro" } } } }, - "report.broken.site" : { - "comment" : "Menu with feedback commands", + "private.search.explenation" : { + "comment" : "feature explanation in settings", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fehlerhafte Website melden" + "value" : "DuckDuckGo Private Search ist deine Standardsuchmaschine – du kannst also das Web durchsuchen, ohne getrackt zu werden." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Report Broken Site" + "value" : "DuckDuckGo Private Search is your default search engine, so you can search the web without being tracked." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Informar de sitio web dañado" + "value" : "La búsqueda privada de DuckDuckGo es tu motor de búsqueda predeterminado para que puedas buscar en la web sin que te rastreen." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Signaler un problème de site" + "value" : "DuckDuckGo Private Search est votre moteur de recherche par défaut, qui vous permet d'effectuer des recherches sur le Web sans être suivi(e)." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Segnala sito danneggiato" + "value" : "DuckDuckGo Private Search è il tuo motore di ricerca predefinito, quindi puoi effettuare ricerche sul Web senza essere tracciato." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Defecte website melden" + "value" : "DuckDuckGo Private Search is je standaardzoekmachine, zodat je op het web kunt zoeken zonder gevolgd te worden." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zgłoś uszkodzoną witrynę" + "value" : "DuckDuckGo Private Search to domyślna wyszukiwarka, dzięki której możesz wyszukiwać treści w sieci, unikając śledzenia." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Denunciar site danificado" + "value" : "O DuckDuckGo Private Search é o teu motor de pesquisa predefinido, para que possas pesquisar a web sem seres rastreado." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сообщить о неработающем сайте" + "value" : "DuckDuckGo Private Search — ваша поисковая система по умолчанию. Вы можете пользоваться поиском в интернете, не опасаясь слежки." } } } }, - "reset-zoom" : { - "comment" : "Button that allows the user to reset the zoom level of the browser page", + "quit" : { + "comment" : "Quit button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zurücksetzen" + "value" : "Verlassen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Reset" + "value" : "Quit" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Reiniciar" + "value" : "Salir" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Réinitialiser" + "value" : "Quitter" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ripristina" + "value" : "Esci" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Herstel" + "value" : "Afsluiten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Resetowanie" + "value" : "Wyjdź" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Reset" + "value" : "Sair" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сбросить" + "value" : "Выйти" } } } }, - "restart.bitwarden" : { - "comment" : "Button to restart Bitwarden application", + "quit.without.clearing" : { + "comment" : "Button to quit the application without clearing data", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden neu starten" + "value" : "Beenden ohne Löschen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Restart Bitwarden" + "value" : "Quit Without Clearing" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Reiniciar bitwarden" + "value" : "Salir sin borrar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Redémarrer Bitwarden" + "value" : "Quitter sans effacer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riavvia Bitwarden" + "value" : "Esci senza cancellare" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden opnieuw opstarten" + "value" : "Stoppen zonder wissen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Uruchom ponownie Bitwarden" + "value" : "Wyjdź bez czyszczenia" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Reiniciar o Bitwarden" + "value" : "Sair sem limpar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Перезапустить Bitwarden" + "value" : "Выйти без очистки" } } } }, - "restart.bitwarden.info" : { - "comment" : "This string represents a message informing the user that Bitwarden is not responding and prompts them to restart the application to initiate communication again.", - "extractionState" : "extracted_with_value", + "Recently Closed" : { + "comment" : "Main Menu History item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden reagiert nicht. Bitte neu starten, um die Kommunikation erneut zu starten" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Bitwarden is not responding. Please restart it to initiate the communication again" + "value" : "Kürzlich geschlossen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden no responde. Reinícialo para volver a iniciar la comunicación" + "value" : "Cerrado recientemente" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden ne répond pas. Veuillez redémarrer l'application pour relancer la communication." + "value" : "Récemment clôturé" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden non risponde. Riavviala per iniziare nuovamente la comunicazione" + "value" : "Chiusa di recente" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bitwarden reageert niet. Start het opnieuw op om de communicatie opnieuw te starten" + "value" : "Onlangs gesloten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Aplikacja Bitwarden nie odpowiada. Uruchom ją ponownie, aby ponownie zainicjować komunikację" + "value" : "Niedawno zamknięte" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O Bitwarden não está a responder. Reinicia-o para reiniciar a comunicação" + "value" : "Fechados recentemente" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Приложение Bitwarden не отвечает. Перезапустите его, чтобы возобновить связь." + "value" : "Недавно закрытые" } } } }, - "save" : { - "comment" : "Save button", + "release.notes.menu.item" : { + "comment" : "Title of the dialog menu item that opens release notes", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Speichern" + "value" : "Versionshinweise" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Save" + "value" : "Release Notes" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar" + "value" : "Notas de la versión" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Enregistrer" + "value" : "Notes de mise à jour" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Salva" + "value" : "Note di rilascio" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Opslaan" + "value" : "Release-opmerkingen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zapisz" + "value" : "Informacje o wersji" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar" + "value" : "Notas de lançamento" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сохранить" + "value" : "Примечания к релизу" } } } }, - "save.image.as" : { - "comment" : "Context menu item", - "extractionState" : "extracted_with_value", + "Reload Page" : { + "comment" : "Main Menu View item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bild speichern unter …" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Save Image As…" + "value" : "Seite neu laden" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar imagen como..." + "value" : "Volver a cargar página" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Enregistrer l'image sous…" + "value" : "Recharger la page" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Salva immagine come…" + "value" : "Ricarica pagina" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Afbeelding opslaan als..." + "value" : "Pagina opnieuw laden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zapisz obraz jako…" + "value" : "Załaduj ponownie stronę" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar imagem como…" + "value" : "Recarregar página" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сохранить изображение как..." + "value" : "Перезагрузить страницу" } } } }, - "scroll.to.find.app.settings" : { - "comment" : "Setup of the integration with Bitwarden app", + "reload.page" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Scrolle, um den Abschnitt App-Einstellungen (Alle Konten) zu finden." + "value" : "Seite neu laden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Scroll to find the App Settings (All Accounts) section." + "value" : "Reload Page" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Desplázate para buscar la sección Configuración de la aplicación (todas las cuentas)." + "value" : "Volver a cargar página" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Faites défiler pour trouver la section Réglages de l'application (Tous les comptes)." + "value" : "Recharger la page" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Scorri per trovare la sezione Impostazioni app (tutti gli account)." + "value" : "Ricarica pagina" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Scroll naar de sectie app-instellingen (Alle accounts)." + "value" : "Pagina opnieuw laden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przewiń, aby znaleźć sekcję ustawień aplikacji (wszystkie konta)." + "value" : "Załaduj ponownie stronę" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Desloca-te até encontrares a secção Definições da aplicação (todas as contas)." + "value" : "Recarregar página" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Прокрутите страницу вниз до раздела «Настройки приложения (все учетные записи)»." + "value" : "Перезагрузить страницу" } } } }, - "search.with.DuckDuckGo" : { - "comment" : "Context menu item", + "remove-favorite" : { + "comment" : "Remove Favorite button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Suchen mit DuckDuckGo" + "value" : "Favorit entfernen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Search with DuckDuckGo" + "value" : "Remove Favorite" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Buscar con DuckDuckGo" + "value" : "Eliminar favorito" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Rechercher avec DuckDuckGo" + "value" : "Supprimer le favori" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cerca con DuckDuckGo" + "value" : "Rimuovi Preferito" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Zoeken met DuckDuckGo" + "value" : "Favoriet verwijderen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyszukaj z DuckDuckGo" + "value" : "Usuń ulubione" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Pesquisar com o DuckDuckGo" + "value" : "Remover favorito" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Поиск в DuckDuckGo" + "value" : "Удалить из избранного" } } } }, - "Select %@ Folder…" : { + "remove.from.favorites" : { + "comment" : "Button for removing bookmarks from favorites", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wähle den Ordner %@ ..." + "value" : "Aus Favoriten entfernen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Remove from Favorites" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Seleccionar Carpeta %@..." + "value" : "Eliminar de favoritos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Sélectionner le dossier %@…" + "value" : "Supprimer des favoris" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Seleziona %@ cartella…" + "value" : "Rimuovi dai preferiti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Map %@ selecteren ..." + "value" : "Verwijderen uit favorieten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wybierz folder %@…" + "value" : "Usuń z ulubionych" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Selecionar pasta %@…" + "value" : "Remover dos favoritos" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выбрать папку %@..." + "value" : "Удалить из избранного" } } } }, - "Select data to import:" : { - "comment" : "Data Import section title for checkboxes of data type to import: Passwords or Bookmarks.", - "extractionState" : "stale", + "Reopen All Windows from Last Session" : { + "comment" : "Main Menu History item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zu importierende Daten auswählen:" + "value" : "Alle Fenster der letzten Sitzung wieder öffnen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Selecciona los datos a importar:" + "value" : "Volver a abrir todas las ventanas de la última sesión" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Sélectionner les données à importer :" + "value" : "Rouvrir toutes les fenêtres de la dernière session" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Seleziona i dati da importare:" + "value" : "Riapri tutte le finestre dell'ultima sessione" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Selecteer gegevens om te importeren:" + "value" : "Alle vensters van de laatste sessie opnieuw openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wybierz dane do zaimportowania:" + "value" : "Otwórz ponownie wszystkie okna z ostatniej sesji" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Selecionar dados para importar:" + "value" : "Reabrir todas as janelas da última sessão" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выберите данные для импорта:" + "value" : "Заново открыть все окна из последнего сеанса" } } } }, - "Select Data to Import:" : { - "comment" : "Data Import section title for checkboxes of data type to import: Passwords or Bookmarks.", + "reopen.last.closed.tab" : { + "comment" : "This string represents an action to reopen the last closed tab in the browser", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zu importierende Daten auswählen:" + "value" : "Letzten geschlossenen Tab erneut öffnen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Reopen Last Closed Tab" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Selecciona los datos a importar:" + "value" : "Volver a abrir la última pestaña cerrada" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Sélectionner les données à importer :" + "value" : "Rouvrir le dernier onglet fermé" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Seleziona i dati da importare:" + "value" : "Riapri l'ultima scheda chiusa" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Selecteer gegevens om te importeren:" + "value" : "Laatste gesloten tabblad opnieuw openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wybierz dane do zaimportowania:" + "value" : "Otwórz ponownie ostatnio zamkniętą kartę" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Selecionar dados para importar:" + "value" : "Reabrir o último separador fechado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выберите данные для импорта:" + "value" : "Заново открыть последнюю закрытую вкладку" } } } }, - "Select Profile:" : { - "comment" : "Browser Profile picker title for Data Import", + "reopen.last.closed.window" : { + "comment" : "This string represents an action to reopen the last closed window in the browser", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Profil auswählen:" + "value" : "Letztes geschlossenes Fenster erneut öffnen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Reopen Last Closed Window" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Selecciona perfil:" + "value" : "Volver a abrir la última ventana cerrada" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Sélectionner le profil :" + "value" : "Rouvrir la dernière fenêtre fermée" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Seleziona profilo:" + "value" : "Riapri l'ultima finestra chiusa" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Profiel selecteren:" + "value" : "Laatste gesloten venster opnieuw openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wybierz profil:" + "value" : "Otwórz ponownie ostatnio zamknięte okno" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Selecionar perfil:" + "value" : "Reabrir a última janela fechada" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выберите профиль:" + "value" : "Заново открыть последнее закрытое окно" } } } }, - "select.bitwarden.preferences" : { - "comment" : "Setup of the integration with Bitwarden app (up to and including macOS 12)", + "report.broken.site" : { + "comment" : "Menu with feedback commands", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wähle Bitwarden → Einstellungen in der Mac-Menüleiste aus." + "value" : "Fehlerhafte Website melden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Select Bitwarden → Preferences from the Mac menu bar." + "value" : "Report Broken Site" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Selecciona Bitwarden → Preferencias en la barra de menú de Mac." + "value" : "Informar de sitio web dañado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Sélectionnez Bitwarden → Préférences dans la barre de menu Mac." + "value" : "Signaler un problème de site" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Seleziona Bitwarden → Preferenze dalla barra dei menu del Mac." + "value" : "Segnala sito danneggiato" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Selecteer Bitwarden → Voorkeuren in de Mac-menubalk." + "value" : "Defecte website melden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wybierz Bitwarden → Preferencje na pasku menu komputera Mac." + "value" : "Zgłoś uszkodzoną witrynę" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Seleciona Bitwarden → Preferências na barra de menus do Mac." + "value" : "Denunciar site danificado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "В строке меню Mac выберите Bitwarden → Предпочтения." + "value" : "Сообщить о неработающем сайте" } } } }, - "select.bitwarden.settings" : { - "comment" : "Setup of the integration with Bitwarden app (macOS 13 and above)", + "reset-zoom" : { + "comment" : "Button that allows the user to reset the zoom level of the browser page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wähle Bitwarden → Einstellungen in der Mac-Menüleiste aus." + "value" : "Zurücksetzen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Select Bitwarden → Settings from the Mac menu bar." + "value" : "Reset" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Selecciona Bitwarden → Configuración en la barra de menú de Mac." + "value" : "Reiniciar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Sélectionnez Bitwarden → Réglages dans la barre de menus Mac." + "value" : "Réinitialiser" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Seleziona Bitwarden → Impostazioni dalla barra dei menu del Mac." + "value" : "Ripristina" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Selecteer Bitwarden → Instellingen in de Mac-menubalk." + "value" : "Herstel" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wybierz Bitwarden → Ustawienia na pasku menu komputera Mac." + "value" : "Resetowanie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Seleciona Bitwarden → Definições na barra de menus do Mac." + "value" : "Reset" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "В строке меню Mac выберите Bitwarden → Настройки." + "value" : "Сбросить" } } } }, - "send.browser.feedback" : { - "comment" : "Menu with feedback commands", + "restart.bitwarden" : { + "comment" : "Button to restart Bitwarden application", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Browser-Feedback senden" + "value" : "Bitwarden neu starten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Send Browser Feedback" + "value" : "Restart Bitwarden" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar comentarios del navegador" + "value" : "Reiniciar bitwarden" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Envoyer des commentaires sur le navigateur" + "value" : "Redémarrer Bitwarden" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Invia feedback sul browser" + "value" : "Riavvia Bitwarden" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Feedback over browser sturen" + "value" : "Bitwarden opnieuw opstarten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyślij opinię o przeglądarce" + "value" : "Uruchom ponownie Bitwarden" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar feedback sobre o navegador" + "value" : "Reiniciar o Bitwarden" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отправить отзыв о браузере" + "value" : "Перезапустить Bitwarden" } } } }, - "send.browser.feedback.feedback-helps" : { - "comment" : "Text shown to the user when they provide feedback.", + "restart.bitwarden.info" : { + "comment" : "This string represents a message informing the user that Bitwarden is not responding and prompts them to restart the application to initiate communication again.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Dein Feedback hilft uns, den DuckDuckGo-Browser zu verbessern." + "value" : "Bitwarden reagiert nicht. Bitte neu starten, um die Kommunikation erneut zu starten" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Your feedback will help us improve the DuckDuckGo browser." + "value" : "Bitwarden is not responding. Please restart it to initiate the communication again" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Tus comentarios nos ayudarán a mejorar el navegador DuckDuckGo." + "value" : "Bitwarden no responde. Reinícialo para volver a iniciar la comunicación" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Vos commentaires nous aideront à améliorer le navigateur DuckDuckGo." + "value" : "Bitwarden ne répond pas. Veuillez redémarrer l'application pour relancer la communication." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Il tuo feedback ci aiuterà a migliorare il browser DuckDuckGo." + "value" : "Bitwarden non risponde. Riavviala per iniziare nuovamente la comunicazione" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Jouw feedback helpt ons de DuckDuckGo-browser te verbeteren." + "value" : "Bitwarden reageert niet. Start het opnieuw op om de communicatie opnieuw te starten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Twoja opinia pomoże nam ulepszyć przeglądarkę DuckDuckGo." + "value" : "Aplikacja Bitwarden nie odpowiada. Uruchom ją ponownie, aby ponownie zainicjować komunikację" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O teu feedback ajuda-nos a melhorar o navegador DuckDuckGo." + "value" : "O Bitwarden não está a responder. Reinicia-o para reiniciar a comunicação" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Ваш ответ поможет браузеру DuckDuckGo стать еще лучше." + "value" : "Приложение Bitwarden не отвечает. Перезапустите его, чтобы возобновить связь." } } } }, - "send.browser.feedback.general-feedback" : { - "comment" : "Name of the option the user can chose to give general browser feedback", + "save" : { + "comment" : "Save button", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Allgemeines Feedback" + "value" : "Speichern" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "General feedback" + "value" : "Save" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Comentarios generales" + "value" : "Guardar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Commentaires d'ordre général" + "value" : "Enregistrer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Feedback generale" + "value" : "Salva" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Algemene feedback" + "value" : "Opslaan" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Opinia ogólna" + "value" : "Zapisz" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Feedback geral" + "value" : "Guardar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Общий комментарий" + "value" : "Сохранить" } } } }, - "send.browser.feedback.report-problem" : { - "comment" : "Name of the option the user can chose to give browser feedback about a problem they enountered", + "save.image.as" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Problem melden" + "value" : "Bild speichern unter …" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Report a problem" + "value" : "Save Image As…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Informar de un problema" + "value" : "Guardar imagen como..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Signaler un problème" + "value" : "Enregistrer l'image sous…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Segnala un problema" + "value" : "Salva immagine come…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Een probleem melden" + "value" : "Afbeelding opslaan als..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zgłoś problem" + "value" : "Zapisz obraz jako…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Comunicar um problema" + "value" : "Guardar imagem como…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отчет о проблеме" + "value" : "Сохранить изображение как..." } } } }, - "send.browser.feedback.request-feature" : { - "comment" : "Name of the option the user can chose to give browser feedback about a feature they would like", + "scroll.to.find.app.settings" : { + "comment" : "Setup of the integration with Bitwarden app", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Eine Funktion anfordern" + "value" : "Scrolle, um den Abschnitt App-Einstellungen (Alle Konten) zu finden." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Request a feature" + "value" : "Scroll to find the App Settings (All Accounts) section." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Solicitar una función" + "value" : "Desplázate para buscar la sección Configuración de la aplicación (todas las cuentas)." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Demander une fonctionnalité" + "value" : "Faites défiler pour trouver la section Réglages de l'application (Tous les comptes)." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Richiedi una funzionalità" + "value" : "Scorri per trovare la sezione Impostazioni app (tutti gli account)." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Een functie aanvragen" + "value" : "Scroll naar de sectie app-instellingen (Alle accounts)." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Poproś o funkcję" + "value" : "Przewiń, aby znaleźć sekcję ustawień aplikacji (wszystkie konta)." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Pedir uma funcionalidade" + "value" : "Desloca-te até encontrares a secção Definições da aplicação (todas as contas)." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Запрос функции" + "value" : "Прокрутите страницу вниз до раздела «Настройки приложения (все учетные записи)»." } } } }, - "send.browser.feedback.select-category" : { - "comment" : "Title of the picker where the user can chose the category of the feedback they want ot send.", + "search.with.DuckDuckGo" : { + "comment" : "Context menu item", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "EINE KATEGORIE AUSWÄHLEN" + "value" : "Suchen mit DuckDuckGo" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Select a category" + "value" : "Search with DuckDuckGo" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "SELECCIONA UNA CATEGORÍA" + "value" : "Buscar con DuckDuckGo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "SÉLECTIONNER UNE CATÉGORIE" + "value" : "Rechercher avec DuckDuckGo" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "SELEZIONA UNA CATEGORIA" + "value" : "Cerca con DuckDuckGo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "SELECTEER EEN CATEGORIE" + "value" : "Zoeken met DuckDuckGo" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "WYBIERZ KATEGORIĘ" + "value" : "Wyszukaj z DuckDuckGo" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "SELECIONA UMA CATEGORIA" + "value" : "Pesquisar com o DuckDuckGo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "ВЫБЕРИТЕ КАТЕГОРИЮ" + "value" : "Поиск в DuckDuckGo" } } } }, - "send.browser.feedback.thankyou" : { - "comment" : "Thanks the user for sending feedback", - "extractionState" : "extracted_with_value", + "Select %@ Folder…" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Vielen Dank!" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Thank you!" + "value" : "Wähle den Ordner %@ ..." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¡Gracias!" + "value" : "Seleccionar Carpeta %@..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Merci !" + "value" : "Sélectionner le dossier %@…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Grazie!" + "value" : "Seleziona %@ cartella…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bedankt!" + "value" : "Map %@ selecteren ..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dziękujemy!" + "value" : "Wybierz folder %@…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Obrigado!" + "value" : "Selecionar pasta %@…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Спасибо!" + "value" : "Выбрать папку %@..." } } } }, - "send.browser.feedback.title" : { - "comment" : "Title of the interface to send feedback on the browser", - "extractionState" : "extracted_with_value", + "Select Data to Import:" : { + "comment" : "Data Import section title for checkboxes of data type to import: Passwords or Bookmarks.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hilf mit, den DuckDuckGo-Browser zu verbessern" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Help Improve the DuckDuckGo Browser" + "value" : "Zu importierende Daten auswählen:" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ayuda a mejorar el navegador DuckDuckGo" + "value" : "Selecciona los datos a importar:" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Contribuer à l'amélioration du navigateur DuckDuckGo" + "value" : "Sélectionner les données à importer :" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Contribuisci a migliorare il browser DuckDuckGo" + "value" : "Seleziona i dati da importare:" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Help de DuckDuckGo-browser te verbeteren" + "value" : "Selecteer gegevens om te importeren:" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pomóż ulepszać przeglądarkę DuckDuckGo" + "value" : "Wybierz dane do zaimportowania:" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Ajudar a melhorar o navegador DuckDuckGo" + "value" : "Selecionar dados para importar:" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Помогите улучшить браузер DuckDuckGo" + "value" : "Выберите данные для импорта:" } } } }, - "settings" : { - "comment" : "Menu item for opening settings", - "extractionState" : "extracted_with_value", + "Select Profile:" : { + "comment" : "Browser Profile picker title for Data Import", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einstellungen" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Settings" + "value" : "Profil auswählen:" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes" + "value" : "Selecciona perfil:" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Paramètres" + "value" : "Sélectionner le profil :" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Impostazioni" + "value" : "Seleziona profilo:" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Instellingen" + "value" : "Profiel selecteren:" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustawienia" + "value" : "Wybierz profil:" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Definições" + "value" : "Selecionar perfil:" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Настройки" + "value" : "Выберите профиль:" } } } }, - "Settings…" : { - "comment" : "Menu item", + "select.bitwarden.preferences" : { + "comment" : "Setup of the integration with Bitwarden app (up to and including macOS 12)", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einstellungen …" + "value" : "Wähle Bitwarden → Einstellungen in der Mac-Menüleiste aus." + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Select Bitwarden → Preferences from the Mac menu bar." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes" + "value" : "Selecciona Bitwarden → Preferencias en la barra de menú de Mac." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Réglages…" + "value" : "Sélectionnez Bitwarden → Préférences dans la barre de menu Mac." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Impostazioni…" + "value" : "Seleziona Bitwarden → Preferenze dalla barra dei menu del Mac." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Instellingen …" + "value" : "Selecteer Bitwarden → Voorkeuren in de Mac-menubalk." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustawienia…" + "value" : "Wybierz Bitwarden → Preferencje na pasku menu komputera Mac." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Definições…" + "value" : "Seleciona Bitwarden → Preferências na barra de menus do Mac." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Настройки..." + "value" : "В строке меню Mac выберите Bitwarden → Предпочтения." } } } }, - "settings.automatic.updates" : { - "comment" : "Title of the checkbox item to set up automatic updates of the browser", + "select.bitwarden.settings" : { + "comment" : "Setup of the integration with Bitwarden app (macOS 13 and above)", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Updates automatisch installieren (empfohlen)" + "value" : "Wähle Bitwarden → Einstellungen in der Mac-Menüleiste aus." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Automatically install updates (recommended)" + "value" : "Select Bitwarden → Settings from the Mac menu bar." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Instalar actualizaciones automáticamente (recomendado)" + "value" : "Selecciona Bitwarden → Configuración en la barra de menú de Mac." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Installer automatiquement les mises à jour (recommandé)" + "value" : "Sélectionnez Bitwarden → Réglages dans la barre de menus Mac." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Installa automaticamente gli aggiornamenti (scelta consigliata)" + "value" : "Seleziona Bitwarden → Impostazioni dalla barra dei menu del Mac." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Updates automatisch installeren (aanbevolen)" + "value" : "Selecteer Bitwarden → Instellingen in de Mac-menubalk." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Automatyczne instalowanie aktualizacji (zalecane)" + "value" : "Wybierz Bitwarden → Ustawienia na pasku menu komputera Mac." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Instalar atualizações automaticamente (recomendado)" + "value" : "Seleciona Bitwarden → Definições na barra de menus do Mac." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Автоматически устанавливать обновления (рекомендуется)" + "value" : "В строке меню Mac выберите Bitwarden → Настройки." } } } }, - "settings.browser.updates.title" : { - "comment" : "Title of the section in Settings where people set up automatic vs manual updates", + "send.browser.feedback" : { + "comment" : "Menu with feedback commands", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Browser-Updates" + "value" : "Browser-Feedback senden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Browser Updates" + "value" : "Send Browser Feedback" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Actualizaciones del navegador" + "value" : "Enviar comentarios del navegador" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mises à jour du navigateur" + "value" : "Envoyer des commentaires sur le navigateur" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aggiornamenti del browser" + "value" : "Invia feedback sul browser" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Browserupdates" + "value" : "Feedback over browser sturen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Aktualizacje przeglądarki" + "value" : "Wyślij opinię o przeglądarce" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Atualizações do navegador" + "value" : "Enviar feedback sobre o navegador" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Обновления браузера" + "value" : "Отправить отзыв о браузере" } } } }, - "settings.checking.for.update" : { - "comment" : "Label informing users the app is currently checking for new update", + "send.browser.feedback.feedback-helps" : { + "comment" : "Text shown to the user when they provide feedback.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nach Update suchen" + "value" : "Dein Feedback hilft uns, den DuckDuckGo-Browser zu verbessern." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Checking for update" + "value" : "Your feedback will help us improve the DuckDuckGo browser." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Buscando actualización" + "value" : "Tus comentarios nos ayudarán a mejorar el navegador DuckDuckGo." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Vérification de la mise à jour" + "value" : "Vos commentaires nous aideront à améliorer le navigateur DuckDuckGo." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Controllo della presenza di aggiornamenti" + "value" : "Il tuo feedback ci aiuterà a migliorare il browser DuckDuckGo." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Controleren op updates" + "value" : "Jouw feedback helpt ons de DuckDuckGo-browser te verbeteren." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Sprawdzanie dostępności aktualizacji" + "value" : "Twoja opinia pomoże nam ulepszyć przeglądarkę DuckDuckGo." } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "A verificar atualizações…" + "value" : "O teu feedback ajuda-nos a melhorar o navegador DuckDuckGo." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Проверка обновлений" + "value" : "Ваш ответ поможет браузеру DuckDuckGo стать еще лучше." } } } }, - "settings.last.checked" : { - "comment" : "Label informing users what is the last time the app checked for the update.", + "send.browser.feedback.general-feedback" : { + "comment" : "Name of the option the user can chose to give general browser feedback", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zuletzt nach Update gesucht" + "value" : "Allgemeines Feedback" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Last checked" + "value" : "General feedback" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Última comprobación" + "value" : "Comentarios generales" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Dernière vérification" + "value" : "Commentaires d'ordre général" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ultimo controllo" + "value" : "Feedback generale" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Laatst gecontroleerd" + "value" : "Algemene feedback" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ostatnio sprawdzane" + "value" : "Opinia ogólna" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Última verificação" + "value" : "Feedback geral" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Последняя проверка" + "value" : "Общий комментарий" } } } }, - "settings.manual.updates" : { - "comment" : "Title of the checkbox item to set up manual updates of the browser", + "send.browser.feedback.report-problem" : { + "comment" : "Name of the option the user can chose to give browser feedback about a problem they enountered", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nach Updates suchen, aber selbst entscheiden, ob du sie installieren möchtest" + "value" : "Problem melden" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Check for updates but let you choose to install them" + "value" : "Report a problem" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Comprueba las actualizaciones, pero deja que tú decidas si las instalas" + "value" : "Informar de un problema" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Rechercher les mises à jour sans les installer automatiquement" + "value" : "Signaler un problème" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Controlla gli aggiornamenti, ma ti fa scegliere se installarli" + "value" : "Segnala un problema" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Controleren op updates, maar keuze om te installeren of niet" + "value" : "Een probleem melden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Sprawdź dostępność aktualizacji, ale użytkownik decyduje o instalacji" + "value" : "Zgłoś problem" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Verificar se existem atualizações, mas deixa-te escolher instalá-las" + "value" : "Comunicar um problema" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Проверять на обновления, но доверить установку вам" + "value" : "Отчет о проблеме" } } } }, - "settings.newer.version.available" : { - "comment" : "Label informing users the newer version of the app is available to install.", + "send.browser.feedback.request-feature" : { + "comment" : "Name of the option the user can chose to give browser feedback about a feature they would like", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neuere Version verfügbar" + "value" : "Eine Funktion anfordern" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Newer version available" + "value" : "Request a feature" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Versión más reciente disponible" + "value" : "Solicitar una función" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Une version plus récente est disponible" + "value" : "Demander une fonctionnalité" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "È disponibile una versione più recente" + "value" : "Richiedi una funzionalità" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuwere versie beschikbaar" + "value" : "Een functie aanvragen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Dostępna nowsza wersja" + "value" : "Poproś o funkcję" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Versão mais recente disponível" + "value" : "Pedir uma funcionalidade" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Доступна более новая версия" + "value" : "Запрос функции" } } } }, - "settings.restart.to.update" : { - "comment" : "Button label trigering restart and update of the application.", + "send.browser.feedback.select-category" : { + "comment" : "Title of the picker where the user can chose the category of the feedback they want ot send.", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zum Aktualisieren neu starten" + "value" : "EINE KATEGORIE AUSWÄHLEN" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Restart to Update" + "value" : "Select a category" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Reiniciar para actualizar" + "value" : "SELECCIONA UNA CATEGORÍA" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Redémarrer pour mettre à jour" + "value" : "SÉLECTIONNER UNE CATÉGORIE" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riavvia per aggiornare" + "value" : "SELEZIONA UNA CATEGORIA" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Start opnieuw om bij te werken" + "value" : "SELECTEER EEN CATEGORIE" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Uruchom ponownie, aby zaktualizować" + "value" : "WYBIERZ KATEGORIĘ" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Reiniciar para atualizar" + "value" : "SELECIONA UMA CATEGORIA" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Перезапустите для обновления" + "value" : "ВЫБЕРИТЕ КАТЕГОРИЮ" } } } }, - "settings.up.to.date" : { - "comment" : "Label informing users the app is currently up to date and no update is required.", + "send.browser.feedback.thankyou" : { + "comment" : "Thanks the user for sending feedback", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo ist auf dem neuesten Stand" + "value" : "Vielen Dank!" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo is up to date" + "value" : "Thank you!" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo está actualizado" + "value" : "¡Gracias!" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo est à jour" + "value" : "Merci !" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo è aggiornato" + "value" : "Grazie!" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo is up-to-date" + "value" : "Bedankt!" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Przeglądarka DuckDuckGo jest aktualna" + "value" : "Dziękujemy!" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O DuckDuckGo está atualizado" + "value" : "Obrigado!" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo обновлен" + "value" : "Спасибо!" } } } }, - "share.menu.item" : { - "comment" : "Menu item title", + "send.browser.feedback.title" : { + "comment" : "Title of the interface to send feedback on the browser", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Teilen" + "value" : "Hilf mit, den DuckDuckGo-Browser zu verbessern" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Share" + "value" : "Help Improve the DuckDuckGo Browser" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Compartir" + "value" : "Ayuda a mejorar el navegador DuckDuckGo" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Partager" + "value" : "Contribuer à l'amélioration du navigateur DuckDuckGo" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Condividi" + "value" : "Contribuisci a migliorare il browser DuckDuckGo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Delen" + "value" : "Help de DuckDuckGo-browser te verbeteren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Udostępnij" + "value" : "Pomóż ulepszać przeglądarkę DuckDuckGo" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Partilhar" + "value" : "Ajudar a melhorar o navegador DuckDuckGo" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Поделиться" + "value" : "Помогите улучшить браузер DuckDuckGo" } } } }, - "share.menu.item.qr.code" : { - "comment" : "Menu item title", + "send.ppro.feedback" : { + "comment" : "Menu with feedback commands", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Send Privacy Pro Feedback" + } + } + } + }, + "settings" : { + "comment" : "Menu item for opening settings", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "QR-Code erstellen" + "value" : "Einstellungen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Create QR Code" + "value" : "Settings" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Crear código QR" + "value" : "Ajustes" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Créer un code QR" + "value" : "Paramètres" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Crea codice QR" + "value" : "Impostazioni" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "QR-code maken" + "value" : "Instellingen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Utwórz kod QR" + "value" : "Ustawienia" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Criar código QR" + "value" : "Definições" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Создать QR-код" + "value" : "Настройки" } } } }, - "sharing.more" : { - "comment" : "Sharing Menu -> More…", - "extractionState" : "extracted_with_value", + "Settings…" : { + "comment" : "Menu item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Mehr …" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "More…" + "value" : "Einstellungen …" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Más…" + "value" : "Ajustes" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Plus…" + "value" : "Réglages…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Ulteriori informazioni..." + "value" : "Impostazioni…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Meer …" + "value" : "Instellingen …" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Więcej…" + "value" : "Ustawienia…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mais…" + "value" : "Definições…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Еще..." + "value" : "Настройки..." } } } }, - "Show Autofill Shortcut" : { - "comment" : "Main Menu View item", + "settings.automatic.updates" : { + "comment" : "Title of the checkbox item to set up automatic updates of the browser", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Autofill-Verknüpfung anzeigen" + "value" : "Updates automatisch installieren (empfohlen)" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Automatically install updates (recommended)" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar acceso directo a Autocompletar" + "value" : "Instalar actualizaciones automáticamente (recomendado)" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher le raccourci de saisie automatique" + "value" : "Installer automatiquement les mises à jour (recommandé)" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra scorciatoia compilazione automatica" + "value" : "Installa automaticamente gli aggiornamenti (scelta consigliata)" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Snelkoppeling voor automatisch invullen weergeven" + "value" : "Updates automatisch installeren (aanbevolen)" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż skrót do autouzupełniania" + "value" : "Automatyczne instalowanie aktualizacji (zalecane)" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar atalho de preenchimento automático" + "value" : "Instalar atualizações automaticamente (recomendado)" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показывать ярлык для автозаполнения" + "value" : "Автоматически устанавливать обновления (рекомендуется)" } } } }, - "Show Bookmarks Shortcut" : { - "comment" : "Main Menu View item", + "settings.browser.updates.title" : { + "comment" : "Title of the section in Settings where people set up automatic vs manual updates", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lesezeichen-Verknüpfung anzeigen" + "value" : "Browser-Updates" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Browser Updates" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar acceso directo a Favoritos" + "value" : "Actualizaciones del navegador" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher le raccourci des signets" + "value" : "Mises à jour du navigateur" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra scorciatoia segnalibri" + "value" : "Aggiornamenti del browser" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Snelkoppeling voor bladwijzers weergeven" + "value" : "Browserupdates" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż skrót do zakładek" + "value" : "Aktualizacje przeglądarki" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar atalho de marcadores" + "value" : "Atualizações do navegador" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показывать ярлык для закладок" + "value" : "Обновления браузера" } } } }, - "Show Downloads Shortcut" : { - "comment" : "Main Menu View item", + "settings.checking.for.update" : { + "comment" : "Label informing users the app is currently checking for new update", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Download-Verknüpfung anzeigen" + "value" : "Nach Update suchen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Checking for update" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar acceso directo a Descargas" + "value" : "Buscando actualización" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher le raccourci des téléchargements" + "value" : "Vérification de la mise à jour" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra scorciatoia download" + "value" : "Controllo della presenza di aggiornamenti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Snelkoppeling voor downloads weergeven" + "value" : "Controleren op updates" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż skrót do pobranych" + "value" : "Sprawdzanie dostępności aktualizacji" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar atalho de transferências" + "value" : "A verificar atualizações…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать ярлык для загрузок" + "value" : "Проверка обновлений" } } } }, - "Show Next Tab" : { - "comment" : "Main Menu Window item", + "settings.last.checked" : { + "comment" : "Label informing users what is the last time the app checked for the update.", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nächsten Tab anzeigen" + "value" : "Zuletzt nach Update gesucht" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Last checked" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar pestaña siguiente" + "value" : "Última comprobación" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher l'onglet suivant" + "value" : "Dernière vérification" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra scheda successiva" + "value" : "Ultimo controllo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Volgende tabblad tonen" + "value" : "Laatst gecontroleerd" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż następną kartę" + "value" : "Ostatnio sprawdzane" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar separador seguinte" + "value" : "Última verificação" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать следующую вкладку" + "value" : "Последняя проверка" } } } }, - "Show Page Source" : { - "comment" : "Main Menu View-Developer item", + "settings.manual.updates" : { + "comment" : "Title of the checkbox item to set up manual updates of the browser", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Seitenquelle anzeigen" + "value" : "Nach Updates suchen, aber selbst entscheiden, ob du sie installieren möchtest" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Check for updates but let you choose to install them" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar origen de página" + "value" : "Comprueba las actualizaciones, pero deja que tú decidas si las instalas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher la source de la page" + "value" : "Rechercher les mises à jour sans les installer automatiquement" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra origine pagina" + "value" : "Controlla gli aggiornamenti, ma ti fa scegliere se installarli" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Paginabron weergeven" + "value" : "Controleren op updates, maar keuze om te installeren of niet" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż źródło strony" + "value" : "Sprawdź dostępność aktualizacji, ale użytkownik decyduje o instalacji" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar código da página" + "value" : "Verificar se existem atualizações, mas deixa-te escolher instalá-las" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать исходный код страницы" + "value" : "Проверять на обновления, но доверить установку вам" } } } }, - "Show Previous Tab" : { - "comment" : "Main Menu Window item", + "settings.newer.version.available" : { + "comment" : "Label informing users the newer version of the app is available to install.", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Vorherigen Tab anzeigen" + "value" : "Neuere Version verfügbar" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Newer version available" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar pestaña anterior" + "value" : "Versión más reciente disponible" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher l'onglet précédent" + "value" : "Une version plus récente est disponible" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra scheda precedente" + "value" : "È disponibile una versione più recente" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Vorig tabblad tonen" + "value" : "Nieuwere versie beschikbaar" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż poprzednią kartę" + "value" : "Dostępna nowsza wersja" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar separador anterior" + "value" : "Versão mais recente disponível" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать предыдущую вкладку" + "value" : "Доступна более новая версия" } } } }, - "Show Resources" : { - "comment" : "Main Menu View-Developer item", + "settings.restart.to.update" : { + "comment" : "Button label trigering restart and update of the application.", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ressourcen anzeigen" + "value" : "Zum Aktualisieren neu starten" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Restart to Update" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar recursos" + "value" : "Reiniciar para actualizar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher les ressources" + "value" : "Redémarrer pour mettre à jour" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra risorse" + "value" : "Riavvia per aggiornare" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Hulpmiddelen tonen" + "value" : "Start opnieuw om bij te werken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż zasoby" + "value" : "Uruchom ponownie, aby zaktualizować" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar recursos" + "value" : "Reiniciar para atualizar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать ресурсы" + "value" : "Перезапустите для обновления" } } } }, - "Show Substitutions" : { - "comment" : "Main Menu Edit-Substitutions item", + "settings.up.to.date" : { + "comment" : "Label informing users the app is currently up to date and no update is required.", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ersetzungen anzeigen" + "value" : "DuckDuckGo ist auf dem neuesten Stand" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "DuckDuckGo is up to date" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar Reemplazos" + "value" : "DuckDuckGo está actualizado" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher les substitutions" + "value" : "DuckDuckGo est à jour" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra Sostituzioni" + "value" : "DuckDuckGo è aggiornato" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Aanpassingen tonen" + "value" : "DuckDuckGo is up-to-date" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż podstawienia" + "value" : "Przeglądarka DuckDuckGo jest aktualna" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar substituições" + "value" : "O DuckDuckGo está atualizado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показывать функции работы с текстом" + "value" : "DuckDuckGo обновлен" } } } }, - "show.data.clearing.settings" : { - "comment" : "Button in Settings. It navigates user to Data Clearing Settings. The Data Clearing string should match the string with the preferences.data-clearing key", + "share.menu.item" : { + "comment" : "Menu item title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einstellungen zum Löschen von Daten öffnen" + "value" : "Teilen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Open Data Clearing Settings" + "value" : "Share" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir configuración de borrado de datos" + "value" : "Compartir" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ouvrir les paramètres d'effacement des données" + "value" : "Partager" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri Impostazioni cancellazione dati" + "value" : "Condividi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Instellingen voor het wissen van open gegevens" + "value" : "Delen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Otwórz ustawienia czyszczenia danych" + "value" : "Udostępnij" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Abrir Definições de limpeza de dados" + "value" : "Partilhar" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Открыть настройки очистки данных" + "value" : "Поделиться" } } } }, - "show.folder.contents" : { - "comment" : "Menu item that shows the content of a folder ", + "share.menu.item.qr.code" : { + "comment" : "Menu item title", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ordnerinhalte anzeigen" + "value" : "QR-Code erstellen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Folder Contents" + "value" : "Create QR Code" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar el contenido de la carpeta" + "value" : "Crear código QR" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Afficher le contenu du dossier" + "value" : "Créer un code QR" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Mostra il contenuto della cartella" + "value" : "Crea codice QR" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Inhoud van map weergeven" + "value" : "QR-code maken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pokaż zawartość folderu" + "value" : "Utwórz kod QR" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Mostrar conteúdos da pasta" + "value" : "Criar código QR" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Показать содержимое папки" + "value" : "Создать QR-код" } } } }, - "Smart Copy/Paste" : { - "comment" : "Main Menu Edit-Substitutions item", + "sharing.more" : { + "comment" : "Sharing Menu -> More…", + "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Intelligentes Kopieren/Einfügen" + "value" : "Mehr …" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "More…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar/Pegar inteligente" + "value" : "Más…" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Copier/coller intelligent" + "value" : "Plus…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Copia/Incolla intelligente" + "value" : "Ulteriori informazioni..." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Slim kopiëren/plakken" + "value" : "Meer …" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Inteligentne kopiowanie/wklejanie" + "value" : "Więcej…" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar/colar inteligente" + "value" : "Mais…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Умное копирование/вставка" + "value" : "Еще..." } } } }, - "Smart Dashes" : { - "comment" : "Main Menu Edit-Substitutions item", + "Show Autofill Shortcut" : { + "comment" : "Main Menu View item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Intelligente Gedankenstriche" + "value" : "Autofill-Verknüpfung anzeigen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Guiones inteligentes" + "value" : "Mostrar acceso directo a Autocompletar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Tirets intelligents" + "value" : "Afficher le raccourci de saisie automatique" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Trattini intelligenti" + "value" : "Mostra scorciatoia compilazione automatica" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Slimme streepjes" + "value" : "Snelkoppeling voor automatisch invullen weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Inteligentne myślniki" + "value" : "Pokaż skrót do autouzupełniania" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Traços formatados" + "value" : "Mostrar atalho de preenchimento automático" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Умные тире" + "value" : "Показывать ярлык для автозаполнения" } } } }, - "Smart Links" : { - "comment" : "Main Menu Edit-Substitutions item", + "Show Bookmarks Shortcut" : { + "comment" : "Main Menu View item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Intelligente Links" + "value" : "Lesezeichen-Verknüpfung anzeigen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Enlaces inteligentes" + "value" : "Mostrar acceso directo a Favoritos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Liens intelligents" + "value" : "Afficher le raccourci des signets" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Link intelligenti" + "value" : "Mostra scorciatoia segnalibri" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Slimme links" + "value" : "Snelkoppeling voor bladwijzers weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Inteligentne łącza" + "value" : "Pokaż skrót do zakładek" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Links inteligentes" + "value" : "Mostrar atalho de marcadores" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Умные ссылки" + "value" : "Показывать ярлык для закладок" } } } }, - "Smart Quotes" : { - "comment" : "Main Menu Edit-Substitutions item", + "Show Downloads Shortcut" : { + "comment" : "Main Menu View item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Intelligente Zitate" + "value" : "Download-Verknüpfung anzeigen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Comillas inteligentes" + "value" : "Mostrar acceso directo a Descargas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Guillemets intelligents" + "value" : "Afficher le raccourci des téléchargements" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Virgolette doppie intelligenti" + "value" : "Mostra scorciatoia download" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Slimme aanhalingstekens" + "value" : "Snelkoppeling voor downloads weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Inteligentne cudzysłowy" + "value" : "Pokaż skrót do pobranych" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Aspas curvas" + "value" : "Mostrar atalho de transferências" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Умные кавычки" + "value" : "Показать ярлык для загрузок" } } } }, - "Speech" : { - "comment" : "Main Menu Edit item", + "Show Next Tab" : { + "comment" : "Main Menu Window item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Rede" + "value" : "Nächsten Tab anzeigen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Voz" + "value" : "Mostrar pestaña siguiente" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Discours" + "value" : "Afficher l'onglet suivant" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Leggi il testo" + "value" : "Mostra scheda successiva" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Spraak" + "value" : "Volgende tabblad tonen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Mowa" + "value" : "Pokaż następną kartę" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Fala" + "value" : "Mostrar separador seguinte" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Речь" + "value" : "Показать следующую вкладку" } } } }, - "ssl.error.certificate.expired.message" : { - "comment" : "Describes an SSL error where a website's security certificate is expired. '%1$@' is a placeholder for the website's domain.", - "extractionState" : "extracted_with_value", + "Show Page Source" : { + "comment" : "Main Menu View-Developer item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Das Sicherheitszertifikat für %1$@ ist abgelaufen." - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "The security certificate for %1$@ is expired." + "value" : "Seitenquelle anzeigen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "El certificado de seguridad de %1$@ ha caducado." + "value" : "Mostrar origen de página" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Le certificat de sécurité de %1$@ a expiré." + "value" : "Afficher la source de la page" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Il certificato di sicurezza per %1$@ è scaduto." + "value" : "Mostra origine pagina" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Het beveiligingscertificaat voor %1$@ is verlopen." + "value" : "Paginabron weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Certyfikat zabezpieczeń dla %1$@ wygasł." + "value" : "Pokaż źródło strony" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O certificado de segurança de %1$@ expirou." + "value" : "Mostrar código da página" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Срок действия сертификата безопасности сайта %1$@ истек." + "value" : "Показать исходный код страницы" } } } }, - "ssl.error.page.advanced.button" : { - "comment" : "Button shown in an error page that warns users of security risks on a website due to SSL issues. The buttons allows the user to see advanced options on click.", - "extractionState" : "extracted_with_value", + "Show Previous Tab" : { + "comment" : "Main Menu Window item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Erweitert …" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Advanced…" + "value" : "Vorherigen Tab anzeigen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Avanzadas..." + "value" : "Mostrar pestaña anterior" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Options avancées" + "value" : "Afficher l'onglet précédent" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Avanzate…" + "value" : "Mostra scheda precedente" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geavanceerd ..." + "value" : "Vorig tabblad tonen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zaawansowane..." + "value" : "Pokaż poprzednią kartę" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Avançado…" + "value" : "Mostrar separador anterior" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Дополнительно..." + "value" : "Показать предыдущую вкладку" } } } }, - "ssl.error.page.advanced.info.body.expired" : { - "comment" : "Body of the text of the Advanced info shown in an error page that warns users of security risks on a website due to SSL issues.", - "extractionState" : "extracted_with_value", + "Show Resources" : { + "comment" : "Main Menu View-Developer item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Es ist möglich, dass die Website falsch konfiguriert ist, ein Angreifer deine Verbindung kompromittiert hat oder deine Systemuhr falsch ist." - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "It’s possible that the website is misconfigured, that an attacker has compromised your connection, or that your system clock is incorrect." + "value" : "Ressourcen anzeigen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Es posible que el sitio web esté mal configurado, que un atacante haya comprometido tu conexión o que el reloj del sistema no sea correcto." + "value" : "Mostrar recursos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Il est possible que le site soit mal configuré, qu'un pirate ait compromis votre connexion ou que l'horloge de votre système soit incorrecte." + "value" : "Afficher les ressources" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "È possibile che il sito web non sia configurato correttamente, che un aggressore abbia compromesso la tua connessione o che l'orologio del tuo sistema non sia corretto." + "value" : "Mostra risorse" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Mogelijk is de website verkeerd geconfigureerd, heeft een aanvaller je verbinding gecompromitteerd of staat de klok van je systeem niet goed." + "value" : "Hulpmiddelen tonen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Możliwe, że witryna jest błędnie skonfigurowana, osoba atakująca naruszyła połączenie lub ustawienie zegara systemowego jest nieprawidłowe." + "value" : "Pokaż zasoby" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "É possível que o site esteja mal configurado, que um invasor tenha comprometido a tua ligação ou que o teu relógio do sistema esteja incorreto." + "value" : "Mostrar recursos" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Возможно, неверно задана конфигурация сайта, соединение перехвачено злоумышленником либо неправильно настроены системные часы." + "value" : "Показать ресурсы" } } } }, - "ssl.error.page.advanced.info.body.wrong.host" : { - "comment" : "Body of the text of the Advanced info shown in an error page that warns users of security risks on a website due to SSL issues.", - "extractionState" : "extracted_with_value", + "Show Substitutions" : { + "comment" : "Main Menu Edit-Substitutions item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Möglicherweise ist die Website falsch konfiguriert oder ein Angreifer hat deine Verbindung kompromittiert." - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "It’s possible that the website is misconfigured or that an attacker has compromised your connection." + "value" : "Ersetzungen anzeigen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Es posible que el sitio web esté mal configurado o que un atacante haya comprometido tu conexión." + "value" : "Mostrar Reemplazos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Il est possible que le site soit mal configuré ou qu'un pirate ait compromis votre connexion." + "value" : "Afficher les substitutions" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "È possibile che il sito web non sia configurato correttamente o che un malintenzionato abbia compromesso la tua connessione." + "value" : "Mostra Sostituzioni" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Mogelijk is de website verkeerd geconfigureerd of heeft een aanvaller je verbinding gecompromitteerd." + "value" : "Aanpassingen tonen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Możliwe, że witryna jest błędnie skonfigurowana lub osoba atakująca naruszyła połączenie." + "value" : "Pokaż podstawienia" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "É possível que o site esteja mal configurado ou que um invasor tenha comprometido a tua ligação." + "value" : "Mostrar substituições" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Возможно, неверно задана конфигурация сайта либо ваше соединение перехвачено злоумышленником." + "value" : "Показывать функции работы с текстом" } } } }, - "ssl.error.page.advanced.info.title" : { - "comment" : "Title of the Advanced info section shown in an error page that warns users of security risks on a website due to SSL issues.", + "show.data.clearing.settings" : { + "comment" : "Button in Settings. It navigates user to Data Clearing Settings. The Data Clearing string should match the string with the preferences.data-clearing key", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo warnt dich, wenn eine Website ein ungültiges Zertifikat hat." + "value" : "Einstellungen zum Löschen von Daten öffnen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "DuckDuckGo warns you when a website has an invalid certificate." + "value" : "Open Data Clearing Settings" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo te avisa cuando un sitio web tiene un certificado no válido." + "value" : "Abrir configuración de borrado de datos" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo vous avertit lorsque le certificat d'un site Web n'est pas valide." + "value" : "Ouvrir les paramètres d'effacement des données" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo ti avverte quando un sito web ha un certificato non valido." + "value" : "Apri Impostazioni cancellazione dati" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo waarschuwt je wanneer een website een ongeldig certificaat heeft." + "value" : "Instellingen voor het wissen van open gegevens" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo ostrzega gdy witryna internetowa ma nieprawidłowy certyfikat." + "value" : "Otwórz ustawienia czyszczenia danych" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O DuckDuckGo avisa-te quando um site tem um certificado inválido." + "value" : "Abrir Definições de limpeza de dados" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "DuckDuckGo предупредит вас, если у сайта окажется недействительный сертификат." + "value" : "Открыть настройки очистки данных" } } } }, - "ssl.error.page.body" : { - "comment" : "Error description shown in an error page that warns users of security risks on a website due to SSL issues. %1$@ represent the site domain.", + "show.folder.contents" : { + "comment" : "Menu item that shows the content of a folder ", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Das Zertifikat für diese Website ist ungültig. Möglicherweise stellst du eine Verbindung zu einem Server her, der vorgibt, %1$@ zu sein, wodurch deine vertraulichen Daten in Gefahr sein könnten." + "value" : "Ordnerinhalte anzeigen" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "The certificate for this site is invalid. You might be connecting to a server that is pretending to be %1$@ which could put your confidential information at risk." + "value" : "Show Folder Contents" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "El certificado de este sitio no es válido. Puede que te estés conectando a un servidor que se hace pasar por %1$@, lo que podría poner en riesgo tu información confidencial." + "value" : "Mostrar el contenido de la carpeta" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Le certificat de ce site n'est pas valide. Vous vous connectez peut-être à un serveur qui se fait passer pour %1$@, ce qui pourrait mettre vos informations confidentielles en danger." + "value" : "Afficher le contenu du dossier" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Il certificato di questo sito non è valido. Potresti collegarti a un server che finge di essere %1$@ e che potrebbe mettere a rischio le tue informazioni riservate." + "value" : "Mostra il contenuto della cartella" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Het certificaat voor deze website is ongeldig. Mogelijk maak je verbinding met een server die zich voordoet als %1$@, waardoor je vertrouwelijke informatie in gevaar kan komen." + "value" : "Inhoud van map weergeven" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Certyfikat tej witryny jest nieprawidłowy. Być może łączysz się z serwerem podszywającym się pod %1$@, co może narazić poufne informacje na niebezpieczeństwo." + "value" : "Pokaż zawartość folderu" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O certificado deste site é inválido. Podes estar a estabelecer ligação a um servidor que finge ser %1$@, o que pode colocar as tuas informações confidenciais em risco." + "value" : "Mostrar conteúdos da pasta" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сертификат этого сайта недействителен. Возможно, вы пытаетесь подключиться к серверу, который выдает себя за %1$@, что ставит под угрозу вашу конфиденциальную информацию." + "value" : "Показать содержимое папки" } } } }, - "ssl.error.page.header" : { - "comment" : "Title shown in an error page that warn users of security risks on a website due to SSL issues", - "extractionState" : "extracted_with_value", + "Smart Copy/Paste" : { + "comment" : "Main Menu Edit-Substitutions item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Warnung: Diese Website ist möglicherweise unsicher" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Warning: This site may be insecure" + "value" : "Intelligentes Kopieren/Einfügen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Advertencia: este sitio puede ser inseguro" + "value" : "Copiar/Pegar inteligente" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Avertissement : ce site n'est peut-être pas sécurisé" + "value" : "Copier/coller intelligent" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Attenzione: questo sito potrebbe non essere sicuro" + "value" : "Copia/Incolla intelligente" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Waarschuwing: Deze website is mogelijk onveilig" + "value" : "Slim kopiëren/plakken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ostrzeżenie: ta witryna może być niebezpieczna" + "value" : "Inteligentne kopiowanie/wklejanie" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Aviso: este site pode ser inseguro" + "value" : "Copiar/colar inteligente" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Внимание! Возможно, сайт небезопасен" + "value" : "Умное копирование/вставка" } } } }, - "ssl.error.page.leave.site.button" : { - "comment" : "Button shown in an error page that warns users of security risks on a website due to SSL issues. The buttons allows the user to leave the website and navigate to previous page.", - "extractionState" : "extracted_with_value", + "Smart Dashes" : { + "comment" : "Main Menu Edit-Substitutions item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Diese Website verlassen" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Leave This Site" + "value" : "Intelligente Gedankenstriche" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Salir de este sitio" + "value" : "Guiones inteligentes" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Quitter ce site" + "value" : "Tirets intelligents" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Esci da questo sito" + "value" : "Trattini intelligenti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Deze website verlaten" + "value" : "Slimme streepjes" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Opuść tę stronę" + "value" : "Inteligentne myślniki" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Deixar este site" + "value" : "Traços formatados" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Покинуть сайт" + "value" : "Умные тире" } } } }, - "ssl.error.page.tab.title" : { - "comment" : "Title shown in an error page tab that warn users of security risks on a website due to SSL issues", - "extractionState" : "extracted_with_value", + "Smart Links" : { + "comment" : "Main Menu Edit-Substitutions item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Warnung: Website ist möglicherweise unsicher" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Warning: Site May Be Insecure" + "value" : "Intelligente Links" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Advertencia: el sitio puede ser inseguro" + "value" : "Enlaces inteligentes" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Avertissement : le site n'est peut-être pas sécurisé" + "value" : "Liens intelligents" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Attenzione: il sito potrebbe non essere sicuro" + "value" : "Link intelligenti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Waarschuwing: Website mogelijk onveilig" + "value" : "Slimme links" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ostrzeżenie: witryna może być niebezpieczna" + "value" : "Inteligentne łącza" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Aviso: o site pode ser inseguro" + "value" : "Links inteligentes" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Внимание! Возможно, сайт небезопасен" + "value" : "Умные ссылки" } } } }, - "ssl.error.page.visit.site.button" : { - "comment" : "Button shown in an error page that warns users of security risks on a website due to SSL issues. The buttons allows the user to visit the website anyway despite the risks.", - "extractionState" : "extracted_with_value", + "Smart Quotes" : { + "comment" : "Main Menu Edit-Substitutions item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Risiko akzeptieren und Website besuchen" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Accept Risk and Visit Site" + "value" : "Intelligente Zitate" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Aceptar el riesgo y visitar el sitio" + "value" : "Comillas inteligentes" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Accepter le risque et visiter le site" + "value" : "Guillemets intelligents" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Accetta il rischio e visita il sito" + "value" : "Virgolette doppie intelligenti" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Risico accepteren en site bezoeken" + "value" : "Slimme aanhalingstekens" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zaakceptuj ryzyko i odwiedź witrynę" + "value" : "Inteligentne cudzysłowy" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Aceitar o risco e visitar o site" + "value" : "Aspas curvas" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Принять риск и перейти на сайт" + "value" : "Умные кавычки" } } } }, - "ssl.error.self.signed.message" : { - "comment" : "Warns the user that the site's security certificate is self-signed and not trusted. '%1$@' is the site's domain.", - "extractionState" : "extracted_with_value", + "Speech" : { + "comment" : "Main Menu Edit item", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Das Sicherheitszertifikat für %1$@ wird vom Betriebssystem deines Geräts als nicht vertrauenswürdig eingestuft." - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "The security certificate for %1$@ is not trusted by your device's operating system." + "value" : "Rede" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "El sistema operativo del dispositivo no confía en el certificado de seguridad de %1$@." + "value" : "Voz" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Le certificat de sécurité de %1$@ n'est pas approuvé par le système d'exploitation de votre appareil." + "value" : "Discours" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Il certificato di sicurezza di %1$@ non è considerato attendibile dal sistema operativo del tuo dispositivo." + "value" : "Leggi il testo" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Het besturingssysteem van je apparaat vertrouwt het beveiligingscertificaat voor %1$@ niet." + "value" : "Spraak" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Certyfikat bezpieczeństwa %1$@ nie jest zaufany przez system operacyjny urządzenia." + "value" : "Mowa" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O sistema operativo do teu dispositivo não confia no certificado de segurança de %1$@." + "value" : "Fala" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Операционная система вашего устройства не доверяет сертификату безопасности сайта %1$@." + "value" : "Речь" } } } }, - "ssl.error.wrong.host.message" : { - "comment" : "Explains an SSL error when a site's certificate doesn't match its domain. '%1$@' is the site's domain.", + "ssl.error.page.tab.title" : { + "comment" : "Title shown in an error page tab that warn users of security risks on a website due to SSL issues", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Das Sicherheitszertifikat für %1$@ stimmt nicht mit *.%2$@ überein." + "value" : "Warnung: Website ist möglicherweise unsicher" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "The security certificate for %1$@ does not match *.%2$@." + "value" : "Warning: Site May Be Insecure" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "El certificado de seguridad de %1$@ no coincide con *.%2$@." + "value" : "Advertencia: el sitio puede ser inseguro" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Le certificat de sécurité de %1$@ ne correspond pas à *.%2$@." + "value" : "Avertissement : le site n'est peut-être pas sécurisé" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Il certificato di protezione per %1$@ non corrisponde a *.%2$@." + "value" : "Attenzione: il sito potrebbe non essere sicuro" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Het beveiligingscertificaat voor %1$@ komt niet overeen met *.%2$@. " + "value" : "Waarschuwing: Website mogelijk onveilig" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Certyfikat zabezpieczeń %1$@ nie jest zgodny z *.%2$@." + "value" : "Ostrzeżenie: witryna może być niebezpieczna" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "O certificado de segurança de %1$@ não corresponde a *.%2$@." + "value" : "Aviso: o site pode ser inseguro" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сертификат безопасности сайта %1$@ не подходит для домена *.%2$@." + "value" : "Внимание! Возможно, сайт небезопасен" } } } @@ -58457,4 +58493,4 @@ } }, "version" : "1.0" -} \ No newline at end of file +} diff --git a/DuckDuckGo/LoginItems/LoginItemsManager.swift b/DuckDuckGo/LoginItems/LoginItemsManager.swift index 1260177691..146191c316 100644 --- a/DuckDuckGo/LoginItems/LoginItemsManager.swift +++ b/DuckDuckGo/LoginItems/LoginItemsManager.swift @@ -20,12 +20,13 @@ import Common import Foundation import LoginItems import PixelKit +import os.log protocol LoginItemsManaging { - func enableLoginItems(_ items: Set, log: OSLog) - func throwingEnableLoginItems(_ items: Set, log: OSLog) throws + func enableLoginItems(_ items: Set) + func throwingEnableLoginItems(_ items: Set) throws func disableLoginItems(_ items: Set) - func restartLoginItems(_ items: Set, log: OSLog) + func restartLoginItems(_ items: Set) func isAnyEnabled(_ items: Set) -> Bool } @@ -41,11 +42,11 @@ final class LoginItemsManager: LoginItemsManaging { // MARK: - Main Interactions - func enableLoginItems(_ items: Set, log: OSLog) { + func enableLoginItems(_ items: Set) { for item in items { do { try item.enable() - os_log("🟢 Enabled successfully %{public}@", log: log, String(describing: item)) + Logger.networkProtection.debug("🟢 Enabled successfully \(String(describing: item), privacy: .public)") } catch let error as NSError { handleError(for: item, action: .enable, error: error) } @@ -54,11 +55,11 @@ final class LoginItemsManager: LoginItemsManaging { /// Throwing version of enableLoginItems /// - func throwingEnableLoginItems(_ items: Set, log: OSLog) throws { + func throwingEnableLoginItems(_ items: Set) throws { for item in items { do { try item.enable() - os_log("🟢 Enabled successfully %{public}@", log: log, String(describing: item)) + Logger.networkProtection.debug("🟢 Enabled successfully \(String(describing: item), privacy: .public)") } catch let error as NSError { handleError(for: item, action: .enable, error: error) throw error @@ -66,11 +67,11 @@ final class LoginItemsManager: LoginItemsManaging { } } - func restartLoginItems(_ items: Set, log: OSLog) { + func restartLoginItems(_ items: Set) { for item in items { do { try item.restart() - os_log("🟢 Restarted successfully %{public}@", log: log, String(describing: item)) + Logger.networkProtection.debug("🟢 Restarted successfully \(String(describing: item), privacy: .public)") } catch let error as NSError { handleError(for: item, action: .restart, error: error) } @@ -95,7 +96,7 @@ final class LoginItemsManager: LoginItemsManaging { buildType: AppVersion.shared.buildType, osVersion: AppVersion.shared.osVersion) PixelKit.fire(DebugEvent(event, error: error), frequency: .dailyAndCount) - os_log("🔴 Could not enable %{public}@: %{public}@", item.debugDescription, error.debugDescription) + Logger.networkProtection.error("Could not enable \(item.debugDescription, privacy: .public): \(error.debugDescription, privacy: .public)") } // MARK: - Debug Interactions diff --git a/DuckDuckGo/MainWindow/MainViewController.swift b/DuckDuckGo/MainWindow/MainViewController.swift index 56d49002c3..47c0364a3c 100644 --- a/DuckDuckGo/MainWindow/MainViewController.swift +++ b/DuckDuckGo/MainWindow/MainViewController.swift @@ -22,6 +22,7 @@ import Combine import Common import NetworkProtection import NetworkProtectionIPC +import os.log final class MainViewController: NSViewController { private lazy var mainView = MainView(frame: NSRect(x: 0, y: 0, width: 600, height: 660)) @@ -385,7 +386,7 @@ final class MainViewController: NSViewController { private func updateFindInPage() { guard let model = tabCollectionViewModel.selectedTabViewModel?.findInPage else { findInPageViewController.makeMeFirstResponder() - os_log("MainViewController: Failed to get find in page model", type: .error) + Logger.general.error("MainViewController: Failed to get find in page model") return } @@ -399,7 +400,7 @@ final class MainViewController: NSViewController { private func updateBackMenuItem() { guard self.view.window?.isMainWindow == true else { return } guard let selectedTabViewModel = tabCollectionViewModel.selectedTabViewModel else { - os_log("MainViewController: No tab view model selected", type: .error) + Logger.general.error("MainViewController: No tab view model selected") return } NSApp.mainMenuTyped.backMenuItem.isEnabled = selectedTabViewModel.canGoBack @@ -408,7 +409,7 @@ final class MainViewController: NSViewController { private func updateForwardMenuItem() { guard self.view.window?.isMainWindow == true else { return } guard let selectedTabViewModel = tabCollectionViewModel.selectedTabViewModel else { - os_log("MainViewController: No tab view model selected", type: .error) + Logger.general.error("MainViewController: No tab view model selected") return } NSApp.mainMenuTyped.forwardMenuItem.isEnabled = selectedTabViewModel.canGoForward @@ -417,7 +418,7 @@ final class MainViewController: NSViewController { private func updateReloadMenuItem() { guard self.view.window?.isMainWindow == true else { return } guard let selectedTabViewModel = tabCollectionViewModel.selectedTabViewModel else { - os_log("MainViewController: No tab view model selected", type: .error) + Logger.general.error("MainViewController: No tab view model selected") return } NSApp.mainMenuTyped.reloadMenuItem.isEnabled = selectedTabViewModel.canReload @@ -426,7 +427,7 @@ final class MainViewController: NSViewController { private func updateStopMenuItem() { guard self.view.window?.isMainWindow == true else { return } guard let selectedTabViewModel = tabCollectionViewModel.selectedTabViewModel else { - os_log("MainViewController: No tab view model selected", type: .error) + Logger.general.error("MainViewController: No tab view model selected") return } NSApp.mainMenuTyped.stopMenuItem.isEnabled = selectedTabViewModel.isLoading diff --git a/DuckDuckGo/Menus/HistoryMenu.swift b/DuckDuckGo/Menus/HistoryMenu.swift index 54b9c3633a..3f2a6ad8d1 100644 --- a/DuckDuckGo/Menus/HistoryMenu.swift +++ b/DuckDuckGo/Menus/HistoryMenu.swift @@ -20,6 +20,7 @@ import Cocoa import Combine import Common import History +import os.log @MainActor final class HistoryMenu: NSMenu { @@ -334,7 +335,7 @@ private extension HistoryCoordinating { func getSortedArrayOfVisits() -> [Visit] { guard let history = history else { - os_log("HistoryCoordinator: No history available", type: .error) + Logger.general.error("HistoryCoordinator: No history available") return [] } diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index fefb5754e8..478ddd8dc9 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -715,8 +715,7 @@ final class MainMenu: NSMenu { let logStore = try OSLogStore(scope: .currentProcessIdentifier) try logStore.getEntries() .compactMap { - guard let entry = $0 as? OSLogEntryLog, - entry.subsystem == OSLog.subsystem else { return nil } + guard let entry = $0 as? OSLogEntryLog else { return nil } return "\(formatter.string(from: entry.date)) [\(entry.category)] \(entry.composedMessage)" } .joined(separator: "\n") diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 824d544a71..314d57a9d5 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -25,6 +25,7 @@ import History import PixelKit import Subscription import WebKit +import os.log // Actions are sent to objects of responder chain @@ -190,11 +191,16 @@ extension AppDelegate { } } + @MainActor + @objc func openPProFeedback(_ sender: Any?) { + WindowControllersManager.shared.showShareFeedbackModal(source: .settings) + } + #endif @objc func navigateToBookmark(_ sender: Any?) { guard let menuItem = sender as? NSMenuItem else { - os_log("AppDelegate: Casting to menu item failed", type: .error) + Logger.general.error("AppDelegate: Casting to menu item failed") return } @@ -380,7 +386,7 @@ extension MainViewController { @objc func openLocation(_ sender: Any?) { makeKeyIfNeeded() guard let addressBarTextField = navigationBarViewController.addressBarViewController?.addressBarTextField else { - os_log("MainViewController: Cannot reference address bar text field", type: .error) + Logger.general.error("MainViewController: Cannot reference address bar text field") return } @@ -595,7 +601,7 @@ extension MainViewController { @objc func openBookmark(_ sender: Any?) { guard let menuItem = sender as? NSMenuItem else { - os_log("MainViewController: Casting to menu item failed", type: .error) + Logger.general.error("MainViewController: Casting to menu item failed") return } @@ -607,7 +613,7 @@ extension MainViewController { @objc func openAllInTabs(_ sender: Any?) { guard let menuItem = sender as? NSMenuItem else { - os_log("MainViewController: Casting to menu item failed", type: .error) + Logger.general.error("MainViewController: Casting to menu item failed") return } @@ -653,11 +659,11 @@ extension MainViewController { @objc func showTab(_ sender: Any?) { makeKeyIfNeeded() guard let sender = sender as? NSMenuItem else { - os_log("MainViewController: Casting to NSMenuItem failed", type: .error) + Logger.general.error("MainViewController: Casting to NSMenuItem failed") return } guard let keyEquivalent = Int(sender.keyEquivalent), keyEquivalent >= 0 && keyEquivalent <= 9 else { - os_log("MainViewController: Key equivalent is not correct for tab selection", type: .error) + Logger.general.error("MainViewController: Key equivalent is not correct for tab selection") return } let index = keyEquivalent - 1 @@ -907,18 +913,14 @@ extension MainViewController { @objc func removeUserScripts(_ sender: Any?) { tabCollectionViewModel.selectedTab?.userContentController?.cleanUpBeforeClosing() tabCollectionViewModel.selectedTab?.reload() - os_log("User scripts removed from the current tab", type: .info) + Logger.general.info("User scripts removed from the current tab") } @objc func reloadConfigurationNow(_ sender: Any?) { - OSLog.loggingCategories.insert(OSLog.AppCategories.config.rawValue) - ConfigurationManager.shared.forceRefresh(isDebug: true) } private func setConfigurationUrl(_ configurationUrl: URL?) { - OSLog.loggingCategories.insert(OSLog.AppCategories.config.rawValue) - var configurationProvider = AppConfigurationURLProvider(customPrivacyConfiguration: configurationUrl) if configurationUrl == nil { configurationProvider.resetToDefaultConfigurationUrl() @@ -926,9 +928,9 @@ extension MainViewController { Configuration.setURLProvider(configurationProvider) ConfigurationManager.shared.forceRefresh(isDebug: true) if let configurationUrl { - os_log("New configuration URL set to \(configurationUrl.absoluteString)", type: .info) + Logger.general.debug("New configuration URL set to \(configurationUrl.absoluteString)") } else { - os_log("New configuration URL reset to default", type: .info) + Logger.general.log("New configuration URL reset to default") } } @@ -938,7 +940,7 @@ extension MainViewController { if alert.runModal() != .cancel { guard let textField = alert.accessoryView as? NSTextField, let newConfigurationUrl = URL(string: textField.stringValue) else { - os_log("Failed to set custom configuration URL", type: .error) + Logger.general.error("Failed to set custom configuration URL") return } diff --git a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift index 318940ba35..9c3f11dfdf 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift @@ -22,6 +22,7 @@ import Cocoa import Combine import Common import Lottie +import os.log protocol AddressBarButtonsViewControllerDelegate: AnyObject { @@ -400,7 +401,7 @@ final class AddressBarButtonsViewController: NSViewController { guard let tabViewModel, let state = tabViewModel.usedPermissions.microphone else { - os_log("%s: Selected tab view model is nil or no microphone state", type: .error, className) + Logger.general.error("Selected tab view model is nil or no microphone state") return } if case .requested(let query) = state { @@ -419,7 +420,7 @@ final class AddressBarButtonsViewController: NSViewController { guard let tabViewModel, let state = tabViewModel.usedPermissions.geolocation else { - os_log("%s: Selected tab view model is nil or no geolocation state", type: .error, className) + Logger.general.error("Selected tab view model is nil or no geolocation state") return } if case .requested(let query) = state { @@ -438,7 +439,7 @@ final class AddressBarButtonsViewController: NSViewController { guard let tabViewModel, let state = tabViewModel.usedPermissions.popups else { - os_log("%s: Selected tab view model is nil or no popups state", type: .error, className) + Logger.general.error("Selected tab view model is nil or no popups state") return } @@ -463,7 +464,7 @@ final class AddressBarButtonsViewController: NSViewController { guard let tabViewModel, let (permissionType, state) = tabViewModel.usedPermissions.first(where: { $0.key.isExternalScheme }) else { - os_log("%s: Selected tab view model is nil or no externalScheme state", type: .error, className) + Logger.general.error("Selected tab view model is nil or no externalScheme state") return } diff --git a/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift b/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift index 79d5bb73cc..5d9611bfbe 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift @@ -24,6 +24,7 @@ import Common import PixelKit import Suggestions import Subscription +import os.log final class AddressBarTextField: NSTextField { @@ -348,7 +349,7 @@ final class AddressBarTextField: NSTextField { private func updateTabUrlWithUrl(_ providedUrl: URL, userEnteredValue: String, downloadRequested: Bool, suggestion: Suggestion?) { guard let selectedTabViewModel = tabCollectionViewModel.selectedTabViewModel else { - os_log("AddressBarTextField: Selected tab view model is nil", type: .error) + Logger.general.error("AddressBarTextField: Selected tab view model is nil") return } @@ -410,7 +411,7 @@ final class AddressBarTextField: NSTextField { makeUrl(suggestion: suggestion, stringValueWithoutSuffix: stringValueWithoutSuffix) { [weak self] url, userEnteredValue, isUpgraded in guard let self, let url else { - os_log("AddressBarTextField: Making url from address bar string failed", type: .error) + Logger.general.error("AddressBarTextField: Making url from address bar string failed") return } let tab = Tab(content: .url(url, source: .userEntered(userEnteredValue)), @@ -500,7 +501,7 @@ final class AddressBarTextField: NSTextField { private func displaySelectedSuggestionViewModel() { guard let suggestionWindow = suggestionWindowController?.window else { - os_log("AddressBarTextField: Window not available", type: .error) + Logger.general.error("AddressBarTextField: Window not available") return } guard suggestionWindow.isVisible else { return } @@ -575,7 +576,7 @@ final class AddressBarTextField: NSTextField { private func showSuggestionWindow() { guard let window = window, let suggestionWindow = suggestionWindowController?.window else { - os_log("AddressBarTextField: Window not available", type: .error) + Logger.general.error("AddressBarTextField: Window not available") return } @@ -607,7 +608,7 @@ final class AddressBarTextField: NSTextField { return } guard let superview = superview else { - os_log("AddressBarTextField: Superview not available", type: .error) + Logger.general.error("AddressBarTextField: Superview not available") return } diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index 085e84e1fa..ffd505d092 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -23,6 +23,7 @@ import BrowserServicesKit import PixelKit import NetworkProtection import Subscription +import os.log protocol OptionsButtonMenuDelegate: AnyObject { @@ -108,7 +109,10 @@ final class MoreOptionsMenu: NSMenu { let feedbackMenuItem = NSMenuItem(title: feedbackString, action: nil, keyEquivalent: "") .withImage(.sendFeedback) - feedbackMenuItem.submenu = FeedbackSubMenu(targetting: self, tabCollectionViewModel: tabCollectionViewModel) + feedbackMenuItem.submenu = FeedbackSubMenu(targetting: self, + tabCollectionViewModel: tabCollectionViewModel, + subscriptionFeatureAvailability: subscriptionFeatureAvailability, + accountManager: accountManager) addItem(feedbackMenuItem) addItem(NSMenuItem.separator()) @@ -166,7 +170,7 @@ final class MoreOptionsMenu: NSMenu { @objc func toggleFireproofing(_ sender: NSMenuItem) { guard let selectedTabViewModel = tabCollectionViewModel.selectedTabViewModel else { - os_log("MainViewController: No tab view model selected", type: .error) + Logger.general.error("MainViewController: No tab view model selected") return } @@ -488,8 +492,15 @@ final class EmailOptionsButtonSubMenu: NSMenu { @MainActor final class FeedbackSubMenu: NSMenu { + private let subscriptionFeatureAvailability: SubscriptionFeatureAvailability + private let accountManager: AccountManager - init(targetting target: AnyObject, tabCollectionViewModel: TabCollectionViewModel) { + init(targetting target: AnyObject, + tabCollectionViewModel: TabCollectionViewModel, + subscriptionFeatureAvailability: SubscriptionFeatureAvailability, + accountManager: AccountManager) { + self.subscriptionFeatureAvailability = subscriptionFeatureAvailability + self.accountManager = accountManager super.init(title: UserText.sendFeedback) updateMenuItems(with: tabCollectionViewModel, targetting: target) } @@ -512,6 +523,16 @@ final class FeedbackSubMenu: NSMenu { keyEquivalent: "") .withImage(.siteBreakage) addItem(reportBrokenSiteItem) + + if subscriptionFeatureAvailability.usesUnifiedFeedbackForm, accountManager.isUserAuthenticated { + addItem(.separator()) + + let sendPProFeedbackItem = NSMenuItem(title: UserText.sendPProFeedback, + action: #selector(AppDelegate.openPProFeedback(_:)), + keyEquivalent: "") + .withImage(.pProFeedback) + addItem(sendPProFeedbackItem) + } } } diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index ed07f0746a..d7eb9f021a 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -95,6 +95,7 @@ final class NavigationBarViewController: NSViewController { private var selectedTabViewModelCancellable: AnyCancellable? private var credentialsToSaveCancellable: AnyCancellable? private var vpnToggleCancellable: AnyCancellable? + private var feedbackFormCancellable: AnyCancellable? private var passwordManagerNotificationCancellable: AnyCancellable? private var pinnedViewsNotificationCancellable: AnyCancellable? private var navigationButtonsCancellables = Set() @@ -151,6 +152,7 @@ final class NavigationBarViewController: NSViewController { listenToPasswordManagerNotifications() listenToPinningManagerNotifications() listenToMessageNotifications() + listenToFeedbackFormNotifications() subscribeToDownloads() addContextMenu() @@ -212,7 +214,7 @@ final class NavigationBarViewController: NSViewController { @IBAction func goBackAction(_ sender: NSButton) { guard let selectedTabViewModel = tabCollectionViewModel.selectedTabViewModel else { - os_log("%s: Selected tab view model is nil", type: .error, className) + Logger.navigation.error("Selected tab view model is nil") return } @@ -228,7 +230,7 @@ final class NavigationBarViewController: NSViewController { @IBAction func goForwardAction(_ sender: NSButton) { guard let selectedTabViewModel = tabCollectionViewModel.selectedTabViewModel else { - os_log("%s: Selected tab view model is nil", type: .error, className) + Logger.navigation.error("Selected tab view model is nil") return } @@ -249,7 +251,7 @@ final class NavigationBarViewController: NSViewController { @IBAction func refreshOrStopAction(_ sender: NSButton) { guard let selectedTabViewModel = tabCollectionViewModel.selectedTabViewModel else { - os_log("%s: Selected tab view model is nil", type: .error, className) + Logger.navigation.error("Selected tab view model is nil") return } @@ -262,7 +264,7 @@ final class NavigationBarViewController: NSViewController { @IBAction func homeButtonAction(_ sender: NSButton) { guard let selectedTabViewModel = tabCollectionViewModel.selectedTabViewModel else { - os_log("%s: Selected tab view model is nil", type: .error, className) + Logger.navigation.error("Selected tab view model is nil") return } selectedTabViewModel.tab.openHomePage() @@ -404,6 +406,12 @@ final class NavigationBarViewController: NSViewController { .store(in: &cancellables) } + func listenToFeedbackFormNotifications() { + feedbackFormCancellable = NotificationCenter.default.publisher(for: .OpenUnifiedFeedbackForm).receive(on: DispatchQueue.main).sink { _ in + WindowControllersManager.shared.showShareFeedbackModal(source: .ppro) + } + } + @objc private func showVPNUninstalledFeedback() { // Only show the popover if we aren't already presenting one: guard view.window?.isKeyWindow == true, (self.presentedViewControllers ?? []).isEmpty else { return } @@ -1169,4 +1177,5 @@ extension NavigationBarViewController { extension Notification.Name { static let ToggleNetworkProtectionInMainWindow = Notification.Name("com.duckduckgo.vpn.toggle-popover-in-main-window") + static let OpenUnifiedFeedbackForm = Notification.Name("com.duckduckgo.subscription.open-unified-feedback-form") } diff --git a/DuckDuckGo/NavigationBar/View/NavigationButtonMenuDelegate.swift b/DuckDuckGo/NavigationBar/View/NavigationButtonMenuDelegate.swift index c6b005c2fa..3a04bf68fe 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationButtonMenuDelegate.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationButtonMenuDelegate.swift @@ -20,6 +20,7 @@ import Cocoa import Common import WebKit import History +import os.log @MainActor final class NavigationButtonMenuDelegate: NSObject { @@ -52,7 +53,7 @@ extension NavigationButtonMenuDelegate: NSMenuDelegate { func menu(_ menu: NSMenu, update item: NSMenuItem, at index: Int, shouldCancel: Bool) -> Bool { guard let listItem = listItems[safe: index] else { - os_log("%s: Index out of bounds", type: .error, className) + Logger.general.error("Index out of bounds") return true } @@ -75,7 +76,7 @@ extension NavigationButtonMenuDelegate: NSMenuDelegate { @objc func menuItemAction(_ sender: NSMenuItem) { let index = sender.tag guard let listItem = listItems[safe: index] else { - os_log("%s: Index out of bounds", type: .error, className) + Logger.general.error("Index out of bounds") return } tabCollectionViewModel.selectedTabViewModel?.tab.go(to: listItem) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/LoginItem+NetworkProtection.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/LoginItem+NetworkProtection.swift index 30d99488bf..1704de0057 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/LoginItem+NetworkProtection.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/LoginItem+NetworkProtection.swift @@ -18,11 +18,17 @@ import Foundation import LoginItems +import NetworkProtection +import os.log extension LoginItem { - static let vpnMenu = LoginItem(bundleId: Bundle.main.vpnMenuAgentBundleId, defaults: .netP, log: .networkProtection) + static let vpnMenu = LoginItem(bundleId: Bundle.main.vpnMenuAgentBundleId, + defaults: .netP, + logger: Logger.networkProtection) #if NETP_SYSTEM_EXTENSION - static let notificationsAgent = LoginItem(bundleId: Bundle.main.notificationsAgentBundleId, defaults: .netP, log: .networkProtection) + static let notificationsAgent = LoginItem(bundleId: Bundle.main.notificationsAgentBundleId, + defaults: .netP, + logger: Logger.networkProtection) #endif } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift index 2d57ecb38d..124e34cb37 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtection+ConvenienceInitializers.swift @@ -38,16 +38,6 @@ extension NetworkProtectionDeviceManager { } } -extension NetworkProtectionCodeRedemptionCoordinator { - convenience init() { - let settings = Application.appDelegate.vpnSettings - self.init(environment: settings.selectedEnvironment, - tokenStore: NetworkProtectionKeychainTokenStore(), - errorEvents: .networkProtectionAppDebugEvents, - isSubscriptionEnabled: DefaultSubscriptionFeatureAvailability().isFeatureAvailable) - } -} - extension NetworkProtectionKeychainTokenStore { convenience init() { self.init(isSubscriptionEnabled: DefaultSubscriptionFeatureAvailability().isFeatureAvailable) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionAppEvents.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionAppEvents.swift index f63eb7551c..e39818559e 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionAppEvents.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionAppEvents.swift @@ -88,7 +88,7 @@ final class NetworkProtectionAppEvents { return } - loginItemsManager.restartLoginItems(LoginItemsManager.networkProtectionLoginItems, log: .networkProtection) + loginItemsManager.restartLoginItems(LoginItemsManager.networkProtectionLoginItems) } } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift index 9afbacf519..47ac1acf82 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift @@ -22,6 +22,7 @@ import Foundation import NetworkProtection import NetworkProtectionProxy import SwiftUI +import os.log /// Controller for the VPN debug menu. /// @@ -176,7 +177,7 @@ final class NetworkProtectionDebugMenu: NSMenu { do { try await debugUtilities.resetAllState(keepAuthToken: false) } catch { - os_log("Error in resetAllState: %{public}@", log: .networkProtection, error.localizedDescription) + Logger.networkProtection.error("Error in resetAllState: \(error.localizedDescription, privacy: .public)") } } } @@ -189,7 +190,7 @@ final class NetworkProtectionDebugMenu: NSMenu { do { try await debugUtilities.resetAllState(keepAuthToken: true) } catch { - os_log("Error in resetAllState: %{public}@", log: .networkProtection, error.localizedDescription) + Logger.networkProtection.error("Error in resetAllState: \(error.localizedDescription, privacy: .public)") } } } @@ -321,9 +322,11 @@ final class NetworkProtectionDebugMenu: NSMenu { private func populateNetworkProtectionEnvironmentListMenuItems() { environmentMenu.items = [ - NSMenuItem(title: "⚠️ The environment can be set in the Subscription > Environment menu", action: nil, target: nil), - NSMenuItem(title: "Production", action: nil, target: nil, keyEquivalent: ""), - NSMenuItem(title: "Staging", action: nil, target: nil, keyEquivalent: ""), + NSMenuItem(title: "⚠️ A staging subscription can be used for the staging VPN environment, a production subscription can be used for both", action: nil, target: nil), + NSMenuItem(title: "⚠️ Please restart the browser after changing environment", action: nil, target: nil), + NSMenuItem.separator(), + NSMenuItem(title: "Production", action: #selector(setSelectedEnvironment(_:)), target: self, keyEquivalent: ""), + NSMenuItem(title: "Staging", action: #selector(setSelectedEnvironment(_:)), target: self, keyEquivalent: ""), ] } @@ -406,10 +409,10 @@ final class NetworkProtectionDebugMenu: NSMenu { private func updateEnvironmentMenu() { let selectedEnvironment = settings.selectedEnvironment - guard environmentMenu.items.count == 3 else { return } + guard environmentMenu.items.count == 5 else { return } - environmentMenu.items[1].state = selectedEnvironment == .production ? .on: .off - environmentMenu.items[2].state = selectedEnvironment == .staging ? .on: .off + environmentMenu.items[3].state = selectedEnvironment == .production ? .on : .off + environmentMenu.items[4].state = selectedEnvironment == .staging ? .on : .off } private func updatePreferredServerMenu() { @@ -480,6 +483,28 @@ final class NetworkProtectionDebugMenu: NSMenu { @objc private func toggleExcludeDDGBrowser() { transparentProxySettings.toggleExclusion(for: ddgBrowserAppIdentifier) } + + // MARK: Environment + + @objc func setSelectedEnvironment(_ menuItem: NSMenuItem) { + let title = menuItem.title + let selectedEnvironment: VPNSettings.SelectedEnvironment + + if title == "Staging" { + selectedEnvironment = .staging + } else { + selectedEnvironment = .production + } + + settings.selectedEnvironment = selectedEnvironment + + Task { + _ = try await NetworkProtectionDeviceManager.create().refreshServerList() + try? await populateNetworkProtectionServerListMenuItems() + + settings.selectedServer = .automatic + } + } } #if DEBUG diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift index 6573e3a550..531fbea107 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift @@ -128,7 +128,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { try? await appLauncher.launchApp(withCommand: VPNAppLaunchCommand.showFAQ) }), NetworkProtectionStatusView.Model.MenuItem( - name: UserText.networkProtectionNavBarStatusViewShareFeedback, + name: UserText.networkProtectionNavBarStatusViewSendFeedback, action: { try? await appLauncher.launchApp(withCommand: VPNAppLaunchCommand.shareFeedback) }) @@ -140,7 +140,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager { try? await appLauncher.launchApp(withCommand: VPNAppLaunchCommand.showFAQ) }), NetworkProtectionStatusView.Model.MenuItem( - name: UserText.networkProtectionNavBarStatusViewShareFeedback, + name: UserText.networkProtectionNavBarStatusViewSendFeedback, action: { try? await appLauncher.launchApp(withCommand: VPNAppLaunchCommand.shareFeedback) }) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index 78d6cda52f..65e8d18b37 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -26,6 +26,7 @@ import NetworkProtectionProxy import NetworkProtectionUI import Networking import PixelKit +import os.log #if NETP_SYSTEM_EXTENSION import SystemExtensionManager @@ -59,10 +60,6 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr /// static var simulationOptions = NetworkProtectionSimulationOptions() - /// The logger that this object will use for errors that are handled by this class. - /// - private let logger: NetworkProtectionLogger - /// Stores the last controller error for the purpose of updating the UI as needed. /// private let controllerErrorStore = NetworkProtectionControllerErrorStore() @@ -166,10 +163,8 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr defaults: UserDefaults, tokenStore: NetworkProtectionTokenStore = NetworkProtectionKeychainTokenStore(), notificationCenter: NotificationCenter = .default, - logger: NetworkProtectionLogger = DefaultNetworkProtectionLogger(), accessTokenStorage: SubscriptionTokenKeychainStorage) { - self.logger = logger self.networkExtensionBundleID = networkExtensionBundleID self.networkExtensionController = networkExtensionController self.notificationCenter = notificationCenter @@ -839,10 +834,10 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr private func fetchAuthToken() throws -> NSString? { if let accessToken = try? accessTokenStorage.getAccessToken() { - os_log(.error, log: .networkProtection, "🟢 TunnelController found token") + Logger.networkProtection.debug("🟢 TunnelController found token") return Self.adaptAccessTokenForVPN(accessToken) as NSString? } - os_log(.error, log: .networkProtection, "🔴 TunnelController found no token :(") + Logger.networkProtection.error("TunnelController found no token") return try tokenStore.fetchToken() as NSString? } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNControllerUDSClient+ConvenienceInitializers.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNControllerUDSClient+ConvenienceInitializers.swift index b4ea2f7d65..930c18315a 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNControllerUDSClient+ConvenienceInitializers.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNControllerUDSClient+ConvenienceInitializers.swift @@ -28,6 +28,6 @@ extension VPNControllerUDSClient { extension UDSClient { static let sharedVPNUDSClient: UDSClient = { - return UDSClient(socketFileURL: VPNIPCResources.socketFileURL, log: .networkProtectionIPCLog) + return UDSClient(socketFileURL: VPNIPCResources.socketFileURL) }() } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift index 3949a24805..5c376db928 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNRedditSessionWorkaround.swift @@ -22,6 +22,7 @@ import NetworkProtectionIPC import Subscription import WebKit import Common +import os.log final class VPNRedditSessionWorkaround { @@ -70,9 +71,9 @@ final class VPNRedditSessionWorkaround { } if requiresRedditSessionCookie { - os_log(.error, log: .networkProtection, "Installing VPN cookie workaround...") + Logger.networkProtection.error("Installing VPN cookie workaround...") await cookieStore.setCookie(redditSessionCookie) - os_log(.error, log: .networkProtection, "Installed VPN cookie workaround") + Logger.networkProtection.error("Installed VPN cookie workaround") } } @@ -85,9 +86,9 @@ final class VPNRedditSessionWorkaround { for cookie in cookies { if cookie.domain == redditSessionCookie.domain, cookie.name == redditSessionCookie.name { if cookie.value == redditSessionCookie.value { - os_log(.error, log: .networkProtection, "Removing VPN cookie workaround") + Logger.networkProtection.error("Removing VPN cookie workaround") await cookieStore.deleteCookie(cookie) - os_log(.error, log: .networkProtection, "Removed VPN cookie workaround") + Logger.networkProtection.error("Removed VPN cookie workaround") } break diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNURLEventHandler.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNURLEventHandler.swift index 54368bc09e..3ca5bb8fef 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNURLEventHandler.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNURLEventHandler.swift @@ -63,7 +63,7 @@ final class VPNURLEventHandler { } func showShareFeedback() { - windowControllerManager.showShareFeedbackModal() + windowControllerManager.showShareFeedbackModal(source: .vpn) } func showMainWindow() { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift index 960974ff29..4bc5ec17c5 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift @@ -22,6 +22,7 @@ import NetworkProtection import NetworkProtectionIPC import PixelKit import UDSHelper +import os.log /// VPN tunnel controller through IPC. /// @@ -75,7 +76,7 @@ final class NetworkProtectionIPCTunnelController { private func enableLoginItems() async throws { do { - try loginItemsManager.throwingEnableLoginItems(LoginItemsManager.networkProtectionLoginItems, log: .networkProtection) + try loginItemsManager.throwingEnableLoginItems(LoginItemsManager.networkProtectionLoginItems) } catch { throw RequestError.internalLoginItemError(error) } @@ -160,11 +161,11 @@ extension NetworkProtectionIPCTunnelController: TunnelController { private func log(_ error: Error) { switch error { case RequestError.notAuthorizedToEnableLoginItem: - os_log("🔴 IPC Controller not authorized to enable the login item", log: .networkProtection) + Logger.networkProtection.error("IPC Controller not authorized to enable the login item: \(error.localizedDescription)") case RequestError.internalLoginItemError(let error): - os_log("🔴 IPC Controller found an error while enabling the login item: \(error)", log: .networkProtection) + Logger.networkProtection.error("IPC Controller found an error while enabling the login item: \(error.localizedDescription)") default: - os_log("🔴 IPC Controller found an unknown error: \(error)", log: .networkProtection) + Logger.networkProtection.error("IPC Controller found an unknown error: \(error.localizedDescription)") } } } diff --git a/DuckDuckGo/NetworkProtection/ExcludedDomains/ExcludedDomainsModel.swift b/DuckDuckGo/NetworkProtection/ExcludedDomains/ExcludedDomainsModel.swift index b3cbdbd75e..bd19fdd10b 100644 --- a/DuckDuckGo/NetworkProtection/ExcludedDomains/ExcludedDomainsModel.swift +++ b/DuckDuckGo/NetworkProtection/ExcludedDomains/ExcludedDomainsModel.swift @@ -18,18 +18,24 @@ import Foundation import NetworkProtectionProxy +import NetworkProtectionUI +import PixelKit protocol ExcludedDomainsViewModel { var domains: [String] { get } func add(domain: String) func remove(domain: String) + + func askUserToReportIssues(withDomain domain: String) } final class DefaultExcludedDomainsViewModel { let proxySettings = TransparentProxySettings(defaults: .netP) + private let pixelKit: PixelFiring? - init() { + init(pixelKit: PixelFiring? = PixelKit.shared) { + self.pixelKit = pixelKit } } @@ -51,4 +57,9 @@ extension DefaultExcludedDomainsViewModel: ExcludedDomainsViewModel { domain == cursor } } + + func askUserToReportIssues(withDomain domain: String) { + let siteIssuesReporter = SiteIssuesReporter(pixelKit: pixelKit) + siteIssuesReporter.askUserToReportIssues(withDomain: domain) + } } diff --git a/DuckDuckGo/NetworkProtection/ExcludedDomains/ExcludedDomainsViewController.swift b/DuckDuckGo/NetworkProtection/ExcludedDomains/ExcludedDomainsViewController.swift index 6192405f21..0806bd2621 100644 --- a/DuckDuckGo/NetworkProtection/ExcludedDomains/ExcludedDomainsViewController.swift +++ b/DuckDuckGo/NetworkProtection/ExcludedDomains/ExcludedDomainsViewController.swift @@ -117,6 +117,8 @@ final class ExcludedDomainsViewController: NSViewController { if let newRowIndex = allDomains.firstIndex(of: domain) { tableView.scrollRowToVisible(newRowIndex) } + + model.askUserToReportIssues(withDomain: domain) } @IBAction func removeSelectedDomain(_ sender: NSButton) { diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 46a84aeca7..bec44225be 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -24,6 +24,8 @@ import NetworkExtension import Networking import PixelKit import Subscription +import os.log +import WireGuard final class MacPacketTunnelProvider: PacketTunnelProvider { @@ -52,7 +54,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { // MARK: - Error Reporting - private static func networkProtectionDebugEvents(controllerErrorStore: NetworkProtectionTunnelErrorStore) -> EventMapping? { + private static func networkProtectionDebugEvents(controllerErrorStore: NetworkProtectionTunnelErrorStore) -> EventMapping { return EventMapping { event, _, _, _ in let domainEvent: NetworkProtectionPixelEvent #if DEBUG @@ -446,9 +448,9 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { let subscriptionEndpointService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let authEndpointService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment) let accountManager = DefaultAccountManager(accessTokenStorage: tokenStore, - entitlementsCache: entitlementsCache, - subscriptionEndpointService: subscriptionEndpointService, - authEndpointService: authEndpointService) + entitlementsCache: entitlementsCache, + subscriptionEndpointService: subscriptionEndpointService, + authEndpointService: authEndpointService) let entitlementsCheck = { await accountManager.hasEntitlement(forProductName: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) @@ -461,6 +463,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { tunnelHealthStore: tunnelHealthStore, controllerErrorStore: controllerErrorStore, snoozeTimingStore: NetworkProtectionSnoozeTimingStore(userDefaults: .netP), + wireGuardInterface: DefaultWireGuardInterface(), keychainType: Bundle.keychainType, tokenStore: tokenStore, debugEvents: debugEvents, @@ -471,6 +474,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { entitlementCheck: entitlementsCheck) setupPixels() + accountManager.delegate = self observeServerChanges() observeStatusUpdateRequests() } @@ -565,7 +569,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { try super.loadVendorOptions(from: provider) guard let vendorOptions = provider?.providerConfiguration else { - os_log("🔵 Provider is nil, or providerConfiguration is not set", log: .networkProtection) + Logger.networkProtection.debug("🔵 Provider is nil, or providerConfiguration is not set") throw ConfigurationError.missingProviderConfiguration } @@ -574,7 +578,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { private func loadDefaultPixelHeaders(from options: [String: Any]) throws { guard let defaultPixelHeaders = options[NetworkProtectionOptionKey.defaultPixelHeaders] as? [String: String] else { - os_log("🔵 Pixel options are not set", log: .networkProtection) + Logger.networkProtection.debug("🔵 Pixel options are not set") throw ConfigurationError.missingPixelHeaders } @@ -626,3 +630,41 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { } } + +final class DefaultWireGuardInterface: WireGuardInterface { + func turnOn(settings: UnsafePointer, handle: Int32) -> Int32 { + wgTurnOn(settings, handle) + } + + func turnOff(handle: Int32) { + wgTurnOff(handle) + } + + func getConfig(handle: Int32) -> UnsafeMutablePointer? { + return wgGetConfig(handle) + } + + func setConfig(handle: Int32, config: String) -> Int64 { + return wgSetConfig(handle, config) + } + + func bumpSockets(handle: Int32) { + wgBumpSockets(handle) + } + + func disableSomeRoamingForBrokenMobileSemantics(handle: Int32) { + wgDisableSomeRoamingForBrokenMobileSemantics(handle) + } + + func setLogger(context: UnsafeMutableRawPointer?, logFunction: (@convention(c) (UnsafeMutableRawPointer?, Int32, UnsafePointer?) -> Void)?) { + wgSetLogger(context, logFunction) + } +} + +extension MacPacketTunnelProvider: AccountManagerKeychainAccessDelegate { + + public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { + PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: accessType, accessError: error), + frequency: .dailyAndCount) + } +} diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift index 0724849265..e2304c6539 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift @@ -20,6 +20,7 @@ import Foundation import Subscription import NetworkProtection import Common +import os.log extension NetworkProtectionKeychainTokenStore: SubscriptionTokenStoring { @@ -32,7 +33,7 @@ extension NetworkProtectionKeychainTokenStore: SubscriptionTokenStoring { if token.hasPrefix("ddg:") { token = token.replacingOccurrences(of: "ddg:", with: "") } - os_log("🟢 Wrapper successfully fetched token %{token}@", log: .networkProtection, token) + Logger.networkProtection.debug("🟢 Wrapper successfully fetched token \(token)") return token } diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/VPNLogger.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/VPNLogger.swift deleted file mode 100644 index 52f8000ea3..0000000000 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/VPNLogger.swift +++ /dev/null @@ -1,118 +0,0 @@ -// -// VPNLogger.swift -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import NetworkProtection -import os.log - -/// Logger for the VPN -/// -/// Since we'll want to ensure this adheres to our privacy standards, grouping the logging logic to be mostly -/// handled by a single class sounds like a good approach to be able to review what's being logged.. -/// -public final class VPNLogger { - public typealias AttemptStep = PacketTunnelProvider.AttemptStep - public typealias ConnectionAttempt = PacketTunnelProvider.ConnectionAttempt - public typealias ConnectionTesterStatus = PacketTunnelProvider.ConnectionTesterStatus - public typealias LogCallback = (OSLogType, OSLogMessage) -> Void - - public init() {} - - public func logStartingWithoutAuthToken() { - os_log("🔴 Starting tunnel without an auth token", log: .networkProtection, type: .error) - } - - public func log(_ step: AttemptStep, named name: String) { - let log = OSLog.networkProtection - - switch step { - case .begin: - os_log("🔵 %{public}@ attempt begins", log: log, name) - case .failure(let error): - os_log("🔴 %{public}@ attempt failed with error: %{public}@", log: log, type: .error, name, error.localizedDescription) - case .success: - os_log("🟢 %{public}@ attempt succeeded", log: log, name) - } - } - - public func log(_ step: ConnectionAttempt) { - let log = OSLog.networkProtection - - switch step { - case .connecting: - os_log("🔵 Connection attempt detected", log: log) - case .failure: - os_log("🔴 Connection attempt failed", log: log, type: .error) - case .success: - os_log("🟢 Connection attempt successful", log: log) - } - } - - public func log(_ status: ConnectionTesterStatus, server: String) { - let log = OSLog.networkProtectionConnectionTesterLog - - switch status { - case .failed(let duration): - os_log("🔴 Connection tester (%{public}@ - %{public}@) failure", log: log, type: .error, duration.rawValue, server) - case .recovered(let duration, let failureCount): - os_log("🟢 Connection tester (%{public}@ - %{public}@) recovery (after %{public}@ failures)", log: log, duration.rawValue, server, String(failureCount)) - } - } - - public func log(_ step: FailureRecoveryStep) { - let log = OSLog.networkProtectionTunnelFailureMonitorLog - - switch step { - case .started: - os_log("🔵 Failure Recovery attempt started", log: log) - case .failed(let error): - os_log("🔴 Failure Recovery attempt failed with error: %{public}@", log: log, type: .error, error.localizedDescription) - case .completed(let health): - switch health { - case .healthy: - os_log("🟢 Failure Recovery attempt completed", log: log) - case .unhealthy: - os_log("🔴 Failure Recovery attempt ended as unhealthy", log: log, type: .error) - } - } - } - - public func log(_ step: NetworkProtectionTunnelFailureMonitor.Result) { - let log = OSLog.networkProtectionTunnelFailureMonitorLog - - switch step { - case .failureDetected: - os_log("🔴 Tunnel failure detected", log: log, type: .error) - case .failureRecovered: - os_log("🟢 Tunnel failure recovered", log: log) - case .networkPathChanged: - os_log("🔵 Tunnel recovery detected path change", log: log) - } - } - - public func log(_ result: NetworkProtectionLatencyMonitor.Result) { - let log = OSLog.networkProtectionLatencyMonitorLog - - switch result { - case .error: - os_log("🔴 There was an error logging the latency", log: log, type: .error) - case .quality(let quality): - os_log("Connection quality is: %{public}@", log: log, quality.rawValue) - } - } -} diff --git a/DuckDuckGo/Onboarding/ContextualOnboarding/ContextualOnboardingDialogs.swift b/DuckDuckGo/Onboarding/ContextualOnboarding/ContextualOnboardingDialogs.swift new file mode 100644 index 0000000000..63981b9303 --- /dev/null +++ b/DuckDuckGo/Onboarding/ContextualOnboarding/ContextualOnboardingDialogs.swift @@ -0,0 +1,278 @@ +// +// ContextualOnboardingDialogs.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI +import Onboarding +import SwiftUIExtensions + +struct OnboardingDialogsContants { + static let titleFont = Font.system(size: 20, weight: .bold, design: .rounded) + static let messageFont = Font.system(size: Self.messageFontSize, weight: .regular, design: .rounded) + static let messageFontSize = 16.0 +} + +struct OnboardingTrySearchDialog: View { + let title = UserText.ContextualOnboarding.onboardingTryASearchTitle + let message = NSAttributedString(string: UserText.ContextualOnboarding.onboardingTryASearchMessage) + let viewModel: OnboardingSearchSuggestionsViewModel + + var body: some View { + DaxDialogView(logoPosition: .left) { + ContextualDaxDialogContent( + orientation: .horizontalStack(alignment: .top), + title: title, + titleFont: OnboardingDialogsContants.titleFont, + message: message, + messageFont: OnboardingDialogsContants.messageFont, + list: viewModel.itemsList, + listAction: viewModel.listItemPressed + ) + } + .padding() + } + +} + +struct OnboardingTryVisitingSiteDialog: View { + let viewModel: OnboardingSiteSuggestionsViewModel + + var body: some View { + DaxDialogView(logoPosition: .left) { + OnboardingTryVisitingSiteDialogContent(viewModel: viewModel) + } + .padding() + + } +} + +struct OnboardingTryVisitingSiteDialogContent: View { + let message = NSAttributedString(string: UserText.ContextualOnboarding.onboardingTryASiteMessage) + + let viewModel: OnboardingSiteSuggestionsViewModel + + var body: some View { + ContextualDaxDialogContent( + orientation: .horizontalStack(alignment: .top), + title: viewModel.title, + titleFont: OnboardingDialogsContants.titleFont, + message: message, + messageFont: OnboardingDialogsContants.messageFont, + list: viewModel.itemsList, + listAction: viewModel.listItemPressed) + } +} + +struct OnboardingFirstSearchDoneDialog: View { + let title = UserText.ContextualOnboarding.onboardingFirstSearchDoneTitle + let message = NSAttributedString(string: UserText.ContextualOnboarding.onboardingFirstSearchDoneMessage) + let cta = UserText.ContextualOnboarding.onboardingGotItButton + + @State private var showNextScreen: Bool = false + + let shouldFollowUp: Bool + let viewModel: OnboardingSiteSuggestionsViewModel + let gotItAction: () -> Void + + var body: some View { + DaxDialogView(logoPosition: .left) { + VStack { + if showNextScreen { + OnboardingTryVisitingSiteDialogContent(viewModel: viewModel) + } else { + ContextualDaxDialogContent( + orientation: .horizontalStack(alignment: .center), + title: title, + titleFont: OnboardingDialogsContants.titleFont, + message: message, + messageFont: OnboardingDialogsContants.messageFont, + customActionView: AnyView( + OnboardingPrimaryCTAButton(title: cta) { + gotItAction() + withAnimation { + if shouldFollowUp { + showNextScreen = true + } + } + } + ) + ) + } + } + } + .padding() + + } +} + +struct OnboardingFireButtonDialogContent: View { + private let attributedMessage: NSAttributedString = { + let firstString = UserText.ContextualOnboarding.onboardingTryFireButtonMessage + let boldString = "Fire Button." + let attributedString = NSMutableAttributedString(string: firstString) + let boldFontAttribute: [NSAttributedString.Key: Any] = [ + .font: NSFont.systemFont(ofSize: OnboardingDialogsContants.messageFontSize, weight: .bold) + ] + if let boldRange = firstString.range(of: boldString) { + let nsBoldRange = NSRange(boldRange, in: firstString) + attributedString.addAttributes(boldFontAttribute, range: nsBoldRange) + } + + return attributedString + }() + + var body: some View { + ContextualDaxDialogContent( + orientation: .horizontalStack(alignment: .center), + message: attributedMessage, + messageFont: OnboardingDialogsContants.messageFont, + customActionView: AnyView(actionView)) + } + + @ViewBuilder + private var actionView: some View { + VStack { + OnboardingPrimaryCTAButton(title: "Try it", action: {}) + OnboardingSecondaryCTAButton(title: "Skip", action: {}) + } + } + +} + +struct OnboardingFireDialog: View { + + var body: some View { + DaxDialogView(logoPosition: .left) { + VStack { + OnboardingFireButtonDialogContent() + } + } + .padding() + + } +} + +struct OnboardingTrackersDoneDialog: View { + let cta = UserText.ContextualOnboarding.onboardingGotItButton + + @State private var showNextScreen: Bool = false + + let shouldFollowUp: Bool + let message: NSAttributedString + let blockedTrackersCTAAction: () -> Void + + var body: some View { + DaxDialogView(logoPosition: .left) { + VStack { + if showNextScreen { + OnboardingFireButtonDialogContent() + } else { + ContextualDaxDialogContent( + orientation: .horizontalStack(alignment: .center), + message: message, + messageFont: OnboardingDialogsContants.messageFont, + customActionView: AnyView( + OnboardingPrimaryCTAButton(title: cta) { + blockedTrackersCTAAction() + if shouldFollowUp { + withAnimation { + showNextScreen = true + } + } + } + ) + ) + } + } + } + .padding() + + } +} + +struct OnboardingPrimaryCTAButton: View { + let title: String + let action: () -> Void + + var body: some View { + Button(action: action) { + Text(title) + .padding(.vertical, 3) + .padding(.horizontal, 24) + } + .buttonStyle(DefaultActionButtonStyle(enabled: true)) + .shadow(radius: 1, x: -0.6, y: +0.6) + } + +} + +struct OnboardingSecondaryCTAButton: View { + @Environment(\.colorScheme) var colorScheme + + let title: String + let action: () -> Void + + var body: some View { + Button(action: action) { + Text(title) + .padding(.vertical, 3) + .padding(.horizontal, 26) + } + .buttonStyle(DismissActionButtonStyle()) + } + +} + +// MARK: - Preview + +#Preview("Try Search") { + OnboardingTrySearchDialog(viewModel: OnboardingSearchSuggestionsViewModel(suggestedSearchesProvider: OnboardingSuggestedSearchesProvider(), pixelReporter: OnboardingPixelReporter())) + .padding() +} + +final class OnboardingPixelReporter: OnboardingSearchSuggestionsPixelReporting, OnboardingSiteSuggestionsPixelReporting { + func trackSiteSuggetionOptionTapped() { + } + func trackSearchSuggetionOptionTapped() { + } +} + +#Preview("Try Site") { + OnboardingTryVisitingSiteDialog(viewModel: OnboardingSiteSuggestionsViewModel(title: UserText.ContextualOnboarding.onboardingTryASiteTitle, suggestedSitesProvider: OnboardingSuggestedSitesProvider(surpriseItemTitle: UserText.ContextualOnboarding.tryASearchOptionSurpriseMeTitle), pixelReporter: OnboardingPixelReporter())) + .padding() +} + +#Preview("First Search Dialog") { + OnboardingFirstSearchDoneDialog(shouldFollowUp: true, viewModel: OnboardingSiteSuggestionsViewModel(title: UserText.ContextualOnboarding.onboardingTryASiteTitle, suggestedSitesProvider: OnboardingSuggestedSitesProvider(surpriseItemTitle: UserText.ContextualOnboarding.tryASearchOptionSurpriseMeTitle), pixelReporter: OnboardingPixelReporter()), gotItAction: {}) + .padding() +} + +#Preview("Try Fire Button") { + DaxDialogView(logoPosition: .left) { + OnboardingFireButtonDialogContent() + } + .padding() +} + +#Preview("Trackers Dialog") { + var message: NSAttributedString = { + let firstString = UserText.ContextualOnboarding.onboardingTryFireButtonMessage + return NSMutableAttributedString(string: firstString) + }() + return OnboardingTrackersDoneDialog(shouldFollowUp: true, message: message, blockedTrackersCTAAction: {}) + .padding() +} diff --git a/DuckDuckGo/Onboarding/ContextualOnboarding/OnboardingSuggestedSearchesProvider.swift b/DuckDuckGo/Onboarding/ContextualOnboarding/OnboardingSuggestedSearchesProvider.swift new file mode 100644 index 0000000000..a51454e8de --- /dev/null +++ b/DuckDuckGo/Onboarding/ContextualOnboarding/OnboardingSuggestedSearchesProvider.swift @@ -0,0 +1,79 @@ +// +// OnboardingSuggestedSearchesProvider.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Onboarding + +struct OnboardingSuggestedSearchesProvider: OnboardingSuggestionsItemsProviding { + private let countryAndLanguageProvider: OnboardingRegionAndLanguageProvider + + init(countryAndLanguageProvider: OnboardingRegionAndLanguageProvider = Locale.current) { + self.countryAndLanguageProvider = countryAndLanguageProvider + } + + var list: [ContextualOnboardingListItem] { + return [ + option1, + option2, + surpriseMe + ] + } + + private var country: String? { + countryAndLanguageProvider.regionCode + } + private var language: String? { + countryAndLanguageProvider.languageCode + } + + private var option1: ContextualOnboardingListItem { + var search: String + if language == "en" { + search = UserText.ContextualOnboarding.tryASearchOption1English + } else { + search = UserText.ContextualOnboarding.tryASearchOption1International + } + return ContextualOnboardingListItem.search(title: search) + } + + private var option2: ContextualOnboardingListItem { + var search: String + if country == "us" { + search = UserText.ContextualOnboarding.tryASearchOption2English + } else { + search = UserText.ContextualOnboarding.tryASearchOption2International + } + return ContextualOnboardingListItem.search(title: search) + } + + private var option3: ContextualOnboardingListItem { + let search = UserText.ContextualOnboarding.tryASearchOption3 + return ContextualOnboardingListItem.search(title: search) + } + + private var surpriseMe: ContextualOnboardingListItem { + var search: String + if country == "us" { + search = UserText.ContextualOnboarding.tryASearchOptionSurpriseMeEnglish + } else { + search = UserText.ContextualOnboarding.tryASearchOptionSurpriseMeInternational + } + return ContextualOnboardingListItem.surprise(title: search, visibleTitle: UserText.ContextualOnboarding.tryASearchOptionSurpriseMeTitle) + } + +} diff --git a/DuckDuckGo/Onboarding/OnboardingActionsManager.swift b/DuckDuckGo/Onboarding/OnboardingActionsManager.swift index 30efbe0e66..22321ea4cc 100644 --- a/DuckDuckGo/Onboarding/OnboardingActionsManager.swift +++ b/DuckDuckGo/Onboarding/OnboardingActionsManager.swift @@ -20,6 +20,7 @@ import Foundation import Combine import PixelKit import Common +import os.log enum OnboardingSteps: String, CaseIterable { case summary @@ -198,7 +199,7 @@ final class OnboardingActionsManager: OnboardingActionsManaging { let message = param["message"] ?? "" let id = param["id"] ?? "" PixelKit.fire(GeneralPixel.onboardingExceptionReported(message: message, id: id), frequency: .standard) - os_log("Onboarding error: %{public}@", log: .error, "\(id): \(message)") + Logger.general.error("Onboarding error: \("\(id): \(message)", privacy: .public)") } private func onboardingHasFinished() { diff --git a/DuckDuckGo/PasswordManager/Bitwarden/Logger+BitWarden.swift b/DuckDuckGo/PasswordManager/Bitwarden/Logger+BitWarden.swift new file mode 100644 index 0000000000..d9d389dd7a --- /dev/null +++ b/DuckDuckGo/PasswordManager/Bitwarden/Logger+BitWarden.swift @@ -0,0 +1,24 @@ +// +// Logger+BitWarden.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import os.log + +public extension Logger { + static var bitWarden = { Logger(subsystem: "Bitwarden", category: "") }() +} diff --git a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift index 4cf474c0b9..12900d8d51 100644 --- a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift +++ b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWManager.swift @@ -21,6 +21,7 @@ import Foundation import SwiftUI import OpenSSL import PixelKit +import os.log final class BWManager: BWManagement, ObservableObject { @@ -125,7 +126,7 @@ final class BWManager: BWManagement, ObservableObject { do { try communicator.runProxyProcess() } catch { - os_log("BWManagement: Running of proxy process failed", type: .error) + Logger.bitWarden.error("BWManagement: Running of proxy process failed") PixelKit.fire(DebugEvent(GeneralPixel.bitwardenNotResponding)) status = .error(error: .runningOfProxyProcessFailed) scheduleConnectionAttempt() @@ -215,7 +216,8 @@ final class BWManager: BWManagement, ObservableObject { status = .notRunning } default: - logOrAssertionFailure("BWManager: Wrong handler") + Logger.bitWarden.fault("BWManager: Wrong handler") + assertionFailure("BWManager: Wrong handler") } } @@ -236,7 +238,8 @@ final class BWManager: BWManagement, ObservableObject { private func handleError(_ error: String) { switch error { case "cannot-decrypt": - logOrAssertionFailure("BWManagement: Bitwarden error - cannot decrypt") + Logger.bitWarden.fault("BWManagement: Bitwarden error - cannot decrypt") + assertionFailure("BWManagement: Bitwarden error - cannot decrypt") PixelKit.fire(DebugEvent(GeneralPixel.bitwardenRespondedCannotDecrypt), frequency: .daily) case "locked": if case let .connected(vault) = status { @@ -245,7 +248,9 @@ final class BWManager: BWManagement, ObservableObject { sendStatus() } return - default: logOrAssertionFailure("BWManager: Unhandled error") + default: + Logger.bitWarden.fault("BWManager: Unhandled error") + assertionFailure("BWManager: Unhandled error") PixelKit.fire(DebugEvent(GeneralPixel.bitwardenRespondedWithError)) } } @@ -293,7 +298,8 @@ final class BWManager: BWManagement, ObservableObject { let ourHmac = encryption.computeHmac(data, iv: ivData) guard ourHmac == hmac else { PixelKit.fire(DebugEvent(GeneralPixel.bitwardenHmacComparisonFailed)) - logOrAssertionFailure("BWManager: HMAC comparison failed") + Logger.bitWarden.fault("BWManager: HMAC comparison failed") + assertionFailure("BWManager: HMAC comparison failed") return } @@ -335,7 +341,8 @@ final class BWManager: BWManagement, ObservableObject { default: break } - logOrAssertionFailure("BWManager: Unhandled response") + Logger.bitWarden.fault("BWManager: Unhandled response") + assertionFailure("BWManager: Unhandled response") } private func handleStatusResponse(payloadItemArray: [BWResponse.PayloadItem]) { @@ -351,7 +358,8 @@ final class BWManager: BWManagement, ObservableObject { private func handleCredentialRetrievalResponse(messageId: MessageId, payload: BWResponse.Payload) { guard let (domain, completion) = retrieveCredentialsCompletionCache[messageId] else { - logOrAssertionFailure("BWManager: Missing or already removed completion block") + Logger.bitWarden.fault("BWManager: Missing or already removed completion block") + assertionFailure("BWManager: Missing or already removed completion block") return } @@ -362,7 +370,8 @@ final class BWManager: BWManagement, ObservableObject { completion(credentials, nil) case .item(let payloadItem): guard let error = payloadItem.error else { - logOrAssertionFailure("BWManager: Unexpected response in credential retrieval") + Logger.bitWarden.fault("BWManager: Unexpected response in credential retrieval") + assertionFailure("BWManager: Unexpected response in credential retrieval") return } @@ -375,7 +384,8 @@ final class BWManager: BWManagement, ObservableObject { private func handleCredentialCreationResponse(messageId: MessageId, payloadItem: BWResponse.PayloadItem) { guard let completion = createCredentialCompletionCache[messageId] else { - logOrAssertionFailure("BWManager: Missing completion block") + Logger.bitWarden.fault("BWManager: Missing completion block") + assertionFailure("BWManager: Missing completion block") return } createCredentialCompletionCache[messageId] = nil @@ -391,7 +401,8 @@ final class BWManager: BWManagement, ObservableObject { private func handleCredentialUpdateResponse(messageId: MessageId, payloadItem: BWResponse.PayloadItem) { guard let completion = updateCredentialCompletionCache[messageId] else { - logOrAssertionFailure("BWManager: Missing completion block") + Logger.bitWarden.fault("BWManager: Missing completion block") + assertionFailure("BWManager: Missing completion block") return } updateCredentialCompletionCache[messageId] = nil @@ -410,13 +421,15 @@ final class BWManager: BWManagement, ObservableObject { func sendHandshake() { guard let publicKey = generateKeyPair() else { - logOrAssertionFailure("BWManager: Public key is missing") + Logger.bitWarden.fault("BWManager: Public key is missing") + assertionFailure("BWManager: Public key is missing") return } guard let messageData = BWRequest.makeHandshakeRequest(with: publicKey, - messageId: messageIdGenerator.generateMessageId()).data else { - logOrAssertionFailure("BWManager: Making the handshake message failed") + messageId: messageIdGenerator.generateMessageId()).data else { + Logger.bitWarden.fault("BWManager: Making the handshake message failed") + assertionFailure("BWManager: Making the handshake message failed") return } @@ -428,7 +441,8 @@ final class BWManager: BWManagement, ObservableObject { let encryptedCommand = encryptCommandData(commandData), let messageData = BWRequest.makeEncryptedCommandRequest(encryptedCommand: encryptedCommand, messageId: messageIdGenerator.generateMessageId()).data else { - logOrAssertionFailure("BWManager: Making the status message failed") + Logger.bitWarden.fault("BWManager: Making the status message failed") + assertionFailure("BWManager: Making the status message failed") PixelKit.fire(DebugEvent(GeneralPixel.bitwardenSendingOfMessageFailed)) status = .error(error: .sendingOfMessageFailed) return @@ -440,11 +454,12 @@ final class BWManager: BWManagement, ObservableObject { private func sendCredentialRetrieval(url: URL, messageId: MessageId) { let payload = BWRequest.EncryptedCommand.Payload(uri: url.absoluteString) guard let commandData = BWRequest.EncryptedCommand(command: .credentialRetrieval, - payload: payload).data, + payload: payload).data, let encryptedCommand = encryptCommandData(commandData), let messageData = BWRequest.makeEncryptedCommandRequest(encryptedCommand: encryptedCommand, messageId: messageId).data else { - logOrAssertionFailure("BWManager: Making the credential retrieval message failed") + Logger.bitWarden.fault("BWManager: Making the credential retrieval message failed") + assertionFailure("BWManager: Making the credential retrieval message failed") PixelKit.fire(DebugEvent(GeneralPixel.bitwardenSendingOfMessageFailed)) status = .error(error: .sendingOfMessageFailed) return @@ -455,16 +470,17 @@ final class BWManager: BWManagement, ObservableObject { private func sendCredentialCreation(_ credential: BWCredential, messageId: MessageId) { let payload = BWRequest.EncryptedCommand.Payload(uri: credential.url, - userId: credential.userId, - userName: credential.username, - password: credential.password, - name: credential.credentialName) + userId: credential.userId, + userName: credential.username, + password: credential.password, + name: credential.credentialName) guard let commandData = BWRequest.EncryptedCommand(command: .credentialCreate, - payload: payload).data, + payload: payload).data, let encryptedCommand = encryptCommandData(commandData), let messageData = BWRequest.makeEncryptedCommandRequest(encryptedCommand: encryptedCommand, messageId: messageId).data else { - logOrAssertionFailure("BWManager: Making the credential creation message failed") + Logger.bitWarden.fault("BWManager: Making the credential creation message failed") + assertionFailure("BWManager: Making the credential creation message failed") PixelKit.fire(DebugEvent(GeneralPixel.bitwardenSendingOfMessageFailed)) status = .error(error: .sendingOfMessageFailed) return @@ -475,17 +491,18 @@ final class BWManager: BWManagement, ObservableObject { private func sendCredentialUpdate(_ credential: BWCredential, messageId: MessageId) { let payload = BWRequest.EncryptedCommand.Payload(uri: credential.url, - userId: credential.userId, - userName: credential.username, - password: credential.password, - name: credential.credentialName, - credentialId: credential.credentialId) + userId: credential.userId, + userName: credential.username, + password: credential.password, + name: credential.credentialName, + credentialId: credential.credentialId) guard let commandData = BWRequest.EncryptedCommand(command: .credentialUpdate, - payload: payload).data, + payload: payload).data, let encryptedCommand = encryptCommandData(commandData), let messageData = BWRequest.makeEncryptedCommandRequest(encryptedCommand: encryptedCommand, messageId: messageId).data else { - logOrAssertionFailure("BWManager: Making the credential update message failed") + Logger.bitWarden.fault("BWManager: Making the credential update message failed") + assertionFailure("BWManager: Making the credential update message failed") PixelKit.fire(DebugEvent(GeneralPixel.bitwardenSendingOfMessageFailed)) status = .error(error: .sendingOfMessageFailed) return @@ -524,7 +541,7 @@ final class BWManager: BWManagement, ObservableObject { @Published private(set) var status: BWStatus = .disabled { didSet { - os_log("Status changed: %s", log: .bitwarden, type: .default, String(describing: status)) + Logger.bitWarden.log("Status changed: \(String(describing: self.status))") // If vault is locked, keep refreshing the latest status if case .connected(vault: let vault) = status, @@ -603,7 +620,8 @@ extension BWManager: BWCommunicatorDelegate { func bitwardenCommunicator(_ bitwardenCommunicator: BWCommunication, didReceiveMessageData messageData: Data) { guard let response = BWResponse(from: messageData) else { - logOrAssertionFailure("BWManager: Can't decode the message") + Logger.bitWarden.fault("BWManager: Can't decode the message") + assertionFailure("BWManager: Can't decode the message") return } @@ -613,7 +631,7 @@ extension BWManager: BWCommunicatorDelegate { } guard let messageId = response.messageId, messageIdGenerator.verify(messageId: messageId) else { - os_log("BWManager: Unkown message id. Ignoring the message", log: .bitwarden, type: .default) + Logger.bitWarden.log("BWManager: Unknown message id. Ignoring the message") return } @@ -635,6 +653,7 @@ extension BWManager: BWCommunicatorDelegate { return } - logOrAssertionFailure("BWManager: Unhandled message from Bitwarden") + Logger.bitWarden.fault("BWManager: Unhandled message from Bitwarden") + assertionFailure("BWManager: Unhandled message from Bitwarden") } } diff --git a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWRequest.swift b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWRequest.swift index 9ea359a3d2..5f80be87eb 100644 --- a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWRequest.swift +++ b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWRequest.swift @@ -17,6 +17,7 @@ // import Foundation +import os.log struct BWRequest: Codable { @@ -109,7 +110,8 @@ struct BWRequest: Codable { do { jsonData = try JSONEncoder().encode(self) } catch { - logOrAssertionFailure("BWRequest: Can't encode the message") + Logger.general.fault("BWRequest: Can't encode the message") + assertionFailure("BWRequest: Can't encode the message") return nil } return jsonData @@ -122,7 +124,8 @@ struct BWRequest: Codable { do { jsonData = try JSONEncoder().encode(self) } catch { - logOrAssertionFailure("BWRequest: Can't encode the message") + Logger.general.fault("BWRequest: Can't encode the message") + assertionFailure("BWRequest: Can't encode the message") return nil } return jsonData diff --git a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWResponse.swift b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWResponse.swift index df71e82c4f..e2b9778b19 100644 --- a/DuckDuckGo/PasswordManager/Bitwarden/Model/BWResponse.swift +++ b/DuckDuckGo/PasswordManager/Bitwarden/Model/BWResponse.swift @@ -17,6 +17,7 @@ // import Foundation +import os.log typealias Base64EncodedString = String typealias MessageId = String @@ -94,7 +95,8 @@ struct BWResponse: Codable { do { self = try JSONDecoder().decode(BWResponse.self, from: messageData) } catch { - logOrAssertionFailure("Decoding the message failed") + Logger.general.fault("Decoding the message failed") + assertionFailure("Decoding the message failed") return nil } } diff --git a/DuckDuckGo/PasswordManager/Bitwarden/Services/BWCommunicator.swift b/DuckDuckGo/PasswordManager/Bitwarden/Services/BWCommunicator.swift index b564e0f089..682981da10 100644 --- a/DuckDuckGo/PasswordManager/Bitwarden/Services/BWCommunicator.swift +++ b/DuckDuckGo/PasswordManager/Bitwarden/Services/BWCommunicator.swift @@ -18,6 +18,7 @@ import Common import Foundation +import os.log protocol BWCommunicatorDelegate: AnyObject { @@ -74,7 +75,7 @@ final class BWCommunicator: BWCommunication { process.terminationHandler = processDidTerminate(_:) try process.run() - os_log("BWCommunicator: Proxy process running", log: .bitwarden, type: .default) + Logger.bitWarden.log("BWCommunicator: Proxy process running") self.process = BitwardenProcess(process: process, readingHandle: outHandle, writingHandle: inputHandle) } @@ -85,7 +86,7 @@ final class BWCommunicator: BWCommunication { } private func processDidTerminate(_ process: Process) { - os_log("BWCommunicator: Proxy process terminated", log: .bitwarden, type: .default) + Logger.bitWarden.log("BWCommunicator: Proxy process terminated") if let runningProcess = self.process?.process { if process != runningProcess { diff --git a/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift b/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift index ca7145d88d..c09b08cfab 100644 --- a/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift +++ b/DuckDuckGo/PasswordManager/PasswordManagerCoordinator.swift @@ -21,6 +21,7 @@ import BrowserServicesKit import Combine import Common import PixelKit +import os.log protocol PasswordManagerCoordinating: BrowserServicesKit.PasswordManager { @@ -207,8 +208,8 @@ final class PasswordManagerCoordinator: PasswordManagerCoordinating { completion: @escaping (Error?) -> Void) { guard case let .connected(vault) = bitwardenManagement.status, let bitwardenCredential = BWCredential(from: credentials, vault: vault) else { + Logger.general.fault("Failed to store credentials: Bitwarden is not connected or bad credential") assertionFailure("Bitwarden is not connected or bad credential") - os_log("Failed to store credentials: Bitwarden is not connected or bad credential", type: .error) return } diff --git a/DuckDuckGo/Permissions/Model/PermissionManager.swift b/DuckDuckGo/Permissions/Model/PermissionManager.swift index d708b30949..af615165eb 100644 --- a/DuckDuckGo/Permissions/Model/PermissionManager.swift +++ b/DuckDuckGo/Permissions/Model/PermissionManager.swift @@ -19,6 +19,7 @@ import Foundation import Combine import Common +import os.log protocol PermissionManagerProtocol: AnyObject { @@ -55,7 +56,7 @@ final class PermissionManager: PermissionManagerProtocol { self.set(entity.permission, forDomain: entity.domain.droppingWwwPrefix(), permissionType: entity.type) } } catch { - os_log("PermissionStore: Failed to load permissions", type: .error) + Logger.general.error("PermissionStore: Failed to load permissions") } } @@ -93,7 +94,7 @@ final class PermissionManager: PermissionManagerProtocol { do { storedPermission = try store.add(domain: domain, permissionType: permissionType, decision: decision) } catch { - os_log("PermissionStore: Failed to store permission", type: .error) + Logger.general.error("PermissionStore: Failed to store permission") return } } diff --git a/DuckDuckGo/PinnedTabs/Model/PinnedTabsManager.swift b/DuckDuckGo/PinnedTabs/Model/PinnedTabsManager.swift index e8d455962d..0f0d15785b 100644 --- a/DuckDuckGo/PinnedTabs/Model/PinnedTabsManager.swift +++ b/DuckDuckGo/PinnedTabs/Model/PinnedTabsManager.swift @@ -20,6 +20,7 @@ import AppKit import Combine import Common import Foundation +import os.log final class PinnedTabsManager { @@ -38,11 +39,11 @@ final class PinnedTabsManager { func unpinTab(at index: Int, published: Bool = false) -> Tab? { guard let tab = tabCollection.tabs[safe: index] else { - os_log("PinnedTabsManager: unable to unpin a tab") + Logger.general.debug("PinnedTabsManager: unable to unpin a tab") return nil } guard tabCollection.removeTab(at: index, published: published) else { - os_log("PinnedTabsManager: unable to unpin a tab") + Logger.general.debug("PinnedTabsManager: unable to unpin a tab") return nil } didUnpinTabSubject.send(index) @@ -55,7 +56,7 @@ final class PinnedTabsManager { func tabViewModel(at index: Int) -> TabViewModel? { guard index >= 0, tabCollection.tabs.count > index else { - os_log("PinnedTabsManager: Index out of bounds", type: .error) + Logger.general.error("PinnedTabsManager: Index out of bounds") return nil } diff --git a/DuckDuckGo/PinnedTabs/Model/PinnedTabsViewModel.swift b/DuckDuckGo/PinnedTabs/Model/PinnedTabsViewModel.swift index 5c77dfdf96..56ca4a9168 100644 --- a/DuckDuckGo/PinnedTabs/Model/PinnedTabsViewModel.swift +++ b/DuckDuckGo/PinnedTabs/Model/PinnedTabsViewModel.swift @@ -19,6 +19,7 @@ import Foundation import Combine import Common +import os.log final class PinnedTabsViewModel: ObservableObject { @@ -164,7 +165,7 @@ extension PinnedTabsViewModel { func unpin(_ tab: Tab) { guard let index = items.firstIndex(of: tab) else { - os_log("PinnedTabsViewModel: Failed to get index of a tab", type: .error) + Logger.bitWarden.error("PinnedTabsViewModel: Failed to get index of a tab") return } contextMenuActionSubject.send(.unpin(index)) @@ -172,7 +173,7 @@ extension PinnedTabsViewModel { func duplicate(_ tab: Tab) { guard let index = items.firstIndex(of: tab) else { - os_log("PinnedTabsViewModel: Failed to get index of a tab", type: .error) + Logger.bitWarden.error("PinnedTabsViewModel: Failed to get index of a tab") return } contextMenuActionSubject.send(.duplicate(index)) @@ -180,7 +181,7 @@ extension PinnedTabsViewModel { func close(_ tab: Tab) { guard let index = items.firstIndex(of: tab) else { - os_log("PinnedTabsViewModel: Failed to get index of a tab", type: .error) + Logger.bitWarden.error("PinnedTabsViewModel: Failed to get index of a tab") return } contextMenuActionSubject.send(.close(index)) diff --git a/DuckDuckGo/Preferences/Model/AppearancePreferences.swift b/DuckDuckGo/Preferences/Model/AppearancePreferences.swift index aa6ffd512a..0513524939 100644 --- a/DuckDuckGo/Preferences/Model/AppearancePreferences.swift +++ b/DuckDuckGo/Preferences/Model/AppearancePreferences.swift @@ -21,6 +21,7 @@ import AppKit import Bookmarks import Common import PixelKit +import os.log protocol AppearancePreferencesPersistor { var showFullURL: Bool { get set } @@ -249,7 +250,7 @@ final class AppearancePreferences: ObservableObject { guard let syncService = (NSApp.delegate as? AppDelegate)?.syncService else { return } - os_log(.debug, log: OSLog.sync, "Requesting sync if enabled") + Logger.sync.debug("Requesting sync if enabled") syncService.scheduler.notifyDataChanged() } } diff --git a/DuckDuckGo/Preferences/Model/DownloadsPreferences.swift b/DuckDuckGo/Preferences/Model/DownloadsPreferences.swift index e8e435c447..81cc59181d 100644 --- a/DuckDuckGo/Preferences/Model/DownloadsPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DownloadsPreferences.swift @@ -116,7 +116,7 @@ final class DownloadsPreferences: ObservableObject { if isStale { setSelectedDownloadLocation(url) // update bookmark data and selectedDownloadLocationController } else { - selectedDownloadLocationController = SecurityScopedFileURLController(url: url, logger: OSLog.downloads) + selectedDownloadLocationController = SecurityScopedFileURLController(url: url) } return url } @@ -135,7 +135,7 @@ final class DownloadsPreferences: ObservableObject { } private func setSelectedDownloadLocation(_ url: URL?) { - selectedDownloadLocationController = url.map { SecurityScopedFileURLController(url: $0, logger: OSLog.downloads) } + selectedDownloadLocationController = url.map { SecurityScopedFileURLController(url: $0) } let locationString: String? #if APPSTORE locationString = (try? url?.bookmarkData(options: .withSecurityScope).base64EncodedString()) ?? url?.absoluteString diff --git a/DuckDuckGo/Preferences/Model/SyncPreferences.swift b/DuckDuckGo/Preferences/Model/SyncPreferences.swift index 99a5416edd..fb42705f35 100644 --- a/DuckDuckGo/Preferences/Model/SyncPreferences.swift +++ b/DuckDuckGo/Preferences/Model/SyncPreferences.swift @@ -26,6 +26,7 @@ import SwiftUI import PDFKit import Navigation import PixelKit +import os.log extension SyncDevice { init(_ account: SyncAccount) { @@ -371,7 +372,7 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { func refreshDevices() { guard !isScreenLocked else { - os_log(.debug, log: .sync, "Screen is locked, skipping devices refresh") + Logger.sync.debug("Screen is locked, skipping devices refresh") return } guard syncService.account != nil else { @@ -383,7 +384,7 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { let registeredDevices = try await syncService.fetchDevices() mapDevices(registeredDevices) } catch { - os_log(.error, log: .sync, "Failed to refresh devices: \(error)") + Logger.sync.debug("Failed to refresh devices: \(error)") } } } diff --git a/DuckDuckGo/Preferences/View/PreferencesRootView.swift b/DuckDuckGo/Preferences/View/PreferencesRootView.swift index ab2a539db6..b559d4a6da 100644 --- a/DuckDuckGo/Preferences/View/PreferencesRootView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesRootView.swift @@ -104,7 +104,8 @@ enum Preferences { case .vpn: VPNView(model: VPNPreferencesModel(), status: model.vpnProtectionStatus()) case .subscription: - SubscriptionUI.PreferencesSubscriptionView(model: subscriptionModel!) + SubscriptionUI.PreferencesSubscriptionView(model: subscriptionModel!, + subscriptionFeatureAvailability: DefaultSubscriptionFeatureAvailability()) case .autofill: AutofillView(model: AutofillPreferencesModel()) case .accessibility: @@ -136,6 +137,8 @@ enum Preferences { case .openVPN: PixelKit.fire(PrivacyProPixel.privacyProVPNSettings) NotificationCenter.default.post(name: .ToggleNetworkProtectionInMainWindow, object: self, userInfo: nil) + case .openFeedback: + NotificationCenter.default.post(name: .OpenUnifiedFeedbackForm, object: self, userInfo: nil) case .openDB: PixelKit.fire(PrivacyProPixel.privacyProPersonalInformationRemovalSettings) WindowControllersManager.shared.showTab(with: .dataBrokerProtection) diff --git a/DuckDuckGo/Preferences/View/PreferencesSyncView.swift b/DuckDuckGo/Preferences/View/PreferencesSyncView.swift index b78c746ab3..0047801645 100644 --- a/DuckDuckGo/Preferences/View/PreferencesSyncView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesSyncView.swift @@ -21,6 +21,7 @@ import Common import SyncUI import SwiftUIExtensions import BrowserServicesKit +import os.log struct SyncView: View { @@ -46,7 +47,7 @@ struct SyncView: View { guard let syncService = (NSApp.delegate as? AppDelegate)?.syncService else { return } - os_log(.debug, log: OSLog.sync, "Requesting sync if enabled") + Logger.sync.debug("Requesting sync if enabled") syncService.scheduler.notifyDataChanged() } } diff --git a/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift b/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift index 93259608d9..2b124ba79a 100644 --- a/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift +++ b/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift @@ -23,6 +23,7 @@ import BrowserServicesKit import PrivacyDashboard import Common import PixelKit +import os.log protocol PrivacyDashboardViewControllerSizeDelegate: AnyObject { @@ -265,7 +266,7 @@ extension PrivacyDashboardViewController: PrivacyDashboardControllerDelegate { let report = try await makeBrokenSiteReport(category: category, description: description, source: privacyDashboardController.source) try brokenSiteReporter.report(report, reportMode: .regular) } catch { - os_log("Failed to generate or send the broken site report: \(error.localizedDescription)", type: .error) + Logger.general.error("Failed to generate or send the broken site report: \(error.localizedDescription)") } } } @@ -283,7 +284,7 @@ extension PrivacyDashboardViewController: PrivacyDashboardControllerDelegate { let report = try await makeBrokenSiteReport(source: source) try toggleProtectionsOffReporter.report(report, reportMode: .toggle) } catch { - os_log("Failed to generate or send the broken site report: %@", type: .error, error.localizedDescription) + Logger.general.error("Failed to generate or send the broken site report: \(error.localizedDescription)") } } } diff --git a/DuckDuckGo/RemoteMessaging/RemoteMessagingDebugMenu.swift b/DuckDuckGo/RemoteMessaging/RemoteMessagingDebugMenu.swift index 1543a87946..d3a6bf5c36 100644 --- a/DuckDuckGo/RemoteMessaging/RemoteMessagingDebugMenu.swift +++ b/DuckDuckGo/RemoteMessaging/RemoteMessagingDebugMenu.swift @@ -19,6 +19,7 @@ import AppKit import RemoteMessaging import AppKitExtensions +import BrowserServicesKit final class RemoteMessagingDebugMenu: NSMenu { diff --git a/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift b/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift index c3a0b0fe7d..3a8e2ee45a 100644 --- a/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift +++ b/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift @@ -25,6 +25,7 @@ import Foundation import SecureStorage import SwiftUI import PixelKit +import os.log protocol PasswordManagementDelegate: AnyObject { @@ -1012,7 +1013,7 @@ final class PasswordManagementViewController: NSViewController { guard let syncService = NSApp.delegateTyped.syncService else { return } - os_log(.debug, log: OSLog.sync, "Requesting sync if enabled") + Logger.sync.debug("Requesting sync if enabled") syncService.scheduler.requestSyncImmediately() } diff --git a/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift b/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift index cddee05928..ae751fb401 100644 --- a/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift +++ b/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift @@ -21,6 +21,7 @@ import BrowserServicesKit import Combine import Common import PixelKit +import os.log protocol SaveCredentialsDelegate: AnyObject { @@ -228,27 +229,27 @@ final class SaveCredentialsViewController: NSViewController { do { if passwordManagerCoordinator.isEnabled { guard !passwordManagerCoordinator.isLocked else { - os_log("Failed to store credentials: Password manager is locked") + Logger.sync.error("Failed to store credentials: Password manager is locked") return } passwordManagerCoordinator.storeWebsiteCredentials(credentials) { error in if let error = error { - os_log("Failed to store credentials: %s", type: .error, error.localizedDescription) + Logger.sync.error("Failed to store credentials: \(error.localizedDescription)") } } } else { let vault = try AutofillSecureVaultFactory.makeVault(reporter: SecureVaultReporter.shared) _ = try vault.storeWebsiteCredentials(credentials) NSApp.delegateTyped.syncService?.scheduler.notifyDataChanged() - os_log(.debug, log: OSLog.sync, "Requesting sync if enabled") + Logger.sync.debug("Requesting sync if enabled") if existingCredentials?.account.id == nil, !LocalPinningManager.shared.isPinned(.autofill), let count = try? vault.accountsCount(), count == 1 { shouldFirePinPromptNotification = true } } } catch { - os_log("%s:%s: failed to store credentials %s", type: .error, className, #function, error.localizedDescription) + Logger.sync.error("failed to store credentials \(error.localizedDescription)") PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } @@ -317,7 +318,7 @@ final class SaveCredentialsViewController: NSViewController { } guard let window = view.window else { - os_log("%s: Window is nil", type: .error, className) + Logger.sync.error("Window is nil") notifyDelegate() return } @@ -343,7 +344,7 @@ final class SaveCredentialsViewController: NSViewController { do { _ = try AutofillNeverPromptWebsitesManager.shared.saveNeverPromptWebsite(domainLabel.stringValue) } catch { - os_log("%: failed to save never prompt for website %s", type: .error, #function, error.localizedDescription) + Logger.sync.error("failed to save never prompt for website \(error.localizedDescription)") } PixelKit.fire(GeneralPixel.autofillLoginsSaveLoginModalExcludeSiteConfirmed) @@ -405,7 +406,7 @@ final class SaveCredentialsViewController: NSViewController { if passwordManagerCoordinator.isEnabled { guard !passwordManagerCoordinator.isLocked else { - os_log("Failed to access credentials: Password manager is locked") + Logger.sync.debug("Failed to access credentials: Password manager is locked") return existingCredentials } diff --git a/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift b/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift index 61ce88b628..f10565a27a 100644 --- a/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift +++ b/DuckDuckGo/SecureVault/View/SaveIdentityViewController.swift @@ -21,6 +21,7 @@ import BrowserServicesKit import Combine import Common import PixelKit +import os.log protocol SaveIdentityDelegate: AnyObject { @@ -74,7 +75,7 @@ final class SaveIdentityViewController: NSViewController { try AutofillSecureVaultFactory.makeVault(reporter: SecureVaultReporter.shared).storeIdentity(identity) PixelKit.fire(GeneralPixel.autofillItemSaved(kind: .identity)) } catch { - os_log("%s:%s: failed to store identity %s", type: .error, className, #function, error.localizedDescription) + Logger.general.error("Failed to store identity \(error.localizedDescription)") PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } diff --git a/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift b/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift index 31c9d6a3b3..239a7f441e 100644 --- a/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift +++ b/DuckDuckGo/SecureVault/View/SavePaymentMethodViewController.swift @@ -21,6 +21,7 @@ import BrowserServicesKit import Combine import Common import PixelKit +import os.log protocol SavePaymentMethodDelegate: AnyObject { @@ -94,7 +95,7 @@ final class SavePaymentMethodViewController: NSViewController { do { try AutofillSecureVaultFactory.makeVault(reporter: SecureVaultReporter.shared).storeCreditCard(paymentMethod) } catch { - os_log("%s:%s: failed to store payment method %s", type: .error, className, #function, error.localizedDescription) + Logger.secureVault.error("Failed to store payment method \(error.localizedDescription)") PixelKit.fire(DebugEvent(GeneralPixel.secureVaultError(error: error))) } } diff --git a/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift b/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift index cb16b2bcef..f97115cb49 100644 --- a/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift +++ b/DuckDuckGo/SmarterEncryption/PrivacyFeatures.swift @@ -21,6 +21,7 @@ import Common import Foundation import Persistence import PixelKit +import os.log protocol PrivacyFeaturesProtocol { var contentBlocking: AnyContentBlocking { get } @@ -78,13 +79,13 @@ final class AppPrivacyFeatures: PrivacyFeaturesProtocol { convenience init(contentBlocking: AnyContentBlocking, database: CoreDataDatabase) { let bloomFilterDataURL = URL.sandboxApplicationSupportURL.appendingPathComponent("HttpsBloomFilter.bin") - let httpsUpgradeStore = AppHTTPSUpgradeStore(database: database, bloomFilterDataURL: bloomFilterDataURL, embeddedResources: Self.embeddedBloomFilterResources, errorEvents: Self.httpsUpgradeDebugEvents, log: .httpsUpgrade) + let httpsUpgradeStore = AppHTTPSUpgradeStore(database: database, bloomFilterDataURL: bloomFilterDataURL, embeddedResources: Self.embeddedBloomFilterResources, errorEvents: Self.httpsUpgradeDebugEvents, logger: Logger.httpsUpgrade) self.init(contentBlocking: contentBlocking, httpsUpgradeStore: httpsUpgradeStore) } init(contentBlocking: AnyContentBlocking, httpsUpgradeStore: HTTPSUpgradeStore) { self.contentBlocking = contentBlocking - self.httpsUpgrade = HTTPSUpgrade(store: httpsUpgradeStore, privacyManager: contentBlocking.privacyConfigurationManager, log: .httpsUpgrade) + self.httpsUpgrade = HTTPSUpgrade(store: httpsUpgradeStore, privacyManager: contentBlocking.privacyConfigurationManager, logger: Logger.httpsUpgrade) } } diff --git a/DuckDuckGo/StateRestoration/AppStateRestorationManager.swift b/DuckDuckGo/StateRestoration/AppStateRestorationManager.swift index 6644e0ae71..3b6c91347a 100644 --- a/DuckDuckGo/StateRestoration/AppStateRestorationManager.swift +++ b/DuckDuckGo/StateRestoration/AppStateRestorationManager.swift @@ -20,6 +20,7 @@ import Foundation import Combine import Common import PixelKit +import os.log @MainActor final class AppStateRestorationManager: NSObject { @@ -72,7 +73,7 @@ final class AppStateRestorationManager: NSObject { } catch CocoaError.fileReadNoSuchFile { // ignore } catch { - os_log("App state could not be decoded: %s", "\(error)") + Logger.general.error("App state could not be decoded: \(error.localizedDescription)") PixelKit.fire(DebugEvent(GeneralPixel.appStateRestorationFailed, error: error), withAdditionalParameters: ["interactive": String(interactive)]) } @@ -141,7 +142,7 @@ final class AppStateRestorationManager: NSObject { } catch CocoaError.fileReadNoSuchFile { // ignore } catch { - os_log("Pinned tabs state could not be decoded: %s", "\(error)") + Logger.general.error("Pinned tabs state could not be decoded: \(error)") PixelKit.fire(DebugEvent(GeneralPixel.appStateRestorationFailed, error: error)) } } diff --git a/DuckDuckGo/StateRestoration/WindowManager+StateRestoration.swift b/DuckDuckGo/StateRestoration/WindowManager+StateRestoration.swift index aaa38da89c..c0f97116f0 100644 --- a/DuckDuckGo/StateRestoration/WindowManager+StateRestoration.swift +++ b/DuckDuckGo/StateRestoration/WindowManager+StateRestoration.swift @@ -18,6 +18,7 @@ import Cocoa import Common +import os.log extension WindowsManager { @@ -90,8 +91,7 @@ final class WindowManagerStateRestoration: NSObject, NSSecureCoding { init?(coder: NSCoder) { guard let restorationArray = coder.decodeObject(of: [NSArray.self, WindowRestorationItem.self], forKey: NSSecureCodingKeys.controllers) as? [WindowRestorationItem] else { - os_log("WindowsManager:initWithCoder: could not decode Restoration Array: %s", type: .error, - String(describing: coder.error)) + Logger.general.error("WindowsManager:initWithCoder: could not decode Restoration Array: \(String(describing: coder.error), privacy: .public)") return nil } self.windows = restorationArray @@ -159,7 +159,7 @@ final class WindowRestorationItem: NSObject, NSSecureCoding { required init?(coder: NSCoder) { guard let model = coder.decodeObject(of: TabCollectionViewModel.self, forKey: NSSecureCodingKeys.model) else { - os_log("WindowRestoration:initWithCoder: could not decode model object: %s", type: .error, String(describing: coder.error)) + Logger.general.error("WindowRestoration:initWithCoder: could not decode model object: \(String(describing: coder.error))") return nil } self.model = model diff --git a/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift b/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift index af022508e6..368f26a504 100644 --- a/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift +++ b/DuckDuckGo/Statistics/ATB/StatisticsLoader.swift @@ -21,6 +21,7 @@ import Foundation import BrowserServicesKit import Networking import PixelKit +import os.log final class StatisticsLoader { @@ -87,19 +88,19 @@ final class StatisticsLoader { guard !isAppRetentionRequestInProgress else { return } isAppRetentionRequestInProgress = true - os_log("Requesting install statistics", log: .atb, type: .debug) + Logger.atb.debug("Requesting install statistics") let configuration = APIRequest.Configuration(url: URL.initialAtb) let request = APIRequest(configuration: configuration, urlSession: URLSession.session(useMainThreadCallbackQueue: true)) request.fetch { response, error in self.isAppRetentionRequestInProgress = false if let error = error { - os_log("Initial atb request failed with error %s", type: .error, error.localizedDescription) + Logger.atb.error("Initial atb request failed with error \(error.localizedDescription)") completion() return } - os_log("Install statistics request succeeded", log: .atb, type: .debug) + Logger.atb.debug("Install statistics request succeeded") if let data = response?.data, let atb = try? self.parser.convert(fromJsonData: data) { self.requestExti(atb: atb, completion: completion) @@ -116,7 +117,7 @@ final class StatisticsLoader { guard !isAppRetentionRequestInProgress else { return } self.isAppRetentionRequestInProgress = true - os_log("Requesting exti", log: .atb, type: .debug) + Logger.atb.debug("Requesting exti") let installAtb = atb.version + (statisticsStore.variant ?? "") @@ -125,12 +126,12 @@ final class StatisticsLoader { request.fetch { _, error in self.isAppRetentionRequestInProgress = false if let error = error { - os_log("Exti request failed with error %s", type: .error, error.localizedDescription) + Logger.atb.error("Extit request failed with error \(error.localizedDescription)") completion() return } - os_log("Exti request succeeded", log: .atb, type: .debug) + Logger.atb.debug("Exti request succeeded") assert(self.statisticsStore.atb == nil) assert(self.statisticsStore.installDate == nil) @@ -151,19 +152,19 @@ final class StatisticsLoader { return } - os_log("Requesting search retention ATB", log: .atb, type: .debug) + Logger.atb.debug("Requesting search retention ATB") let url = URL.searchAtb(atbWithVariant: atbWithVariant, setAtb: searchRetentionAtb, isSignedIntoEmailProtection: emailManager.isSignedIn) let configuration = APIRequest.Configuration(url: url) let request = APIRequest(configuration: configuration, urlSession: URLSession.session(useMainThreadCallbackQueue: true)) request.fetch { (response, error) in if let error = error { - os_log("Search atb request failed with error %s", type: .error, error.localizedDescription) + Logger.atb.error("Search atb request failed with error \(error.localizedDescription)") completion() return } - os_log("Search retention ATB request succeeded", log: .atb, type: .debug) + Logger.atb.debug("Search retention ATB request succeeded") if let data = response?.data, let atb = try? self.parser.convert(fromJsonData: data) { self.statisticsStore.searchRetentionAtb = atb.version @@ -186,7 +187,7 @@ final class StatisticsLoader { return } - os_log("Requesting app retention ATB", log: .atb, type: .debug) + Logger.atb.debug("Requesting app retention ATB") isAppRetentionRequestInProgress = true @@ -197,12 +198,12 @@ final class StatisticsLoader { self.isAppRetentionRequestInProgress = false if let error = error { - os_log("App atb request failed with error %s", type: .error, error.localizedDescription) + Logger.atb.error("App atb request failed with error \(error.localizedDescription)") completion() return } - os_log("App retention ATB request succeeded", log: .atb, type: .debug) + Logger.atb.debug("App retention ATB request succeeded") if let data = response?.data, let atb = try? self.parser.convert(fromJsonData: data) { self.statisticsStore.appRetentionAtb = atb.version diff --git a/DuckDuckGo/Statistics/ATB/VariantManager.swift b/DuckDuckGo/Statistics/ATB/VariantManager.swift index b63b38c301..060d05d3fc 100644 --- a/DuckDuckGo/Statistics/ATB/VariantManager.swift +++ b/DuckDuckGo/Statistics/ATB/VariantManager.swift @@ -19,6 +19,7 @@ import BrowserServicesKit import Common import Foundation +import os.log struct Variant: BrowserServicesKit.Variant { @@ -78,17 +79,17 @@ final class DefaultVariantManager: VariantManager { func assignVariantIfNeeded(_ newInstallCompletion: (VariantManager) -> Void) { guard !storage.hasInstallStatistics else { - os_log("ATB: No new variant needed for existing user", type: .debug) + Logger.atb.debug("ATB: No new variant needed for existing user") return } if let variant = currentVariant { - os_log("ATB: Already assigned variant: %s", type: .debug, String(describing: variant)) + Logger.atb.debug("ATB: Already assigned variant: \(String(describing: variant))") return } guard let variant = selectVariant() else { - os_log("ATB: Failed to assign variant", type: .debug) + Logger.atb.debug("ATB: Failed to assign variant") // it's possible this failed because there are none to assign, we should still let new install logic execute _ = newInstallCompletion(self) diff --git a/DuckDuckGo/Statistics/GeneralPixel.swift b/DuckDuckGo/Statistics/GeneralPixel.swift index 0fd4143a3a..78b911cb8b 100644 --- a/DuckDuckGo/Statistics/GeneralPixel.swift +++ b/DuckDuckGo/Statistics/GeneralPixel.swift @@ -140,6 +140,15 @@ enum GeneralPixel: PixelKitEventV2 { // VPN case vpnBreakageReport(category: String, description: String, metadata: String) + // Unified Feedback + case pproFeedbackFeatureRequest(description: String, source: String) + case pproFeedbackGeneralFeedback(description: String, source: String) + case pproFeedbackReportIssue(source: String, category: String, subcategory: String, description: String, metadata: String) + + case pproFeedbackFormShow + case pproFeedbackSubmitScreenShow(source: String, reportType: String, category: String, subcategory: String) + case pproFeedbackSubmitScreenFAQClick(source: String, reportType: String, category: String, subcategory: String) + case networkProtectionEnabledOnSearch case networkProtectionGeoswitchingOpened case networkProtectionGeoswitchingSetNearest @@ -625,6 +634,19 @@ enum GeneralPixel: PixelKitEventV2 { case .vpnBreakageReport: return "m_mac_vpn_breakage_report" + case .pproFeedbackFeatureRequest: + return "m_mac_ppro_feedback_feature-request" + case .pproFeedbackGeneralFeedback: + return "m_mac_ppro_feedback_general-feedback" + case .pproFeedbackReportIssue: + return "m_mac_ppro_feedback_report-issue" + case .pproFeedbackFormShow: + return "m_mac_ppro_feedback_general-screen_show" + case .pproFeedbackSubmitScreenShow: + return "m_mac_ppro_feedback_submit-screen_show" + case .pproFeedbackSubmitScreenFAQClick: + return "m_mac_ppro_feedback_submit-screen-faq_click" + case .networkProtectionEnabledOnSearch: return "m_mac_netp_ev_enabled_on_search" @@ -1078,6 +1100,39 @@ enum GeneralPixel: PixelKitEventV2 { PixelKit.Parameters.vpnBreakageMetadata: metadata ] + case .pproFeedbackFeatureRequest(let description, let source): + return [ + PixelKit.Parameters.pproIssueDescription: description, + PixelKit.Parameters.pproIssueSource: source, + ] + case .pproFeedbackGeneralFeedback(let description, let source): + return [ + PixelKit.Parameters.pproIssueDescription: description, + PixelKit.Parameters.pproIssueSource: source, + ] + case .pproFeedbackReportIssue(let source, let category, let subcategory, let description, let metadata): + return [ + PixelKit.Parameters.pproIssueSource: source, + PixelKit.Parameters.pproIssueCategory: category, + PixelKit.Parameters.pproIssueSubcategory: subcategory, + PixelKit.Parameters.pproIssueDescription: description, + PixelKit.Parameters.pproIssueMetadata: metadata, + ] + case .pproFeedbackSubmitScreenShow(let source, let reportType, let category, let subcategory): + return [ + PixelKit.Parameters.pproIssueSource: source, + PixelKit.Parameters.pproIssueReportType: reportType, + PixelKit.Parameters.pproIssueCategory: category, + PixelKit.Parameters.pproIssueSubcategory: subcategory, + ] + case .pproFeedbackSubmitScreenFAQClick(let source, let reportType, let category, let subcategory): + return [ + PixelKit.Parameters.pproIssueSource: source, + PixelKit.Parameters.pproIssueReportType: reportType, + PixelKit.Parameters.pproIssueCategory: category, + PixelKit.Parameters.pproIssueSubcategory: subcategory, + ] + case .onboardingCohortAssigned(let cohort): return [PixelKit.Parameters.experimentCohort: cohort] case .onboardingHomeButtonEnabled(let cohort): diff --git a/DuckDuckGo/Statistics/PrivacyProPixel.swift b/DuckDuckGo/Statistics/PrivacyProPixel.swift index 5609e24ed1..c4a88a6b52 100644 --- a/DuckDuckGo/Statistics/PrivacyProPixel.swift +++ b/DuckDuckGo/Statistics/PrivacyProPixel.swift @@ -17,6 +17,7 @@ // import Foundation +import Subscription import PixelKit // swiftlint:disable private_over_fileprivate @@ -119,3 +120,29 @@ enum PrivacyProPixel: PixelKitEventV2 { return nil } } + +enum PrivacyProErrorPixel: PixelKitEventV2 { + + case privacyProKeychainAccessError(accessType: AccountKeychainAccessType, accessError: AccountKeychainAccessError) + + var name: String { + switch self { + case .privacyProKeychainAccessError: return "m_mac_privacy-pro_keychain_access_error" + } + } + + var parameters: [String: String]? { + switch self { + case .privacyProKeychainAccessError(let accessType, let accessError): + return [ + "type": accessType.rawValue, + "error": accessError.errorDescription + ] + } + } + + var error: (any Error)? { + return nil + } + +} diff --git a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift index e9562194eb..c92a66516d 100644 --- a/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift +++ b/DuckDuckGo/Subscription/SubscriptionManager+StandardConfiguration.swift @@ -19,6 +19,7 @@ import Foundation import Subscription import Common +import PixelKit extension DefaultSubscriptionManager { @@ -53,5 +54,15 @@ extension DefaultSubscriptionManager { authEndpointService: authEndpointService, subscriptionEnvironment: subscriptionEnvironment) } + + accountManager.delegate = self + } +} + +extension DefaultSubscriptionManager: AccountManagerKeychainAccessDelegate { + + public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { + PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: accessType, accessError: error), + frequency: .dailyAndCount) } } diff --git a/DuckDuckGo/Subscription/VPNSettings+Environment.swift b/DuckDuckGo/Subscription/VPNSettings+Environment.swift index 4e046637ed..cffa7251a6 100644 --- a/DuckDuckGo/Subscription/VPNSettings+Environment.swift +++ b/DuckDuckGo/Subscription/VPNSettings+Environment.swift @@ -26,8 +26,10 @@ public extension VPNSettings { func alignTo(subscriptionEnvironment: SubscriptionEnvironment) { switch subscriptionEnvironment.serviceEnvironment { case .production: - self.selectedEnvironment = .production + // Do nothing for a production subscription, as it can be used for both VPN environments. + break case .staging: + // If using a staging subscription, force the staging VPN environment as it is not compatible with anything else. self.selectedEnvironment = .staging } } diff --git a/DuckDuckGo/Suggestions/Model/SuggestionContainer.swift b/DuckDuckGo/Suggestions/Model/SuggestionContainer.swift index 94f4a59e46..1c9b8927c3 100644 --- a/DuckDuckGo/Suggestions/Model/SuggestionContainer.swift +++ b/DuckDuckGo/Suggestions/Model/SuggestionContainer.swift @@ -21,6 +21,7 @@ import Suggestions import Common import History import PixelKit +import os.log final class SuggestionContainer { @@ -63,18 +64,14 @@ final class SuggestionContainer { guard self?.latestQuery == query else { return } guard let result = result else { self?.result = nil - os_log("Suggestions: Failed to get suggestions - %s", - type: .error, - "\(String(describing: error))") + Logger.general.error("Suggestions: Failed to get suggestions - \(String(describing: error))") PixelKit.fire(DebugEvent(GeneralPixel.suggestionsFetchFailed, error: error)) return } if let error = error { // Fetching remote suggestions failed but local can be presented - os_log("Suggestions: Error when getting suggestions - %s", - type: .error, - "\(String(describing: error))") + Logger.general.error("Suggestions: Error when getting suggestions - \(error.localizedDescription)") } self?.result = result diff --git a/DuckDuckGo/Suggestions/View/SuggestionTableCellView.swift b/DuckDuckGo/Suggestions/View/SuggestionTableCellView.swift index b28e21545d..2f7a2823f0 100644 --- a/DuckDuckGo/Suggestions/View/SuggestionTableCellView.swift +++ b/DuckDuckGo/Suggestions/View/SuggestionTableCellView.swift @@ -18,6 +18,7 @@ import Cocoa import Common +import os.log final class SuggestionTableCellView: NSTableCellView { @@ -57,7 +58,7 @@ final class SuggestionTableCellView: NSTableCellView { private func updateTextField() { guard let attributedString = attributedString else { - os_log("SuggestionTableCellView: Attributed strings are nil", type: .error) + Logger.general.error("SuggestionTableCellView: Attributed strings are nil") return } if isSelected { diff --git a/DuckDuckGo/Suggestions/ViewModel/SuggestionContainerViewModel.swift b/DuckDuckGo/Suggestions/ViewModel/SuggestionContainerViewModel.swift index 8ad5149a9b..2a90c1747d 100644 --- a/DuckDuckGo/Suggestions/ViewModel/SuggestionContainerViewModel.swift +++ b/DuckDuckGo/Suggestions/ViewModel/SuggestionContainerViewModel.swift @@ -20,6 +20,7 @@ import Foundation import Combine import Common import BrowserServicesKit +import os.log final class SuggestionContainerViewModel { @@ -110,7 +111,7 @@ final class SuggestionContainerViewModel { let items = suggestionContainer.result?.all ?? [] guard index < items.count else { - os_log("SuggestionContainerViewModel: Absolute index is out of bounds", type: .error) + Logger.general.error("SuggestionContainerViewModel: Absolute index is out of bounds") return nil } @@ -119,7 +120,7 @@ final class SuggestionContainerViewModel { func select(at index: Int) { guard index >= 0, index < numberOfSuggestions else { - os_log("SuggestionContainerViewModel: Index out of bounds", type: .error) + Logger.general.error("SuggestionContainerViewModel: Index out of bounds") selectionIndex = nil return } diff --git a/DuckDuckGo/Sync/SyncBookmarksAdapter.swift b/DuckDuckGo/Sync/SyncBookmarksAdapter.swift index 53fc8a10fb..f23ccc4357 100644 --- a/DuckDuckGo/Sync/SyncBookmarksAdapter.swift +++ b/DuckDuckGo/Sync/SyncBookmarksAdapter.swift @@ -23,6 +23,7 @@ import DDGSync import Persistence import SyncDataProviders import PixelKit +import os.log public class BookmarksFaviconsFetcherErrorHandler: EventMapping { @@ -74,8 +75,7 @@ final class SyncBookmarksAdapter { self.syncErrorHandler = syncErrorHandler databaseCleaner = BookmarkDatabaseCleaner( bookmarkDatabase: database, - errorEvents: BookmarksCleanupErrorHandling(), - log: .bookmarks + errorEvents: BookmarksCleanupErrorHandling() ) } @@ -106,7 +106,6 @@ final class SyncBookmarksAdapter { database: database, metadataStore: metadataStore, metricsEvents: metricsEventsHandler, - log: OSLog.sync, syncDidUpdateData: { [weak self] in self?.syncErrorHandler.syncBookmarksSucceded() guard let manager = self?.bookmarkManager as? LocalBookmarkManager else { return } @@ -142,7 +141,7 @@ final class SyncBookmarksAdapter { stateStore = try BookmarksFaviconsFetcherStateStore(applicationSupportURL: URL.sandboxApplicationSupportURL) } catch { PixelKit.fire(DebugEvent(GeneralPixel.bookmarksFaviconsFetcherStateStoreInitializationFailed, error: error)) - os_log(.error, log: OSLog.sync, "Failed to initialize BookmarksFaviconsFetcherStateStore: %{public}s", String(reflecting: error)) + Logger.sync.error("Failed to initialize BookmarksFaviconsFetcherStateStore: \(String(reflecting: error), privacy: .public)") return nil } @@ -151,8 +150,7 @@ final class SyncBookmarksAdapter { stateStore: stateStore, fetcher: FaviconFetcher(), faviconStore: FaviconManager.shared, - errorEvents: BookmarksFaviconsFetcherErrorHandler(), - log: .sync + errorEvents: BookmarksFaviconsFetcherErrorHandler() ) } diff --git a/DuckDuckGo/Sync/SyncCredentialsAdapter.swift b/DuckDuckGo/Sync/SyncCredentialsAdapter.swift index aa857a40cb..fe3c682a4a 100644 --- a/DuckDuckGo/Sync/SyncCredentialsAdapter.swift +++ b/DuckDuckGo/Sync/SyncCredentialsAdapter.swift @@ -66,7 +66,6 @@ final class SyncCredentialsAdapter { secureVaultErrorReporter: SecureVaultReporter.shared, metadataStore: metadataStore, metricsEvents: metricsEventsHandler, - log: OSLog.sync, syncDidUpdateData: { [weak self] in self?.syncDidCompleteSubject.send() self?.syncErrorHandler.syncCredentialsSucceded() diff --git a/DuckDuckGo/Sync/SyncErrorHandler.swift b/DuckDuckGo/Sync/SyncErrorHandler.swift index bb73ba5dcc..f44cf0edf1 100644 --- a/DuckDuckGo/Sync/SyncErrorHandler.swift +++ b/DuckDuckGo/Sync/SyncErrorHandler.swift @@ -23,6 +23,7 @@ import PixelKit import Persistence import Combine import SyncDataProviders +import os.log /// The SyncErrorHandling protocol defines methods for handling sync errors related to specific data types such as bookmarks and credentials. protocol SyncErrorHandling { @@ -252,7 +253,7 @@ extension SyncErrorHandler: SyncErrorHandling { PixelKit.fire(DebugEvent(modelType.syncFailedPixel, error: error), withAdditionalParameters: params) } let modelTypeString = modelType.rawValue.capitalized - os_log(.error, log: OSLog.sync, "%{public}@ Sync error: %{public}s", modelTypeString, String(reflecting: error)) + Logger.sync.error("\(modelTypeString, privacy: .public) Sync error: \(String(reflecting: error), privacy: .public)") } } diff --git a/DuckDuckGo/Sync/SyncSettingsAdapter.swift b/DuckDuckGo/Sync/SyncSettingsAdapter.swift index 4cac3ddcc3..300b8305fd 100644 --- a/DuckDuckGo/Sync/SyncSettingsAdapter.swift +++ b/DuckDuckGo/Sync/SyncSettingsAdapter.swift @@ -56,7 +56,6 @@ final class SyncSettingsAdapter { metadataStore: metadataStore, settingsHandlers: [FavoritesDisplayModeSyncHandler(), EmailProtectionSyncHandler(emailManager: emailManager)], metricsEvents: metricsEventsHandler, - log: OSLog.sync, syncDidUpdateData: { [weak self] in self?.syncDidCompleteSubject.send() } diff --git a/DuckDuckGo/Tab/Model/SpecialPagesUserScriptExtension.swift b/DuckDuckGo/Tab/Model/SpecialPagesUserScriptExtension.swift index 15dc8d38a1..f5cff41746 100644 --- a/DuckDuckGo/Tab/Model/SpecialPagesUserScriptExtension.swift +++ b/DuckDuckGo/Tab/Model/SpecialPagesUserScriptExtension.swift @@ -18,6 +18,7 @@ import Foundation import BrowserServicesKit +import SpecialErrorPages extension SpecialPagesUserScript { @MainActor @@ -38,8 +39,10 @@ extension SpecialPagesUserScript { } func withErrorPages() { - let sslErrorPageUserScript = SSLErrorPageUserScript() - self.registerSubfeature(delegate: sslErrorPageUserScript) + let lenguageCode = Locale.current.languageCode ?? "en" + let specialErrorPageUserScript = SpecialErrorPageUserScript(localeStrings: SpecialErrorPageUserScript.localeStrings(for: lenguageCode), + languageCode: lenguageCode) + self.registerSubfeature(delegate: specialErrorPageUserScript) } @MainActor diff --git a/DuckDuckGo/Tab/Model/Tab.swift b/DuckDuckGo/Tab/Model/Tab.swift index c556a18b0f..6ffad6c568 100644 --- a/DuckDuckGo/Tab/Model/Tab.swift +++ b/DuckDuckGo/Tab/Model/Tab.swift @@ -56,7 +56,7 @@ protocol NewWindowPolicyDecisionMaker { fileprivate weak var delegate: TabDelegate? func setDelegate(_ delegate: TabDelegate) { self.delegate = delegate } - private let navigationDelegate = DistributedNavigationDelegate(log: .navigation) + private let navigationDelegate = DistributedNavigationDelegate() private var newWindowPolicyDecisionMakers: [NewWindowPolicyDecisionMaker]? private var onNewWindow: ((WKNavigationAction?) -> NavigationDecision)? @@ -697,7 +697,7 @@ protocol NewWindowPolicyDecisionMaker { } guard let backForwardNavigation else { - os_log(.error, "item `\(item.title ?? "") – \(item.url?.absoluteString ?? "")` is not in the backForwardList") + Logger.navigation.error("item `\(item.title ?? "") – \(item.url?.absoluteString ?? "")` is not in the backForwardList") return nil } diff --git a/DuckDuckGo/Tab/Model/TabExtensionsBuilder.swift b/DuckDuckGo/Tab/Model/TabExtensionsBuilder.swift index 4dcb0f822d..72f6c579c5 100644 --- a/DuckDuckGo/Tab/Model/TabExtensionsBuilder.swift +++ b/DuckDuckGo/Tab/Model/TabExtensionsBuilder.swift @@ -20,6 +20,7 @@ import Combine import Foundation import AppKit import Common +import os.log protocol TabExtensionsBuilderProtocol { @MainActor @@ -237,7 +238,7 @@ struct TabExtensions { #if DEBUG assert(!NSApp.runType.requiresEnvironment || tabExtension != nil) #else - os_log("%s Tab Extension not initialised for Unit Tests, activate it in TabExtensions.swift", log: .autoconsent, type: .debug, "\(T.self)") + Logger.autoconsent.debug("Tab Extension not initialised for Unit Tests, activate it in TabExtensions.swift") #endif return tabExtension } diff --git a/DuckDuckGo/Tab/Services/WebsiteDataStore.swift b/DuckDuckGo/Tab/Services/WebsiteDataStore.swift index eb003c99fe..69504c873e 100644 --- a/DuckDuckGo/Tab/Services/WebsiteDataStore.swift +++ b/DuckDuckGo/Tab/Services/WebsiteDataStore.swift @@ -19,6 +19,7 @@ import Common import WebKit import GRDB +import os.log public protocol HTTPCookieStore { func allCookies() async -> [HTTPCookie] @@ -74,7 +75,7 @@ internal class WebCacheManager { do { try fm.createDirectory(at: tmpDir, withIntermediateDirectories: false, attributes: nil) } catch { - os_log("Could not create temporary directory: %s", type: .error, "\(error)") + Logger.general.error("Could not create temporary directory: \(error.localizedDescription)") return } @@ -104,7 +105,7 @@ internal class WebCacheManager { do { try fm.createDirectory(at: tmpDir, withIntermediateDirectories: false, attributes: nil) } catch { - os_log("Could not create temporary directory: %s", type: .error, "\(error)") + Logger.general.error("Could not create temporary directory: \(error.localizedDescription)") return } @@ -157,7 +158,7 @@ internal class WebCacheManager { } for cookie in cookiesToRemove { - os_log("Deleting cookie for %s named %s", log: .fire, cookie.domain, cookie.name) + Logger.fire.debug("Deleting cookie for \(cookie.domain) named \(cookie.name)") await cookieStore.deleteCookie(cookie) } } @@ -208,7 +209,7 @@ internal class WebCacheManager { } } } catch { - os_log("Failed to clear observations database: %s", log: .fire, error.localizedDescription) + Logger.fire.error("Failed to clear observations database: \(error.localizedDescription)") } } diff --git a/DuckDuckGo/Tab/TabExtensions/AdClickAttributionTabExtension.swift b/DuckDuckGo/Tab/TabExtensions/AdClickAttributionTabExtension.swift index 89ebd0716b..abae248fef 100644 --- a/DuckDuckGo/Tab/TabExtensions/AdClickAttributionTabExtension.swift +++ b/DuckDuckGo/Tab/TabExtensions/AdClickAttributionTabExtension.swift @@ -84,7 +84,6 @@ final class AdClickAttributionTabExtension: TabExtension { tld: dependencies.tld, eventReporting: dependencies.attributionEvents, errorReporting: dependencies.attributionDebugEvents, - log: OSLog.attribution, cpmExperimentOn: cpmExperimentOn) detection.delegate = delegate return detection @@ -96,8 +95,7 @@ final class AdClickAttributionTabExtension: TabExtension { rulesProvider: dependencies.adClickAttributionRulesProvider, tld: dependencies.tld, eventReporting: dependencies.attributionEvents, - errorReporting: dependencies.attributionDebugEvents, - log: OSLog.attribution) + errorReporting: dependencies.attributionDebugEvents) } private static func makeAdClickAttribution(with dependencies: any AdClickAttributionDependencies, cpmExperimentOn: Bool?) -> (AdClickLogicProtocol, AdClickAttributionDetecting) { diff --git a/DuckDuckGo/Tab/TabExtensions/SSLErrorPageTabExtension.swift b/DuckDuckGo/Tab/TabExtensions/SpecialErrorPageTabExtension.swift similarity index 73% rename from DuckDuckGo/Tab/TabExtensions/SSLErrorPageTabExtension.swift rename to DuckDuckGo/Tab/TabExtensions/SpecialErrorPageTabExtension.swift index aac116210a..753ed648da 100644 --- a/DuckDuckGo/Tab/TabExtensions/SSLErrorPageTabExtension.swift +++ b/DuckDuckGo/Tab/TabExtensions/SpecialErrorPageTabExtension.swift @@ -1,5 +1,5 @@ // -// SSLErrorPageTabExtension.swift +// SpecialErrorPageTabExtension.swift // // Copyright © 2024 DuckDuckGo. All rights reserved. // @@ -22,25 +22,31 @@ import WebKit import Combine import ContentScopeScripts import BrowserServicesKit +import SpecialErrorPages +import Common -protocol SSLErrorPageScriptProvider { - var sslErrorPageUserScript: SSLErrorPageUserScript? { get } +protocol SpecialErrorPageScriptProvider { + var specialErrorPageUserScript: SpecialErrorPageUserScript? { get } } -extension UserScripts: SSLErrorPageScriptProvider {} +extension UserScripts: SpecialErrorPageScriptProvider {} -final class SSLErrorPageTabExtension { +final class SpecialErrorPageTabExtension { weak var webView: ErrorPageTabExtensionNavigationDelegate? - private weak var sslErrorPageUserScript: SSLErrorPageUserScript? + private weak var specialErrorPageUserScript: SpecialErrorPageUserScript? private var shouldBypassSSLError = false private var urlCredentialCreator: URLCredentialCreating private var featureFlagger: FeatureFlagger + private let tld = TLD() private var cancellables = Set() + var errorData: SpecialErrorData? + var failingURL: URL? + init( webViewPublisher: some Publisher, - scriptsPublisher: some Publisher, + scriptsPublisher: some Publisher, urlCredentialCreator: URLCredentialCreating = URLCredentialCreator(), featureFlagger: FeatureFlagger = NSApp.delegateTyped.featureFlagger) { self.featureFlagger = featureFlagger @@ -49,22 +55,13 @@ final class SSLErrorPageTabExtension { self?.webView = webView }.store(in: &cancellables) scriptsPublisher.sink { [weak self] scripts in - self?.sslErrorPageUserScript = scripts.sslErrorPageUserScript - self?.sslErrorPageUserScript?.delegate = self + self?.specialErrorPageUserScript = scripts.specialErrorPageUserScript + self?.specialErrorPageUserScript?.delegate = self }.store(in: &cancellables) } - @MainActor - private func loadSSLErrorHTML(url: URL, alternate: Bool, errorCode: Int) { - let domain: String = url.host ?? url.toString(decodePunycode: true, dropScheme: true, dropTrailingSlash: true) - let html = SSLErrorPageHTMLTemplate(domain: domain, errorCode: errorCode).makeHTMLFromTemplate() - webView?.loadAlternateHTML(html, baseURL: .error, forUnreachableURL: url) - loadHTML(html: html, url: url, alternate: alternate) - } - - @MainActor - private func loadErrorHTML(_ error: WKError, header: String, forUnreachableURL url: URL, alternate: Bool) { - let html = ErrorPageHTMLTemplate(error: error, header: header).makeHTMLFromTemplate() + @MainActor private func loadSSLErrorHTML(url: URL, alternate: Bool) { + let html = SpecialErrorPageHTMLTemplate.htmlFromTemplate loadHTML(html: html, url: url, alternate: alternate) } @@ -76,10 +73,9 @@ final class SSLErrorPageTabExtension { webView?.setDocumentHtml(html) } } - } -extension SSLErrorPageTabExtension: NavigationResponder { +extension SpecialErrorPageTabExtension: NavigationResponder { @MainActor func navigation(_ navigation: Navigation, didFailWith error: WKError) { let url = error.failingUrl ?? navigation.url @@ -87,21 +83,22 @@ extension SSLErrorPageTabExtension: NavigationResponder { guard error.errorCode != NSURLErrorCannotFindHost else { return } if !error.isFrameLoadInterrupted, !error.isNavigationCancelled { - // when already displaying the error page and reload navigation fails again: don‘t navigate, just update page HTML guard let webView else { return } let shouldPerformAlternateNavigation = navigation.url != webView.url || navigation.navigationAction.targetFrame?.url != .error if featureFlagger.isFeatureOn(.sslCertificatesBypass), error.errorCode == NSURLErrorServerCertificateUntrusted, let errorCode = error.userInfo["_kCFStreamErrorCodeKey"] as? Int { - sslErrorPageUserScript?.failingURL = url - loadSSLErrorHTML(url: url, alternate: shouldPerformAlternateNavigation, errorCode: errorCode) + failingURL = url + let domain: String = url.host ?? url.toString(decodePunycode: true, dropScheme: true, dropTrailingSlash: true) + errorData = SpecialErrorData(kind: .ssl, errorType: SSLErrorType.forErrorCode(errorCode).rawValue, domain: domain, eTldPlus1: tld.eTLDplus1(failingURL?.host)) + loadSSLErrorHTML(url: url, alternate: shouldPerformAlternateNavigation) } } } @MainActor func navigationDidFinish(_ navigation: Navigation) { - sslErrorPageUserScript?.isEnabled = navigation.url == sslErrorPageUserScript?.failingURL + specialErrorPageUserScript?.isEnabled = navigation.url == failingURL } @MainActor @@ -116,7 +113,8 @@ extension SSLErrorPageTabExtension: NavigationResponder { } } -extension SSLErrorPageTabExtension: SSLErrorPageUserScriptDelegate { +extension SpecialErrorPageTabExtension: SpecialErrorPageUserScriptDelegate { + func leaveSite() { guard webView?.canGoBack == true else { webView?.close() @@ -129,18 +127,20 @@ extension SSLErrorPageTabExtension: SSLErrorPageUserScriptDelegate { shouldBypassSSLError = true _ = webView?.reloadPage() } + + func advancedInfoPresented() {} } protocol ErrorPageTabExtensionProtocol: AnyObject, NavigationResponder {} -extension SSLErrorPageTabExtension: TabExtension, ErrorPageTabExtensionProtocol { +extension SpecialErrorPageTabExtension: TabExtension, ErrorPageTabExtensionProtocol { typealias PublicProtocol = ErrorPageTabExtensionProtocol func getPublicProtocol() -> PublicProtocol { self } } extension TabExtensions { var errorPage: ErrorPageTabExtensionProtocol? { - resolve(SSLErrorPageTabExtension.self) + resolve(SpecialErrorPageTabExtension.self) } } diff --git a/DuckDuckGo/Tab/TabExtensions/SpecialErrorPageUserScriptExtension.swift b/DuckDuckGo/Tab/TabExtensions/SpecialErrorPageUserScriptExtension.swift new file mode 100644 index 0000000000..900c2de99e --- /dev/null +++ b/DuckDuckGo/Tab/TabExtensions/SpecialErrorPageUserScriptExtension.swift @@ -0,0 +1,34 @@ +// +// SpecialErrorPageUserScriptExtension.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import SpecialErrorPages +import ContentScopeScripts + +extension SpecialErrorPageUserScript { + + static func localeStrings(for languageCode: String = Locale.current.languageCode ?? "en") -> String? { + if let localizedFile = ContentScopeScripts.Bundle.path(forResource: "special-error", + ofType: "json", + inDirectory: "pages/special-error/locales/\(languageCode)") { + return try? String(contentsOfFile: localizedFile) + } + return nil + } + +} diff --git a/DuckDuckGo/Tab/TabExtensions/TabExtensions.swift b/DuckDuckGo/Tab/TabExtensions/TabExtensions.swift index c4a5f773d2..e998c25902 100644 --- a/DuckDuckGo/Tab/TabExtensions/TabExtensions.swift +++ b/DuckDuckGo/Tab/TabExtensions/TabExtensions.swift @@ -196,7 +196,7 @@ extension TabExtensionsBuilder { } add { - SSLErrorPageTabExtension(webViewPublisher: args.webViewFuture, + SpecialErrorPageTabExtension(webViewPublisher: args.webViewFuture, scriptsPublisher: userScripts.compactMap { $0 }) } #if SPARKLE diff --git a/DuckDuckGo/Tab/TabExtensions/TabSnapshotExtension.swift b/DuckDuckGo/Tab/TabExtensions/TabSnapshotExtension.swift index 9374d573bf..24c999b913 100644 --- a/DuckDuckGo/Tab/TabExtensions/TabSnapshotExtension.swift +++ b/DuckDuckGo/Tab/TabExtensions/TabSnapshotExtension.swift @@ -21,6 +21,7 @@ import Common import Foundation import Navigation import WebKit +import os.log final class TabSnapshotExtension { @@ -88,7 +89,7 @@ final class TabSnapshotExtension { guard let self = self else { return } guard let image = await self.store.loadSnapshot(for: identifier) as NSImage? else { - os_log("No snapshot restored", log: .tabSnapshots) + Logger.tabSnapshots.debug("No snapshot restored") return } @@ -101,7 +102,7 @@ final class TabSnapshotExtension { image: image, webviewBoundsSize: NSSize.zero, isRestored: true) - os_log("Snapshot restored", log: .tabSnapshots) + Logger.tabSnapshots.debug("Snapshot restored") self.renderSnapshotAfterLoad = false } @@ -168,7 +169,7 @@ final class TabSnapshotExtension { !userDidInteractWithWebsite, snapshotData.webviewBoundsSize == webView.bounds.size, snapshotData.url == url { - os_log("Skipping snapshot rendering, it is already rendered. url: \(url)", log: .tabSnapshots) + Logger.tabSnapshots.debug("Skipping snapshot rendering, it is already rendered. url: \(url)") return } @@ -195,7 +196,7 @@ final class TabSnapshotExtension { } snapshotData = SnapshotData.snapshotDataForRegularView(from: snapshot) - os_log("Snapshot of native page rendered", log: .tabSnapshots) + Logger.tabSnapshots.debug("Snapshot of native page rendered") } } @@ -231,7 +232,7 @@ extension TabSnapshotExtension: NSCodingExtension { guard let uuidString = decoder.decodeObject(of: NSString.self, forKey: NSSecureCodingKeys.tabSnapshotIdentifier), let identifier = UUID(uuidString: uuidString as String) else { - os_log("Snapshot not available in the session state", log: .tabSnapshots) + Logger.tabSnapshots.debug("Snapshot not available in the session state") return } diff --git a/DuckDuckGo/Tab/TabLazyLoader/TabLazyLoader.swift b/DuckDuckGo/Tab/TabLazyLoader/TabLazyLoader.swift index b87239b969..f3bb827a15 100644 --- a/DuckDuckGo/Tab/TabLazyLoader/TabLazyLoader.swift +++ b/DuckDuckGo/Tab/TabLazyLoader/TabLazyLoader.swift @@ -19,6 +19,7 @@ import Foundation import Combine import Common +import os.log final class TabLazyLoader { @@ -43,7 +44,7 @@ final class TabLazyLoader { init?(dataSource: DataSource) { guard dataSource.qualifiesForLazyLoading else { - os_log("Lazy loading not applicable", log: .tabLazyLoading, type: .debug) + Logger.tabLazyLoading.debug("Lazy loading not applicable") return nil } @@ -52,7 +53,7 @@ final class TabLazyLoader { if let selectedTabIndex = dataSource.selectedTabIndex, dataSource.tabs.filter({ $0.isUrl }).count > Const.maxNumberOfLazyLoadedTabs { - os_log("%d open URL tabs, will load adjacent tabs first", log: .tabLazyLoading, type: .debug, dataSource.tabs.count) + Logger.tabLazyLoading.debug("\(dataSource.tabs.count) open URL tabs, will load adjacent tabs first") shouldLoadAdjacentTabs = true // Adjacent tab loading only applies to non-pinned tabs. If a pinned tab is selected, @@ -65,7 +66,7 @@ final class TabLazyLoader { func scheduleLazyLoading() { guard let currentTab = dataSource.selectedTab else { - os_log("Lazy loading not applicable", log: .tabLazyLoading, type: .debug) + Logger.tabLazyLoading.debug("Lazy loading not applicable") lazyLoadingDidFinishSubject.send(false) return } @@ -115,7 +116,7 @@ final class TabLazyLoader { private func startLazyLoadingRecentlySelectedTabs() { guard hasAnyTabsToLoad() else { - os_log("No tabs to load", log: .tabLazyLoading, type: .debug) + Logger.tabLazyLoading.debug("No tabs to load") let loadedAnyTab = numberOfTabsRemaining < Const.maxNumberOfLazyLoadedTabs lazyLoadingDidFinishSubject.send(loadedAnyTab) return @@ -125,12 +126,12 @@ final class TabLazyLoader { .prefix(Const.maxNumberOfLazyLoadedTabs) .sink(receiveCompletion: { [weak self] _ in - os_log("Lazy tab loading finished, preloaded %d tabs", log: .tabLazyLoading, type: .debug, Const.maxNumberOfLazyLoadedTabs) + Logger.tabLazyLoading.debug("Lazy tab loading finished, preloaded \(Const.maxNumberOfLazyLoadedTabs) tabs") self?.lazyLoadingDidFinishSubject.send(true) }, receiveValue: { [weak self] tab in - os_log("Tab did finish loading %s", log: .tabLazyLoading, type: .debug, String(reflecting: tab.url)) + Logger.tabLazyLoading.debug("Tab did finish loading \(String(reflecting: tab.url))") self?.numberOfTabsInProgress.value -= 1 }) @@ -149,7 +150,7 @@ final class TabLazyLoader { guard let self = self else { return false } if self.dataSource.isSelectedTabLoading { - os_log("Selected tab is currently loading, pausing lazy loading until it finishes", log: .tabLazyLoading, type: .debug) + Logger.tabLazyLoading.debug("Selected tab is currently loading, pausing lazy loading until it finishes") self.isLazyLoadingPausedSubject.send(true) return false } @@ -169,7 +170,7 @@ final class TabLazyLoader { private func findAndReloadNextTab() { guard numberOfTabsRemaining > 0 else { - os_log("Maximum allowed tabs loaded (%d), skipping", log: .tabLazyLoading, type: .debug, Const.maxNumberOfLazyLoadedTabs) + Logger.tabLazyLoading.debug("Maximum allowed tabs loaded (\(Const.maxNumberOfLazyLoadedTabs), skipping") return } @@ -177,7 +178,7 @@ final class TabLazyLoader { switch (tabToLoad, numberOfTabsInProgress.value) { case (.none, 0): - os_log("No more tabs suitable for lazy loading", log: .tabLazyLoading, type: .debug) + Logger.tabLazyLoading.debug("No more tabs suitable for lazy loading") lazyLoadingDidFinishSubject.send(true) case (.none, _): break @@ -203,7 +204,7 @@ final class TabLazyLoader { if tab != nil { if !dryRun { - os_log("Will reload recently selected pinned tab", log: .tabLazyLoading, type: .debug) + Logger.tabLazyLoading.debug("Will reload recently selected pinned tab") } } else { if shouldLoadAdjacentTabs, numberOfAdjacentTabsRemaining > 0 { @@ -212,16 +213,14 @@ final class TabLazyLoader { adjacentItemEnumerator?.reset() } else if tab != nil { numberOfAdjacentTabsRemaining -= 1 - os_log("Will reload adjacent tab #%d of %d", log: .tabLazyLoading, type: .debug, - Const.maxNumberOfLazyLoadedAdjacentTabs - numberOfAdjacentTabsRemaining, - Const.maxNumberOfLazyLoadedAdjacentTabs) + Logger.tabLazyLoading.debug("Will reload adjacent tab #\(Const.maxNumberOfLazyLoadedAdjacentTabs - self.numberOfAdjacentTabsRemaining) of \(Const.maxNumberOfLazyLoadedAdjacentTabs)") } } if tab == nil { tab = findRecentlySelectedTabToLoad(from: dataSource.tabs) if !dryRun, tab != nil { - os_log("Will reload recently selected tab", log: .tabLazyLoading, type: .debug) + Logger.tabLazyLoading.debug("Will reload recently selected tab") } } } @@ -249,7 +248,7 @@ final class TabLazyLoader { } private func lazyLoadTab(_ tab: DataSource.Tab) { - os_log("Reloading %s", log: .tabLazyLoading, type: .debug, String(reflecting: tab.url)) + Logger.tabLazyLoading.debug("Reloading \(String(reflecting: tab.url))") subscribeToTabLoadingFinished(tab) idsOfTabsSelectedOrReloadedInThisSession.insert(tab.id) diff --git a/DuckDuckGo/Tab/UserScripts/DebugUserScript.swift b/DuckDuckGo/Tab/UserScripts/DebugUserScript.swift index a1d8d7a628..2fa41ddda3 100644 --- a/DuckDuckGo/Tab/UserScripts/DebugUserScript.swift +++ b/DuckDuckGo/Tab/UserScripts/DebugUserScript.swift @@ -18,6 +18,7 @@ import WebKit import UserScript +import os.log protocol TabInstrumentationProtocol: AnyObject { func request(url: String, allowedIn timeInMs: Double) @@ -66,7 +67,7 @@ final class DebugUserScript: NSObject, StaticUserScript { private func handleLog(message: WKScriptMessage) { // Used to log JS debug events. This is noisy every time a new tab is opened, so it's commented out unless needed. - // os_log("%s", type: .debug, String(describing: message.body)) +// Logger.general.debug("Handle log \(String(describing: message.body))") } private func handleSignpost(message: WKScriptMessage) { diff --git a/DuckDuckGo/Tab/UserScripts/UserScripts.swift b/DuckDuckGo/Tab/UserScripts/UserScripts.swift index b82117b5ad..0804b63aad 100644 --- a/DuckDuckGo/Tab/UserScripts/UserScripts.swift +++ b/DuckDuckGo/Tab/UserScripts/UserScripts.swift @@ -21,6 +21,7 @@ import BrowserServicesKit import UserScript import WebKit import Subscription +import SpecialErrorPages @MainActor final class UserScripts: UserScriptsProvider { @@ -44,7 +45,7 @@ final class UserScripts: UserScriptsProvider { let autoconsentUserScript: UserScriptWithAutoconsent let youtubeOverlayScript: YoutubeOverlayUserScript? let youtubePlayerUserScript: YoutubePlayerUserScript? - let sslErrorPageUserScript: SSLErrorPageUserScript? + let specialErrorPageUserScript: SpecialErrorPageUserScript? let onboardingUserScript: OnboardingUserScript? #if SPARKLE let releaseNotesUserScript: ReleaseNotesUserScript? @@ -68,7 +69,9 @@ final class UserScripts: UserScriptsProvider { autoconsentUserScript = AutoconsentUserScript(scriptSource: sourceProvider, config: sourceProvider.privacyConfigurationManager.privacyConfig) - sslErrorPageUserScript = SSLErrorPageUserScript() + let lenguageCode = Locale.current.languageCode ?? "en" + specialErrorPageUserScript = SpecialErrorPageUserScript(localeStrings: SpecialErrorPageUserScript.localeStrings(for: lenguageCode), + languageCode: lenguageCode) onboardingUserScript = OnboardingUserScript(onboardingActionsManager: sourceProvider.onboardingActionsManager!) @@ -95,8 +98,8 @@ final class UserScripts: UserScriptsProvider { } if let specialPages = specialPages { - if let sslErrorPageUserScript { - specialPages.registerSubfeature(delegate: sslErrorPageUserScript) + if let specialErrorPageUserScript { + specialPages.registerSubfeature(delegate: specialErrorPageUserScript) } if let youtubePlayerUserScript { specialPages.registerSubfeature(delegate: youtubePlayerUserScript) diff --git a/DuckDuckGo/Tab/View/BrowserTabViewController.swift b/DuckDuckGo/Tab/View/BrowserTabViewController.swift index 3649438405..051665ebde 100644 --- a/DuckDuckGo/Tab/View/BrowserTabViewController.swift +++ b/DuckDuckGo/Tab/View/BrowserTabViewController.swift @@ -24,6 +24,7 @@ import SwiftUI import WebKit import Subscription import PixelKit +import os.log final class BrowserTabViewController: NSViewController { @@ -503,7 +504,7 @@ final class BrowserTabViewController: NSViewController { let contentView = viewToMakeFirstResponderAfterAdding?() else { return } guard contentView.window === window else { - os_log("BrowserTabViewController: Content view window is \(contentView.window?.description ?? "") but expected: \(window)", type: .error) + Logger.general.error("BrowserTabViewController: Content view window is \(contentView.window?.description ?? "") but expected: \(window)") return } viewToMakeFirstResponderAfterAdding = nil @@ -1201,7 +1202,7 @@ extension BrowserTabViewController { dispatchPrecondition(condition: .onQueue(.main)) guard let webView = webView else { - os_log("BrowserTabViewController: failed to create a snapshot of webView", type: .error) + Logger.general.error("BrowserTabViewController: failed to create a snapshot of webView") return } @@ -1210,7 +1211,7 @@ extension BrowserTabViewController { webView.takeSnapshot(with: config) { [weak self] image, _ in guard let image = image else { - os_log("BrowserTabViewController: failed to create a snapshot of webView", type: .error) + Logger.general.error("BrowserTabViewController: failed to create a snapshot of webView") return } self?.showWebViewSnapshot(with: image) diff --git a/DuckDuckGo/TabBar/View/TabBarCollectionView.swift b/DuckDuckGo/TabBar/View/TabBarCollectionView.swift index 6c24959c26..df3222decc 100644 --- a/DuckDuckGo/TabBar/View/TabBarCollectionView.swift +++ b/DuckDuckGo/TabBar/View/TabBarCollectionView.swift @@ -18,6 +18,7 @@ import Cocoa import Common +import os.log final class TabBarCollectionView: NSCollectionView { @@ -53,7 +54,7 @@ final class TabBarCollectionView: NSCollectionView { func scrollToSelected() { guard selectionIndexPaths.count == 1, let indexPath = selectionIndexPaths.first else { - os_log("TabBarCollectionView: More than 1 item or no item highlighted", type: .error) + Logger.general.error("TabBarCollectionView: More than 1 item or no item highlighted") return } scroll(to: indexPath) @@ -104,7 +105,7 @@ extension NSCollectionView { var isAtEndScrollPosition: Bool { guard let clipView = clipView else { - os_log("TabBarCollectionView: Clip view is nil", type: .error) + Logger.general.error("TabBarCollectionView: Clip view is nil") return false } @@ -113,7 +114,7 @@ extension NSCollectionView { var isAtStartScrollPosition: Bool { guard let clipView = clipView else { - os_log("TabBarCollectionView: Clip view is nil", type: .error) + Logger.general.error("TabBarCollectionView: Clip view is nil") return false } diff --git a/DuckDuckGo/TabBar/View/TabBarViewController.swift b/DuckDuckGo/TabBar/View/TabBarViewController.swift index 58021902f5..89abfcf544 100644 --- a/DuckDuckGo/TabBar/View/TabBarViewController.swift +++ b/DuckDuckGo/TabBar/View/TabBarViewController.swift @@ -22,6 +22,7 @@ import Common import Lottie import SwiftUI import WebKit +import os.log final class TabBarViewController: NSViewController { @@ -312,13 +313,13 @@ final class TabBarViewController: NSViewController { duplicateTab(at: .pinned(index)) case let .bookmark(tab): guard let url = tab.url, let tabViewModel = tabCollectionViewModel.pinnedTabsManager?.tabViewModels[tab] else { - os_log("TabBarViewController: Failed to get url from tab") + Logger.general.debug("TabBarViewController: Failed to get url from tab") return } bookmarkTab(with: url, title: tabViewModel.title) case let .removeBookmark(tab): guard let url = tab.url else { - os_log("TabBarViewController: Failed to get url from tab") + Logger.general.debug("TabBarViewController: Failed to get url from tab") return } deleteBookmark(with: url) @@ -342,7 +343,7 @@ final class TabBarViewController: NSViewController { } guard let selectionIndex = tabCollectionViewModel.selectionIndex else { - os_log("TabBarViewController: Selection index is nil", type: .error) + Logger.general.error("TabBarViewController: Selection index is nil") return } @@ -581,7 +582,7 @@ final class TabBarViewController: NSViewController { let tabViewModel = tabCollectionViewModel.tabViewModel(at: indexPath.item), let clipView = collectionView.clipView else { - os_log("TabBarViewController: Showing tab preview window failed", type: .error) + Logger.general.error("TabBarViewController: Showing tab preview window failed") return } @@ -591,7 +592,7 @@ final class TabBarViewController: NSViewController { private func showPinnedTabPreview(at index: Int) { guard let tabViewModel = tabCollectionViewModel.pinnedTabsManager?.tabViewModel(at: index) else { - os_log("TabBarViewController: Showing pinned tab preview window failed", type: .error) + Logger.general.error("TabBarViewController: Showing pinned tab preview window failed") return } @@ -607,7 +608,7 @@ final class TabBarViewController: NSViewController { isSelected: isSelected) guard let window = view.window else { - os_log("TabBarViewController: Showing tab preview window failed", type: .error) + Logger.general.error("TabBarViewController: Showing tab preview window failed") return } @@ -788,7 +789,7 @@ extension TabBarViewController: TabCollectionViewModelDelegate { private func deleteBookmark(with url: URL) { guard let bookmark = bookmarkManager.getBookmark(for: url) else { - os_log("TabBarViewController: Failed to fetch bookmark for url \(url)", type: .error) + Logger.general.error("TabBarViewController: Failed to fetch bookmark for url \(url)") return } bookmarkManager.remove(bookmark: bookmark) @@ -796,7 +797,7 @@ extension TabBarViewController: TabCollectionViewModelDelegate { private func fireproof(_ tab: Tab) { guard let url = tab.url, let host = url.host else { - os_log("TabBarViewController: Failed to get url of tab bar view item", type: .error) + Logger.general.error("TabBarViewController: Failed to get url of tab bar view item") return } @@ -805,7 +806,7 @@ extension TabBarViewController: TabCollectionViewModelDelegate { private func removeFireproofing(from tab: Tab) { guard let host = tab.url?.host else { - os_log("TabBarViewController: Failed to get url of tab bar view item", type: .error) + Logger.general.error("TabBarViewController: Failed to get url of tab bar view item") return } @@ -820,7 +821,7 @@ extension TabBarViewController: TabCollectionViewModelDelegate { let tabViewModel = tabCollectionViewModel.tabViewModel(at: indexPath.item), let url = tabViewModel.tab.content.userEditableUrl else { - os_log("TabBarViewController: Failed to get index path of tab bar view item", type: .error) + Logger.general.error("TabBarViewController: Failed to get index path of tab bar view item") return nil } diff --git a/DuckDuckGo/TabBar/ViewModel/TabCollectionViewModel.swift b/DuckDuckGo/TabBar/ViewModel/TabCollectionViewModel.swift index 68dc3ee7a4..59090bb527 100644 --- a/DuckDuckGo/TabBar/ViewModel/TabCollectionViewModel.swift +++ b/DuckDuckGo/TabBar/ViewModel/TabCollectionViewModel.swift @@ -21,6 +21,7 @@ import Foundation import Combine import History import PixelKit +import os.log /** * The delegate callbacks are triggered for events related to unpinned tabs only. @@ -154,7 +155,7 @@ final class TabCollectionViewModel: NSObject { func setUpLazyLoadingIfNeeded() { guard !isTabLazyLoadingRequested else { - os_log("Lazy loading already requested in this session, skipping.", log: .tabLazyLoading, type: .debug) + Logger.tabLazyLoading.debug("Lazy loading already requested in this session, skipping.") return } @@ -164,7 +165,7 @@ final class TabCollectionViewModel: NSObject { tabLazyLoader?.lazyLoadingDidFinishPublisher .sink { [weak self] _ in self?.tabLazyLoader = nil - os_log("Disposed of Tab Lazy Loader", log: .tabLazyLoading, type: .debug) + Logger.tabLazyLoading.debug("Disposed of Tab Lazy Loader") } .store(in: &cancellables) @@ -229,7 +230,7 @@ final class TabCollectionViewModel: NSObject { func selectNext() { guard changesEnabled else { return } guard allTabsCount > 0 else { - os_log("TabCollectionViewModel: No tabs for selection", type: .error) + Logger.tabLazyLoading.error("TabCollectionViewModel: No tabs for selection") return } @@ -243,7 +244,7 @@ final class TabCollectionViewModel: NSObject { func selectPrevious() { guard changesEnabled else { return } guard allTabsCount > 0 else { - os_log("TabCollectionViewModel: No tabs for selection", type: .error) + Logger.tabLazyLoading.debug("TabCollectionViewModel: No tabs for selection") return } @@ -257,7 +258,7 @@ final class TabCollectionViewModel: NSObject { @discardableResult private func selectUnpinnedTab(at index: Int, forceChange: Bool = false) -> Bool { guard changesEnabled || forceChange else { return false } guard index >= 0, index < tabCollection.tabs.count else { - os_log("TabCollectionViewModel: Index out of bounds", type: .error) + Logger.tabLazyLoading.error("TabCollectionViewModel: Index out of bounds") selectionIndex = nil return false } @@ -272,7 +273,7 @@ final class TabCollectionViewModel: NSObject { guard let pinnedTabsCollection = pinnedTabsCollection else { return false } guard index >= 0, index < pinnedTabsCollection.tabs.count else { - os_log("TabCollectionViewModel: Index out of bounds", type: .error) + Logger.tabLazyLoading.error("TabCollectionViewModel: Index out of bounds") selectionIndex = nil return false } @@ -330,7 +331,7 @@ final class TabCollectionViewModel: NSObject { func insert(_ tab: Tab, at index: TabIndex, selected: Bool = true) { guard changesEnabled else { return } guard let tabCollection = tabCollection(for: index) else { - os_log("TabCollectionViewModel: Tab collection for index %s not found", type: .error, String(describing: index)) + Logger.tabLazyLoading.error("TabCollectionViewModel: Tab collection for index \(String(describing: index)) not found") return } @@ -351,7 +352,7 @@ final class TabCollectionViewModel: NSObject { guard changesEnabled else { return } guard let parentTab = parentTab ?? tab.parentTab, let parentTabIndex = indexInAllTabs(of: parentTab) else { - os_log("TabCollection: No parent tab", type: .error) + Logger.tabLazyLoading.error("TabCollection: No parent tab") return } @@ -450,7 +451,7 @@ final class TabCollectionViewModel: NSObject { } guard let selectionIndex = selectionIndex else { - os_log("TabCollection: No tab selected", type: .error) + Logger.tabLazyLoading.error("TabCollection: No tab selected") notifyDelegate() return } @@ -547,7 +548,7 @@ final class TabCollectionViewModel: NSObject { func removeTabsAndAppendNew(at indexSet: IndexSet, forceChange: Bool = false) { guard !indexSet.isEmpty, changesEnabled || forceChange else { return } guard let selectionIndex = selectionIndex?.item else { - os_log("TabCollection: No tab selected", type: .error) + Logger.tabLazyLoading.error("TabCollection: No tab selected") return } @@ -573,7 +574,7 @@ final class TabCollectionViewModel: NSObject { guard changesEnabled || forceChange else { return } guard let selectionIndex = selectionIndex else { - os_log("TabCollectionViewModel: No tab selected", type: .error) + Logger.tabLazyLoading.error("TabCollectionViewModel: No tab selected") return } @@ -586,7 +587,7 @@ final class TabCollectionViewModel: NSObject { guard changesEnabled else { return } guard let tab = tab(at: tabIndex) else { - os_log("TabCollectionViewModel: Index out of bounds", type: .error) + Logger.tabLazyLoading.error("TabCollectionViewModel: Index out of bounds") return } @@ -606,7 +607,7 @@ final class TabCollectionViewModel: NSObject { guard let pinnedTabsCollection = pinnedTabsCollection else { return } guard index >= 0, index < tabCollection.tabs.count else { - os_log("TabCollectionViewModel: Index out of bounds", type: .error) + Logger.tabLazyLoading.error("TabCollectionViewModel: Index out of bounds") return } @@ -625,7 +626,7 @@ final class TabCollectionViewModel: NSObject { } guard let tab = pinnedTabsManager?.unpinTab(at: index, published: false) else { - os_log("Unable to unpin a tab", type: .error) + Logger.tabLazyLoading.error("Unable to unpin a tab") return } @@ -658,14 +659,14 @@ final class TabCollectionViewModel: NSObject { func replaceTab(at index: TabIndex, with tab: Tab, forceChange: Bool = false) { guard changesEnabled || forceChange else { return } guard let tabCollection = tabCollection(for: index) else { - os_log("TabCollectionViewModel: Tab collection for index %s not found", type: .error, String(describing: index)) + Logger.tabLazyLoading.error("TabCollectionViewModel: Tab collection for index \(String(describing: index)) not found") return } tabCollection.replaceTab(at: index.item, with: tab) guard let selectionIndex = selectionIndex else { - os_log("TabCollectionViewModel: No tab selected", type: .error) + Logger.tabLazyLoading.error("TabCollectionViewModel: No tab selected") return } select(at: selectionIndex, forceChange: forceChange) diff --git a/DuckDuckGo/TabPreview/Model/ViewSnapshotRenderer.swift b/DuckDuckGo/TabPreview/Model/ViewSnapshotRenderer.swift index d11cf0d4fc..33a76cf894 100644 --- a/DuckDuckGo/TabPreview/Model/ViewSnapshotRenderer.swift +++ b/DuckDuckGo/TabPreview/Model/ViewSnapshotRenderer.swift @@ -19,6 +19,7 @@ import Foundation import Common import WebKit +import os.log protocol ViewSnapshotRendering { @@ -40,11 +41,11 @@ final class ViewSnapshotRenderer: ViewSnapshotRendering { func renderSnapshot(view: NSView, completion: @escaping (NSImage?) -> Void) { let originalBounds = view.bounds - os_log("Native snapshot rendering started", log: .tabSnapshots) + Logger.tabSnapshots.debug("Native snapshot rendering started") DispatchQueue.global(qos: .userInitiated).async { guard let resizedImage = self.createResizedImage(from: view, with: originalBounds) else { DispatchQueue.main.async { - os_log("Native snapshot rendering failed", log: .tabSnapshots, type: .error) + Logger.tabSnapshots.error("Native snapshot rendering failed") completion(nil) } return @@ -53,7 +54,7 @@ final class ViewSnapshotRenderer: ViewSnapshotRendering { DispatchQueue.main.async { completion(resizedImage) - os_log("Snapshot of native page rendered", log: .tabSnapshots) + Logger.tabSnapshots.debug("Snapshot of native page rendered") } } } diff --git a/DuckDuckGo/TabPreview/Model/WebViewSnapshotRenderer.swift b/DuckDuckGo/TabPreview/Model/WebViewSnapshotRenderer.swift index 667a79ae03..d7bb9e33a4 100644 --- a/DuckDuckGo/TabPreview/Model/WebViewSnapshotRenderer.swift +++ b/DuckDuckGo/TabPreview/Model/WebViewSnapshotRenderer.swift @@ -19,6 +19,7 @@ import Foundation import Common import WebKit +import os.log protocol WebViewSnapshotRendering { @@ -32,17 +33,17 @@ final class WebViewSnapshotRenderer: WebViewSnapshotRendering { @MainActor func renderSnapshot(webView: WKWebView) async -> NSImage? { dispatchPrecondition(condition: .onQueue(.main)) - os_log("Preview rendering started for \(String(describing: webView.url))", log: .tabSnapshots) + Logger.tabSnapshots.debug("Preview rendering started for \(String(describing: webView.url))") let configuration = WKSnapshotConfiguration.makePreviewSnapshotConfiguration() do { let image = try await webView.takeSnapshot(configuration: configuration) - os_log("Preview rendered for \(String(describing: webView.url))", log: .tabSnapshots) + Logger.tabSnapshots.debug("Preview rendered for \(String(describing: webView.url))") return image } catch { - os_log("Failed to render snapshot for \(String(describing: webView.url))", log: .tabSnapshots, type: .error) + Logger.tabSnapshots.error("Failed to render snapshot for \(String(describing: webView.url))") return nil } } diff --git a/DuckDuckGo/TabPreview/Services/TabSnapshotStore.swift b/DuckDuckGo/TabPreview/Services/TabSnapshotStore.swift index 1b233bf766..accea10851 100644 --- a/DuckDuckGo/TabPreview/Services/TabSnapshotStore.swift +++ b/DuckDuckGo/TabPreview/Services/TabSnapshotStore.swift @@ -18,6 +18,7 @@ import Cocoa import Common +import os.log protocol TabSnapshotStoring { @@ -40,7 +41,7 @@ final class TabSnapshotStore: TabSnapshotStoring { func persistSnapshot(_ snapshot: NSImage, id: UUID) { guard let data = snapshot.tiffRepresentation else { - os_log("TabSnapshotPersistenceService: Failed to create tiff representation", type: .error) + Logger.tabSnapshots.error("TabSnapshotPersistenceService: Failed to create tiff representation") return } @@ -48,7 +49,7 @@ final class TabSnapshotStore: TabSnapshotStoring { let url = URL.persistenceLocation(for: id) createDirectoryIfNeeded() guard fileStore.persist(data, url: url) else { - os_log("TabSnapshotPersistenceService: Saving of snapshot failed", type: .error) + Logger.tabSnapshots.error("TabSnapshotPersistenceService: Saving of snapshot failed") return } } @@ -68,7 +69,7 @@ final class TabSnapshotStore: TabSnapshotStoring { let image = NSImage(data: data) { return image as NSImageSendable } else { - os_log("TabSnapshotPersistenceService: Loading of snapshot failed", type: .error) + Logger.tabSnapshots.error("TabSnapshotPersistenceService: Loading of snapshot failed") return nil } } @@ -101,7 +102,7 @@ final class TabSnapshotStore: TabSnapshotStoring { uuids.append(uuid) } } catch { - os_log("Failed to load stored snapshot ids: %@", type: .error, error.localizedDescription) + Logger.tabSnapshots.error("Failed to load stored snapshot ids: \(error.localizedDescription, privacy: .public)") } return uuids diff --git a/DuckDuckGo/TabPreview/TabPreviewWindowController.swift b/DuckDuckGo/TabPreview/TabPreviewWindowController.swift index f9fca79fcd..c292546db8 100644 --- a/DuckDuckGo/TabPreview/TabPreviewWindowController.swift +++ b/DuckDuckGo/TabPreview/TabPreviewWindowController.swift @@ -18,6 +18,7 @@ import Cocoa import Common +import os.log final class TabPreviewWindowController: NSWindowController { @@ -82,7 +83,7 @@ final class TabPreviewWindowController: NSWindowController { guard let childWindows = parentWindow.childWindows, let tabPreviewWindow = self.window else { - os_log("TabPreviewWindowController: Showing tab preview window failed", type: .error) + Logger.general.error("TabPreviewWindowController: Showing tab preview window failed") return } diff --git a/DuckDuckGo/UnifiedFeedbackForm/FeedbackCategoryProviding.swift b/DuckDuckGo/UnifiedFeedbackForm/FeedbackCategoryProviding.swift new file mode 100644 index 0000000000..da53250652 --- /dev/null +++ b/DuckDuckGo/UnifiedFeedbackForm/FeedbackCategoryProviding.swift @@ -0,0 +1,198 @@ +// +// FeedbackCategoryProviding.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +protocol FeedbackCategoryProviding: Hashable, CaseIterable, Identifiable, RawRepresentable { + static var prompt: Self { get } + var displayName: String { get } +} + +protocol FeedbackFAQProviding { + var url: URL? { get } +} + +extension FeedbackCategoryProviding where RawValue == String { + var id: String { + rawValue + } +} + +enum UnifiedFeedbackReportType: String, FeedbackCategoryProviding { + case selectReportType + case reportIssue + case requestFeature + case general + + static var prompt = UnifiedFeedbackReportType.selectReportType + + var displayName: String { + switch self { + case .selectReportType: return UserText.browserFeedbackSelectCategory + case .reportIssue: return UserText.browserFeedbackReportProblem + case .requestFeature: return UserText.browserFeedbackRequestFeature + case .general: return UserText.browserFeedbackGeneralFeedback + } + } +} + +enum UnifiedFeedbackCategory: String, FeedbackCategoryProviding { + case selectFeature + case subscription + case vpn + case pir + case itr + + static var prompt = UnifiedFeedbackCategory.selectFeature + + var displayName: String { + switch self { + case .selectFeature: return UserText.generalFeedbackFormCategorySelect + case .subscription: return UserText.generalFeedbackFormCategoryPPro + case .vpn: return UserText.generalFeedbackFormCategoryVPN + case .pir: return UserText.generalFeedbackFormCategoryPIR + case .itr: return UserText.generalFeedbackFormCategoryITR + } + } +} + +enum PrivacyProFeedbackSubcategory: String, FeedbackCategoryProviding, FeedbackFAQProviding { + case selectSubcategory + case otp + case somethingElse + + static var prompt = PrivacyProFeedbackSubcategory.selectSubcategory + + var displayName: String { + switch self { + case .selectSubcategory: return UserText.pproFeedbackFormCategorySelect + case .otp: return UserText.pproFeedbackFormCategoryOTP + case .somethingElse: return UserText.pproFeedbackFormCategoryOther + } + } + + var url: URL? { + switch self { + case .selectSubcategory: return nil + case .otp: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/payments/")! + case .somethingElse: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/payments/")! + } + } +} + +enum VPNFeedbackSubcategory: String, FeedbackCategoryProviding, FeedbackFAQProviding { + case selectSubcategory + case unableToInstall + case failsToConnect + case tooSlow + case issueWithAppOrWebsite + case appCrashesOrFreezes + case cantConnectToLocalDevice + case somethingElse + + static var prompt = VPNFeedbackSubcategory.selectSubcategory + + var displayName: String { + switch self { + case .selectSubcategory: return UserText.vpnFeedbackFormCategorySelect + case .unableToInstall: return UserText.vpnFeedbackFormCategoryUnableToInstall + case .failsToConnect: return UserText.vpnFeedbackFormCategoryFailsToConnect + case .tooSlow: return UserText.vpnFeedbackFormCategoryTooSlow + case .issueWithAppOrWebsite: return UserText.vpnFeedbackFormCategoryIssuesWithApps + case .appCrashesOrFreezes: return UserText.vpnFeedbackFormCategoryBrowserCrashOrFreeze + case .cantConnectToLocalDevice: return UserText.vpnFeedbackFormCategoryLocalDeviceConnectivity + case .somethingElse: return UserText.vpnFeedbackFormCategoryOther + } + } + + var url: URL? { + switch self { + case .selectSubcategory: return nil + case .unableToInstall: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/troubleshooting/")! + case .failsToConnect: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/troubleshooting/")! + case .tooSlow: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/troubleshooting/")! + case .issueWithAppOrWebsite: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/troubleshooting/")! + case .appCrashesOrFreezes: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/troubleshooting/")! + case .cantConnectToLocalDevice: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/troubleshooting/")! + case .somethingElse: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/")! + } + } +} + +enum PIRFeedbackSubcategory: String, FeedbackCategoryProviding, FeedbackFAQProviding { + case selectSubcategory + case nothingOnSpecificSite + case notMe + case scanStuck + case removalStuck + case somethingElse + + static var prompt = PIRFeedbackSubcategory.selectSubcategory + + var displayName: String { + switch self { + case .selectSubcategory: return UserText.pirFeedbackFormCategorySelect + case .nothingOnSpecificSite: return UserText.pirFeedbackFormCategoryNothingOnSpecificSite + case .notMe: return UserText.pirFeedbackFormCategoryNotMe + case .scanStuck: return UserText.pirFeedbackFormCategoryScanStuck + case .removalStuck: return UserText.pirFeedbackFormCategoryRemovalStuck + case .somethingElse: return UserText.pirFeedbackFormCategoryOther + } + } + + var url: URL? { + switch self { + case .selectSubcategory: return nil + case .nothingOnSpecificSite: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/personal-information-removal/removal-process/")! + case .notMe: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/personal-information-removal/removal-process/")! + case .scanStuck: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/personal-information-removal/removal-process/")! + case .removalStuck: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/personal-information-removal/removal-process/")! + case .somethingElse: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/personal-information-removal/")! + } + } +} + +enum ITRFeedbackSubcategory: String, FeedbackCategoryProviding, FeedbackFAQProviding { + case selectSubcategory + case accessCode + case cantContactAdvisor + case advisorUnhelpful + case somethingElse + + static var prompt = ITRFeedbackSubcategory.selectSubcategory + + var displayName: String { + switch self { + case .selectSubcategory: return UserText.itrFeedbackFormCategorySelect + case .accessCode: return UserText.itrFeedbackFormCategoryAccessCode + case .cantContactAdvisor: return UserText.itrFeedbackFormCategoryCantContactAdvisor + case .advisorUnhelpful: return UserText.itrFeedbackFormCategoryUnhelpful + case .somethingElse: return UserText.itrFeedbackFormCategorySomethingElse + } + } + + var url: URL? { + switch self { + case .selectSubcategory: return nil + case .accessCode: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/identity-theft-restoration/")! + case .cantContactAdvisor: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/identity-theft-restoration/iris/")! + case .advisorUnhelpful: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/identity-theft-restoration/")! + case .somethingElse: return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/identity-theft-restoration/")! + } + } +} diff --git a/DuckDuckGo/UnifiedFeedbackForm/MetadataCollectors/EmptyMetadataCollector.swift b/DuckDuckGo/UnifiedFeedbackForm/MetadataCollectors/EmptyMetadataCollector.swift new file mode 100644 index 0000000000..b8af990ffa --- /dev/null +++ b/DuckDuckGo/UnifiedFeedbackForm/MetadataCollectors/EmptyMetadataCollector.swift @@ -0,0 +1,32 @@ +// +// EmptyMetadataCollector.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +struct EmptyFeedbackMetadata: UnifiedFeedbackMetadata { + +} + +/// Default implementation for Privacy Pro metadata collector +/// Intentionally left blank as we currently don't collect any metadata for PIR and ITR +/// See `DefaultVPNMetadataCollector` for a reference implementation +final class EmptyMetadataCollector: UnifiedMetadataCollector { + func collectMetadata() async -> EmptyFeedbackMetadata { + EmptyFeedbackMetadata() + } +} diff --git a/DuckDuckGo/UnifiedFeedbackForm/MetadataCollectors/UnifiedMetadataCollector.swift b/DuckDuckGo/UnifiedFeedbackForm/MetadataCollectors/UnifiedMetadataCollector.swift new file mode 100644 index 0000000000..a720ac8bdc --- /dev/null +++ b/DuckDuckGo/UnifiedFeedbackForm/MetadataCollectors/UnifiedMetadataCollector.swift @@ -0,0 +1,42 @@ +// +// UnifiedMetadataCollector.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +protocol UnifiedMetadataCollector { + associatedtype Metadata: UnifiedFeedbackMetadata + + func collectMetadata() async -> Metadata +} + +protocol UnifiedFeedbackMetadata: Encodable { + func toBase64() -> String +} + +extension UnifiedFeedbackMetadata { + func toBase64() -> String { + let encoder = JSONEncoder() + + do { + let encodedMetadata = try encoder.encode(self) + return encodedMetadata.base64EncodedString() + } catch { + return "Failed to encode metadata to JSON, error message: \(error.localizedDescription)" + } + } +} diff --git a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormView.swift b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormView.swift new file mode 100644 index 0000000000..011f00efc9 --- /dev/null +++ b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormView.swift @@ -0,0 +1,310 @@ +// +// UnifiedFeedbackFormView.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct UnifiedFeedbackFormView: View { + + @EnvironmentObject var viewModel: UnifiedFeedbackFormViewModel + + var body: some View { + VStack(spacing: 0) { + Group { + Text(UserText.feedbackFormTitle) + .font(.system(size: 15, weight: .semibold)) + .foregroundColor(.secondary) + } + .frame(height: 70) + .frame(maxWidth: .infinity) + .background(Color.secondary.opacity(0.1)) + + Divider() + + switch viewModel.viewState { + case .feedbackPending, .feedbackSending, .feedbackSendingFailed: + FeedbackFormBodyView() + .padding([.top, .leading, .trailing], 20) + + if viewModel.viewState == .feedbackSendingFailed { + Text(UserText.vpnFeedbackFormSendingConfirmationError) + .foregroundColor(.red) + .padding(.top, 15) + } + case .feedbackSent: + FeedbackFormSentView() + .padding([.top, .leading, .trailing], 20) + } + + Spacer(minLength: 0) + + FeedbackFormButtons() + .padding(20) + } + .onChange(of: viewModel.needsSubmitShowReport) { needsSubmitShowReport in + if needsSubmitShowReport { + Task { + await viewModel.process(action: .reportSubmitShow) + } + } + } + .task { + await viewModel.process(action: .reportShow) + } + } + +} + +private struct FeedbackFormBodyView: View { + + @EnvironmentObject var viewModel: UnifiedFeedbackFormViewModel + + var body: some View { + CategoryPicker(sources: UnifiedFeedbackReportType.self, selection: $viewModel.selectedReportType) { + switch UnifiedFeedbackReportType(rawValue: viewModel.selectedReportType) { + case .selectReportType, nil: + EmptyView() + case .general: + FeedbackFormIssueDescriptionView { + Text(UserText.pproFeedbackFormGeneralFeedbackPlaceholder) + } + case .requestFeature: + FeedbackFormIssueDescriptionView { + Text(UserText.pproFeedbackFormRequestFeaturePlaceholder) + } + case .reportIssue: + reportProblemView() + } + } + } + + @ViewBuilder + func reportProblemView() -> some View { + CategoryPicker(sources: UnifiedFeedbackCategory.self, selection: $viewModel.selectedCategory) { + switch UnifiedFeedbackCategory(rawValue: viewModel.selectedCategory) { + case .selectFeature, nil: + EmptyView() + case .subscription: + CategoryPicker(sources: PrivacyProFeedbackSubcategory.self, selection: $viewModel.selectedSubcategory) { + issueDescriptionView() + } + case .vpn: + CategoryPicker(sources: VPNFeedbackSubcategory.self, selection: $viewModel.selectedSubcategory) { + issueDescriptionView() + } + case .pir: + CategoryPicker(sources: PIRFeedbackSubcategory.self, selection: $viewModel.selectedSubcategory) { + issueDescriptionView() + } + case .itr: + CategoryPicker(sources: ITRFeedbackSubcategory.self, selection: $viewModel.selectedSubcategory) { + issueDescriptionView() + } + } + } + } + + @ViewBuilder + func issueDescriptionView() -> some View { + FeedbackFormIssueDescriptionView { + Text(LocalizedStringKey(UserText.pproFeedbackFormText1)) + .onURLTap { _ in + Task { + await viewModel.process(action: .reportFAQClick) + await viewModel.process(action: .faqClick) + } + } + } footer: { + Text(UserText.pproFeedbackFormText2) + VStack(alignment: .leading) { + Text(UserText.pproFeedbackFormText3) + Text(UserText.pproFeedbackFormText4) + } + Text(UserText.pproFeedbackFormText5) + } + } +} + +private struct CategoryPicker: View where Category.AllCases == [Category], Category.RawValue == String { + let sources: Category.Type + let selection: Binding + let content: () -> Content + + init(sources: Category.Type, + selection: Binding, + @ViewBuilder content: @escaping () -> Content) { + self.sources = sources + self.selection = selection + self.content = content + } + + var body: some View { + Group { + Picker(selection: selection, content: { + ForEach(sources.allCases) { option in + Text(option.displayName).tag(option.rawValue) + } + }, label: {}) + .controlSize(.large) + .padding(.bottom, 0) + + if Category(rawValue: selection.wrappedValue) == .prompt { + Spacer() + .frame(height: 50) + } else { + content() + } + } + } +} + +private struct FeedbackFormIssueDescriptionView: View { + @EnvironmentObject var viewModel: UnifiedFeedbackFormViewModel + + let label: () -> Label + let content: () -> Content + let footer: () -> Footer + + init(@ViewBuilder label: @escaping () -> Label, + @ViewBuilder content: @escaping () -> Content, + @ViewBuilder footer: @escaping () -> Footer) { + self.label = label + self.content = content + self.footer = footer + } + + init(@ViewBuilder label: @escaping () -> Label, + @ViewBuilder footer: @escaping () -> Footer) where Content == EmptyView { + self.init { + label() + } content: { + EmptyView() + } footer: { + footer() + } + } + + init(@ViewBuilder label: @escaping () -> Label) where Content == EmptyView, Footer == Text { + self.init { + label() + } content: { + EmptyView() + } footer: { + Text(UserText.pproFeedbackFormDisclaimer) + } + } + + var body: some View { + VStack(alignment: .leading, spacing: 10) { + label() + .multilineTextAlignment(.leading) + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + textEditor() + content() + footer() + .multilineTextAlignment(.leading) + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + .foregroundColor(.secondary) + } + } + + @ViewBuilder + func textEditor() -> some View { +#if APPSTORE + FocusableTextEditor(text: $viewModel.feedbackFormText, characterLimit: 1000) +#else + if #available(macOS 12, *) { + FocusableTextEditor(text: $viewModel.feedbackFormText, characterLimit: 1000) + } else { + TextEditor(text: $viewModel.feedbackFormText) + .frame(height: 197.0) + .font(.body) + .foregroundColor(.primary) + .onChange(of: viewModel.feedbackFormText) { + viewModel.feedbackFormText = String($0.prefix(1000)) + } + .padding(EdgeInsets(top: 3.0, leading: 6.0, bottom: 5.0, trailing: 0.0)) + .clipShape(RoundedRectangle(cornerRadius: 8.0, style: .continuous)) + .background( + ZStack { + RoundedRectangle(cornerRadius: 8.0) + .stroke(Color(.textEditorBorder), lineWidth: 0.4) + RoundedRectangle(cornerRadius: 8.0) + .fill(Color(.textEditorBackground)) + } + ) + } +#endif + } +} + +private struct FeedbackFormSentView: View { + + var body: some View { + VStack(spacing: 0) { + Image(.vpnFeedbackSent) + .padding(.top, 20) + + Text(UserText.pproFeedbackFormSendingConfirmationTitle) + .font(.system(size: 18, weight: .medium)) + .padding(.top, 30) + + Text(UserText.pproFeedbackFormSendingConfirmationDescription) + .multilineTextAlignment(.center) + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + .padding(.top, 10) + } + } + +} + +private struct FeedbackFormButtons: View { + + @EnvironmentObject var viewModel: UnifiedFeedbackFormViewModel + + var body: some View { + HStack { + if viewModel.viewState == .feedbackSent { + button(text: UserText.pproFeedbackFormButtonDone, action: .cancel) + .keyboardShortcut(.defaultAction) + } else { + button(text: UserText.pproFeedbackFormButtonCancel, action: .cancel) + button(text: viewModel.viewState == .feedbackSending ? UserText.pproFeedbackFormButtonSubmitting : UserText.pproFeedbackFormButtonSubmit, action: .submit) + .keyboardShortcut(.defaultAction) + .disabled(!viewModel.submitButtonEnabled) + } + } + } + + @ViewBuilder + func button(text: String, action: UnifiedFeedbackFormViewModel.ViewAction) -> some View { + Button(action: { + Task { + await viewModel.process(action: action) + } + }, label: { + Text(text) + .frame(maxWidth: .infinity) + }) + .controlSize(.large) + .frame(maxWidth: .infinity) + } + +} diff --git a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewController.swift b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewController.swift new file mode 100644 index 0000000000..8fa9d63fdc --- /dev/null +++ b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewController.swift @@ -0,0 +1,129 @@ +// +// UnifiedFeedbackFormViewController.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import AppKit +import SwiftUI +import Combine +import PixelKit + +final class UnifiedFeedbackFormViewController: NSViewController { + // Using a dynamic height in the form was causing layout problems and couldn't be completed in time for the release that needed this form. + // As a temporary measure, the heights of each form state are hardcoded. + // This should be cleaned up later, and eventually use the `sizingOptions` property of NSHostingController. + enum Constants { + static let landingPageHeight = 260.0 + static let feedbackFormCompactHeight = 430.0 + static let feedbackFormHeight = 650.0 + static let feedbackSentHeight = 350.0 + static let feedbackErrorHeight = 560.0 + } + + private let defaultSize = CGSize(width: 480, height: Constants.landingPageHeight) + + private let feedbackSender: UnifiedFeedbackSender + private let viewModel: UnifiedFeedbackFormViewModel + + private var heightConstraint: NSLayoutConstraint? + private var cancellables = Set() + + init(feedbackSender: UnifiedFeedbackSender = DefaultFeedbackSender(), + source: UnifiedFeedbackSource = .default) { + self.feedbackSender = feedbackSender + self.viewModel = UnifiedFeedbackFormViewModel( + vpnMetadataCollector: DefaultVPNMetadataCollector(accountManager: Application.appDelegate.subscriptionManager.accountManager), + feedbackSender: feedbackSender, + source: source + ) + super.init(nibName: nil, bundle: nil) + self.viewModel.delegate = self + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = NSView(frame: NSRect(origin: CGPoint.zero, size: defaultSize)) + } + + override func viewDidLoad() { + super.viewDidLoad() + + let feedbackFormView = UnifiedFeedbackFormView() + let hostingView = NSHostingView(rootView: feedbackFormView.environmentObject(self.viewModel)) + hostingView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(hostingView) + + let heightConstraint = hostingView.heightAnchor.constraint(equalToConstant: defaultSize.height) + self.heightConstraint = heightConstraint + + NSLayoutConstraint.activate([ + heightConstraint, + hostingView.widthAnchor.constraint(equalToConstant: defaultSize.width), + hostingView.topAnchor.constraint(equalTo: view.topAnchor), + hostingView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + hostingView.leftAnchor.constraint(equalTo: view.leftAnchor), + hostingView.rightAnchor.constraint(equalTo: view.rightAnchor) + ]) + + subscribeToViewModelChanges() + } + + func subscribeToViewModelChanges() { + viewModel.$viewState + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.updateViewHeight() + } + .store(in: &cancellables) + + viewModel.$selectedReportType + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.updateViewHeight() + } + .store(in: &cancellables) + } + + private func updateViewHeight() { + switch viewModel.viewState { + case .feedbackPending: + if UnifiedFeedbackReportType(rawValue: viewModel.selectedReportType) == .prompt { + heightConstraint?.constant = Constants.landingPageHeight + } else { + heightConstraint?.constant = viewModel.usesCompactForm ? Constants.feedbackFormCompactHeight : Constants.feedbackFormHeight + } + case .feedbackSending: + heightConstraint?.constant = viewModel.usesCompactForm ? Constants.feedbackFormCompactHeight : Constants.feedbackFormHeight + case .feedbackSent: + heightConstraint?.constant = Constants.feedbackSentHeight + case .feedbackSendingFailed: + heightConstraint?.constant = Constants.feedbackErrorHeight + } + } + +} + +extension UnifiedFeedbackFormViewController: UnifiedFeedbackFormViewModelDelegate { + + func feedbackViewModelDismissedView(_ viewModel: UnifiedFeedbackFormViewModel) { + dismiss() + } + +} diff --git a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift new file mode 100644 index 0000000000..5d89a79282 --- /dev/null +++ b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackFormViewModel.swift @@ -0,0 +1,238 @@ +// +// UnifiedFeedbackFormViewModel.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Combine +import SwiftUI +import PixelKit + +protocol UnifiedFeedbackFormViewModelDelegate: AnyObject { + func feedbackViewModelDismissedView(_ viewModel: UnifiedFeedbackFormViewModel) +} + +final class UnifiedFeedbackFormViewModel: ObservableObject { + enum ViewState { + case feedbackPending + case feedbackSending + case feedbackSendingFailed + case feedbackSent + + var canSubmit: Bool { + switch self { + case .feedbackPending: return true + case .feedbackSending: return false + case .feedbackSendingFailed: return true + case .feedbackSent: return false + } + } + } + + enum ViewAction { + case cancel + case submit + case faqClick + case reportShow + case reportSubmitShow + case reportFAQClick + } + + @Published var viewState: ViewState { + didSet { + updateSubmitButtonStatus() + } + } + + @Published var feedbackFormText: String = "" { + didSet { + updateSubmitButtonStatus() + } + } + + @Published private(set) var submitButtonEnabled: Bool = false + @Published var selectedReportType: String = UnifiedFeedbackReportType.prompt.rawValue { + didSet { + let defaultCategory: UnifiedFeedbackCategory + switch source { + case .ppro: defaultCategory = .subscription + case .vpn: defaultCategory = .vpn + case .pir: defaultCategory = .pir + case .itr: defaultCategory = .itr + default: defaultCategory = .prompt + } + selectedCategory = defaultCategory.rawValue + updateSubmitShowStatus() + } + } + @Published var selectedCategory: String = UnifiedFeedbackCategory.prompt.rawValue { + didSet { + selectedSubcategory = selectedSubcategoryPrompt + updateSubmitShowStatus() + } + } + @Published var selectedSubcategory = "" { + didSet { + updateSubmitShowStatus() + } + } + + private var selectedSubcategoryPrompt: String { + switch UnifiedFeedbackCategory(rawValue: selectedCategory) { + case .selectFeature, nil: return "" + case .subscription: return PrivacyProFeedbackSubcategory.prompt.rawValue + case .vpn: return VPNFeedbackSubcategory.prompt.rawValue + case .pir: return PIRFeedbackSubcategory.prompt.rawValue + case .itr: return ITRFeedbackSubcategory.prompt.rawValue + } + } + + @Published var needsSubmitShowReport = false + + var usesCompactForm: Bool { + switch UnifiedFeedbackReportType(rawValue: selectedReportType) { + case .reportIssue: + return false + default: + return true + } + } + + weak var delegate: UnifiedFeedbackFormViewModelDelegate? + + private let vpnMetadataCollector: any UnifiedMetadataCollector + private let defaultMetadataCollector: any UnifiedMetadataCollector + private let feedbackSender: any UnifiedFeedbackSender + + let source: UnifiedFeedbackSource + + init(vpnMetadataCollector: any UnifiedMetadataCollector, + defaultMetadataCollector: any UnifiedMetadataCollector = EmptyMetadataCollector(), + feedbackSender: any UnifiedFeedbackSender = DefaultFeedbackSender(), + source: UnifiedFeedbackSource = .default) { + self.viewState = .feedbackPending + + self.vpnMetadataCollector = vpnMetadataCollector + self.defaultMetadataCollector = defaultMetadataCollector + self.feedbackSender = feedbackSender + self.source = source + } + + @MainActor + func process(action: ViewAction) async { + switch action { + case .cancel: + delegate?.feedbackViewModelDismissedView(self) + case .submit: + self.viewState = .feedbackSending + + do { + try await sendFeedback() + self.viewState = .feedbackSent + } catch { + self.viewState = .feedbackSendingFailed + } + case .faqClick: + await openFAQ() + case .reportShow: + feedbackSender.sendFormShowPixel() + case .reportSubmitShow: + feedbackSender.sendSubmitScreenShowPixel(source: source, + reportType: selectedReportType, + category: selectedCategory, + subcategory: selectedSubcategory) + needsSubmitShowReport = false + case .reportFAQClick: + feedbackSender.sendSubmitScreenFAQClickPixel(source: source, + reportType: selectedReportType, + category: selectedCategory, + subcategory: selectedSubcategory) + } + } + + private func openFAQ() async { + guard !selectedReportType.isEmpty, UnifiedFeedbackReportType(rawValue: selectedReportType) == .reportIssue, + !selectedCategory.isEmpty, let category = UnifiedFeedbackCategory(rawValue: selectedCategory), + !selectedSubcategory.isEmpty else { + return + } + + let url: URL? = { + switch category { + case .selectFeature: return nil + case .subscription: return PrivacyProFeedbackSubcategory(rawValue: selectedSubcategory)?.url + case .vpn: return VPNFeedbackSubcategory(rawValue: selectedSubcategory)?.url + case .pir: return PIRFeedbackSubcategory(rawValue: selectedSubcategory)?.url + case .itr: return ITRFeedbackSubcategory(rawValue: selectedSubcategory)?.url + } + }() + + if let url { + NSWorkspace.shared.open(url) + } + } + + private func sendFeedback() async throws { + switch UnifiedFeedbackReportType(rawValue: selectedReportType) { + case .selectReportType, nil: + return + case .requestFeature: + try await feedbackSender.sendFeatureRequestPixel(description: feedbackFormText, + source: source) + case .general: + try await feedbackSender.sendGeneralFeedbackPixel(description: feedbackFormText, + source: source) + case .reportIssue: + try await reportProblem() + } + } + + private func reportProblem() async throws { + switch UnifiedFeedbackCategory(rawValue: selectedCategory) { + case .vpn: + let metadata = await vpnMetadataCollector.collectMetadata() + try await feedbackSender.sendReportIssuePixel(source: source, + category: selectedCategory, + subcategory: selectedSubcategory, + description: feedbackFormText, + metadata: metadata as? VPNMetadata) + default: + let metadata = await defaultMetadataCollector.collectMetadata() + try await feedbackSender.sendReportIssuePixel(source: source, + category: selectedCategory, + subcategory: selectedSubcategory, + description: feedbackFormText, + metadata: metadata as? EmptyFeedbackMetadata) + } + } + + private func updateSubmitButtonStatus() { + self.submitButtonEnabled = viewState.canSubmit && !feedbackFormText.isEmpty + } + + private func updateSubmitShowStatus() { + needsSubmitShowReport = { + switch UnifiedFeedbackReportType(rawValue: selectedReportType) { + case .selectReportType, nil: + return false + case .requestFeature, .general: + return true + case .reportIssue: + return selectedCategory != UnifiedFeedbackCategory.prompt.rawValue && selectedSubcategory != selectedSubcategoryPrompt + } + }() + } +} diff --git a/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackSender.swift b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackSender.swift new file mode 100644 index 0000000000..edbb839d04 --- /dev/null +++ b/DuckDuckGo/UnifiedFeedbackForm/UnifiedFeedbackSender.swift @@ -0,0 +1,118 @@ +// +// UnifiedFeedbackSender.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import PixelKit + +enum UnifiedFeedbackSource: String, StringRepresentable { + case settings, ppro, vpn, pir, itr, unknown + static var `default` = UnifiedFeedbackSource.unknown +} + +protocol UnifiedFeedbackSender { + func sendFeatureRequestPixel(description: String, source: UnifiedFeedbackSource) async throws + func sendGeneralFeedbackPixel(description: String, source: UnifiedFeedbackSource) async throws + func sendReportIssuePixel(source: UnifiedFeedbackSource, category: String, subcategory: String, description: String, metadata: T?) async throws + + func sendFormShowPixel() + func sendSubmitScreenShowPixel(source: UnifiedFeedbackSource, reportType: String, category: String, subcategory: String) + func sendSubmitScreenFAQClickPixel(source: UnifiedFeedbackSource, reportType: String, category: String, subcategory: String) +} + +extension UnifiedFeedbackSender { + func sendStandardPixel(_ pixel: PixelKitEventV2) async throws { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + PixelKit.fire(pixel, frequency: .standard) { _, error in + if let error { + continuation.resume(throwing: error) + } else { + continuation.resume() + } + } + } + } +} + +protocol StringRepresentable: RawRepresentable { + static var `default`: Self { get } +} + +extension StringRepresentable where RawValue == String { + static func from(_ text: String) -> String { + (Self(rawValue: text) ?? .default).rawValue + } +} + +struct DefaultFeedbackSender: UnifiedFeedbackSender { + enum ReportType: String, StringRepresentable { + case general, reportIssue, requestFeature + static var `default` = ReportType.general + } + + enum Category: String, StringRepresentable { + case subscription, vpn, pir, itr, unknown + static var `default` = Category.unknown + } + + enum Subcategory: String, StringRepresentable { + case otp + case unableToInstall, failsToConnect, tooSlow, issueWithAppOrWebsite, appCrashesOrFreezes, cantConnectToLocalDevice + case nothingOnSpecificSite, notMe, scanStuck, removalStuck + case accessCode, cantContactAdvisor, advisorUnhelpful + case somethingElse + static var `default` = Subcategory.somethingElse + } + + func sendFeatureRequestPixel(description: String, source: UnifiedFeedbackSource) async throws { + try await sendStandardPixel(GeneralPixel.pproFeedbackFeatureRequest(description: description, + source: source.rawValue)) + } + + func sendGeneralFeedbackPixel(description: String, source: UnifiedFeedbackSource) async throws { + try await sendStandardPixel(GeneralPixel.pproFeedbackGeneralFeedback(description: description, + source: source.rawValue)) + } + + func sendReportIssuePixel(source: UnifiedFeedbackSource, category: String, subcategory: String, description: String, metadata: T?) async throws { + try await sendStandardPixel(GeneralPixel.pproFeedbackReportIssue(source: source.rawValue, + category: Category.from(category), + subcategory: Subcategory.from(subcategory), + description: description, + metadata: metadata?.toBase64() ?? "")) + } + + func sendFormShowPixel() { + PixelKit.fire(GeneralPixel.pproFeedbackFormShow, frequency: .dailyAndCount) + } + + func sendSubmitScreenShowPixel(source: UnifiedFeedbackSource, reportType: String, category: String, subcategory: String) { + PixelKit.fire(GeneralPixel.pproFeedbackSubmitScreenShow(source: source.rawValue, + reportType: ReportType.from(reportType), + category: Category.from(category), + subcategory: Subcategory.from(subcategory)), + frequency: .dailyAndCount) + } + + func sendSubmitScreenFAQClickPixel(source: UnifiedFeedbackSource, reportType: String, category: String, subcategory: String) { + PixelKit.fire(GeneralPixel.pproFeedbackSubmitScreenFAQClick(source: source.rawValue, + reportType: ReportType.from(reportType), + category: Category.from(category), + subcategory: Subcategory.from(subcategory)), + frequency: .dailyAndCount) + } +} diff --git a/DuckDuckGo/UnprotectedDomains/LocalUnprotectedDomains.swift b/DuckDuckGo/UnprotectedDomains/LocalUnprotectedDomains.swift index 86bdc1bcf6..a9103b81ca 100644 --- a/DuckDuckGo/UnprotectedDomains/LocalUnprotectedDomains.swift +++ b/DuckDuckGo/UnprotectedDomains/LocalUnprotectedDomains.swift @@ -19,6 +19,7 @@ import Common import Foundation import CoreData import BrowserServicesKit +import os.log typealias UnprotectedDomainsStore = CoreDataStore final class LocalUnprotectedDomains: DomainsProtectionStore { @@ -74,7 +75,7 @@ final class LocalUnprotectedDomains: DomainsProtectionStore { return try store.load(into: [:]) { $0[$1.value] = $1.id } } catch { - os_log("UnprotectedDomainStore: Failed to load Unprotected Domains", type: .error) + Logger.general.error("UnprotectedDomainStore: Failed to load Unprotected Domains") return [:] } } diff --git a/DuckDuckGo/Updates/BinaryOwnershipChecker.swift b/DuckDuckGo/Updates/BinaryOwnershipChecker.swift index acfda5b14c..e3825f92ae 100644 --- a/DuckDuckGo/Updates/BinaryOwnershipChecker.swift +++ b/DuckDuckGo/Updates/BinaryOwnershipChecker.swift @@ -18,6 +18,7 @@ import Foundation import Common +import os.log protocol BinaryOwnershipChecking { func isCurrentUserOwner() -> Bool @@ -43,7 +44,7 @@ final class BinaryOwnershipChecker: BinaryOwnershipChecking { } guard let binaryPath = Bundle.main.executablePath else { - os_log("Failed to get the binary path", log: .updates) + Logger.updates.debug("Failed to get the binary path") ownershipCache = false return false } @@ -56,9 +57,7 @@ final class BinaryOwnershipChecker: BinaryOwnershipChecking { return isOwner } } catch { - os_log("Failed to get binary file attributes: %{public}@", - log: .updates, - error.localizedDescription) + Logger.updates.error("Failed to get binary file attributes: \(error.localizedDescription, privacy: .public)") } ownershipCache = false diff --git a/DuckDuckGo/Updates/UpdateController.swift b/DuckDuckGo/Updates/UpdateController.swift index ddee5dcfa8..e7748d8689 100644 --- a/DuckDuckGo/Updates/UpdateController.swift +++ b/DuckDuckGo/Updates/UpdateController.swift @@ -24,6 +24,7 @@ import BrowserServicesKit import SwiftUIExtensions import PixelKit import SwiftUI +import os.log protocol UpdateControllerProtocol: AnyObject { @@ -98,7 +99,7 @@ final class UpdateController: NSObject, UpdateControllerProtocol { @UserDefaultsWrapper(key: .automaticUpdates, defaultValue: true) var areAutomaticUpdatesEnabled: Bool { didSet { - os_log("areAutomaticUpdatesEnabled: \(areAutomaticUpdatesEnabled)", log: .updates) + Logger.updates.debug("areAutomaticUpdatesEnabled: \(self.areAutomaticUpdatesEnabled)") if updater.updater.automaticallyDownloadsUpdates != areAutomaticUpdatesEnabled { updater.updater.automaticallyDownloadsUpdates = areAutomaticUpdatesEnabled @@ -151,13 +152,13 @@ final class UpdateController: NSObject, UpdateControllerProtocol { } func checkForUpdate() { - os_log("Checking for updates", log: .updates) + Logger.updates.debug("Checking for updates") updater.updater.checkForUpdates() } func checkForUpdateInBackground() { - os_log("Checking for updates in background", log: .updates) + Logger.updates.debug("Checking for updates in background") updater.updater.checkForUpdatesInBackground() } @@ -211,7 +212,7 @@ extension UpdateController: SPUStandardUserDriverDelegate { extension UpdateController: SPUUpdaterDelegate { func updater(_ updater: SPUUpdater, mayPerform updateCheck: SPUUpdateCheck) throws { - os_log("Updater started performing the update check. (isInternalUser: \(internalUserDecider.isInternalUser)", log: .updates) + Logger.updates.debug("Updater started performing the update check. (isInternalUser: \(self.internalUserDecider.isInternalUser)") onUpdateCheckStart() } @@ -234,10 +235,7 @@ extension UpdateController: SPUUpdaterDelegate { } func updater(_ updater: SPUUpdater, didAbortWithError error: Error) { - os_log("Updater did abort with error: %{public}@", - log: .updates, - error.localizedDescription) - + Logger.updates.error("Updater did abort with error: \(error.localizedDescription)") let errorCode = (error as NSError).code guard ![Int(Sparkle.SUError.noUpdateError.rawValue), Int(Sparkle.SUError.installationCanceledError.rawValue), @@ -250,9 +248,7 @@ extension UpdateController: SPUUpdaterDelegate { } func updater(_ updater: SPUUpdater, didFindValidUpdate item: SUAppcastItem) { - os_log("Updater did find valid update: %{public}@", - log: .updates, - "\(item.displayVersionString)(\(item.versionString))") + Logger.updates.debug("Updater did find valid update: \(item.displayVersionString)(\(item.versionString))") PixelKit.fire(DebugEvent(GeneralPixel.updaterDidFindUpdate)) @@ -265,9 +261,7 @@ extension UpdateController: SPUUpdaterDelegate { func updaterDidNotFindUpdate(_ updater: SPUUpdater, error: any Error) { let item = (error as NSError).userInfo["SULatestAppcastItemFound"] as? SUAppcastItem - os_log("Updater did not find update: %{public}@", - log: .updates, - "\(item?.displayVersionString ?? "")(\(item?.versionString ?? ""))") + Logger.updates.debug("Updater did not find update: \(String(describing: item?.displayVersionString))(\(String(describing: item?.versionString)))") if let item { // User is running the latest version updateCheckResult = UpdateCheckResult(item: item, isInstalled: true) @@ -277,9 +271,7 @@ extension UpdateController: SPUUpdaterDelegate { } func updater(_ updater: SPUUpdater, didDownloadUpdate item: SUAppcastItem) { - os_log("Updater did download update: %{public}@", - log: .updates, - "\(item.displayVersionString)(\(item.versionString))") + Logger.updates.debug("Updater did download update: \(item.displayVersionString)(\(item.versionString))") if automaticUpdateFlow { // For automatic updates, the available item has to be downloaded @@ -291,7 +283,7 @@ extension UpdateController: SPUUpdaterDelegate { } func updater(_ updater: SPUUpdater, didFinishUpdateCycleFor updateCheck: SPUUpdateCheck, error: (any Error)?) { - os_log("Updater did finish update cycle", log: .updates) + Logger.updates.debug("Updater did finish update cycle") onUpdateCheckEnd() } diff --git a/DuckDuckGo/Updates/UpdateNotificationPresenter.swift b/DuckDuckGo/Updates/UpdateNotificationPresenter.swift index e11ea6929b..5c68f0c64b 100644 --- a/DuckDuckGo/Updates/UpdateNotificationPresenter.swift +++ b/DuckDuckGo/Updates/UpdateNotificationPresenter.swift @@ -19,13 +19,14 @@ import Cocoa import SwiftUI import Common +import os.log final class UpdateNotificationPresenter { static let presentationTimeInterval: TimeInterval = 10 func showUpdateNotification(icon: NSImage, text: String, buttonText: String? = nil, presentMultiline: Bool = false) { - os_log("Notification presented: \(text)", log: .updates) + Logger.updates.debug("Notification presented: \(text)") DispatchQueue.main.async { guard let windowController = WindowControllersManager.shared.lastKeyMainWindowController ?? WindowControllersManager.shared.mainWindowControllers.last, diff --git a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormView.swift b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormView.swift index fb3f6ccf4b..a34ccfcdc1 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormView.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormView.swift @@ -39,7 +39,7 @@ struct VPNFeedbackFormView: View { switch viewModel.viewState { case .feedbackPending, .feedbackSending, .feedbackSendingFailed: VPNFeedbackFormBodyView() - .padding([.top, .leading, .trailing], 20) + .padding([.top, .leading, .trailing], 20) if viewModel.viewState == .feedbackSendingFailed { Text(UserText.vpnFeedbackFormSendingConfirmationError) diff --git a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift index 590d958513..22e38142a2 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewController.swift @@ -81,15 +81,15 @@ final class VPNFeedbackFormViewController: NSViewController { .receive(on: DispatchQueue.main) .sink { [weak self] _ in self?.updateViewHeight() - } - .store(in: &cancellables) + } + .store(in: &cancellables) viewModel.$selectedFeedbackCategory .receive(on: DispatchQueue.main) .sink { [weak self] _ in self?.updateViewHeight() - } - .store(in: &cancellables) + } + .store(in: &cancellables) } private func updateViewHeight() { diff --git a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewModel.swift b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewModel.swift index 70aeb3e110..3fff69c5d2 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewModel.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNFeedbackFormViewModel.swift @@ -85,7 +85,7 @@ final class VPNFeedbackFormViewModel: ObservableObject { self.viewState = .feedbackSending do { - let metadata = await self.metadataCollector.collectMetadata() + let metadata = await self.metadataCollector.collectVPNMetadata() try await self.feedbackSender.send(metadata: metadata, category: selectedFeedbackCategory, userText: feedbackFormText) self.viewState = .feedbackSent } catch { diff --git a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift index 1c27d86d1c..778b039358 100644 --- a/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift +++ b/DuckDuckGo/VPNFeedbackForm/VPNMetadataCollector.swift @@ -115,7 +115,7 @@ struct VPNMetadata: Encodable { } protocol VPNMetadataCollector { - func collectMetadata() async -> VPNMetadata + func collectVPNMetadata() async -> VPNMetadata } final class DefaultVPNMetadataCollector: VPNMetadataCollector { @@ -162,7 +162,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { } @MainActor - func collectMetadata() async -> VPNMetadata { + func collectVPNMetadata() async -> VPNMetadata { let appInfoMetadata = collectAppInfoMetadata() let deviceInfoMetadata = collectDeviceInfoMetadata() let networkInfoMetadata = await collectNetworkInformation() @@ -216,17 +216,17 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { } private func getMachineArchitecture() -> String { - #if arch(arm) - return "arm" - #elseif arch(arm64) - return "arm64" - #elseif arch(i386) - return "i386" - #elseif arch(x86_64) - return "x86_64" - #else - return "unknown" - #endif +#if arch(arm) + return "arm" +#elseif arch(arm64) + return "arm64" +#elseif arch(i386) + return "i386" +#elseif arch(x86_64) + return "x86_64" +#else + return "unknown" +#endif } func collectNetworkInformation() async -> VPNMetadata.NetworkInfo { @@ -329,3 +329,13 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { } } + +// MARK: - Unified feedback form support + +extension VPNMetadata: UnifiedFeedbackMetadata {} + +extension DefaultVPNMetadataCollector: UnifiedMetadataCollector { + func collectMetadata() async -> VPNMetadata { + await collectVPNMetadata() + } +} diff --git a/DuckDuckGo/Waitlist/IPCServiceLauncher.swift b/DuckDuckGo/Waitlist/IPCServiceLauncher.swift index f898790aa6..8c6aa5d593 100644 --- a/DuckDuckGo/Waitlist/IPCServiceLauncher.swift +++ b/DuckDuckGo/Waitlist/IPCServiceLauncher.swift @@ -69,7 +69,7 @@ final class IPCServiceLauncher { runningApplication = try await appLauncher.runApp(withCommand: UDSLaunchAppCommand()) case .loginItem(let loginItem, let loginItemsManager): - try loginItemsManager.throwingEnableLoginItems([loginItem], log: .disabled) + try loginItemsManager.throwingEnableLoginItems([loginItem]) } } diff --git a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift index 64540b2924..1569d68f97 100644 --- a/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift +++ b/DuckDuckGo/Waitlist/VPNFeatureGatekeeper.swift @@ -47,7 +47,6 @@ struct DefaultVPNFeatureGatekeeper: VPNFeatureGatekeeper { init(networkProtectionFeatureActivation: NetworkProtectionFeatureActivation = NetworkProtectionKeychainTokenStore(), vpnUninstaller: VPNUninstalling = VPNUninstaller(), defaults: UserDefaults = .netP, - log: OSLog = .networkProtection, subscriptionManager: SubscriptionManager) { self.networkProtectionFeatureActivation = networkProtectionFeatureActivation diff --git a/DuckDuckGo/Waitlist/VPNUninstaller.swift b/DuckDuckGo/Waitlist/VPNUninstaller.swift index b8afa97d09..d164cf21b3 100644 --- a/DuckDuckGo/Waitlist/VPNUninstaller.swift +++ b/DuckDuckGo/Waitlist/VPNUninstaller.swift @@ -124,7 +124,6 @@ final class VPNUninstaller: VPNUninstalling { } } - private let log: OSLog private let ipcServiceLauncher: IPCServiceLauncher private let loginItemsManager: LoginItemsManaging private let pinningManager: LocalPinningManager @@ -144,16 +143,13 @@ final class VPNUninstaller: VPNUninstalling { settings: VPNSettings = .init(defaults: .netP), ipcClient: VPNControllerIPCClient = VPNControllerUDSClient(), vpnMenuLoginItem: LoginItem = .vpnMenu, - pixelKit: PixelFiring? = PixelKit.shared, - log: OSLog = .networkProtection) { + pixelKit: PixelFiring? = PixelKit.shared) { let vpnAgentBundleID = Bundle.main.vpnMenuAgentBundleId let appLauncher = AppLauncher(appBundleURL: Bundle.main.vpnMenuAgentURL) let ipcServiceLaunchMethod = IPCServiceLauncher.LaunchMethod.direct( bundleID: vpnAgentBundleID, appLauncher: appLauncher) - - self.log = log self.ipcServiceLauncher = ipcServiceLauncher ?? IPCServiceLauncher(launchMethod: ipcServiceLaunchMethod) self.loginItemsManager = loginItemsManager self.pinningManager = pinningManager diff --git a/DuckDuckGo/Windows/View/WindowControllersManager.swift b/DuckDuckGo/Windows/View/WindowControllersManager.swift index 3cbb3630d8..1f54a4936e 100644 --- a/DuckDuckGo/Windows/View/WindowControllersManager.swift +++ b/DuckDuckGo/Windows/View/WindowControllersManager.swift @@ -19,6 +19,8 @@ import Cocoa import Combine import Common +import os.log +import BrowserServicesKit @MainActor protocol WindowControllersManagerProtocol { @@ -36,14 +38,18 @@ protocol WindowControllersManagerProtocol { @MainActor final class WindowControllersManager: WindowControllersManagerProtocol { - static let shared = WindowControllersManager(pinnedTabsManager: Application.appDelegate.pinnedTabsManager) + static let shared = WindowControllersManager(pinnedTabsManager: Application.appDelegate.pinnedTabsManager, + subscriptionFeatureAvailability: DefaultSubscriptionFeatureAvailability() + ) var activeViewController: MainViewController? { lastKeyMainWindowController?.mainViewController } - init(pinnedTabsManager: PinnedTabsManager) { + init(pinnedTabsManager: PinnedTabsManager, + subscriptionFeatureAvailability: SubscriptionFeatureAvailability) { self.pinnedTabsManager = pinnedTabsManager + self.subscriptionFeatureAvailability = subscriptionFeatureAvailability } /** @@ -52,6 +58,7 @@ final class WindowControllersManager: WindowControllersManagerProtocol { @Published private(set) var isInInitialState: Bool = true @Published private(set) var mainWindowControllers = [MainWindowController]() private(set) var pinnedTabsManager: PinnedTabsManager + private let subscriptionFeatureAvailability: SubscriptionFeatureAvailability weak var lastKeyMainWindowController: MainWindowController? { didSet { @@ -89,7 +96,7 @@ final class WindowControllersManager: WindowControllersManagerProtocol { func unregister(_ windowController: MainWindowController) { guard let idx = mainWindowControllers.firstIndex(of: windowController) else { - os_log("WindowControllersManager: Window Controller not registered", type: .error) + Logger.general.error("WindowControllersManager: Window Controller not registered") return } mainWindowControllers.remove(at: idx) @@ -226,8 +233,14 @@ extension WindowControllersManager { windowController.mainViewController.navigationBarViewController.showNetworkProtectionStatus() } - func showShareFeedbackModal() { - let feedbackFormViewController = VPNFeedbackFormViewController() + func showShareFeedbackModal(source: UnifiedFeedbackSource = .default) { + let feedbackFormViewController: NSViewController = { + if subscriptionFeatureAvailability.usesUnifiedFeedbackForm { + return UnifiedFeedbackFormViewController(source: source) + } else { + return VPNFeedbackFormViewController() + } + }() let feedbackFormWindowController = feedbackFormViewController.wrappedInWindowController() guard let feedbackFormWindow = feedbackFormWindowController.window else { diff --git a/DuckDuckGo/YoutubePlayer/DuckURLSchemeHandler.swift b/DuckDuckGo/YoutubePlayer/DuckURLSchemeHandler.swift index 3be41aeab5..513aa59d02 100644 --- a/DuckDuckGo/YoutubePlayer/DuckURLSchemeHandler.swift +++ b/DuckDuckGo/YoutubePlayer/DuckURLSchemeHandler.swift @@ -67,7 +67,6 @@ extension DuckURLSchemeHandler { private func handleNativeUIPages(requestURL: URL, urlSchemeTask: WKURLSchemeTask) { // return empty page for native UI pages navigations (like the Home page or Settings) if the request is not for the Duck Player let data = Self.emptyHtml.utf8data - let response = URLResponse(url: requestURL, mimeType: "text/html", expectedContentLength: data.count, @@ -108,7 +107,7 @@ private extension DuckURLSchemeHandler { private extension DuckURLSchemeHandler { func handleSpecialPages(urlSchemeTask: WKURLSchemeTask) { guard let requestURL = urlSchemeTask.request.url else { - assertionFailure("No URL for Onboarding scheme handler") + assertionFailure("No URL for Special Pages scheme handler") return } guard let (response, data) = response(for: requestURL) else { return } diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index 96fc3c0878..df118fecf3 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -25,6 +25,7 @@ import BrowserServicesKit import PixelKit import Networking import Subscription +import os.log @objc(Application) final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { @@ -32,7 +33,7 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { private let subscriptionManager: SubscriptionManager override init() { - os_log(.error, log: .dbpBackgroundAgent, "🟢 DBP background Agent starting: %{public}d", NSRunningApplication.current.processIdentifier) + Logger.dbpBackgroundAgent.info("🟢 Starting: \(NSRunningApplication.current.processIdentifier, privacy: .public)") let dryRun: Bool #if DEBUG @@ -62,7 +63,7 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { // prevent agent from running twice if let anotherInstance = NSRunningApplication.runningApplications(withBundleIdentifier: Bundle.main.bundleIdentifier!).first(where: { $0 != .current }) { - os_log(.error, log: .dbpBackgroundAgent, "🔴 Stopping: another instance is running: %{public}d.", anotherInstance.processIdentifier) + Logger.dbpBackgroundAgent.error("Stopping: another instance is running: \(anotherInstance.processIdentifier, privacy: .public).") pixelHandler.fire(.backgroundAgentStartedStoppingDueToAnotherInstanceRunning) exit(0) } @@ -96,7 +97,7 @@ final class DuckDuckGoDBPBackgroundAgentAppDelegate: NSObject, NSApplicationDele @MainActor func applicationDidFinishLaunching(_ aNotification: Notification) { - os_log("DuckDuckGoAgent started", log: .dbpBackgroundAgent, type: .info) + Logger.dbpBackgroundAgent.info("DuckDuckGoAgent started") let redeemUseCase = RedeemUseCase(authenticationService: AuthenticationService(), authenticationRepository: KeychainAuthenticationData()) diff --git a/DuckDuckGoDBPBackgroundAgent/Logger+DBPBackgroundAgent.swift b/DuckDuckGoDBPBackgroundAgent/Logger+DBPBackgroundAgent.swift new file mode 100644 index 0000000000..a3dc66ebdd --- /dev/null +++ b/DuckDuckGoDBPBackgroundAgent/Logger+DBPBackgroundAgent.swift @@ -0,0 +1,24 @@ +// +// Logger+DBPBackgroundAgent.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import os.log + +extension Logger { + static var dbpBackgroundAgent = { Logger(subsystem: "DBP Background Agent", category: "") }() +} diff --git a/DuckDuckGoDBPBackgroundAgent/UserText.swift b/DuckDuckGoDBPBackgroundAgent/UserText.swift index 608cab9e14..05eb833e12 100644 --- a/DuckDuckGoDBPBackgroundAgent/UserText.swift +++ b/DuckDuckGoDBPBackgroundAgent/UserText.swift @@ -21,6 +21,6 @@ import Foundation final class UserText { // MARK: - Status Menu - static let networkProtectionStatusMenuShareFeedback = NSLocalizedString("network.protection.status.menu.share.feedback", value: "Share VPN Feedback…", comment: "The status menu 'Share VPN Feedback' menu item") + static let networkProtectionStatusMenuSendFeedback = NSLocalizedString("network.protection.status.menu.send.feedback", value: "Send Feedback…", comment: "The status menu 'Send Feedback' menu item") static let networkProtectionStatusMenuOpenDuckDuckGo = NSLocalizedString("network.protection.status.menu.open.duckduckgo", value: "Open DuckDuckGo…", comment: "The status menu 'Open DuckDuckGo' menu item") } diff --git a/DuckDuckGoNotifications/DuckDuckGoNotificationsAppDelegate.swift b/DuckDuckGoNotifications/DuckDuckGoNotificationsAppDelegate.swift index 236b5dad27..1263fc422f 100644 --- a/DuckDuckGoNotifications/DuckDuckGoNotificationsAppDelegate.swift +++ b/DuckDuckGoNotifications/DuckDuckGoNotificationsAppDelegate.swift @@ -23,17 +23,18 @@ import Common import NetworkExtension import NetworkProtection import VPNAppLauncher +import os.log @objc(Application) final class DuckDuckGoNotificationsApplication: NSApplication { private let _delegate = DuckDuckGoNotificationsAppDelegate() override init() { - os_log(.error, log: .networkProtection, "🟢 Notifications Agent starting: %{public}d", ProcessInfo.processInfo.processIdentifier) + Logger.networkProtection.error("🟢 Notifications Agent starting: \(ProcessInfo.processInfo.processIdentifier, privacy: .public)") // prevent agent from running twice if let anotherInstance = NSRunningApplication.runningApplications(withBundleIdentifier: Bundle.main.bundleIdentifier!).first(where: { $0 != .current }) { - os_log(.error, log: .networkProtection, "🔴 Stopping: another instance is running: %{public}d.", anotherInstance.processIdentifier) + Logger.networkProtection.error("Stopping: another instance is running: \(anotherInstance.processIdentifier, privacy: .public).") exit(EXIT_SUCCESS) } @@ -69,14 +70,14 @@ final class DuckDuckGoNotificationsAppDelegate: NSObject, NSApplicationDelegate private var cancellables = Set() func applicationDidFinishLaunching(_ aNotification: Notification) { - os_log("Login item finished launching", log: .networkProtectionLoginItemLog, type: .info) + Logger.networkProtection.info("Login item finished launching") startObservingVPNStatusChanges() - os_log("Login item listening") + Logger.networkProtection.info("Login item listening") } private func startObservingVPNStatusChanges() { - os_log("Register with sysex") + Logger.networkProtection.info("Register with sysex") distributedNotificationCenter.publisher(for: .showIssuesStartedNotification) .receive(on: DispatchQueue.main) @@ -110,7 +111,7 @@ final class DuckDuckGoNotificationsAppDelegate: NSObject, NSApplicationDelegate }.store(in: &cancellables) distributedNotificationCenter.publisher(for: .serverSelected).sink { [weak self] _ in - os_log("Got notification: listener started") + Logger.networkProtection.info("Got notification: listener started") self?.notificationsPresenter.requestAuthorization() }.store(in: &cancellables) @@ -128,33 +129,33 @@ final class DuckDuckGoNotificationsAppDelegate: NSObject, NSApplicationDelegate // MARK: - Showing Notifications func showConnectedNotification(serverLocation: String?) { - os_log("Presenting reconnected notification", log: .networkProtection, type: .info) + Logger.networkProtection.info("Presenting reconnected notification") notificationsPresenter.showConnectedNotification(serverLocation: serverLocation, snoozeEnded: false) } func showReconnectingNotification() { - os_log("Presenting reconnecting notification", log: .networkProtection, type: .info) + Logger.networkProtection.info("Presenting reconnecting notification") notificationsPresenter.showReconnectingNotification() } func showConnectionFailureNotification() { - os_log("Presenting failure notification", log: .networkProtection, type: .info) + Logger.networkProtection.info("Presenting failure notification") notificationsPresenter.showConnectionFailureNotification() } func showSupersededNotification() { - os_log("Presenting Superseded notification", log: .networkProtection, type: .info) + Logger.networkProtection.info("Presenting Superseded notification") notificationsPresenter.showSupersededNotification() } func showEntitlementNotification() { - os_log("Presenting Entitlements notification", log: .networkProtection, type: .info) + Logger.networkProtection.info("Presenting Entitlements notification") notificationsPresenter.showEntitlementNotification() } func showTestNotification() { - os_log("Presenting test notification", log: .networkProtection, type: .info) + Logger.networkProtection.info("Presenting test notification") notificationsPresenter.showTestNotification() } diff --git a/DuckDuckGoNotifications/Logging.swift b/DuckDuckGoNotifications/Logging.swift deleted file mode 100644 index 30c5d6fbe5..0000000000 --- a/DuckDuckGoNotifications/Logging.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// Logging.swift -// -// Copyright © 2023 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import os.log - -extension OSLog { - - public static var networkProtectionLoginItemLog: OSLog { - Logging.networkProtectionLoginItemLoggingEnabled ? Logging.networkProtectionLoginItemLog : .disabled - } - - public static var networkProtectionIPCLoginItemLog: OSLog { - Logging.networkProtectionIPCLoginItemLoggingEnabled ? Logging.networkProtectionIPCLoginItemLog : .disabled - } - - public static var networkProtectionMemoryLog: OSLog { - Logging.networkProtectionMemoryLoggingEnabled ? Logging.networkProtectionMemoryLog : .disabled - } -} - -struct Logging { - - static let subsystem = Bundle.main.bundleIdentifier ?? "DuckDuckGo" - - fileprivate static let networkProtectionLoginItemLoggingEnabled = false - fileprivate static let networkProtectionLoginItemLog: OSLog = OSLog(subsystem: subsystem, category: "VPN: Login Item") - - fileprivate static let networkProtectionIPCLoginItemLoggingEnabled = false - fileprivate static let networkProtectionIPCLoginItemLog: OSLog = OSLog(subsystem: subsystem, category: "VPN: IPC (Login Item)") - - fileprivate static let networkProtectionMemoryLoggingEnabled = false - fileprivate static let networkProtectionMemoryLog: OSLog = OSLog(subsystem: subsystem, category: "VPN: Memory") -} diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index b205ba17d8..f271288894 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -30,24 +30,20 @@ import ServiceManagement import PixelKit import Subscription import VPNAppLauncher +import os.log @objc(Application) final class DuckDuckGoVPNApplication: NSApplication { - public let accountManager: AccountManager + public var accountManager: AccountManager private let _delegate: DuckDuckGoVPNAppDelegate override init() { - os_log(.default, - log: .networkProtection, - "🟢 Status Bar Agent starting\nPath: (%{public}@)\nVersion: %{public}@\nPID: %{public}d", - Bundle.main.bundlePath, - "\(Bundle.main.versionNumber!).\(Bundle.main.buildNumber)", - NSRunningApplication.current.processIdentifier) + Logger.networkProtection.debug("🟢 Status Bar Agent starting\nPath: (\(Bundle.main.bundlePath, privacy: .public))\nVersion: \("\(Bundle.main.versionNumber!).\(Bundle.main.buildNumber)", privacy: .public)\nPID: \(NSRunningApplication.current.processIdentifier, privacy: .public)") // prevent agent from running twice if let anotherInstance = NSRunningApplication.runningApplications(withBundleIdentifier: Bundle.main.bundleIdentifier!).first(where: { $0 != .current }) { - os_log(.error, log: .networkProtection, "🔴 Stopping: another instance is running: %{public}d.", anotherInstance.processIdentifier) + Logger.networkProtection.error("Stopping: another instance is running: \(anotherInstance.processIdentifier, privacy: .public).") exit(0) } @@ -62,9 +58,9 @@ final class DuckDuckGoVPNApplication: NSApplication { settings: UserDefaultsCacheSettings(defaultExpirationInterval: .minutes(20))) let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup))) accountManager = DefaultAccountManager(accessTokenStorage: accessTokenStorage, - entitlementsCache: entitlementsCache, - subscriptionEndpointService: subscriptionEndpointService, - authEndpointService: authEndpointService) + entitlementsCache: entitlementsCache, + subscriptionEndpointService: subscriptionEndpointService, + authEndpointService: authEndpointService) _delegate = DuckDuckGoVPNAppDelegate(accountManager: accountManager, accessTokenStorage: accessTokenStorage, @@ -73,12 +69,13 @@ final class DuckDuckGoVPNApplication: NSApplication { setupPixelKit() self.delegate = _delegate + accountManager.delegate = _delegate #if DEBUG if accountManager.accessToken != nil { - os_log(.error, log: .networkProtection, "🟢 VPN Agent found token") + Logger.networkProtection.debug("🟢 VPN Agent found token") } else { - os_log(.error, log: .networkProtection, "🔴 VPN Agent found no token") + Logger.networkProtection.error("VPN Agent found no token") } #endif } @@ -331,7 +328,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { StatusBarMenu.MenuItem(name: UserText.networkProtectionStatusMenuFAQ, action: { [weak self] in try? await self?.appLauncher.launchApp(withCommand: VPNAppLaunchCommand.showFAQ) }), - StatusBarMenu.MenuItem(name: UserText.networkProtectionStatusMenuShareFeedback, action: { [weak self] in + StatusBarMenu.MenuItem(name: UserText.networkProtectionStatusMenuSendFeedback, action: { [weak self] in try? await self?.appLauncher.launchApp(withCommand: VPNAppLaunchCommand.shareFeedback) }), StatusBarMenu.MenuItem(name: UserText.networkProtectionStatusMenuOpenDuckDuckGo, action: { [weak self] in @@ -361,7 +358,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { APIRequest.Headers.setUserAgent(UserAgent.duckDuckGoUserAgent()) - os_log("DuckDuckGoVPN started", log: .networkProtectionLoginItemLog) + Logger.networkProtection.info("DuckDuckGoVPN started") setupMenuVisibility() @@ -440,3 +437,11 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { } } } + +extension DuckDuckGoVPNAppDelegate: AccountManagerKeychainAccessDelegate { + + public func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) { + PixelKit.fire(PrivacyProErrorPixel.privacyProKeychainAccessError(accessType: accessType, accessError: error), + frequency: .dailyAndCount) + } +} diff --git a/DuckDuckGoVPN/TunnelControllerIPCService.swift b/DuckDuckGoVPN/TunnelControllerIPCService.swift index c95c4fc0b1..0be0213a78 100644 --- a/DuckDuckGoVPN/TunnelControllerIPCService.swift +++ b/DuckDuckGoVPN/TunnelControllerIPCService.swift @@ -89,7 +89,7 @@ final class TunnelControllerIPCService { self.defaults = defaults self.pixelKit = pixelKit - udsServer = UDSServer(socketFileURL: VPNIPCResources.socketFileURL, log: .networkProtectionIPCLog) + udsServer = UDSServer(socketFileURL: VPNIPCResources.socketFileURL) subscribeToErrorChanges() subscribeToStatusUpdates() diff --git a/DuckDuckGoVPN/UserText.swift b/DuckDuckGoVPN/UserText.swift index e7fbe48d86..0e7d32e691 100644 --- a/DuckDuckGoVPN/UserText.swift +++ b/DuckDuckGoVPN/UserText.swift @@ -25,4 +25,5 @@ final class UserText { static let networkProtectionStatusMenuFAQ = NSLocalizedString("network.protection.status.menu.faq", value: "FAQs and Support…", comment: "The status menu 'FAQ' menu item") static let networkProtectionStatusMenuOpenDuckDuckGo = NSLocalizedString("network.protection.status.menu.vpn.open-duckduckgo", value: "Open DuckDuckGo…", comment: "The status menu 'Open DuckDuckGo' menu item") static let networkProtectionStatusMenuShareFeedback = NSLocalizedString("network.protection.status.menu.share.feedback", value: "Share VPN Feedback…", comment: "The status menu 'Share VPN Feedback' menu item") + static let networkProtectionStatusMenuSendFeedback = NSLocalizedString("network.protection.status.menu.send.feedback", value: "Send Feedback…", comment: "The status menu 'Send Feedback' menu item") } diff --git a/DuckDuckGoVPN/VPNAppEventsHandler.swift b/DuckDuckGoVPN/VPNAppEventsHandler.swift index 5ac88e4a2b..612b10ccb0 100644 --- a/DuckDuckGoVPN/VPNAppEventsHandler.swift +++ b/DuckDuckGoVPN/VPNAppEventsHandler.swift @@ -19,6 +19,7 @@ import Foundation import Common import NetworkProtection +import os.log final class VPNAppEventsHandler { @@ -36,7 +37,7 @@ final class VPNAppEventsHandler { } let restartTunnel = { - os_log(.info, log: .networkProtection, "App updated from %{public}s to %{public}s: updating login items", versionStore.lastAgentVersionRun ?? "null", currentVersion) + Logger.networking.info("App updated from \(versionStore.lastAgentVersionRun ?? "null", privacy: .public) to \(currentVersion, privacy: .public): updating login items") self.restartTunnel() } diff --git a/Gemfile.lock b/Gemfile.lock index 33a08b76f8..1277000bcc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,9 @@ GIT remote: https://github.com/duckduckgo/fastlane-plugin-ddg_apple_automation - revision: 766668da77fe379e9cae0ae183083ca82fe1b663 - tag: 0.1.0 + revision: 5d267b66e9c63f879eecbb4ae1f40cb42547026e + tag: 0.2.0 specs: - fastlane-plugin-ddg_apple_automation (0.1.0) + fastlane-plugin-ddg_apple_automation (0.2.0) GEM remote: https://rubygems.org/ diff --git a/IntegrationTests/AutoconsentIntegrationTests.swift b/IntegrationTests/AutoconsentIntegrationTests.swift index f0720c46ef..ce5be346e4 100644 --- a/IntegrationTests/AutoconsentIntegrationTests.swift +++ b/IntegrationTests/AutoconsentIntegrationTests.swift @@ -20,6 +20,7 @@ import Combine import Common import PrivacyDashboard import XCTest +import os.log @testable import DuckDuckGo_Privacy_Browser @@ -150,11 +151,11 @@ class AutoconsentIntegrationTests: XCTestCase { .first() .promise() - os_log("starting navigation to http://privacy-test-pages.site/features/autoconsent/banner.html") + Logger.general.debug("starting navigation to http://privacy-test-pages.site/features/autoconsent/banner.html") let navigation = tab.setUrl(url, source: .link) navigation?.appendResponder(navigationResponse: { response in - os_log("navigationResponse: %s", "\(String(describing: response))") + Logger.general.debug("navigationResponse: \(String(describing: response))") // cause UserScripts reload (ContentBlockingUpdating) WebTrackingProtectionPreferences.shared.isGPCEnabled = true @@ -164,7 +165,7 @@ class AutoconsentIntegrationTests: XCTestCase { }) _=await navigation?.result - os_log("navigation done") + Logger.general.debug("navigation done") let cookieConsentManaged = try await cookieConsentManagedPromise.value XCTAssertTrue(cookieConsentManaged == true) diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index 871782e9ca..a79685ddb4 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "186.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "190.0.0"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper"), ], diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionFeature.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionFeature.swift index be97096c4e..4e5732d38c 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionFeature.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionFeature.swift @@ -20,7 +20,7 @@ import Foundation import WebKit import BrowserServicesKit import UserScript -import Common +import os.log protocol CCFCommunicationDelegate: AnyObject { func loadURL(url: URL) async @@ -60,20 +60,20 @@ struct DataBrokerProtectionFeature: Subfeature { case .actionError: return onActionError } } else { - os_log("Cant parse method: %{public}@", log: .action, methodName) + Logger.action.debug("Cant parse method: \(methodName, privacy: .public)") return nil } } func onActionCompleted(params: Any, original: WKScriptMessage) async throws -> Encodable? { - os_log("Action completed", log: .action) + Logger.action.debug("Action completed") await parseActionCompleted(params: params) return nil } func parseActionCompleted(params: Any) async { - os_log("Parse action completed", log: .action) + Logger.action.debug("Parse action completed") guard let data = try? JSONSerialization.data(withJSONObject: params), let result = try? JSONDecoder().decode(CCFResult.self, from: data) else { @@ -91,7 +91,7 @@ struct DataBrokerProtectionFeature: Subfeature { } func parseSuccess(success: CCFSuccessResponse) async { - os_log("Parse success: %{public}@", log: .action, String(describing: success.actionType.rawValue)) + Logger.action.debug("Parse success: \(String(describing: success.actionType.rawValue), privacy: .public)") switch success.response { case .navigate(let navigate): @@ -114,7 +114,7 @@ struct DataBrokerProtectionFeature: Subfeature { func onActionError(params: Any, original: WKScriptMessage) async throws -> Encodable? { let error = DataBrokerProtectionError.parse(params: params) - os_log("Action Error: %{public}@", log: .action, String(describing: error.localizedDescription)) + Logger.action.debug("Action Error: \(String(describing: error.localizedDescription), privacy: .public)") await delegate?.onError(error: error) return nil @@ -129,7 +129,7 @@ struct DataBrokerProtectionFeature: Subfeature { assertionFailure("Cannot continue without broker instance") return } - os_log("Pushing into WebView: %@ params %@", log: .action, method.rawValue, String(describing: params)) + Logger.action.debug("Pushing into WebView: \(method.rawValue) params \(String(describing: params))") broker.push(method: method.rawValue, params: params, for: self, into: webView) } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift index 245020941c..c67450cedb 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift @@ -19,7 +19,7 @@ import Foundation import WebKit import BrowserServicesKit import UserScript -import Common +import os.log import Combine final class DataBrokerUserContentController: WKUserContentController { @@ -51,7 +51,7 @@ final class DataBrokerUserContentController: WKUserContentController { @MainActor public func cleanUpBeforeClosing() { - os_log("Cleaning up DBP user scripts", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Cleaning up DBP user scripts") self.removeAllUserScripts() self.removeAllScriptMessageHandlers() @@ -61,7 +61,7 @@ final class DataBrokerUserContentController: WKUserContentController { } deinit { - os_log("DataBrokerUserContentController Deinit", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("DataBrokerUserContentController Deinit") } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/WebViewHandler.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/WebViewHandler.swift index 26be41ebee..ebb3537640 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/WebViewHandler.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/WebViewHandler.swift @@ -21,6 +21,7 @@ import WebKit import BrowserServicesKit import UserScript import Common +import os.log protocol WebViewHandler: NSObject { func initializeWebView(showWebView: Bool) async @@ -82,7 +83,7 @@ final class DataBrokerProtectionWebViewHandler: NSObject, WebViewHandler { func load(url: URL) async throws { webView?.load(url) - os_log("Loading URL: %@", log: .action, String(describing: url.absoluteString)) + Logger.action.debug("Loading URL: \(String(describing: url.absoluteString))") try await waitForWebViewLoad() } @@ -93,11 +94,11 @@ final class DataBrokerProtectionWebViewHandler: NSObject, WebViewHandler { } func finish() { - os_log("WebViewHandler finished", log: .action) + Logger.action.debug("WebViewHandler finished") webView?.stopLoading() userContentController?.cleanUpBeforeClosing() WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache], modifiedSince: Date(timeIntervalSince1970: 0)) { - os_log("WKWebView data store deleted correctly", log: .action) + Logger.action.debug("WKWebView data store deleted correctly") } webViewConfiguration = nil @@ -107,7 +108,7 @@ final class DataBrokerProtectionWebViewHandler: NSObject, WebViewHandler { } deinit { - os_log("WebViewHandler Deinit", log: .action) + Logger.action.debug("WebViewHandler Deinit") } func waitForWebViewLoad() async throws { @@ -117,7 +118,7 @@ final class DataBrokerProtectionWebViewHandler: NSObject, WebViewHandler { } func execute(action: Action, data: CCFRequestData) { - os_log("Executing action: %{public}@", log: .action, String(describing: action.actionType.rawValue)) + Logger.action.debug("Executing action: \(String(describing: action.actionType.rawValue), privacy: .public)") userContentController?.dataBrokerUserScripts?.dataBrokerFeature.pushAction( method: .onActionReceived, @@ -154,7 +155,7 @@ final class DataBrokerProtectionWebViewHandler: NSObject, WebViewHandler { try htmlString.write(to: fileURL, atomically: true, encoding: .utf8) print("HTML content saved to file: \(fileURL)") } catch { - os_log(.error, "Error writing HTML content to file: \(error)") + Logger.action.error("Error writing HTML content to file: \(error)") } } @@ -200,20 +201,20 @@ extension DataBrokerProtectionWebViewHandler: WKNavigationDelegate { } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - os_log("WebViewHandler didFinish", log: .action) + Logger.action.debug("WebViewHandler didFinish") self.activeContinuation?.resume() self.activeContinuation = nil } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { - os_log("WebViewHandler didFail: %{public}@", log: .action, String(describing: error.localizedDescription)) + Logger.action.error("WebViewHandler didFail: \(error.localizedDescription, privacy: .public)") self.activeContinuation?.resume(throwing: error) self.activeContinuation = nil } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { - os_log("WebViewHandler didFailProvisionalNavigation: %{public}@", log: .action, String(describing: error.localizedDescription)) + Logger.action.error("WebViewHandler didFailProvisionalNavigation: \(error.localizedDescription, privacy: .public)") self.activeContinuation?.resume(throwing: error) self.activeContinuation = nil } @@ -225,7 +226,7 @@ extension DataBrokerProtectionWebViewHandler: WKNavigationDelegate { } if statusCode >= 400 { - os_log("WebViewHandler failed with status code: %{public}@", log: .action, String(describing: statusCode)) + Logger.action.debug("WebViewHandler failed with status code: \(String(describing: statusCode), privacy: .public)") self.activeContinuation?.resume(throwing: DataBrokerProtectionError.httpError(code: statusCode)) self.activeContinuation = nil } @@ -264,6 +265,6 @@ private class WebView: WKWebView { deinit { configuration.userContentController.removeAllUserScripts() - os_log("DBP WebView Deinit", log: .action) + Logger.action.debug("DBP WebView Deinit") } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift index 5a33e63831..00f51edc6f 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift @@ -18,6 +18,7 @@ import Foundation import Common +import os.log public protocol DataBrokerProtectionDataManaging { var cache: InMemoryDataCache { get } @@ -65,7 +66,7 @@ public class DataBrokerProtectionDataManager: DataBrokerProtectionDataManaging { public func fetchProfile() throws -> DataBrokerProtectionProfile? { if cache.profile != nil { - os_log("Returning cached profile", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Returning cached profile") return cache.profile } @@ -85,7 +86,7 @@ public class DataBrokerProtectionDataManager: DataBrokerProtectionDataManaging { cache.profile = profile return profile } else { - os_log("No profile found", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("No profile found") return nil } } @@ -94,13 +95,13 @@ public class DataBrokerProtectionDataManager: DataBrokerProtectionDataManaging { if let profile = try database.fetchProfile() { cache.profile = profile } else { - os_log("No profile found", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("No profile found") } } public func fetchBrokerProfileQueryData(ignoresCache: Bool = false) throws -> [BrokerProfileQueryData] { if !ignoresCache, !cache.brokerProfileQueryData.isEmpty { - os_log("Returning cached brokerProfileQueryData", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Returning cached brokerProfileQueryData") return cache.brokerProfileQueryData } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift index ea5afe6e22..fa24220206 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift @@ -19,6 +19,7 @@ import Foundation import Common import SecureStorage +import os.log protocol DataBrokerProtectionRepository { func save(_ profile: DataBrokerProtectionProfile) async throws @@ -93,7 +94,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { try await saveNewProfile(profile, vault: vault) } } catch { - os_log("Database error: save profile, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: save profile, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.save profile")) throw error } @@ -104,7 +105,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(reporter: secureVaultErrorReporter) return try vault.fetchProfile(with: Self.profileId) } catch { - os_log("Database error: fetchProfile, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: fetchProfile, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.fetchProfile")) throw error } @@ -115,7 +116,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(reporter: secureVaultErrorReporter) try vault.deleteProfileData() } catch { - os_log("Database error: deleteProfileData, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: deleteProfileData, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.deleteProfileData")) throw error } @@ -126,7 +127,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(reporter: secureVaultErrorReporter) return try vault.fetchChildBrokers(for: parentBroker) } catch { - os_log("Database error: fetchChildBrokers, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: fetchChildBrokers, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.fetchChildBrokers for parentBroker")) throw error } @@ -138,7 +139,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { return try vault.save(extractedProfile: extractedProfile, brokerId: brokerId, profileQueryId: profileQueryId) } catch { - os_log("Database error: extractedProfile, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: extractedProfile, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.save extractedProfile brokerId profileQueryId")) throw error } @@ -151,7 +152,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { let profileQuery = try vault.fetchProfileQuery(with: profileQueryId), let scanJob = try vault.fetchScan(brokerId: brokerId, profileQueryId: profileQueryId) else { let error = DataBrokerProtectionError.dataNotInDatabase - os_log("Database error: brokerProfileQueryData, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: brokerProfileQueryData, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.brokerProfileQueryData for brokerId and profileQueryId")) throw error } @@ -165,7 +166,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { optOutJobData: optOutJobs ) } catch { - os_log("Database error: brokerProfileQueryData, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: brokerProfileQueryData, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.brokerProfileQueryData for brokerId and profileQueryId")) throw error } @@ -176,7 +177,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(reporter: secureVaultErrorReporter) return try vault.fetchExtractedProfiles(for: brokerId) } catch { - os_log("Database error: fetchExtractedProfiles, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: fetchExtractedProfiles, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.fetchExtractedProfiles for brokerId")) throw error } @@ -187,7 +188,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(reporter: secureVaultErrorReporter) try vault.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) } catch { - os_log("Database error: updatePreferredRunDate without extractedProfileID, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: updatePreferredRunDate without extractedProfileID, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.updatePreferredRunDate date brokerID profileQueryId")) throw error } @@ -203,7 +204,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) } catch { - os_log("Database error: updatePreferredRunDate with extractedProfileID, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: updatePreferredRunDate with extractedProfileID, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.updatePreferredRunDate date brokerID profileQueryId extractedProfileID")) throw error } @@ -214,7 +215,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(reporter: secureVaultErrorReporter) try vault.updateLastRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) } catch { - os_log("Database error: updateLastRunDate without extractedProfileID, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: updateLastRunDate without extractedProfileID, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.updateLastRunDate date brokerID profileQueryId")) throw error } @@ -231,7 +232,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { extractedProfileId: extractedProfileId ) } catch { - os_log("Database error: updateLastRunDate with extractedProfileID, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: updateLastRunDate with extractedProfileID, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.updateLastRunDate date brokerId profileQueryId extractedProfileId")) throw error } @@ -251,7 +252,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { extractedProfileId: extractedProfileId ) } catch { - os_log("Database error: updateSubmittedSuccessfullyDate, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: updateSubmittedSuccessfullyDate, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.updateSubmittedSuccessfullyDate date forBrokerId profileQueryId extractedProfileId")) throw error } @@ -271,7 +272,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { extractedProfileId: extractedProfileId ) } catch { - os_log("Database error: updateSevenDaysConfirmationPixelFired, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: updateSevenDaysConfirmationPixelFired, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.updateSevenDaysConfirmationPixelFired pixelFired forBrokerId profileQueryId extractedProfileId")) throw error } @@ -291,7 +292,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { extractedProfileId: extractedProfileId ) } catch { - os_log("Database error: updateFourteenDaysConfirmationPixelFired, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: updateFourteenDaysConfirmationPixelFired, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.updateFourteenDaysConfirmationPixelFired pixelFired forBrokerId profileQueryId extractedProfileId")) throw error } @@ -311,7 +312,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { extractedProfileId: extractedProfileId ) } catch { - os_log("Database error: updateTwentyOneDaysConfirmationPixelFired, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: updateTwentyOneDaysConfirmationPixelFired, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.updateTwentyOneDaysConfirmationPixelFired pixelFired forBrokerId profileQueryId extractedProfileId")) throw error } @@ -322,7 +323,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(reporter: secureVaultErrorReporter) try vault.updateRemovedDate(for: extractedProfileId, with: date) } catch { - os_log("Database error: updateRemovedDate, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: updateRemovedDate, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.updateRemovedDate date on extractedProfileId")) throw error } @@ -338,7 +339,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { try vault.save(historyEvent: historyEvent, brokerId: historyEvent.brokerId, profileQueryId: historyEvent.profileQueryId) } } catch { - os_log("Database error: add historyEvent, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: add historyEvent, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.add historyEvent")) throw error } @@ -371,7 +372,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { return brokerProfileQueryDataList } catch { - os_log("Database error: fetchAllBrokerProfileQueryData, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: fetchAllBrokerProfileQueryData, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.fetchAllBrokerProfileQueryData")) throw error } @@ -392,7 +393,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { fourteenDaysConfirmationPixelFired: optOut.fourteenDaysConfirmationPixelFired, twentyOneDaysConfirmationPixelFired: optOut.twentyOneDaysConfirmationPixelFired) } catch { - os_log("Database error: saveOptOutOperation, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: saveOptOutOperation, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.saveOptOutOperation optOut extractedProfile")) throw error } @@ -405,7 +406,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { return events.max(by: { $0.date < $1.date }) } catch { - os_log("Database error: fetchLastEvent, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: fetchLastEvent, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "fetchLastEvent brokerId profileQueryId")) throw error } @@ -416,7 +417,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(reporter: secureVaultErrorReporter) return try vault.hasMatches() } catch { - os_log("Database error: hasMatches, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: hasMatches, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.hasMatches")) throw error } @@ -431,7 +432,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { return scan.historyEvents } catch { - os_log("Database error: fetchHistoryEvents, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: fetchHistoryEvents, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "fetchScanHistoryEvents brokerId profileQueryId")) throw error } @@ -446,7 +447,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { return optOut.historyEvents } catch { - os_log("Database error: fetchHistoryEvents, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: fetchHistoryEvents, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.fetchOptOutHistoryEvents brokerId profileQueryId extractedProfileId")) throw error } @@ -457,7 +458,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(reporter: secureVaultErrorReporter) return try vault.fetchAttemptInformation(for: extractedProfileId) } catch { - os_log("Database error: fetchAttemptInformation, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: fetchAttemptInformation, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.fetchAttemptInformation for extractedProfileId")) throw error } @@ -472,7 +473,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { lastStageDate: lastStageDate, startTime: startTime) } catch { - os_log("Database error: addAttempt, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: addAttempt, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.addAttempt extractedProfileId attemptUUID dataBroker lastStageDate startTime")) throw error } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift index c32392586c..1a9064acee 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift @@ -21,6 +21,7 @@ import BrowserServicesKit import Common import ContentScopeScripts import Combine +import os.log struct ExtractedAddress: Codable { let state: String @@ -335,10 +336,10 @@ final class DataBrokerRunCustomJSONViewModel: ObservableObject { let fileURL = URL(fileURLWithPath: "\(path)/\(fileName)") try csv.write(to: fileURL, atomically: true, encoding: .utf8) } else { - os_log("Error getting path") + Logger.dataBrokerProtection.debug("Error getting path") } } catch { - os_log("Error writing to file: \(error)") + Logger.dataBrokerProtection.error("Error writing to file: \(error)") } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DebugScanJob.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DebugScanJob.swift index e8900027d3..c10044b1bb 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DebugScanJob.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DebugScanJob.swift @@ -21,6 +21,7 @@ import WebKit import BrowserServicesKit import UserScript import Common +import os.log struct DebugScanReturnValue { let brokerURL: String @@ -174,15 +175,15 @@ final class DebugScanJob: DataBrokerJob { func executeNextStep() async { retriesCountOnError = 0 // We reset the retries on error when it is successful - os_log("SCAN Waiting %{public}f seconds...", log: .action, operationAwaitTime) + Logger.action.debug("SCAN Waiting \(self.operationAwaitTime, privacy: .public) seconds...") try? await Task.sleep(nanoseconds: UInt64(operationAwaitTime) * 1_000_000_000) if let action = actionsHandler?.nextAction() { - os_log("Next action: %{public}@", log: .action, String(describing: action.actionType.rawValue)) + Logger.action.debug("Next action: \(String(describing: action.actionType.rawValue), privacy: .public)") await runNextAction(action) } else { - os_log("Releasing the web view", log: .action) + Logger.action.debug("Releasing the web view") await webViewHandler?.finish() // If we executed all steps we release the web view continuation = nil webViewHandler = nil @@ -200,6 +201,6 @@ final class DebugScanJob: DataBrokerJob { } deinit { - os_log("DebugScanOperation Deinit", log: .action) + Logger.action.debug("DebugScanOperation Deinit") } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCClient.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCClient.swift index 22ba75d0af..25d033a053 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCClient.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCClient.swift @@ -20,6 +20,7 @@ import Combine import Common import Foundation import XPCHelper +import os.log /// This protocol describes the server-side IPC interface for controlling the tunnel /// @@ -109,7 +110,7 @@ extension DataBrokerProtectionIPCClient: IPCServerInterface { xpc.execute(call: { server in server.openBrowser(domain: domain) }, xpcReplyErrorHandler: { error in - os_log("Error \(error.localizedDescription)") + Logger.dataBrokerProtection.error("Error \(error.localizedDescription)") // Intentional no-op as there's no completion block // If you add a completion block, please remember to call it here too! }) @@ -119,7 +120,7 @@ extension DataBrokerProtectionIPCClient: IPCServerInterface { xpc.execute(call: { server in server.startImmediateOperations(showWebView: showWebView) }, xpcReplyErrorHandler: { error in - os_log("Error \(error.localizedDescription)") + Logger.dataBrokerProtection.error("Error \(error.localizedDescription)") // Intentional no-op as there's no completion block // If you add a completion block, please remember to call it here too! }) @@ -129,7 +130,7 @@ extension DataBrokerProtectionIPCClient: IPCServerInterface { xpc.execute(call: { server in server.startScheduledOperations(showWebView: showWebView) }, xpcReplyErrorHandler: { error in - os_log("Error \(error.localizedDescription)") + Logger.dataBrokerProtection.error("Error \(error.localizedDescription)") // Intentional no-op as there's no completion block // If you add a completion block, please remember to call it here too! }) } @@ -138,7 +139,7 @@ extension DataBrokerProtectionIPCClient: IPCServerInterface { xpc.execute(call: { server in server.runAllOptOuts(showWebView: showWebView) }, xpcReplyErrorHandler: { error in - os_log("Error \(error.localizedDescription)") + Logger.dataBrokerProtection.error("Error \(error.localizedDescription)") // Intentional no-op as there's no completion block // If you add a completion block, please remember to call it here too! }) @@ -151,7 +152,7 @@ extension DataBrokerProtectionIPCClient: IPCServerInterface { continuation.resume(returning: metaData) } }, xpcReplyErrorHandler: { error in - os_log("Error \(error.localizedDescription)") + Logger.dataBrokerProtection.error("Error \(error.localizedDescription)") continuation.resume(returning: nil) }) } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift index 926ee58947..18e5ef22b6 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift @@ -21,6 +21,7 @@ import Combine import WebKit import BrowserServicesKit import Common +import os.log protocol DBPUIScanOps: AnyObject { func updateCacheWithCurrentScans() async @@ -81,7 +82,7 @@ extension DBPUIViewModel: DBPUIScanOps { do { try dataManager.prepareBrokerProfileQueryDataCache() } catch { - os_log("DBPUIViewModel error: updateCacheWithCurrentScans, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("DBPUIViewModel error: updateCacheWithCurrentScans, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DBPUIViewModel.updateCacheWithCurrentScans")) } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift index 2363fec57e..afb6d99efc 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift @@ -18,6 +18,7 @@ import Foundation import Common +import os.log struct DataBrokerScheduleConfig: Codable { let retryError: Int @@ -198,7 +199,7 @@ struct DataBroker: Codable, Sendable { let broker = try jsonDecoder.decode(DataBroker.self, from: data) return broker } catch { - os_log("DataBroker error: initFromResource, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("DataBroker error: initFromResource, error: \(error.localizedDescription, privacy: .public)") throw error } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerJobRunner.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerJobRunner.swift index 08916286f8..0c8cb4987d 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerJobRunner.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerJobRunner.swift @@ -19,6 +19,7 @@ import Foundation import BrowserServicesKit import Common +import os.log protocol WebJobRunner { @@ -124,6 +125,6 @@ final class DataBrokerJobRunner: WebJobRunner { } deinit { - os_log("WebOperationRunner Deinit", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("WebOperationRunner Deinit") } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift index 5004e9c9da..bb9b72f09a 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift @@ -18,6 +18,7 @@ import Foundation import Common +import os.log protocol DataBrokerOperationDependencies { var database: DataBrokerProtectionRepository { get } @@ -62,7 +63,7 @@ class DataBrokerOperation: Operation, @unchecked Sendable { private var _isFinished = false deinit { - os_log("Deinit operation: %{public}@", log: .dataBrokerProtection, String(describing: id.uuidString)) + Logger.dataBrokerProtection.debug("Deinit operation: \(String(describing: self.id.uuidString), privacy: .public)") } init(dataBrokerID: Int64, @@ -144,7 +145,7 @@ class DataBrokerOperation: Operation, @unchecked Sendable { do { allBrokerProfileQueryData = try operationDependencies.database.fetchAllBrokerProfileQueryData() } catch { - os_log("DataBrokerOperationsCollection error: runOperation, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("DataBrokerOperationsCollection error: runOperation, error: \(error.localizedDescription, privacy: .public)") return } @@ -154,11 +155,11 @@ class DataBrokerOperation: Operation, @unchecked Sendable { operationType: operationType, priorityDate: priorityDate) - os_log("filteredAndSortedOperationsData count: %{public}d for brokerID %{public}d", log: .dataBrokerProtection, filteredAndSortedOperationsData.count, dataBrokerID) + Logger.dataBrokerProtection.debug("filteredAndSortedOperationsData count: \(filteredAndSortedOperationsData.count, privacy: .public) for brokerID \(self.dataBrokerID, privacy: .public)") for operationData in filteredAndSortedOperationsData { if isCancelled { - os_log("Cancelled operation, returning...", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Cancelled operation, returning...") return } @@ -170,7 +171,7 @@ class DataBrokerOperation: Operation, @unchecked Sendable { continue } do { - os_log("Running operation: %{public}@", log: .dataBrokerProtection, String(describing: operationData)) + Logger.dataBrokerProtection.debug("Running operation: \(String(describing: operationData), privacy: .public)") try await DataBrokerProfileQueryOperationManager().runOperation(operationData: operationData, brokerProfileQueryData: brokerProfileData, @@ -187,10 +188,10 @@ class DataBrokerOperation: Operation, @unchecked Sendable { }) let sleepInterval = operationDependencies.config.intervalBetweenSameBrokerOperations - os_log("Waiting...: %{public}f", log: .dataBrokerProtection, sleepInterval) + Logger.dataBrokerProtection.debug("Waiting...: \(sleepInterval, privacy: .public)") try await Task.sleep(nanoseconds: UInt64(sleepInterval) * 1_000_000_000) } catch { - os_log("Error: %{public}@", log: .dataBrokerProtection, error.localizedDescription) + Logger.dataBrokerProtection.error("Error: \(error.localizedDescription, privacy: .public)") errorDelegate?.dataBrokerOperationDidError(error, withBrokerName: brokerProfileQueriesData.first?.dataBroker.name) } @@ -209,7 +210,7 @@ class DataBrokerOperation: Operation, @unchecked Sendable { didChangeValue(forKey: #keyPath(isExecuting)) didChangeValue(forKey: #keyPath(isFinished)) - os_log("Finished operation: %{public}@", log: .dataBrokerProtection, String(describing: id.uuidString)) + Logger.dataBrokerProtection.debug("Finished operation: \(self.id.uuidString, privacy: .public)") } } // swiftlint:enable explicit_non_final_class diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift index 2e49861bff..126661c472 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift @@ -18,6 +18,7 @@ import Foundation import Common +import os.log enum OperationsError: Error { case idsMissingForBrokerOrProfileQuery @@ -110,7 +111,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { isManual: Bool = false, userNotificationService: DataBrokerProtectionUserNotificationService, shouldRunNextStep: @escaping () -> Bool) async throws { - os_log("Running scan operation: %{public}@", log: .dataBrokerProtection, String(describing: brokerProfileQueryData.dataBroker.name)) + Logger.dataBrokerProtection.debug("Running scan operation: \(brokerProfileQueryData.dataBroker.name, privacy: .public)") guard let brokerId = brokerProfileQueryData.dataBroker.id, let profileQueryId = brokerProfileQueryData.profileQuery.id else { // Maybe send pixel? @@ -119,7 +120,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { defer { try? database.updateLastRunDate(Date(), brokerId: brokerId, profileQueryId: profileQueryId) - os_log("Finished scan operation: %{public}@", log: .dataBrokerProtection, String(describing: brokerProfileQueryData.dataBroker.name)) + Logger.dataBrokerProtection.debug("Finished scan operation: \(brokerProfileQueryData.dataBroker.name, privacy: .public)") notificationCenter.post(name: DataBrokerProtectionNotifications.didFinishScan, object: brokerProfileQueryData.dataBroker.name) } @@ -134,7 +135,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { try database.add(event) let extractedProfiles = try await runner.scan(brokerProfileQueryData, stageCalculator: stageCalculator, pixelHandler: pixelHandler, showWebView: showWebView, shouldRunNextStep: shouldRunNextStep) - os_log("Extracted profiles: %@", log: .dataBrokerProtection, extractedProfiles) + Logger.dataBrokerProtection.debug("Extracted profiles: \(extractedProfiles)") if !extractedProfiles.isEmpty { stageCalculator.fireScanSuccess(matchesFound: extractedProfiles.count) @@ -157,7 +158,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { try database.updateRemovedDate(nil, on: id) } - os_log("Extracted profile already exists in database: %@", log: .dataBrokerProtection, id.description) + Logger.dataBrokerProtection.debug("Extracted profile already exists in database: \(id.description)") } else { // If it's a new found profile, we'd like to opt-out ASAP // If this broker has a parent opt out, we set the preferred date to nil, as we will only perform the operation within the parent. @@ -183,7 +184,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { try database.saveOptOutJob(optOut: optOutJobData, extractedProfile: extractedProfile) - os_log("Creating new opt-out operation data for: %@", log: .dataBrokerProtection, String(describing: extractedProfile.name)) + Logger.dataBrokerProtection.debug("Creating new opt-out operation data for: \(String(describing: extractedProfile.name))") } } } else { @@ -216,7 +217,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { database: database ) - os_log("Profile removed from optOutsData: %@", log: .dataBrokerProtection, String(describing: removedProfile)) + Logger.dataBrokerProtection.debug("Profile removed from optOutsData: \(String(describing: removedProfile))") if let attempt = try database.fetchAttemptInformation(for: extractedProfileId), let attemptUUID = UUID(uuidString: attempt.attemptId) { let now = Date() @@ -289,12 +290,12 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { } guard extractedProfile.removedDate == nil else { - os_log("Profile already extracted, skipping...", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Profile already extracted, skipping...") return } guard let optOutStep = brokerProfileQueryData.dataBroker.optOutStep(), optOutStep.optOutType != .parentSiteOptOut else { - os_log("Broker opts out in parent, skipping...", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Broker opts out in parent, skipping...") return } @@ -303,10 +304,10 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { dataBrokerVersion: brokerProfileQueryData.dataBroker.version, handler: pixelHandler) stageDurationCalculator.fireOptOutStart() - os_log("Running opt-out operation: %{public}@", log: .dataBrokerProtection, String(describing: brokerProfileQueryData.dataBroker.name)) + Logger.dataBrokerProtection.debug("Running opt-out operation: \(brokerProfileQueryData.dataBroker.name, privacy: .public)") defer { - os_log("Finished opt-out operation: %{public}@", log: .dataBrokerProtection, String(describing: brokerProfileQueryData.dataBroker.name)) + Logger.dataBrokerProtection.debug("Finished opt-out operation: \(brokerProfileQueryData.dataBroker.name, privacy: .public)") try? database.updateLastRunDate(Date(), brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) do { @@ -423,9 +424,9 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { database: database ) } catch { - os_log("Can't update operation date after error") + Logger.dataBrokerProtection.debug("Can't update operation date after error") } - os_log("Error on operation : %{public}@", log: .dataBrokerProtection, error.localizedDescription) + Logger.dataBrokerProtection.error("Error on operation : \(error.localizedDescription, privacy: .public)") } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift index 8e47a7b6a7..ffa67b2f73 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift @@ -19,6 +19,7 @@ import Foundation import Common import SecureStorage +import os.log protocol ResourcesRepository { func fetchBrokerFromResourceFiles() throws -> [DataBroker]? @@ -38,8 +39,8 @@ final class FileResources: ResourcesRepository { func fetchBrokerFromResourceFiles() throws -> [DataBroker]? { guard let resourceURL = Bundle.module.resourceURL else { + Logger.dataBrokerProtection.fault("DataBrokerProtectionUpdater: error FileResources fetchBrokerFromResourceFiles, error: Bundle.module.resourceURL is nil") assertionFailure() - os_log("DataBrokerProtectionUpdater: error FileResources fetchBrokerFromResourceFiles, error: Bundle.module.resourceURL is nil", log: .error) throw FileResourcesError.bundleResourceURLNil } @@ -56,7 +57,7 @@ final class FileResources: ResourcesRepository { return try brokerJSONFiles.map(DataBroker.initFromResource(_:)) } catch { - os_log("DataBrokerProtectionUpdater: error FileResources error: fetchBrokerFromResourceFiles, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("DataBrokerProtectionUpdater: error FileResources error: fetchBrokerFromResourceFiles, error: \(error.localizedDescription, privacy: .public)") throw error } } @@ -129,7 +130,7 @@ public struct DefaultDataBrokerProtectionBrokerUpdater: DataBrokerProtectionBrok return DefaultDataBrokerProtectionBrokerUpdater(vault: vault) } - os_log("Error when trying to create vault for data broker protection updater debug menu item", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Error when trying to create vault for data broker protection updater debug menu item") return nil } @@ -138,7 +139,7 @@ public struct DefaultDataBrokerProtectionBrokerUpdater: DataBrokerProtectionBrok do { brokers = try resources.fetchBrokerFromResourceFiles() } catch { - os_log("DataBrokerProtectionBrokerUpdater updateBrokers, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("DataBrokerProtectionBrokerUpdater updateBrokers, error: \(error.localizedDescription, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionBrokerUpdater.updateBrokers")) return } @@ -148,7 +149,7 @@ public struct DefaultDataBrokerProtectionBrokerUpdater: DataBrokerProtectionBrok do { try update(broker) } catch { - os_log("Error updating broker: %{public}@, with version: %{public}@", log: .dataBrokerProtection, broker.name, broker.version) + Logger.dataBrokerProtection.debug("Error updating broker: \(broker.name, privacy: .public), with version: \(broker.version, privacy: .public)") pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionBrokerUpdater.updateBrokers")) } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift index f0b49136fc..eb57e90e84 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift @@ -18,6 +18,7 @@ import Foundation import Common +import os.log enum OperationPreferredDateUpdaterOrigin { case optOut @@ -83,7 +84,7 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { } } } catch { - os_log("OperationPreferredDateUpdaterUseCase error: updateChildrenBrokerForParentBroker, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("OperationPreferredDateUpdaterUseCase error: updateChildrenBrokerForParentBroker, error: \(error.localizedDescription, privacy: .public)") throw error } } @@ -168,10 +169,10 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { try database.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) } } catch { - os_log("OperationPreferredDateUpdaterUseCase error: updatePreferredRunDate, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("OperationPreferredDateUpdaterUseCase error: updatePreferredRunDate, error: \(error.localizedDescription, privacy: .public)") throw error } - os_log("Updating preferredRunDate on operation with brokerId %{public}@ and profileQueryId %{public}@", log: .dataBrokerProtection, brokerId.description, profileQueryId.description) + Logger.dataBrokerProtection.debug("Updating preferredRunDate on operation with brokerId \(brokerId.description, privacy: .public) and profileQueryId \(profileQueryId.description, privacy: .public)") } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OptOutJob.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OptOutJob.swift index b1d32657e2..2adbab2c2c 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OptOutJob.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OptOutJob.swift @@ -20,6 +20,7 @@ import Foundation import WebKit import BrowserServicesKit import UserScript +import os.log import Common final class OptOutJob: DataBrokerJob { @@ -118,7 +119,7 @@ final class OptOutJob: DataBrokerJob { func executeNextStep() async { retriesCountOnError = 0 // We reset the retries on error when it is successful - os_log("OPTOUT Waiting %{public}f seconds...", log: .action, operationAwaitTime) + Logger.action.debug("OPTOUT Waiting \(self.operationAwaitTime, privacy: .public) seconds...") try? await Task.sleep(nanoseconds: UInt64(operationAwaitTime) * 1_000_000_000) if let action = actionsHandler?.nextAction(), self.shouldRunNextStep() { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculator.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculator.swift index ae72a5ae18..e0a19d727b 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculator.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculator.swift @@ -17,8 +17,9 @@ // import Foundation -import Common import BrowserServicesKit +import os.log +import Common enum MismatchValues: Int { case parentSiteHasMoreMatches @@ -50,7 +51,7 @@ struct DefaultMismatchCalculator: MismatchCalculator { do { brokerProfileQueryData = try database.fetchAllBrokerProfileQueryData() } catch { - os_log("MismatchCalculatorUseCase error: calculateMismatches, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("MismatchCalculatorUseCase error: calculateMismatches, error: \(error.localizedDescription, privacy: .public)") return } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanJob.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanJob.swift index 334017ae18..2ac6dd27b2 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanJob.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanJob.swift @@ -21,6 +21,7 @@ import WebKit import BrowserServicesKit import UserScript import Common +import os.log final class ScanJob: DataBrokerJob { typealias ReturnValue = [ExtractedProfile] @@ -107,15 +108,15 @@ final class ScanJob: DataBrokerJob { func executeNextStep() async { retriesCountOnError = 0 // We reset the retries on error when it is successful - os_log("SCAN Waiting %{public}f seconds...", log: .action, operationAwaitTime) + Logger.action.debug("SCAN Waiting \(self.operationAwaitTime, privacy: .public) seconds...") try? await Task.sleep(nanoseconds: UInt64(operationAwaitTime) * 1_000_000_000) if let action = actionsHandler?.nextAction() { - os_log("Next action: %{public}@", log: .action, String(describing: action.actionType.rawValue)) + Logger.action.debug("Next action: \(String(describing: action.actionType.rawValue), privacy: .public)") await runNextAction(action) } else { - os_log("Releasing the web view", log: .action) + Logger.action.debug("Releasing the web view") await webViewHandler?.finish() // If we executed all steps we release the web view } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEngagementPixels.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEngagementPixels.swift index 918edd298f..72daf99102 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEngagementPixels.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEngagementPixels.swift @@ -17,9 +17,10 @@ // import Foundation -import Common +import os.log import BrowserServicesKit import PixelKit +import Common protocol DataBrokerProtectionEngagementPixelsRepository { func markDailyPixelSent() @@ -106,7 +107,7 @@ final class DataBrokerProtectionEngagementPixels { func fireEngagementPixel(currentDate: Date = Date()) { guard (try? database.fetchProfile()) != nil else { - os_log("No profile. We do not fire any pixel because we do not consider it an engaged user.", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("No profile. We do not fire any pixel because we do not consider it an engaged user.") return } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEventPixels.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEventPixels.swift index ea371e0d27..7f82c0513d 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEventPixels.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEventPixels.swift @@ -17,9 +17,10 @@ // import Foundation -import Common +import os.log import BrowserServicesKit import PixelKit +import Common protocol DataBrokerProtectionEventPixelsRepository { func markWeeklyPixelSent() @@ -92,7 +93,7 @@ final class DataBrokerProtectionEventPixels { do { data = try database.fetchAllBrokerProfileQueryData() } catch { - os_log("Database error: when attempting to fireWeeklyReportPixels, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: when attempting to fireWeeklyReportPixels, error: \(error.localizedDescription, privacy: .public)") return } let dataInThePastWeek = data.filter(hadScanThisWeek(_:)) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionAgentManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionAgentManager.swift index d6b4da3b93..facbfb9dad 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionAgentManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionAgentManager.swift @@ -20,6 +20,7 @@ import Foundation import Common import BrowserServicesKit import PixelKit +import os.log // This is to avoid exposing all the dependancies outside of the DBP package public class DataBrokerProtectionAgentManagerProvider { @@ -202,15 +203,15 @@ extension DataBrokerProtectionAgentManager: DataBrokerProtectionAgentAppEvents { switch oneTimeError { case DataBrokerProtectionQueueError.interrupted: self.pixelHandler.fire(.ipcServerImmediateScansInterrupted) - os_log("Interrupted during DataBrokerProtectionAgentManager.profileSaved in queueManager.startImmediateOperationsIfPermitted(), error: %{public}@", log: .dataBrokerProtection, oneTimeError.localizedDescription) + Logger.dataBrokerProtection.debug("Interrupted during DataBrokerProtectionAgentManager.profileSaved in queueManager.startImmediateOperationsIfPermitted(), error: \(oneTimeError.localizedDescription, privacy: .public)") default: self.pixelHandler.fire(.ipcServerImmediateScansFinishedWithError(error: oneTimeError)) - os_log("Error during DataBrokerProtectionAgentManager.profileSaved in queueManager.startImmediateOperationsIfPermitted, error: %{public}@", log: .dataBrokerProtection, oneTimeError.localizedDescription) + Logger.dataBrokerProtection.debug("Error during DataBrokerProtectionAgentManager.profileSaved in queueManager.startImmediateOperationsIfPermitted, error: \(oneTimeError.localizedDescription, privacy: .public)") } } if let operationErrors = errors.operationErrors, operationErrors.count != 0 { - os_log("Operation error(s) during DataBrokerProtectionAgentManager.profileSaved in queueManager.startImmediateOperationsIfPermitted, count: %{public}d", log: .dataBrokerProtection, operationErrors.count) + Logger.dataBrokerProtection.debug("Operation error(s) during DataBrokerProtectionAgentManager.profileSaved in queueManager.startImmediateOperationsIfPermitted, count: \(operationErrors.count, privacy: .public)") } } @@ -240,18 +241,18 @@ extension DataBrokerProtectionAgentManager: DataBrokerProtectionAgentAppEvents { switch oneTimeError { case DataBrokerProtectionQueueError.interrupted: self.pixelHandler.fire(.ipcServerAppLaunchedScheduledScansInterrupted) - os_log("Interrupted during DataBrokerProtectionAgentManager.appLaunched in queueManager.startScheduledOperationsIfPermitted(), error: %{public}@", log: .dataBrokerProtection, oneTimeError.localizedDescription) + Logger.dataBrokerProtection.debug("Interrupted during DataBrokerProtectionAgentManager.appLaunched in queueManager.startScheduledOperationsIfPermitted(), error: \(oneTimeError.localizedDescription, privacy: .public)") case DataBrokerProtectionQueueError.cannotInterrupt: self.pixelHandler.fire(.ipcServerAppLaunchedScheduledScansBlocked) - os_log("Cannot interrupt during DataBrokerProtectionAgentManager.appLaunched in queueManager.startScheduledOperationsIfPermitted()") + Logger.dataBrokerProtection.debug("Cannot interrupt during DataBrokerProtectionAgentManager.appLaunched in queueManager.startScheduledOperationsIfPermitted()") default: self.pixelHandler.fire(.ipcServerAppLaunchedScheduledScansFinishedWithError(error: oneTimeError)) - os_log("Error during DataBrokerProtectionAgentManager.appLaunched in queueManager.startScheduledOperationsIfPermitted, error: %{public}@", log: .dataBrokerProtection, oneTimeError.localizedDescription) + Logger.dataBrokerProtection.debug("Error during DataBrokerProtectionAgentManager.appLaunched in queueManager.startScheduledOperationsIfPermitted, error: \(oneTimeError.localizedDescription, privacy: .public)") } } if let operationErrors = errors.operationErrors, operationErrors.count != 0 { - os_log("Operation error(s) during DataBrokerProtectionAgentManager.profileSaved in queueManager.startImmediateOperationsIfPermitted, count: %{public}d", log: .dataBrokerProtection, operationErrors.count) + Logger.dataBrokerProtection.debug("Operation error(s) during DataBrokerProtectionAgentManager.profileSaved in queueManager.startImmediateOperationsIfPermitted, count: \(operationErrors.count, privacy: .public)") } } @@ -268,7 +269,7 @@ extension DataBrokerProtectionAgentManager: DataBrokerProtectionAgentAppEvents { self.pixelHandler.fire(.initialScanTotalDuration(duration: durationSinceStart.rounded(.towardZero), profileQueries: profileQueries)) } catch { - os_log("Initial Scans Error when trying to fetch the profile to get the profile queries", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Initial Scans Error when trying to fetch the profile to get the profile queries") } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionBackgroundActivityScheduler.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionBackgroundActivityScheduler.swift index 8df894d8c1..d98adfdeda 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionBackgroundActivityScheduler.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionBackgroundActivityScheduler.swift @@ -19,6 +19,7 @@ import Foundation import Common import BrowserServicesKit +import os.log public protocol DataBrokerProtectionBackgroundActivityScheduler { func startScheduler() @@ -51,9 +52,9 @@ public final class DefaultDataBrokerProtectionBackgroundActivityScheduler: DataB activity.schedule { completion in self.lastTriggerTimestamp = Date() - os_log("Scheduler running...", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Scheduler running...") self.delegate?.dataBrokerProtectionBackgroundActivitySchedulerDidTrigger(self) { - os_log("Scheduler finished...", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Scheduler finished...") completion(.finished) } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionQueueManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionQueueManager.swift index 297b3bc094..dd312afbb1 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionQueueManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionQueueManager.swift @@ -18,6 +18,7 @@ import Common import Foundation +import os.log protocol DataBrokerProtectionOperationQueue { var maxConcurrentOperationCount: Int { get set } @@ -226,7 +227,7 @@ private extension DefaultDataBrokerProtectionQueueManager { operationQueue.addOperation(collection) } } catch { - os_log("DataBrokerProtectionProcessor error: addOperations, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("DataBrokerProtectionProcessor error: addOperations, error: \(error.localizedDescription, privacy: .public)") completion?(DataBrokerProtectionAgentErrorCollection(oneTimeError: error)) return } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/CaptchaService.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/CaptchaService.swift index b29112dab0..762afd13b7 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/CaptchaService.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/CaptchaService.swift @@ -18,6 +18,7 @@ import Foundation import Common +import os.log typealias CaptchaTransactionId = String typealias CaptchaResolveData = String @@ -183,7 +184,7 @@ struct CaptchaService: CaptchaServiceProtocol { throw CaptchaServiceError.cantGenerateCaptchaServiceURL } - os_log("Submitting captcha request ...", log: .service) + Logger.service.debug("Submitting captcha request ...") var request = URLRequest(url: url) guard let authHeader = authenticationManager.getAuthHeader() else { @@ -232,13 +233,13 @@ struct CaptchaService: CaptchaServiceProtocol { switch captchaResolveResult.message { case .ready: if let data = captchaResolveResult.data { - os_log("Captcha ready ...", log: .service) + Logger.service.debug("Captcha ready ...") return data } else { throw CaptchaServiceError.nilDataWhenFetchingCaptchaResult } case .notReady: - os_log("Captcha not ready ...", log: .service) + Logger.service.debug("Captcha not ready ...") if retries == 0 { throw CaptchaServiceError.timedOutWhenFetchingCaptchaResult } @@ -249,10 +250,10 @@ struct CaptchaService: CaptchaServiceProtocol { attemptId: attemptId, shouldRunNextStep: shouldRunNextStep) case .failure: - os_log("Captcha failure ...", log: .service) + Logger.service.debug("Captcha failure ...") throw CaptchaServiceError.failureWhenFetchingCaptchaResult case .invalidRequest: - os_log("Captcha invalid request ...", log: .service) + Logger.service.debug("Captcha invalid request ...") throw CaptchaServiceError.invalidRequestWhenFetchingCaptchaResult } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/EmailService.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/EmailService.swift index 48cd0364e7..731ac86fe1 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/EmailService.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/EmailService.swift @@ -18,6 +18,7 @@ import Foundation import Common +import os.log public enum EmailError: Error, Equatable, Codable { case cantGenerateURL @@ -125,17 +126,17 @@ struct EmailService: EmailServiceProtocol { switch emailResult.status { case .ready: if let link = emailResult.link, let url = URL(string: link) { - os_log("Email received", log: .service) + Logger.service.debug("Email received") return url } else { - os_log("Invalid email link", log: .service) + Logger.service.debug("Invalid email link") throw EmailError.invalidEmailLink } case .pending: if numberOfRetries == 0 { throw EmailError.linkExtractionTimedOut } - os_log("No email yet. Waiting for a new request ...", log: .service) + Logger.service.debug("No email yet. Waiting for a new request ...") try await Task.sleep(nanoseconds: pollingTimeInNanoSecondsSeconds) return try await getConfirmationLink(from: email, numberOfRetries: numberOfRetries - 1, diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/DataBrokerProtectionDatabaseMigrationsProvider.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/DataBrokerProtectionDatabaseMigrationsProvider.swift index ee5693929b..ed5deccc2a 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/DataBrokerProtectionDatabaseMigrationsProvider.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/DataBrokerProtectionDatabaseMigrationsProvider.swift @@ -19,6 +19,7 @@ import Foundation import GRDB import Common +import os.log enum DataBrokerProtectionDatabaseMigrationErrors: Error { case deleteOrphanedRecordFailed @@ -324,7 +325,7 @@ final class DefaultDataBrokerProtectionDatabaseMigrationsProvider: DataBrokerPro try database.execute(sql: sql, arguments: [violation.originRowID]) } } catch { - os_log("Database error: error cleaning up foreign key violations, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Database error: error cleaning up foreign key violations, error: \(error.localizedDescription, privacy: .public)") } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/DataBrokerProtectionKeyStoreProvider.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/DataBrokerProtectionKeyStoreProvider.swift index 24971a4cba..e903375725 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/DataBrokerProtectionKeyStoreProvider.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/DataBrokerProtectionKeyStoreProvider.swift @@ -20,6 +20,7 @@ import Common import Foundation import BrowserServicesKit import SecureStorage +import os.log final class DataBrokerProtectionKeyStoreProvider: SecureStorageKeyStoreProvider { @@ -57,17 +58,11 @@ final class DataBrokerProtectionKeyStoreProvider: SecureStorageKeyStoreProvider let keychainService: KeychainService private let groupNameProvider: GroupNameProviding - private let getLog: () -> OSLog - private var log: OSLog { - getLog() - } init(keychainService: KeychainService = DefaultKeychainService(), - groupNameProvider: GroupNameProviding = Bundle.main, - log: @escaping @autoclosure () -> OSLog = .disabled) { + groupNameProvider: GroupNameProviding = Bundle.main) { self.keychainService = keychainService self.groupNameProvider = groupNameProvider - self.getLog = log } func readData(named: String, serviceName: String) throws -> Data? { @@ -109,7 +104,7 @@ private extension DataBrokerProtectionKeyStoreProvider { let legacyAttributes = whenUnlockedQueryAttributes(named: name, serviceName: serviceName) let accessibilityValueString = legacyAttributes[kSecAttrAccessible as String] as? String ?? "[value unavailable]" - os_log("Attempting read and migrate of DBP Keychain data with kSecAttrAccessible value of \(accessibilityValueString)", log: .dataBrokerProtection, type: .debug) + Logger.dataBrokerProtection.debug("Attempting read and migrate of DBP Keychain data with kSecAttrAccessible value of \(accessibilityValueString)") if let data = try read(serviceName: serviceName, queryAttributes: legacyAttributes) { // We found Keychain data, so update it's `kSecAttrAccessible` value to `kSecAttrAccessibleAfterFirstUnlock` @@ -169,7 +164,7 @@ private extension DataBrokerProtectionKeyStoreProvider { } let accessibilityValueString = attributeUpdate[kSecAttrAccessible as String] as? String ?? "[value unavailable]" - os_log("Updated DBP Keychain data kSecAttrAccessible value to \(accessibilityValueString)", log: .dataBrokerProtection, type: .debug) + Logger.dataBrokerProtection.debug("Updated DBP Keychain data kSecAttrAccessible value to \(accessibilityValueString)") } func afterFirstUnlockQueryAttributes(named name: String, serviceName: String) -> [String: Any] { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift index 3df461b1ed..7a45fc4aa1 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift @@ -21,6 +21,7 @@ import WebKit import BrowserServicesKit import UserScript import Common +import os.log protocol DBPUICommunicationDelegate: AnyObject { func saveProfile() async throws @@ -85,7 +86,7 @@ struct DBPUICommunicationLayer: Subfeature { func handler(forMethodNamed methodName: String) -> Handler? { guard let actionResult = DBPUIReceivedMethodName(rawValue: methodName) else { - os_log("Cant parse method: %{public}@", log: .dataBrokerProtection, methodName) + Logger.dataBrokerProtection.debug("Cant parse method: \(methodName, privacy: .public)") return nil } @@ -113,27 +114,27 @@ struct DBPUICommunicationLayer: Subfeature { func handshake(params: Any, original: WKScriptMessage) async throws -> Encodable? { guard let data = try? JSONSerialization.data(withJSONObject: params), let result = try? JSONDecoder().decode(DBPUIHandshake.self, from: data) else { - os_log("Failed to parse handshake message", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Failed to parse handshake message") throw DBPUIError.malformedRequest } if result.version != Constants.version { - os_log("Incorrect protocol version presented by UI", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Incorrect protocol version presented by UI") return DBPUIStandardResponse(version: Constants.version, success: false) } - os_log("Successful handshake made by UI", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Successful handshake made by UI") return DBPUIStandardResponse(version: Constants.version, success: true) } func saveProfile(params: Any, original: WKScriptMessage) async throws -> Encodable? { - os_log("Web UI requested to save the profile", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Web UI requested to save the profile") do { try await delegate?.saveProfile() return DBPUIStandardResponse(version: Constants.version, success: true) } catch { - os_log("DBPUICommunicationLayer saveProfile, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("DBPUICommunicationLayer saveProfile, error: \(error.localizedDescription, privacy: .public)") return DBPUIStandardResponse(version: Constants.version, success: false) } } @@ -151,7 +152,7 @@ struct DBPUICommunicationLayer: Subfeature { try delegate?.deleteProfileData() return DBPUIStandardResponse(version: Constants.version, success: true) } catch { - os_log("DBPUICommunicationLayer deleteUserProfileData, error: %{public}@", log: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("DBPUICommunicationLayer deleteUserProfileData, error: \(error.localizedDescription, privacy: .public)") return DBPUIStandardResponse(version: Constants.version, success: false) } } @@ -159,7 +160,7 @@ struct DBPUICommunicationLayer: Subfeature { func addNameToCurrentUserProfile(params: Any, original: WKScriptMessage) async throws -> Encodable? { guard let data = try? JSONSerialization.data(withJSONObject: params), let result = try? JSONDecoder().decode(DBPUIUserProfileName.self, from: data) else { - os_log("Failed to parse addNameToCurrentUserProfile message", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Failed to parse addNameToCurrentUserProfile message") throw DBPUIError.malformedRequest } @@ -173,7 +174,7 @@ struct DBPUICommunicationLayer: Subfeature { func setNameAtIndexInCurrentUserProfile(params: Any, original: WKScriptMessage) async throws -> Encodable? { guard let data = try? JSONSerialization.data(withJSONObject: params), let result = try? JSONDecoder().decode(DBPUINameAtIndex.self, from: data) else { - os_log("Failed to parse removeNameFromCurrentUserProfile message", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Failed to parse removeNameFromCurrentUserProfile message") throw DBPUIError.malformedRequest } @@ -187,7 +188,7 @@ struct DBPUICommunicationLayer: Subfeature { func removeNameAtIndexFromCurrentUserProfile(params: Any, original: WKScriptMessage) async throws -> Encodable? { guard let data = try? JSONSerialization.data(withJSONObject: params), let result = try? JSONDecoder().decode(DBPUIIndex.self, from: data) else { - os_log("Failed to parse removeNameAtIndexFromCurrentUserProfile message", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Failed to parse removeNameAtIndexFromCurrentUserProfile message") throw DBPUIError.malformedRequest } @@ -201,7 +202,7 @@ struct DBPUICommunicationLayer: Subfeature { func setBirthYearForCurrentUserProfile(params: Any, original: WKScriptMessage) async throws -> Encodable? { guard let data = try? JSONSerialization.data(withJSONObject: params), let result = try? JSONDecoder().decode(DBPUIBirthYear.self, from: data) else { - os_log("Failed to parse setBirthYearForCurrentUserProfile message", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Failed to parse setBirthYearForCurrentUserProfile message") throw DBPUIError.malformedRequest } @@ -215,7 +216,7 @@ struct DBPUICommunicationLayer: Subfeature { func addAddressToCurrentUserProfile(params: Any, original: WKScriptMessage) async throws -> Encodable? { guard let data = try? JSONSerialization.data(withJSONObject: params), let result = try? JSONDecoder().decode(DBPUIUserProfileAddress.self, from: data) else { - os_log("Failed to parse addAddressToCurrentUserProfile message", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Failed to parse addAddressToCurrentUserProfile message") throw DBPUIError.malformedRequest } @@ -229,7 +230,7 @@ struct DBPUICommunicationLayer: Subfeature { func setAddressAtIndexInCurrentUserProfile(params: Any, original: WKScriptMessage) async throws -> Encodable? { guard let data = try? JSONSerialization.data(withJSONObject: params), let result = try? JSONDecoder().decode(DBPUIAddressAtIndex.self, from: data) else { - os_log("Failed to parse removeAddressFromCurrentUserProfile message", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Failed to parse removeAddressFromCurrentUserProfile message") throw DBPUIError.malformedRequest } @@ -243,7 +244,7 @@ struct DBPUICommunicationLayer: Subfeature { func removeAddressAtIndexFromCurrentUserProfile(params: Any, original: WKScriptMessage) async throws -> Encodable? { guard let data = try? JSONSerialization.data(withJSONObject: params), let result = try? JSONDecoder().decode(DBPUIIndex.self, from: data) else { - os_log("Failed to parse removeNameAtIndexFromCurrentUserProfile message", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Failed to parse removeNameAtIndexFromCurrentUserProfile message") throw DBPUIError.malformedRequest } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/UIMapper.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/UIMapper.swift index 04001ca81e..5a7bbf5571 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/UIMapper.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/UIMapper.swift @@ -18,6 +18,7 @@ import Foundation import Common +import os.log struct MapperToUI { @@ -259,10 +260,10 @@ struct MapperToUI { encoder.outputFormatting = .prettyPrinted let jsonData = try encoder.encode(metadataUI) if let jsonString = String(data: jsonData, encoding: .utf8) { - os_log("Metadata: %{public}s", log: OSLog.default, type: .info, jsonString) + Logger.dataBrokerProtection.debug("Metadata: \(jsonString, privacy: .public)") } } catch { - os_log("Error encoding struct to JSON: %{public}@", log: OSLog.default, type: .error, error.localizedDescription) + Logger.dataBrokerProtection.error("Error encoding struct to JSON: \(error.localizedDescription, privacy: .public)") } #endif diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UserNotifications/DataBrokerProtectionUserNotificationService.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UserNotifications/DataBrokerProtectionUserNotificationService.swift index 4208867430..ef8599d437 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UserNotifications/DataBrokerProtectionUserNotificationService.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UserNotifications/DataBrokerProtectionUserNotificationService.swift @@ -20,6 +20,7 @@ import Foundation import UserNotifications import Common import AppKit +import os.log public enum DataBrokerProtectionNotificationCommand: String { case showDashboard = "databrokerprotection://show_dashboard" @@ -76,7 +77,7 @@ public class DefaultDataBrokerProtectionUserNotificationService: NSObject, DataB if let days = days { let calendar = Calendar.current guard let date = calendar.date(byAdding: .day, value: days, to: Date()) else { - os_log("Notification scheduled for an invalid date", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Notification scheduled for an invalid date") return } let components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date) @@ -89,9 +90,9 @@ public class DefaultDataBrokerProtectionUserNotificationService: NSObject, DataB userNotificationCenter.add(request) { error in if error == nil { if days != nil { - os_log("Notification scheduled", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Notification scheduled") } else { - os_log("Notification sent", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Notification sent") } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift index fe6c7734be..747f2b18a6 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionAgentStopper.swift @@ -17,6 +17,7 @@ // import Foundation +import os.log import Common protocol DataBrokerProtectionAgentStopper { @@ -51,13 +52,13 @@ struct DefaultDataBrokerProtectionAgentStopper: DataBrokerProtectionAgentStopper do { guard try dataManager.fetchProfile() != nil, authenticationManager.isUserAuthenticated else { - os_log("Prerequisites are invalid", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Prerequisites are invalid") stopAgent() return } - os_log("Prerequisites are valid", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Prerequisites are valid") } catch { - os_log("Error validating prerequisites, error: %{public}@", log: .dataBrokerProtection, error.localizedDescription) + Logger.dataBrokerProtection.error("Error validating prerequisites, error: \(error.localizedDescription, privacy: .public)") stopAgent() } @@ -83,14 +84,14 @@ struct DefaultDataBrokerProtectionAgentStopper: DataBrokerProtectionAgentStopper private func stopAgentBasedOnEntitlementCheckResult(_ result: DataBrokerProtectionEntitlementMonitorResult) { switch result { case .enabled: - os_log("Valid entitlement", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Valid entitlement") pixelHandler.fire(.entitlementCheckValid) case .disabled: - os_log("Invalid entitlement", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Invalid entitlement") pixelHandler.fire(.entitlementCheckInvalid) stopAgent() case .error: - os_log("Error when checking entitlement", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Error when checking entitlement") /// We don't want to disable the agent in case of an error while checking for entitlements. /// Since this is a destructive action, the only situation that should cause the data to be deleted and the agent to be removed is .success(false) pixelHandler.fire(.entitlementCheckError) @@ -104,7 +105,7 @@ protocol DataProtectionStopAction { struct DefaultDataProtectionStopAction: DataProtectionStopAction { func stopAgent() { - os_log("Stopping DataBrokerProtection Agent", log: .dataBrokerProtection) + Logger.dataBrokerProtection.debug("Stopping DataBrokerProtection Agent") exit(EXIT_SUCCESS) } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionSleepObserver.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionSleepObserver.swift index a38a78ec29..69088e8cb9 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionSleepObserver.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/DataBrokerProtectionSleepObserver.swift @@ -19,6 +19,7 @@ import Foundation import Cocoa import Common +import os.log protocol SleepObserver { func totalSleepTime() -> TimeInterval @@ -40,7 +41,7 @@ final class DataBrokerProtectionSleepObserver: SleepObserver { } deinit { - os_log(.debug, log: .dataBrokerProtection, "SleepObserver: Deinit %{public}s %{public}s %{public}s", brokerProfileQueryData.dataBroker.name, brokerProfileQueryData.profileQuery.firstName, brokerProfileQueryData.profileQuery.city) + Logger.dataBrokerProtection.debug("SleepObserver: Deinit \(self.brokerProfileQueryData.dataBroker.name, privacy: .public) \(self.brokerProfileQueryData.profileQuery.firstName, privacy: .public) \(self.brokerProfileQueryData.profileQuery.city, privacy: .public)") NotificationCenter.default.removeObserver(self) } @@ -49,18 +50,18 @@ final class DataBrokerProtectionSleepObserver: SleepObserver { return 0 } - os_log(.debug, log: .dataBrokerProtection, "SleepObserver: Total Sleep time more than zero: %{public}s", String(totalSleepTime)) + Logger.dataBrokerProtection.debug("SleepObserver: Total Sleep time more than zero: \(String(totalSleepTime), privacy: .public)") return totalSleepTime } @objc func willSleepNotification(_ notification: Notification) { - os_log(.debug, log: .dataBrokerProtection, "SleepObserver: Computer will sleep on %{public}s %{public}s %{public}s %{public}s", brokerProfileQueryData.dataBroker.name, brokerProfileQueryData.profileQuery.firstName, brokerProfileQueryData.profileQuery.city) + Logger.dataBrokerProtection.debug("SleepObserver: Computer will sleep on \(self.brokerProfileQueryData.dataBroker.name, privacy: .public) \(self.brokerProfileQueryData.profileQuery.firstName, privacy: .public) \(self.brokerProfileQueryData.profileQuery.city, privacy: .public)") startSleepTime = Date() } @objc func didWakeNotification(_ notification: Notification) { - os_log(.debug, log: .dataBrokerProtection, "SleepObserver: Computer waking up %{public}s %{public}s %{public}s", brokerProfileQueryData.dataBroker.name, brokerProfileQueryData.profileQuery.firstName, brokerProfileQueryData.profileQuery.city) + Logger.dataBrokerProtection.debug("SleepObserver: Computer waking up \(self.brokerProfileQueryData.dataBroker.name, privacy: .public) \(self.brokerProfileQueryData.profileQuery.firstName, privacy: .public) \(self.brokerProfileQueryData.profileQuery.city, privacy: .public)") guard let startSleepTime = self.startSleepTime else { return } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/Logger+DataBrokerProtection.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/Logger+DataBrokerProtection.swift new file mode 100644 index 0000000000..7d8609c26c --- /dev/null +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/Logger+DataBrokerProtection.swift @@ -0,0 +1,31 @@ +// +// Logger+DataBrokerProtection.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import os.log + +public extension Logger { + fileprivate static let subsystem = "com.duckduckgo.macos.browser.databroker-protection" + + static var dataBrokerProtection = { Logger(subsystem: subsystem, category: "") }() + static var action = { Logger(subsystem: subsystem, category: "Action") }() + static var service = { Logger(subsystem: subsystem, category: "Service") }() + static var backgroundAgent = { Logger(subsystem: subsystem, category: "Background Agent") }() + static var backgroundAgentMemoryManagement = { Logger(subsystem: subsystem, category: "Background Agent Memory Management") }() + static var pixel = { Logger(subsystem: subsystem, category: "Pixel") }() +} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/Logging.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/Logging.swift deleted file mode 100644 index 2b8016c99a..0000000000 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/Logging.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// Logging.swift -// -// Copyright © 2021 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import Common - -struct Logging { - - static let subsystem = "com.duckduckgo.macos.browser.databroker-protection" - - fileprivate static let dataBrokerProtectionLoggingEnabled = true - fileprivate static let dataBrokerProtection: OSLog = OSLog(subsystem: subsystem, category: "Data Broker Protection") - - fileprivate static let actionLoggingEnabled = true - fileprivate static let action: OSLog = OSLog(subsystem: subsystem, category: "Data Broker Protection Action") - - fileprivate static let serviceLoggingEnabled = true - fileprivate static let service: OSLog = OSLog(subsystem: subsystem, category: "Data Broker Protection Service") - - fileprivate static let errorsLoggingEnabled = true - fileprivate static let error: OSLog = OSLog(subsystem: subsystem, category: "Data Broker Protection Errors") - - fileprivate static let backgroundAgentLoggingEnabled = true - fileprivate static let backgroundAgent: OSLog = OSLog(subsystem: subsystem, category: "Data Broker Protection Background Agent") - - fileprivate static let backgroundAgentMemoryManagementLoggingEnabled = true - fileprivate static let backgroundAgentMemoryManagement: OSLog = OSLog(subsystem: subsystem, category: "Data Broker Protection Background Agent Memory Management") - - fileprivate static let backgroundAgentPixelLoggingEnabled = true - fileprivate static let backgroundAgentPixel: OSLog = OSLog(subsystem: subsystem, category: "Data Broker Protection Background Agent Pixel") -} - -extension OSLog { - - public static var dataBrokerProtection: OSLog { - Logging.dataBrokerProtectionLoggingEnabled ? Logging.dataBrokerProtection : .disabled - } - - public static var action: OSLog { - Logging.actionLoggingEnabled ? Logging.action : .disabled - } - - public static var service: OSLog { - Logging.serviceLoggingEnabled ? Logging.service : .disabled - } - - public static var error: OSLog { - Logging.errorsLoggingEnabled ? Logging.error : .disabled - } - - public static var dbpBackgroundAgent: OSLog { - Logging.backgroundAgentLoggingEnabled ? Logging.backgroundAgent : .disabled - } - - public static var dbpBackgroundAgentMemoryManagement: OSLog { - Logging.backgroundAgentMemoryManagementLoggingEnabled ? Logging.backgroundAgentMemoryManagement : .disabled - } - - public static var dbpBackgroundAgentPixel: OSLog { - Logging.backgroundAgentPixelLoggingEnabled ? Logging.backgroundAgentPixel : .disabled - } -} diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionStatsPixelsTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionStatsPixelsTests.swift index bcc6879ce9..69e3c2ffd5 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionStatsPixelsTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionStatsPixelsTests.swift @@ -19,7 +19,6 @@ import XCTest import Foundation @testable import DataBrokerProtection -import PixelKitTestingUtilities @testable import PixelKit final class DataBrokerProtectionStatsPixelsTests: XCTestCase { diff --git a/LocalPackages/LoginItems/Sources/LoginItems/LoginItem.swift b/LocalPackages/LoginItems/Sources/LoginItems/LoginItem.swift index eecea5a8ba..db30866437 100644 --- a/LocalPackages/LoginItems/Sources/LoginItems/LoginItem.swift +++ b/LocalPackages/LoginItems/Sources/LoginItems/LoginItem.swift @@ -17,9 +17,9 @@ // import AppKit -import os.log import Foundation import ServiceManagement +import os.log public enum SMLoginItemSetEnabledError: Error { case failed @@ -28,11 +28,10 @@ public enum SMLoginItemSetEnabledError: Error { /// Takes care of enabling and disabling a login item. /// public struct LoginItem: Equatable, Hashable { - public let agentBundleID: String private let launchInformation: LoginItemLaunchInformation private let defaults: UserDefaults - private let log: OSLog + private let logger: Logger public var isRunning: Bool { !runningApplications.isEmpty @@ -74,21 +73,21 @@ public struct LoginItem: Equatable, Hashable { $0["Label"] as? String == agentBundleID }) else { return .notRegistered } - os_log("🟢 found login item job: %{public}@", log: log, job.debugDescription) + logger.debug("🟢 found login item job: \(job.debugDescription, privacy: .public)") return job["OnDemand"] as? Bool == true ? .enabled : .requiresApproval } return Status(SMAppService.loginItem(identifier: agentBundleID).status) } - public init(bundleId: String, defaults: UserDefaults, log: OSLog = .disabled) { + public init(bundleId: String, defaults: UserDefaults, logger: Logger) { self.agentBundleID = bundleId self.defaults = defaults self.launchInformation = LoginItemLaunchInformation(agentBundleID: bundleId, defaults: defaults) - self.log = log + self.logger = logger } public func enable() throws { - os_log("🟢 registering login item %{public}@", log: log, self.debugDescription) + logger.debug("🟢 registering login item \(self.debugDescription, privacy: .public)") if #available(macOS 13.0, *) { try SMAppService.loginItem(identifier: agentBundleID).register() @@ -103,7 +102,7 @@ public struct LoginItem: Equatable, Hashable { } public func disable() throws { - os_log("🟢 unregistering login item %{public}@", log: log, self.debugDescription) + logger.debug("🟢 unregistering login item \(self.debugDescription, privacy: .public)") if #available(macOS 13.0, *) { try SMAppService.loginItem(identifier: agentBundleID).unregister() @@ -121,7 +120,7 @@ public struct LoginItem: Equatable, Hashable { /// public func restart() throws { guard [.enabled].contains(status) else { - os_log("🟢 restart not needed for login item %{public}@", log: log, self.debugDescription) + logger.debug("🟢 restart not needed for login item \(self.debugDescription, privacy: .public)") return } try? disable() @@ -130,9 +129,18 @@ public struct LoginItem: Equatable, Hashable { public func forceStop() { let runningApplications = runningApplications - os_log("🟢 stopping %{public}@", log: log, runningApplications.map { $0.processIdentifier }.description) + logger.debug("🟢 stopping \(runningApplications.map { $0.processIdentifier }.description, privacy: .public)") runningApplications.forEach { $0.terminate() } } + + public static func == (lhs: LoginItem, rhs: LoginItem) -> Bool { + lhs.agentBundleID == rhs.agentBundleID && lhs.launchInformation == rhs.launchInformation + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(agentBundleID) + hasher.combine(launchInformation) + } } extension LoginItem: CustomDebugStringConvertible { diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 2c50733ad9..b1da627366 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -32,7 +32,7 @@ let package = Package( .library(name: "VPNAppLauncher", targets: ["VPNAppLauncher"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "186.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "190.0.0"), .package(url: "https://github.com/airbnb/lottie-spm", exact: "4.4.3"), .package(path: "../AppLauncher"), .package(path: "../UDSHelper"), @@ -84,11 +84,24 @@ let package = Package( ] ), + // MARK: - VPNPixels + + .target( + name: "VPNPixels", + dependencies: [ + .product(name: "PixelKit", package: "BrowserServicesKit"), + ], + swiftSettings: [ + .define("DEBUG", .when(configuration: .debug)) + ] + ), + // MARK: - NetworkProtectionUI .target( name: "NetworkProtectionUI", dependencies: [ + "VPNPixels", .product(name: "NetworkProtection", package: "BrowserServicesKit"), .product(name: "PixelKit", package: "BrowserServicesKit"), .product(name: "SwiftUIExtensions", package: "SwiftUIExtensions"), diff --git a/LocalPackages/NetworkProtectionMac/README.md b/LocalPackages/NetworkProtectionMac/README.md index 49354a0caa..4fb9bba395 100644 --- a/LocalPackages/NetworkProtectionMac/README.md +++ b/LocalPackages/NetworkProtectionMac/README.md @@ -4,3 +4,4 @@ This package contains several components of VPN that are specific to macOS. - NetworkProtectionUI: UI elements used in the VPN that are reusable across build targets. - NetworkProtectionIPC: code to support IPC communication between VPN targets. +- VPNPixels: module for NetworkProtectionUI to have pixel support for shared UI components. diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/FlowManagers/TCPFlowManager.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/FlowManagers/TCPFlowManager.swift index dcfb727d6b..b94170fadd 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/FlowManagers/TCPFlowManager.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/FlowManagers/TCPFlowManager.swift @@ -82,12 +82,12 @@ final class TCPFlowManager { do { try await startDataCopyLoop(for: remoteConnection) - logger.log("🔴 Stopping proxy connection to \(remoteEndpoint, privacy: .public)") + logger.log("Stopping proxy connection to \(remoteEndpoint, privacy: .public)") remoteConnection.cancel() flow.closeReadWithError(nil) flow.closeWriteWithError(nil) } catch { - logger.log("🔴 Stopping proxy connection to \(remoteEndpoint, privacy: .public) with error \(String(reflecting: error), privacy: .public)") + logger.log("Stopping proxy connection to \(remoteEndpoint, privacy: .public) with error \(String(reflecting: error), privacy: .public)") remoteConnection.cancel() flow.closeReadWithError(error) diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Extensions/UserText+NetworkProtectionUI.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Extensions/UserText+NetworkProtectionUI.swift index e418d7ce5a..e86879002c 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Extensions/UserText+NetworkProtectionUI.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Extensions/UserText+NetworkProtectionUI.swift @@ -34,7 +34,7 @@ final class UserText { static let vpnLocationSelected = NSLocalizedString("network.protection.vpn.location.selected", value: "Selected Location", comment: "Description of the location type in the VPN status view") static let vpnDnsServer = NSLocalizedString("network.protection.vpn.dns-server", value: "DNS Server", comment: "Title for the DNS server section in the VPN status view") static let vpnDataVolume = NSLocalizedString("network.protection.vpn.data-volume", value: "Data Volume", comment: "Title for the data volume section in the VPN status view") - static let vpnShareFeedback = NSLocalizedString("network.protection.vpn.share-feedback", value: "Share VPN Feedback…", comment: "Action button title for the Share VPN feedback option") + static let vpnSendFeedback = NSLocalizedString("network.protection.vpn.send-feedback", value: "Send Feedback…", comment: "Action button title for the Send feedback option") static let vpnOperationNotPermittedMessage = NSLocalizedString("network.protection.vpn.failure.operation-not-permitted", value: "Unable to connect due to an unexpected error. Restarting your Mac can usually fix the issue.", comment: "Error message for the Operation not permitted error") static let vpnLoginItemVersionMismatchedMessage = NSLocalizedString("network.protection.vpn.failure.login-item-version-mismatched", value: "Unable to connect due to versioning conflict. If you have multiple versions of the browser installed, remove all but the most recent version of DuckDuckGo and restart your Mac.", comment: "Error message for the Login item version mismatched error") static let vpnRegisteredServerFetchingFailedMessage = NSLocalizedString("network.protection.vpn.failure.registered-server-fetching-failed", value: "Unable to connect. Double check your internet connection. Make sure other software or services aren't blocking DuckDuckGo VPN servers.", comment: "Error message for the Failed to fetch registered server error") diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteIssuesReporter/SiteIssuesReporter.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteIssuesReporter/SiteIssuesReporter.swift new file mode 100644 index 0000000000..51fead7b56 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteIssuesReporter/SiteIssuesReporter.swift @@ -0,0 +1,60 @@ +// +// SiteIssuesReporter.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppKit +import Foundation +import PixelKit +import VPNPixels + +public struct SiteIssuesReporter { + + private let pixelKit: PixelFiring? + + public init(pixelKit: PixelFiring? = PixelKit.shared) { + self.pixelKit = pixelKit + } + + private func makeAlert(title: String, message: String? = nil, buttonNames: [String] = ["Ok"]) -> NSAlert{ + + let alert = NSAlert() + alert.messageText = title + + if let message = message { + alert.informativeText = message + } + + for buttonName in buttonNames { + alert.addButton(withTitle: buttonName) + } + alert.accessoryView = NSView(frame: NSRect(x: 0, y: 0, width: 200, height: 0)) + return alert + } + + public func askUserToReportIssues(withDomain domain: String) { + let alert = makeAlert(title: "Report Site Issues?", + message: "Help us improve by anonymously reporting that \(domain) doesn't work correctly through the VPN.", + buttonNames: ["Report", "Don't Report"]) + + let response = alert.runModal() + + if response == .alertFirstButtonReturn { + let pixel: SiteTroubleshootingPixel = .reportIssues(domain: domain) + pixelKit?.fire(pixel) + } + } +} diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingViewModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingViewModel.swift index b026feed85..2ccc1d8a88 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingViewModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SiteTroubleshootingView/SiteTroubleshootingViewModel.swift @@ -16,9 +16,12 @@ // limitations under the License. // +import AppKit import Combine import Foundation import NetworkProtection +import PixelKit +import VPNPixels extension SiteTroubleshootingView { @@ -42,14 +45,17 @@ extension SiteTroubleshootingView { } private let uiActionHandler: VPNUIActionHandling + private let pixelKit: PixelFiring? private var cancellables = Set() public init(featureFlagPublisher: AnyPublisher, connectionStatusPublisher: AnyPublisher, siteTroubleshootingInfoPublisher: AnyPublisher, - uiActionHandler: VPNUIActionHandling) { + uiActionHandler: VPNUIActionHandling, + pixelKit: PixelFiring? = PixelKit.shared) { self.uiActionHandler = uiActionHandler + self.pixelKit = pixelKit subscribeToConnectionStatusChanges(connectionStatusPublisher) subscribeToSiteTroubleshootingInfoChanges(siteTroubleshootingInfoPublisher) @@ -81,7 +87,20 @@ extension SiteTroubleshootingView { func setExclusion(_ exclude: Bool, forDomain domain: String) { Task { @MainActor in + guard let siteInfo, + siteInfo.excluded != exclude else { + return + } + + let engagementPixel = DomainExclusionsEngagementPixel(added: exclude) + pixelKit?.fire(engagementPixel) + await uiActionHandler.setExclusion(exclude, forDomain: domain) + + if exclude && true { + let siteIssuesReporter = SiteIssuesReporter(pixelKit: pixelKit) + siteIssuesReporter.askUserToReportIssues(withDomain: domain) + } } } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusViewModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusViewModel.swift index 835078e15b..8385f23f26 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusViewModel.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusViewModel.swift @@ -345,7 +345,7 @@ extension NetworkProtectionStatusView { var warningViewModel: WarningView.Model? { if let warningMessage = warningMessage(for: knownFailure) { return WarningView.Model(message: warningMessage, - actionTitle: UserText.vpnShareFeedback, + actionTitle: UserText.vpnSendFeedback, action: openFeedbackForm) } diff --git a/LocalPackages/NetworkProtectionMac/Sources/VPNPixels/DomainExclusionsEngagementPixel.swift b/LocalPackages/NetworkProtectionMac/Sources/VPNPixels/DomainExclusionsEngagementPixel.swift new file mode 100644 index 0000000000..acc2ad9197 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/VPNPixels/DomainExclusionsEngagementPixel.swift @@ -0,0 +1,64 @@ +// +// DomainExclusionsEngagementPixel.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import PixelKit + +/// Pixels to understand domain exclusion engagement +/// +public enum DomainExclusionsEngagementPixel: VPNPixel { + + /// One or more exclusions were added. + /// + case exclusionAdded + + /// One or more exclusions were removed. + /// + case exclusionRemoved + + public init(added: Bool) { + self = added ? .exclusionAdded : .exclusionRemoved + } + + // The name is provided by the convenience implementation in VPNPixel, + // so we don't need to implement it here. + // + // var name: String + + public var unscopedPixelName: String { + switch self { + case .exclusionAdded: + return "domain_exclusion_enabled" + case .exclusionRemoved: + return "domain_exclusion_disabled" + } + } + + public var parameters: [String: String]? { + switch self { + case .exclusionAdded: + return [:] + case .exclusionRemoved: + return [:] + } + } + + public var error: Error? { + return nil + } +} diff --git a/LocalPackages/NetworkProtectionMac/Sources/VPNPixels/SiteTroubleshootingPixel.swift b/LocalPackages/NetworkProtectionMac/Sources/VPNPixels/SiteTroubleshootingPixel.swift new file mode 100644 index 0000000000..7eeac03830 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/VPNPixels/SiteTroubleshootingPixel.swift @@ -0,0 +1,52 @@ +// +// SiteTroubleshootingPixel.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import PixelKit + +/// Site troubleshooting pixels +/// +public enum SiteTroubleshootingPixel: VPNPixel { + + /// The user decided to report site issues. + /// + case reportIssues(domain: String) + + // The name is provided by the convenience implementation in VPNPixel, + // so we don't need to implement it here. + // + // var name: String + + public var unscopedPixelName: String { + switch self { + case .reportIssues: + return "report_site_issues" + } + } + + public var parameters: [String: String]? { + switch self { + case .reportIssues(let domain): + return [PixelKit.Parameters.domain: domain] + } + } + + public var error: Error? { + return nil + } +} diff --git a/LocalPackages/NetworkProtectionMac/Sources/VPNPixels/VPNPixel.swift b/LocalPackages/NetworkProtectionMac/Sources/VPNPixels/VPNPixel.swift new file mode 100644 index 0000000000..cdb906b760 --- /dev/null +++ b/LocalPackages/NetworkProtectionMac/Sources/VPNPixels/VPNPixel.swift @@ -0,0 +1,44 @@ +// +// VPNPixel.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import PixelKit + +/// The prefix that should be used for all pixels in this module +/// +let vpnPixelModulePrefix = "vpn" + +public protocol VPNPixel: PixelKitEventV2 { + + /// The name of the pixel without the module prefix. + /// + var unscopedPixelName: String { get } +} + +extension VPNPixel { + + /// Convenience method to provide the scoped pixel name. + /// + public var name: String { + scopedPixelName(forUnscopedPixelName: unscopedPixelName) + } + + func scopedPixelName(forUnscopedPixelName unscopedName: String) -> String { + "\(vpnPixelModulePrefix)_\(unscopedName)" + } +} diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift index 28af28412f..dc054cbeb6 100644 --- a/LocalPackages/SubscriptionUI/Package.swift +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -12,13 +12,14 @@ let package = Package( targets: ["SubscriptionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "186.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "190.0.0"), .package(path: "../SwiftUIExtensions") ], targets: [ .target( name: "SubscriptionUI", dependencies: [ + .product(name: "BrowserServicesKit", package: "BrowserServicesKit"), .product(name: "Subscription", package: "BrowserServicesKit"), .product(name: "SwiftUIExtensions", package: "SwiftUIExtensions"), .product(name: "PreferencesViews", package: "SwiftUIExtensions"), diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift index bfacdc587e..53dcbb131e 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift @@ -57,6 +57,7 @@ public final class PreferencesSubscriptionModel: ObservableObject { case openVPN, openDB, openITR, + openFeedback, iHaveASubscriptionClick, activateAddEmailClick, postSubscriptionAddEmailClick, @@ -282,6 +283,11 @@ public final class PreferencesSubscriptionModel: ObservableObject { openURLHandler(subscriptionManager.url(for: .faq)) } + @MainActor + func openUnifiedFeedbackForm() { + userEventHandler(.openFeedback) + } + @MainActor func refreshSubscriptionPendingState() { if subscriptionManager.currentEnvironment.purchasePlatform == .appStore { diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionView.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionView.swift index 57a37fe5c4..283f5e7f7e 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionView.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionView.swift @@ -19,6 +19,7 @@ import PreferencesViews import SwiftUI import SwiftUIExtensions +import BrowserServicesKit public struct PreferencesSubscriptionView: View { @@ -30,8 +31,12 @@ public struct PreferencesSubscriptionView: View { @State private var manageSubscriptionSheet: ManageSubscriptionSheet? - public init(model: PreferencesSubscriptionModel) { + private let subscriptionFeatureAvailability: SubscriptionFeatureAvailability + + public init(model: PreferencesSubscriptionModel, + subscriptionFeatureAvailability: SubscriptionFeatureAvailability) { self.model = model + self.subscriptionFeatureAvailability = subscriptionFeatureAvailability } public var body: some View { @@ -96,6 +101,11 @@ public struct PreferencesSubscriptionView: View { // Help section helpSection + + // Feedback section + if subscriptionFeatureAvailability.usesUnifiedFeedbackForm, state == .subscriptionActive { + feedbackSection + } } .onAppear(perform: { if model.isUserAuthenticated { @@ -277,6 +287,17 @@ public struct PreferencesSubscriptionView: View { } } + @ViewBuilder + private var feedbackSection: some View { + PreferencePaneSection { + TextMenuItemHeader(UserText.preferencesSubscriptionFeedbackTitle, bottomPadding: 0) + HStack(alignment: .top, spacing: 6) { + TextMenuItemCaption(UserText.preferencesSubscriptionFeedbackCaption) + Button(UserText.preferencesSubscriptionFeedbackButton) { model.openUnifiedFeedbackForm() } + } + } + } + @ViewBuilder private var emailView: some View { VStack { diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift index 388c2c0131..0a22093283 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift @@ -50,6 +50,9 @@ enum UserText { static let preferencesSubscriptionFooterTitle = NSLocalizedString("subscription.preferences.subscription.footer.title", value: "Need help with Privacy Pro?", comment: "Title for the subscription preferences pane footer") static let preferencesSubscriptionFooterCaption = NSLocalizedString("subscription.preferences.subscription.footer.caption", value: "Get answers to frequently asked questions or contact Privacy Pro support from our help pages.", comment: "Caption for the subscription preferences pane footer") static let viewFaqsButton = NSLocalizedString("subscription.preferences.view.faqs.button", value: "FAQs and Support", comment: "Button to open page for FAQs") + static let preferencesSubscriptionFeedbackTitle = NSLocalizedString("subscription.preferences.feedback.title", value: "Send Feedback", comment: "Title for the subscription feedback section") + static let preferencesSubscriptionFeedbackCaption = NSLocalizedString("subscription.preferences.feedback.caption", value: "Help improve Privacy Pro. Your feedback matters to us. Feel free to report any issues or provide general feedback.", comment: "Caption for the subscription feedback section") + static let preferencesSubscriptionFeedbackButton = NSLocalizedString("subscription.preferences.feedback.button", value: "Send Feedback", comment: "Title for the subscription feedback button") static func preferencesSubscriptionActiveRenewCaption(period: String, formattedDate: String) -> String { let localized = NSLocalizedString("subscription.preferences.subscription.active.renew.caption", value: "Your %@ Privacy Pro subscription renews on %@.", comment: "Caption for the subscription preferences pane when the subscription is active and will renew. First parameter is renewal period (monthly/yearly). Second parameter is date.") diff --git a/LocalPackages/UDSHelper/Sources/UDSHelper/Logger+UDSHelper.swift b/LocalPackages/UDSHelper/Sources/UDSHelper/Logger+UDSHelper.swift new file mode 100644 index 0000000000..5a4d5b9675 --- /dev/null +++ b/LocalPackages/UDSHelper/Sources/UDSHelper/Logger+UDSHelper.swift @@ -0,0 +1,24 @@ +// +// Logger+UDSHelper.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import os.log + +extension Logger { + static var udsHelper = { Logger(subsystem: "UDS Helper", category: "") }() +} diff --git a/LocalPackages/UDSHelper/Sources/UDSHelper/UDSClient.swift b/LocalPackages/UDSHelper/Sources/UDSHelper/UDSClient.swift index 1c3f153176..58dfde0afe 100644 --- a/LocalPackages/UDSHelper/Sources/UDSHelper/UDSClient.swift +++ b/LocalPackages/UDSHelper/Sources/UDSHelper/UDSClient.swift @@ -33,7 +33,6 @@ public actor UDSClient { private let socketFileURL: URL private let receiver: UDSReceiver private let queue = DispatchQueue(label: "com.duckduckgo.UDSConnection.queue.\(UUID().uuidString)") - private let log: OSLog private let payloadHandler: PayloadHandler? // MARK: - Message completion callbacks @@ -47,15 +46,12 @@ public actor UDSClient { /// This should not be called directly because the socketFileURL needs to comply with some requirements in terms of /// maximum length of the path. Use any public factory method provided below instead. /// - public init(socketFileURL: URL, - log: OSLog, - payloadHandler: PayloadHandler? = nil) { + public init(socketFileURL: URL, payloadHandler: PayloadHandler? = nil) { - os_log("UDSClient - Initialized with path: %{public}@", log: log, type: .info, socketFileURL.path) + Logger.udsHelper.info("UDSClient - Initialized with path: \(socketFileURL.path, privacy: .public)") - self.receiver = UDSReceiver(log: log) + self.receiver = UDSReceiver() self.socketFileURL = socketFileURL - self.log = log self.payloadHandler = payloadHandler } @@ -106,19 +102,19 @@ public actor UDSClient { private func statusUpdateHandler(_ state: NWConnection.State) { switch state { case .cancelled: - os_log("UDSClient - Connection cancelled", log: self.log, type: .info) + Logger.udsHelper.info("UDSClient - Connection cancelled") self.releaseConnection() case .failed(let error): - os_log("UDSClient - Connection failed with error: %{public}@", log: self.log, type: .error, String(describing: error)) + Logger.udsHelper.error("UDSClient - Connection failed with error: \(error.localizedDescription, privacy: .public)") self.releaseConnection() case .ready: - os_log("UDSClient - Connection ready", log: self.log, type: .info) + Logger.udsHelper.info("UDSClient - Connection ready") case .waiting(let error): - os_log("UDSClient - Waiting to connect... %{public}@", log: self.log, type: .info, String(describing: error)) + Logger.udsHelper.error("UDSClient - Waiting to connect... \(error.localizedDescription, privacy: .public)") default: - os_log("UDSClient - Unexpected state", log: self.log, type: .info) + Logger.udsHelper.info("UDSClient - Unexpected state") } } @@ -171,12 +167,12 @@ public actor UDSClient { try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in connection.send(content: payload, completion: .contentProcessed { error in if let error { - os_log("UDSClient - Send Error %{public}@", log: self.log, String(describing: error)) + Logger.udsHelper.error("UDSClient - Send Error \(error.localizedDescription, privacy: .public)") continuation.resume(throwing: error) return } - os_log("UDSClient - Send Success", log: self.log) + Logger.udsHelper.info("UDSClient - Send Success") continuation.resume() }) } diff --git a/LocalPackages/UDSHelper/Sources/UDSHelper/UDSReceiver.swift b/LocalPackages/UDSHelper/Sources/UDSHelper/UDSReceiver.swift index 6ad9e5bef6..425292af7e 100644 --- a/LocalPackages/UDSHelper/Sources/UDSHelper/UDSReceiver.swift +++ b/LocalPackages/UDSHelper/Sources/UDSHelper/UDSReceiver.swift @@ -35,12 +35,6 @@ struct UDSReceiver { case connectionClosed } - private let log: OSLog - - init(log: OSLog) { - self.log = log - } - /// Starts receiveing messages for a specific connection /// /// - Parameters: @@ -66,37 +60,25 @@ struct UDSReceiver { } catch { switch error { case ReadError.notEnoughData(let expected, let received): - os_log("UDSServer - Connection closing due to error: Not enough data (expected: %{public}@, received: %{public}@", - log: log, - type: .error, - String(describing: expected), - String(describing: received)) + Logger.udsHelper.error("UDSServer - Connection closing due to error: Not enough data (expected: \(String(describing: expected), privacy: .public), received: \(String(describing: received), privacy: .public)") guard await errorHandler(error) else { return } case ReadError.connectionError(let error): - os_log("UDSServer - Connection closing due to a connection error: %{public}@", - log: log, - type: .error, - String(describing: error)) + Logger.udsHelper.error("UDSServer - Connection closing due to a connection error: \(error.localizedDescription, privacy: .public)") guard await errorHandler(error) else { return } case ReadError.connectionClosed: - os_log("UDSServer - Connection closing: End of file reached", - log: log, - type: .info) + Logger.udsHelper.info("UDSServer - Connection closing: End of file reached") guard await errorHandler(error) else { return } default: - os_log("UDSServer - Connection closing due to error: %{public}@", - log: log, - type: .error, - String(describing: error)) + Logger.udsHelper.error("UDSServer - Connection closing due to error: \(error.localizedDescription, privacy: .public)") guard await errorHandler(error) else { return diff --git a/LocalPackages/UDSHelper/Sources/UDSHelper/UDSServer.swift b/LocalPackages/UDSHelper/Sources/UDSHelper/UDSServer.swift index ed72e65fb7..f56ddd57ef 100644 --- a/LocalPackages/UDSHelper/Sources/UDSHelper/UDSServer.swift +++ b/LocalPackages/UDSHelper/Sources/UDSHelper/UDSServer.swift @@ -73,7 +73,6 @@ public final class UDSServer { private let fileManager: FileManager private let socketFileURL: URL - private let log: OSLog /// Default initializer /// @@ -82,13 +81,11 @@ public final class UDSServer { /// to share this socket with other apps in the same app group, this path should be in an app group /// that both apps have access to. /// - socketFileName: the name of the socket file - /// - log: the log to use /// - public init(socketFileURL: URL, fileManager: FileManager = .default, log: OSLog) { + public init(socketFileURL: URL, fileManager: FileManager = .default) { self.fileManager = fileManager self.socketFileURL = socketFileURL - self.log = log - self.receiver = UDSReceiver(log: log) + self.receiver = UDSReceiver() do { try fileManager.removeItem(at: socketFileURL) @@ -96,7 +93,7 @@ public final class UDSServer { print(error) } - os_log("UDSServer - Initialized with path: %{public}@", log: log, type: .info, socketFileURL.path) + Logger.udsHelper.info("UDSServer - Initialized with path: \(socketFileURL.path, privacy: .public)") } public func start(messageHandler: @escaping (Data) async throws -> Data?) throws { @@ -114,10 +111,7 @@ public final class UDSServer { listener = try NWListener(using: params) self.listener = listener } catch { - os_log("UDSServer - Error creating listener: %{public}@", - log: log, - type: .error, - String(describing: error)) + Logger.udsHelper.error("UDSServer - Error creating listener: \(error.localizedDescription, privacy: .public)") throw error } @@ -130,12 +124,12 @@ public final class UDSServer { switch state { case .ready: - os_log("UDSServer - Listener is ready", log: log, type: .info) + Logger.udsHelper.info("UDSServer - Listener is ready") case .failed(let error): - os_log("UDSServer - Listener failed with error: %{public}@", log: log, type: .error, String(describing: error)) + Logger.udsHelper.error("UDSServer - Listener failed with error: \(error.localizedDescription, privacy: .public)") stop() case .cancelled: - os_log("UDSServer - Listener cancelled", log: log, type: .info) + Logger.udsHelper.info("UDSServer - Listener cancelled") default: break } @@ -168,23 +162,20 @@ public final class UDSServer { private func handleNewConnection(_ connection: NWConnection, messageHandler: @escaping (Data) async throws -> Data?) { Task { - os_log("UDSServer - New connection: %{public}@", - log: log, - type: .info, - String(describing: connection.hashValue)) + Logger.udsHelper.info("UDSServer - New connection: \(String(describing: connection.hashValue), privacy: .public)") connection.stateUpdateHandler = { [weak self] state in guard let self else { return } switch state { case .ready: - os_log("UDSServer - Client connection is ready", log: log, type: .info) + Logger.udsHelper.info("UDSServer - Client connection is ready") self.startReceivingMessages(on: connection, messageHandler: messageHandler) case .failed(let error): - os_log("UDSServer - Client connection failed with error: %{public}@", log: log, type: .error, String(describing: error)) + Logger.udsHelper.error("UDSServer - Client connection failed with error: \(error.localizedDescription, privacy: .public)") self.closeConnection(connection) case .cancelled: - os_log("UDSServer - Client connection cancelled", log: log, type: .info) + Logger.udsHelper.info("UDSServer - Client connection cancelled") default: break } @@ -262,12 +253,12 @@ public final class UDSServer { try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in connection.send(content: lengthData + data, completion: .contentProcessed { error in if let error { - os_log("UDSServer - Send Error %{public}@", log: self.log, String(describing: error)) + Logger.udsHelper.error("UDSServer - Send Error \(error.localizedDescription, privacy: .public)") continuation.resume(throwing: error) return } - os_log("UDSServer - Send Success", log: self.log) + Logger.udsHelper.info("UDSServer - Send Success") continuation.resume() }) } diff --git a/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift b/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift index f85d03a145..b64e193a4b 100644 --- a/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift +++ b/UnitTests/DBP/Tests/DataBrokerProtectionFeatureGatekeeperTests.swift @@ -123,12 +123,15 @@ private class MockFeatureDisabler: DataBrokerProtectionFeatureDisabling { private class MockFeatureAvailability: SubscriptionFeatureAvailability { var mockFeatureAvailable: Bool = false var mockSubscriptionPurchaseAllowed: Bool = false + var mockUsesUnifiedFeedbackForm: Bool = false var isFeatureAvailable: Bool { mockFeatureAvailable } var isSubscriptionPurchaseAllowed: Bool { mockSubscriptionPurchaseAllowed } + var usesUnifiedFeedbackForm: Bool { mockUsesUnifiedFeedbackForm } func reset() { mockFeatureAvailable = false mockSubscriptionPurchaseAllowed = false + mockUsesUnifiedFeedbackForm = false } } diff --git a/UnitTests/DuckSchemeHandler/DuckSchemeHandlerTests.swift b/UnitTests/DuckSchemeHandler/DuckSchemeHandlerTests.swift index df01397e81..cec7c92df1 100644 --- a/UnitTests/DuckSchemeHandler/DuckSchemeHandlerTests.swift +++ b/UnitTests/DuckSchemeHandler/DuckSchemeHandlerTests.swift @@ -26,7 +26,7 @@ final class DuckSchemeHandlerTests: XCTestCase { func testWebViewFromOnboardingHandlerReturnsResponseAndData() throws { // Given - let onboardingURL = URL(string: "duck://onboarding?platform=integration")! + let onboardingURL = URL(string: "duck://onboarding")! let handler = DuckURLSchemeHandler() let webView = WKWebView() let schemeTask = MockSchemeTask(request: URLRequest(url: onboardingURL)) @@ -43,6 +43,25 @@ final class DuckSchemeHandlerTests: XCTestCase { XCTAssertNil(schemeTask.error) } + func testWebViewFromReleaseNoteHandlerReturnsResponseAndData() throws { + // Given + let releaseNotesURL = URL(string: "duck://release-notes")! + let handler = DuckURLSchemeHandler() + let webView = WKWebView() + let schemeTask = MockSchemeTask(request: URLRequest(url: releaseNotesURL)) + + // When + handler.webView(webView, start: schemeTask) + + // Then + XCTAssertEqual(schemeTask.response?.url, releaseNotesURL) + XCTAssertEqual(schemeTask.response?.mimeType, "text/html") + XCTAssertNotNil(schemeTask.data) + XCTAssertTrue(schemeTask.data?.utf8String()?.contains("Browser Release Notes") ?? false) + XCTAssertTrue(schemeTask.didFinishCalled) + XCTAssertNil(schemeTask.error) + } + @MainActor func testWebViewFromDuckPlayerHandlerReturnsResponseAndData() throws { // Given diff --git a/UnitTests/Menus/MoreOptionsMenuTests.swift b/UnitTests/Menus/MoreOptionsMenuTests.swift index 97b37a84c0..6d95c92e88 100644 --- a/UnitTests/Menus/MoreOptionsMenuTests.swift +++ b/UnitTests/Menus/MoreOptionsMenuTests.swift @@ -81,7 +81,8 @@ final class MoreOptionsMenuTests: XCTestCase { passwordManagerCoordinator: passwordManagerCoordinator, vpnFeatureGatekeeper: networkProtectionVisibilityMock, subscriptionFeatureAvailability: SubscriptionFeatureAvailabilityMock(isFeatureAvailable: true, - isSubscriptionPurchaseAllowed: true), + isSubscriptionPurchaseAllowed: true, + usesUnifiedFeedbackForm: false), sharingMenu: NSMenu(), internalUserDecider: internalUserDecider, subscriptionManager: subscriptionManager) diff --git a/UnitTests/VPNFeedbackForm/VPNFeedbackFormViewModelTests.swift b/UnitTests/NetworkProtection/VPNFeedbackFormViewModelTests.swift similarity index 99% rename from UnitTests/VPNFeedbackForm/VPNFeedbackFormViewModelTests.swift rename to UnitTests/NetworkProtection/VPNFeedbackFormViewModelTests.swift index a12d3a8fa1..9494befe17 100644 --- a/UnitTests/VPNFeedbackForm/VPNFeedbackFormViewModelTests.swift +++ b/UnitTests/NetworkProtection/VPNFeedbackFormViewModelTests.swift @@ -80,7 +80,7 @@ private class MockVPNMetadataCollector: VPNMetadataCollector { var collectedMetadata: Bool = false - func collectMetadata() async -> VPNMetadata { + func collectVPNMetadata() async -> VPNMetadata { self.collectedMetadata = true let appInfo = VPNMetadata.AppInfo( diff --git a/UnitTests/Onboarding/OnboardingSuggestedSearchesProviderTests.swift b/UnitTests/Onboarding/OnboardingSuggestedSearchesProviderTests.swift new file mode 100644 index 0000000000..405f242732 --- /dev/null +++ b/UnitTests/Onboarding/OnboardingSuggestedSearchesProviderTests.swift @@ -0,0 +1,77 @@ +// +// OnboardingSuggestedSearchesProviderTests.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +import XCTest +import Onboarding +@testable import DuckDuckGo_Privacy_Browser + +class OnboardingSuggestedSearchesProviderTests: XCTestCase { + + let userText = UserText.ContextualOnboarding.self + + func testSearchesListForEnglishLanguageAndUsRegion() { + let mockProvider = MockOnboardingRegionAndLanguageProvider(regionCode: "us", languageCode: "en") + let provider = OnboardingSuggestedSearchesProvider(countryAndLanguageProvider: mockProvider) + + let expectedSearches = [ + ContextualOnboardingListItem.search(title: userText.tryASearchOption1English), + ContextualOnboardingListItem.search(title: userText.tryASearchOption2English), + ContextualOnboardingListItem.surprise(title: userText.tryASearchOptionSurpriseMeEnglish, visibleTitle: UserText.ContextualOnboarding.tryASearchOptionSurpriseMeTitle) + ] + + XCTAssertEqual(provider.list, expectedSearches) + } + + func testSearchesListForNonEnglishLanguageAndNonUSRegion() { + let mockProvider = MockOnboardingRegionAndLanguageProvider(regionCode: "fr", languageCode: "fr") + let provider = OnboardingSuggestedSearchesProvider(countryAndLanguageProvider: mockProvider) + + let expectedSearches = [ + ContextualOnboardingListItem.search(title: userText.tryASearchOption1International), + ContextualOnboardingListItem.search(title: userText.tryASearchOption2International), + ContextualOnboardingListItem.surprise(title: userText.tryASearchOptionSurpriseMeInternational, visibleTitle: UserText.ContextualOnboarding.tryASearchOptionSurpriseMeTitle) + ] + + XCTAssertEqual(provider.list, expectedSearches) + } + + func testSearchesListForUSRegionAndNonEnglishLanguage() { + let mockProvider = MockOnboardingRegionAndLanguageProvider(regionCode: "us", languageCode: "es") + let provider = OnboardingSuggestedSearchesProvider(countryAndLanguageProvider: mockProvider) + + let expectedSearches = [ + ContextualOnboardingListItem.search(title: userText.tryASearchOption1International), + ContextualOnboardingListItem.search(title: userText.tryASearchOption2English), + ContextualOnboardingListItem.surprise(title: userText.tryASearchOptionSurpriseMeEnglish, visibleTitle: UserText.ContextualOnboarding.tryASearchOptionSurpriseMeTitle) + ] + + XCTAssertEqual(provider.list, expectedSearches) + } +} + +class MockOnboardingRegionAndLanguageProvider: OnboardingRegionAndLanguageProvider { + var regionCode: String? + var languageCode: String? + + init(regionCode: String?, languageCode: String?) { + self.regionCode = regionCode + self.languageCode = languageCode + } +} diff --git a/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift b/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift index 5b1eed5211..939334597c 100644 --- a/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift +++ b/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift @@ -63,11 +63,11 @@ final class BrokenSiteReportingReferenceTests: XCTestCase { for test in testData.reportURL.tests { if test.exceptPlatforms.contains(PrivacyReferenceTestHelper.privacyReferenceTestPlatformName) { - os_log("Skipping test, ignore platform for [%s]", type: .info, test.name) + Logger.general.debug("Skipping test, ignore platform for [\(test.name)]") continue } - os_log("Testing [%s]", type: .info, test.name) + Logger.general.debug("Testing [\(test.name)]") var errors: [Error]? if let errs = test.errorDescriptions { diff --git a/UnitTests/PrivacyReferenceTests/FireproofingReferenceTests.swift b/UnitTests/PrivacyReferenceTests/FireproofingReferenceTests.swift index ef5f327672..a4c8691b83 100644 --- a/UnitTests/PrivacyReferenceTests/FireproofingReferenceTests.swift +++ b/UnitTests/PrivacyReferenceTests/FireproofingReferenceTests.swift @@ -59,7 +59,7 @@ final class FireproofingReferenceTests: XCTestCase { @MainActor private func runReferenceTest(_ test: Test) async { - os_log("Testing %s", test.name) + Logger.general.debug("Testing \(test.name)") let loginDomains = testData.fireButtonFireproofing.fireproofedSites.map { sanitizedSite($0) } let logins = MockPreservedLogins(domains: loginDomains, tld: ContentBlocking.shared.tld) diff --git a/UnitTests/Subscription/Mocks/SubscriptionFeatureAvailabilityMock.swift b/UnitTests/Subscription/Mocks/SubscriptionFeatureAvailabilityMock.swift index d6153695f7..aa5c9a0b6f 100644 --- a/UnitTests/Subscription/Mocks/SubscriptionFeatureAvailabilityMock.swift +++ b/UnitTests/Subscription/Mocks/SubscriptionFeatureAvailabilityMock.swift @@ -23,9 +23,11 @@ import BrowserServicesKit public struct SubscriptionFeatureAvailabilityMock: SubscriptionFeatureAvailability { public var isFeatureAvailable: Bool public var isSubscriptionPurchaseAllowed: Bool + public var usesUnifiedFeedbackForm: Bool - public init(isFeatureAvailable: Bool, isSubscriptionPurchaseAllowed: Bool) { + public init(isFeatureAvailable: Bool, isSubscriptionPurchaseAllowed: Bool, usesUnifiedFeedbackForm: Bool) { self.isFeatureAvailable = isFeatureAvailable self.isSubscriptionPurchaseAllowed = isSubscriptionPurchaseAllowed + self.usesUnifiedFeedbackForm = usesUnifiedFeedbackForm } } diff --git a/UnitTests/TabExtensionsTests/ErrorPageTabExtensionTest.swift b/UnitTests/TabExtensionsTests/ErrorPageTabExtensionTest.swift index 51cfbfff44..9426baccfe 100644 --- a/UnitTests/TabExtensionsTests/ErrorPageTabExtensionTest.swift +++ b/UnitTests/TabExtensionsTests/ErrorPageTabExtensionTest.swift @@ -22,23 +22,24 @@ import Navigation import Common import WebKit import XCTest +import SpecialErrorPages @testable import DuckDuckGo_Privacy_Browser final class ErrorPageTabExtensionTest: XCTestCase { var mockWebViewPublisher: PassthroughSubject! - var scriptPublisher: PassthroughSubject! - var errorPageExtention: SSLErrorPageTabExtension! + var scriptPublisher: PassthroughSubject! + var errorPageExtention: SpecialErrorPageTabExtension! var credentialCreator: MockCredentialCreator! let errorURLString = "com.example.error" override func setUpWithError() throws { mockWebViewPublisher = PassthroughSubject() - scriptPublisher = PassthroughSubject() + scriptPublisher = PassthroughSubject() credentialCreator = MockCredentialCreator() let featureFlagger = MockFeatureFlagger() - errorPageExtention = SSLErrorPageTabExtension(webViewPublisher: mockWebViewPublisher, scriptsPublisher: scriptPublisher, urlCredentialCreator: credentialCreator, featureFlagger: featureFlagger) + errorPageExtention = SpecialErrorPageTabExtension(webViewPublisher: mockWebViewPublisher, scriptsPublisher: scriptPublisher, urlCredentialCreator: credentialCreator, featureFlagger: featureFlagger) } override func tearDownWithError() throws { @@ -59,7 +60,7 @@ final class ErrorPageTabExtensionTest: XCTestCase { XCTAssertTrue(errorPageExtention.webView === aWebView) } - @MainActor func testWhenCertificateExpired_ThenExpectedErrorPageIsShown() { + @MainActor func testWhenCertificateExpired_ThenTabExtenstionErrorIsExpectedError() { // GIVEN let mockWebView = MockWKWebView(url: URL(string: errorURLString)!) errorPageExtention.webView = mockWebView @@ -71,8 +72,7 @@ final class ErrorPageTabExtensionTest: XCTestCase { errorPageExtention.navigation(navigation, didFailWith: error) // THEN - let expectedSpecificMessage = SSLErrorType.expired.specificMessage(for: errorURLString, eTldPlus1: eTldPlus1).replacingOccurrences(of: "", with: "<\\/b>").escapedUnicodeHtmlString() - XCTAssertTrue(mockWebView.capturedHTML.contains(expectedSpecificMessage)) + XCTAssertEqual(errorPageExtention.errorData, SpecialErrorData(kind: .ssl, errorType: "expired", domain: eTldPlus1)) } @MainActor func testWhenCertificateSelfSigned_ThenExpectedErrorPageIsShown() { @@ -87,8 +87,7 @@ final class ErrorPageTabExtensionTest: XCTestCase { errorPageExtention.navigation(navigation, didFailWith: error) // THEN - let expectedSpecificMessage = SSLErrorType.selfSigned.specificMessage(for: errorURLString, eTldPlus1: eTldPlus1).replacingOccurrences(of: "", with: "<\\/b>").escapedUnicodeHtmlString() - XCTAssertTrue(mockWebView.capturedHTML.contains(expectedSpecificMessage)) + XCTAssertEqual(errorPageExtention.errorData, SpecialErrorData(kind: .ssl, errorType: "selfSigned", domain: eTldPlus1)) } @MainActor func testWhenCertificateWrongHost_ThenExpectedErrorPageIsShown() { @@ -103,27 +102,27 @@ final class ErrorPageTabExtensionTest: XCTestCase { errorPageExtention.navigation(navigation, didFailWith: error) // THEN - let expectedSpecificMessage = SSLErrorType.wrongHost.specificMessage(for: errorURLString, eTldPlus1: eTldPlus1).replacingOccurrences(of: "", with: "<\\/b>").escapedUnicodeHtmlString() - XCTAssertTrue(mockWebView.capturedHTML.contains(expectedSpecificMessage)) - + XCTAssertEqual(errorPageExtention.errorData, SpecialErrorData(kind: .ssl, errorType: "wrongHost", domain: eTldPlus1)) } @MainActor func test_WhenUserScriptsPublisherPublishSSLErrorPageScript_ThenErrorPageExtensionIsSetAsUserScriptDelegate() { // GIVEN - let aSSLErrorUserScript = SSLErrorPageUserScript() - let mockScriptProvider = MockSSLErrorPageScriptProvider(script: aSSLErrorUserScript) + let userScript = SpecialErrorPageUserScript(localeStrings: SpecialErrorPageUserScript.localeStrings(), + languageCode: Locale.current.languageCode ?? "en") + let mockScriptProvider = MockSpecialErrorPageScriptProvider(script: userScript) // WHEN scriptPublisher.send(mockScriptProvider) // THEN - XCTAssertNotNil(aSSLErrorUserScript.delegate) + XCTAssertNotNil(userScript.delegate) } @MainActor func testWhenNavigationEnded_IfNoFailure_SSLUserScriptIsNotEnabled() { // GIVEN - let userScript = SSLErrorPageUserScript() - let mockScriptProvider = MockSSLErrorPageScriptProvider(script: userScript) + let userScript = SpecialErrorPageUserScript(localeStrings: SpecialErrorPageUserScript.localeStrings(), + languageCode: Locale.current.languageCode ?? "en") + let mockScriptProvider = MockSpecialErrorPageScriptProvider(script: userScript) let mockWebView = MockWKWebView(url: URL(string: errorURLString)!) let action = NavigationAction(request: URLRequest(url: URL(string: "com.example.error")!), navigationType: .custom(.userEnteredUrl), currentHistoryItemIdentity: nil, redirectHistory: nil, isUserInitiated: true, sourceFrame: FrameInfo(frame: WKFrameInfo()), targetFrame: nil, shouldDownload: false, mainFrameNavigation: nil) let navigation = Navigation(identity: .init(nil), responders: .init(), state: .started, redirectHistory: [action], isCurrent: true, isCommitted: true) @@ -135,13 +134,14 @@ final class ErrorPageTabExtensionTest: XCTestCase { // THEN XCTAssertFalse(userScript.isEnabled) - XCTAssertNil(userScript.failingURL) + XCTAssertNil(errorPageExtention.failingURL) } @MainActor func testWhenNavigationEnded_IfNonSSLFailure_SSLUserScriptIsNotEnabled() { // GIVEN - let userScript = SSLErrorPageUserScript() - let mockScriptProvider = MockSSLErrorPageScriptProvider(script: userScript) + let userScript = SpecialErrorPageUserScript(localeStrings: SpecialErrorPageUserScript.localeStrings(), + languageCode: Locale.current.languageCode ?? "en") + let mockScriptProvider = MockSpecialErrorPageScriptProvider(script: userScript) let mockWebView = MockWKWebView(url: URL(string: errorURLString)!) let action = NavigationAction(request: URLRequest(url: URL(string: "com.example.error")!), navigationType: .custom(.userEnteredUrl), currentHistoryItemIdentity: nil, redirectHistory: nil, isUserInitiated: true, sourceFrame: FrameInfo(frame: WKFrameInfo()), targetFrame: nil, shouldDownload: false, mainFrameNavigation: nil) let navigation = Navigation(identity: .init(nil), responders: .init(), state: .started, redirectHistory: [action], isCurrent: true, isCommitted: true) @@ -156,13 +156,14 @@ final class ErrorPageTabExtensionTest: XCTestCase { // THEN XCTAssertFalse(userScript.isEnabled) - XCTAssertNil(userScript.failingURL) + XCTAssertNil(errorPageExtention.failingURL) } @MainActor func testWhenNavigationEnded_IfSSLFailure_AndErrorURLIsDifferentFromNavigationURL_SSLUserScriptIsNotEnabled() { // GIVEN - let userScript = SSLErrorPageUserScript() - let mockScriptProvider = MockSSLErrorPageScriptProvider(script: userScript) + let userScript = SpecialErrorPageUserScript(localeStrings: SpecialErrorPageUserScript.localeStrings(), + languageCode: Locale.current.languageCode ?? "en") + let mockScriptProvider = MockSpecialErrorPageScriptProvider(script: userScript) let mockWebView = MockWKWebView(url: URL(string: errorURLString)!) let action = NavigationAction(request: URLRequest(url: URL(string: "com.different.error")!), navigationType: .custom(.userEnteredUrl), currentHistoryItemIdentity: nil, redirectHistory: nil, isUserInitiated: true, sourceFrame: FrameInfo(frame: WKFrameInfo()), targetFrame: nil, shouldDownload: false, mainFrameNavigation: nil) let navigation = Navigation(identity: .init(nil), responders: .init(), state: .started, redirectHistory: [action], isCurrent: true, isCommitted: true) @@ -176,13 +177,14 @@ final class ErrorPageTabExtensionTest: XCTestCase { // THEN XCTAssertFalse(userScript.isEnabled) - XCTAssertEqual(userScript.failingURL?.absoluteString, errorURLString) + XCTAssertEqual(errorPageExtention.failingURL?.absoluteString, errorURLString) } @MainActor func testWhenNavigationEnded_IfSSLFailure_AndErrorURLIsTheSameAsNavigationURL_SSLUserScriptIsEnabled() { // GIVEN - let userScript = SSLErrorPageUserScript() - let mockScriptProvider = MockSSLErrorPageScriptProvider(script: userScript) + let userScript = SpecialErrorPageUserScript(localeStrings: SpecialErrorPageUserScript.localeStrings(), + languageCode: Locale.current.languageCode ?? "en") + let mockScriptProvider = MockSpecialErrorPageScriptProvider(script: userScript) let mockWebView = MockWKWebView(url: URL(string: errorURLString)!) let action = NavigationAction(request: URLRequest(url: URL(string: "com.example.error")!), navigationType: .custom(.userEnteredUrl), currentHistoryItemIdentity: nil, redirectHistory: nil, isUserInitiated: true, sourceFrame: FrameInfo(frame: WKFrameInfo()), targetFrame: nil, shouldDownload: false, mainFrameNavigation: nil) let navigation = Navigation(identity: .init(nil), responders: .init(), state: .started, redirectHistory: [action], isCurrent: true, isCommitted: true) @@ -196,7 +198,7 @@ final class ErrorPageTabExtensionTest: XCTestCase { // THEN XCTAssertTrue(userScript.isEnabled) - XCTAssertEqual(userScript.failingURL?.absoluteString, errorURLString) + XCTAssertEqual(errorPageExtention.failingURL?.absoluteString, errorURLString) } func testWhenLeaveSiteCalled_AndCanGoBackTrue_ThenWebViewGoesBack() { @@ -351,11 +353,11 @@ class MockWKWebView: NSObject, ErrorPageTabExtensionNavigationDelegate { } } -class MockSSLErrorPageScriptProvider: SSLErrorPageScriptProvider { - var sslErrorPageUserScript: SSLErrorPageUserScript? +class MockSpecialErrorPageScriptProvider: SpecialErrorPageScriptProvider { + var specialErrorPageUserScript: SpecialErrorPageUserScript? - init(script: SSLErrorPageUserScript?) { - self.sslErrorPageUserScript = script + init(script: SpecialErrorPageUserScript?) { + self.specialErrorPageUserScript = script } } diff --git a/UnitTests/TabExtensionsTests/SpecialErrorPageUserScriptTests.swift b/UnitTests/TabExtensionsTests/SpecialErrorPageUserScriptTests.swift new file mode 100644 index 0000000000..37080466c1 --- /dev/null +++ b/UnitTests/TabExtensionsTests/SpecialErrorPageUserScriptTests.swift @@ -0,0 +1,49 @@ +// +// SpecialErrorPageUserScriptTests.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +import SpecialErrorPages +@testable import DuckDuckGo_Privacy_Browser + + final class SpecialErrorPageUserScriptTests: XCTestCase { + + func testLocaleStringsForNotSupportedLanguage() { + // Given + let languageCode = "ko" + + // When + let result = SpecialErrorPageUserScript.localeStrings(for: languageCode) + + // Then + XCTAssertNil(result, "The result should be nil for Korean language") + } + + func testLocaleStringsForPolishLanguage() { + // Given + let languageCode = "pl" + + // When + let result = SpecialErrorPageUserScript.localeStrings(for: languageCode) + + // Then + XCTAssertNotNil(result, "The result should not be nil for the Polish language code.") + let expectedSubstring = "Ostrzeżenie: ta witryna może być niebezpieczna" + XCTAssertTrue(result!.contains(expectedSubstring), "The result should contain the expected Polish string.") + } + + } diff --git a/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift b/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift new file mode 100644 index 0000000000..2e7dae8803 --- /dev/null +++ b/UnitTests/UnifiedFeedbackForm/UnifiedFeedbackFormViewModelTests.swift @@ -0,0 +1,195 @@ +// +// UnifiedFeedbackFormViewModelTests.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import DuckDuckGo_Privacy_Browser + +final class UnifiedFeedbackFormViewModelTests: XCTestCase { + + func testWhenCreatingViewModel_ThenInitialStateIsFeedbackPending() throws { + let collector = MockVPNMetadataCollector() + let sender = MockVPNFeedbackSender() + let viewModel = UnifiedFeedbackFormViewModel(vpnMetadataCollector: collector, feedbackSender: sender) + + XCTAssertEqual(viewModel.viewState, .feedbackPending) + } + + func testWhenSendingFeedbackSucceeds_ThenFeedbackIsSent() async throws { + let collector = MockVPNMetadataCollector() + let sender = MockVPNFeedbackSender() + let viewModel = UnifiedFeedbackFormViewModel(vpnMetadataCollector: collector, feedbackSender: sender) + viewModel.selectedReportType = UnifiedFeedbackReportType.reportIssue.rawValue + let text = "Some feedback report text" + viewModel.feedbackFormText = text + + XCTAssertFalse(sender.sentMetadata) + await viewModel.process(action: .submit) + XCTAssertTrue(sender.sentMetadata) + XCTAssertEqual(sender.receivedData!.4, text) + } + + func testWhenSendingFeedbackFails_ThenFeedbackIsNotSent() async throws { + let collector = MockVPNMetadataCollector() + let sender = MockVPNFeedbackSender() + let viewModel = UnifiedFeedbackFormViewModel(vpnMetadataCollector: collector, feedbackSender: sender) + viewModel.selectedReportType = UnifiedFeedbackReportType.reportIssue.rawValue + let text = "Some feedback report text" + viewModel.feedbackFormText = text + sender.throwErrorWhenSending = true + + XCTAssertFalse(sender.sentMetadata) + await viewModel.process(action: .submit) + XCTAssertFalse(sender.sentMetadata) + XCTAssertEqual(viewModel.viewState, .feedbackSendingFailed) + } + + func testWhenCancelActionIsReceived_ThenViewModelSendsCancelActionToDelegate() async throws { + let collector = MockVPNMetadataCollector() + let sender = MockVPNFeedbackSender() + let delegate = MockVPNFeedbackFormViewModelDelegate() + let viewModel = UnifiedFeedbackFormViewModel(vpnMetadataCollector: collector, feedbackSender: sender) + viewModel.delegate = delegate + + XCTAssertFalse(delegate.receivedDismissedViewCallback) + await viewModel.process(action: .cancel) + XCTAssertTrue(delegate.receivedDismissedViewCallback) + } +} + +// MARK: - Mocks + +private class MockVPNMetadataCollector: UnifiedMetadataCollector { + var collectedMetadata = false + + func collectMetadata() async -> VPNMetadata { + self.collectedMetadata = true + + let appInfo = VPNMetadata.AppInfo( + appVersion: "1.2.3", + lastAgentVersionRun: "1.2.3", + lastExtensionVersionRun: "1.2.3", + isInternalUser: false, + isInApplicationsDirectory: true + ) + + let deviceInfo = VPNMetadata.DeviceInfo( + osVersion: "14.0.0", + buildFlavor: "dmg", + lowPowerModeEnabled: false, + cpuArchitecture: "arm64" + ) + + let networkInfo = VPNMetadata.NetworkInfo(currentPath: "path") + + let vpnState = VPNMetadata.VPNState( + onboardingState: "onboarded", + connectionState: "connected", + lastStartErrorDescription: "none", + lastTunnelErrorDescription: "none", + lastKnownFailureDescription: "none", + connectedServer: "Paoli, PA", + connectedServerIP: "123.123.123.123" + ) + + let vpnSettingsState = VPNMetadata.VPNSettingsState( + connectOnLoginEnabled: true, + includeAllNetworksEnabled: true, + enforceRoutesEnabled: true, + excludeLocalNetworksEnabled: true, + notifyStatusChangesEnabled: true, + showInMenuBarEnabled: true, + selectedServer: "server", + selectedEnvironment: "production", + customDNS: false + ) + + let loginItemState = VPNMetadata.LoginItemState( + vpnMenuState: "enabled", + vpnMenuIsRunning: true, + notificationsAgentState: "enabled", + notificationsAgentIsRunning: true + ) + + let privacyProInfo = VPNMetadata.PrivacyProInfo( + hasPrivacyProAccount: true, + hasVPNEntitlement: true + ) + + return VPNMetadata( + appInfo: appInfo, + deviceInfo: deviceInfo, + networkInfo: networkInfo, + vpnState: vpnState, + vpnSettingsState: vpnSettingsState, + loginItemState: loginItemState, + privacyProInfo: privacyProInfo + ) + } + +} + +private class MockVPNFeedbackSender: UnifiedFeedbackSender { + var throwErrorWhenSending: Bool = false + var sentMetadata: Bool = false + + var receivedData: (VPNMetadata?, UnifiedFeedbackSource, String?, String?, String?)? + + enum SomeError: Error { + case error + } + + func sendFeatureRequestPixel(description: String, source: UnifiedFeedbackSource) async throws { + if throwErrorWhenSending { + throw SomeError.error + } + + self.sentMetadata = true + self.receivedData = (nil, source, nil, nil, description) + } + + func sendGeneralFeedbackPixel(description: String, source: UnifiedFeedbackSource) async throws { + if throwErrorWhenSending { + throw SomeError.error + } + + self.sentMetadata = true + self.receivedData = (nil, source, nil, nil, description) + } + + func sendReportIssuePixel(source: UnifiedFeedbackSource, category: String, subcategory: String, description: String, metadata: T?) async throws { + if throwErrorWhenSending { + throw SomeError.error + } + + self.sentMetadata = true + self.receivedData = (metadata as? VPNMetadata, source, category, subcategory, description) + } + + func sendFormShowPixel() {} + func sendSubmitScreenShowPixel(source: UnifiedFeedbackSource, reportType: String, category: String, subcategory: String) {} + func sendSubmitScreenFAQClickPixel(source: UnifiedFeedbackSource, reportType: String, category: String, subcategory: String) {} +} + +private class MockVPNFeedbackFormViewModelDelegate: UnifiedFeedbackFormViewModelDelegate { + var receivedDismissedViewCallback: Bool = false + + func feedbackViewModelDismissedView(_ viewModel: UnifiedFeedbackFormViewModel) { + receivedDismissedViewCallback = true + } + +} diff --git a/UnitTests/UserScripts/SSLErrorPageUserScriptTests.swift b/UnitTests/UserScripts/SSLErrorPageUserScriptTests.swift deleted file mode 100644 index f94b23f889..0000000000 --- a/UnitTests/UserScripts/SSLErrorPageUserScriptTests.swift +++ /dev/null @@ -1,122 +0,0 @@ -// -// SSLErrorPageUserScriptTests.swift -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import XCTest -import UserScript - -@testable import DuckDuckGo_Privacy_Browser - -final class SSLErrorPageUserScriptTests: XCTestCase { - - var delegate: CapturingSSLErrorPageUserScriptDelegate! - var userScript: SSLErrorPageUserScript! - - override func setUpWithError() throws { - delegate = CapturingSSLErrorPageUserScriptDelegate() - userScript = SSLErrorPageUserScript() - userScript.delegate = delegate - } - - override func tearDownWithError() throws { - delegate = nil - userScript = nil - } - - func test_FeatureHasCorrectName() throws { - XCTAssertEqual(userScript.featureName, "sslErrorPage") - } - - func test_BrokerIsCorrectlyAdded() throws { - // WHEN - let broker = UserScriptMessageBroker(context: "some contect") - userScript.with(broker: broker) - - // THEN - XCTAssertEqual(userScript.broker, broker) - } - - @MainActor - func test_WhenHandlerForLeaveSiteCalled_AndIsEnabledFalse_ThenNoHandlerIsReturned() { - // WHEN - let handler = userScript.handler(forMethodNamed: "leaveSite") - - // THEN - XCTAssertNil(handler) - } - - @MainActor - func test_WhenHandlerForVisitSiteCalled_AndIsEnabledFalse_ThenNoHandlerIsReturned() { - // WHEN - let handler = userScript.handler(forMethodNamed: "visitSite") - - // THEN - XCTAssertNil(handler) - } - - @MainActor - func test_WhenHandlerForLeaveSiteCalled_AndIsEnabledTrue_ThenLeaveSiteCalled() async { - // GIVEN - var encodable: Encodable? - userScript.isEnabled = true - - // WHEN - let handler = userScript.handler(forMethodNamed: "leaveSite") - if let handler { - encodable = try? await handler(Data(), WKScriptMessage()) - } - - // THEN - XCTAssertNotNil(handler) - XCTAssertNil(encodable) - XCTAssertTrue(delegate.leaveSiteCalled) - XCTAssertFalse(delegate.visitSiteCalled) - } - - @MainActor - func test_WhenHandlerForVisitSiteCalled_AndIsEnabledTrue_ThenVisitSiteCalled() async { - // GIVEN - var encodable: Encodable? - userScript.isEnabled = true - - // WHEN - let handler = userScript.handler(forMethodNamed: "visitSite") - if let handler { - encodable = try? await handler(Data(), WKScriptMessage()) - } - - // THEN - XCTAssertNotNil(handler) - XCTAssertNil(encodable) - XCTAssertTrue(delegate.visitSiteCalled) - XCTAssertFalse(delegate.leaveSiteCalled) - } - -} - -class CapturingSSLErrorPageUserScriptDelegate: SSLErrorPageUserScriptDelegate { - var leaveSiteCalled = false - var visitSiteCalled = false - - func leaveSite() { - leaveSiteCalled = true - } - - func visitSite() { - visitSiteCalled = true - } -} diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile index 3f02b29113..cd249a9c24 100644 --- a/fastlane/Pluginfile +++ b/fastlane/Pluginfile @@ -2,4 +2,4 @@ # # Ensure this file is checked in to source control! -gem 'fastlane-plugin-ddg_apple_automation', git: 'https://github.com/duckduckgo/fastlane-plugin-ddg_apple_automation', tag: '0.1.0' +gem 'fastlane-plugin-ddg_apple_automation', git: 'https://github.com/duckduckgo/fastlane-plugin-ddg_apple_automation', tag: '0.2.0' diff --git a/sandbox-test-tool/SandboxTestTool.swift b/sandbox-test-tool/SandboxTestTool.swift index 1234426615..d7f0e3a6b2 100644 --- a/sandbox-test-tool/SandboxTestTool.swift +++ b/sandbox-test-tool/SandboxTestTool.swift @@ -20,6 +20,7 @@ import AppKit import Combine import Common import Foundation +import os.log @main struct SandboxTestTool { @@ -46,60 +47,9 @@ final class SandboxTestToolApp: NSApplication { } -extension FileLogger: FilePresenterLogger { - func log(_ message: @autoclosure () -> String) { - log(message(), includeTimestamp: true) - } -} - -final class FileLogger { - static let shared = FileLogger() - - private init() { - if !FileManager.default.fileExists(atPath: fileURL.path) { - FileManager.default.createFile(atPath: fileURL.path, contents: nil) - } - } - - private let pid = ProcessInfo().processIdentifier - - private let fileURL: URL = { - let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] - return documentsURL.appendingPathComponent("logfile.txt") - }() - - private let dateFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateFormat = "HH:mm:ss.SSS" - return formatter - }() - - private let queue = DispatchQueue(label: "log queue") - private lazy var fileHandle: FileHandle = { - let fileHandle = (try? FileHandle(forWritingTo: fileURL))! - fileHandle.seekToEndOfFile() - return fileHandle - }() - - func log(_ message: String, includeTimestamp: Bool) { - os_log("%{public}s", message) - queue.sync { - let logMessage = includeTimestamp ? "[\(pid)] \(dateFormatter.string(from: Date())): \(message)\n" : (message + "\n") - - fileHandle.write(logMessage.data(using: .utf8)!) - } - } - -} - final class SandboxTestToolAppDelegate: NSObject, NSApplicationDelegate { - // uncomment these for logging -// #if CI - let logger = OSLog.disabled -// #else -// let logger = FileLogger.shared -// #endif + let logger = Logger(subsystem: "SandboxTestTool", category: "") override init() { logger.log("\n\n\n🚦 starting…\n") @@ -180,7 +130,7 @@ final class SandboxTestToolAppDelegate: NSObject, NSApplicationDelegate { return } do { - let filePresenter = try BookmarkFilePresenter(fileBookmarkData: bookmark, logger: logger) + let filePresenter = try BookmarkFilePresenter(fileBookmarkData: bookmark) guard let url = filePresenter.url else { throw NSError(domain: "SandboxTestTool", code: -1, userInfo: [NSLocalizedDescriptionKey: "FilePresenter URL is nil"]) } filePresenter.urlPublisher.dropFirst().sink { [unowned self] url in