diff --git a/.github/readme/app-dark.png b/.github/readme/app-dark.png index 68bf4eb..88ee8a0 100644 Binary files a/.github/readme/app-dark.png and b/.github/readme/app-dark.png differ diff --git a/.github/readme/app-light.png b/.github/readme/app-light.png index d231aaa..d3735d3 100644 Binary files a/.github/readme/app-light.png and b/.github/readme/app-light.png differ diff --git a/.github/readme/localize_add.png b/.github/readme/localize_add.png deleted file mode 100644 index eec0fe2..0000000 Binary files a/.github/readme/localize_add.png and /dev/null differ diff --git a/.github/readme/localize_sidebar.png b/.github/readme/localize_sidebar.png deleted file mode 100644 index 50e25cb..0000000 Binary files a/.github/readme/localize_sidebar.png and /dev/null differ diff --git a/.github/readme/localize_translate.png b/.github/readme/localize_translate.png deleted file mode 100644 index 6d859dd..0000000 Binary files a/.github/readme/localize_translate.png and /dev/null differ diff --git a/.github/readme/notification.png b/.github/readme/notification.png index 47d53fc..d3bf5bf 100644 Binary files a/.github/readme/notification.png and b/.github/readme/notification.png differ diff --git a/.github/readme/touchid.png b/.github/readme/touchid.png index bc80ad6..e8d0b9f 100644 Binary files a/.github/readme/touchid.png and b/.github/readme/touchid.png differ diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 01c9e72..cff97ab 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -33,7 +33,7 @@ jobs: DATE=$(date "+%Y-%m-%d") sed -i '' -e "s/GITHUB_CI_VERSION/0.0.0_nightly-$DATE/g" Sources/Config/Config.xcconfig sed -i '' -e "s/GITHUB_BUILD_NUMBER/1.$RUN_ID/g" Sources/Config/Config.xcconfig - sed -i '' -e "s/GITHUB_BUILD_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Secretive/Credits.rtf + sed -i '' -e "s/GITHUB_BUILD_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Config/Config.xcconfig - name: Build run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive - name: Create ZIP diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 91ddbc7..f3e51a4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,7 +55,7 @@ jobs: export CLEAN_TAG=$(echo $TAG_NAME | sed -e 's/refs\/tags\/v//') sed -i '' -e "s/GITHUB_CI_VERSION/$CLEAN_TAG/g" Sources/Config/Config.xcconfig sed -i '' -e "s/GITHUB_BUILD_NUMBER/1.$RUN_ID/g" Sources/Config/Config.xcconfig - sed -i '' -e "s/GITHUB_BUILD_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Secretive/Credits.rtf + sed -i '' -e "s/GITHUB_BUILD_URL/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Config/Config.xcconfig - name: Build run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive - name: Create ZIP @@ -77,13 +77,13 @@ jobs: uses: actions/attest-build-provenance@v2 with: subject-name: "Secretive.zip" - subject-digest: ${{ steps.upload.outputs.artifact-digest }} + subject-digest: sha256:${{ steps.upload.outputs.artifact-digest }} - name: Create Release run: | sed -i.tmp "s/RUN_ID/$RUN_ID/g" .github/templates/release.md sed -i.tmp "s/ATTESTATION_ID/$ATTESTATION_ID/g" .github/templates/release.md gh release create $TAG_NAME -d -F .github/templates/release.md - gh release upload Secretive.zip + gh release upload $TAG_NAME Secretive.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG_NAME: ${{ github.ref }} diff --git a/README.md b/README.md index 66d2b04..0787fd8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Secretive [![Test](https://github.com/maxgoedjen/secretive/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/maxgoedjen/secretive/actions/workflows/test.yml) ![Release](https://github.com/maxgoedjen/secretive/workflows/Release/badge.svg) -Secretive is an app for storing and managing SSH keys in the Secure Enclave. It is inspired by the [sekey project](https://github.com/sekey/sekey), but rewritten in Swift with no external dependencies and with a handy native management app. - +Secretive is an app for protecting and managing SSH keys with the Secure Enclave. - Screenshot of Secretive + + Screenshot of Secretive @@ -13,7 +13,7 @@ Secretive is an app for storing and managing SSH keys in the Secure Enclave. It ### Safer Storage -The most common setup for SSH keys is just keeping them on disk, guarded by proper permissions. This is fine in most cases, but it's not super hard for malicious users or malware to copy your private key. If you store your keys in the Secure Enclave, it's impossible to export them, by design. +The most common setup for SSH keys is just keeping them on disk, guarded by proper permissions. This is fine in most cases, but it's not super hard for malicious users or malware to copy your private key. If you protect your keys with the Secure Enclave, it's impossible to export them, by design. ### Access Control @@ -53,7 +53,7 @@ Builds are produced by GitHub Actions with an auditable build and release genera ### A Note Around Code Signing and Keychains -While Secretive uses the Secure Enclave for key storage, it still relies on Keychain APIs to access them. Keychain restricts reads of keys to the app (and specifically, the bundle ID) that created them. If you build Secretive from source, make sure you are consistent in which bundle ID you use so that the Keychain is able to locate your keys. +While Secretive uses the Secure Enclave to protect keys, it still relies on Keychain APIs to store and access them. Keychain restricts reads of keys to the app (and specifically, the bundle ID) that created them. If you build Secretive from source, make sure you are consistent in which bundle ID you use so that the Keychain is able to locate your keys. ### Backups and Transfers to New Machines @@ -62,3 +62,11 @@ Because secrets in the Secure Enclave are not exportable, they are not able to b ## Security Secretive's security policy is detailed in [SECURITY.md](SECURITY.md). To report security issues, please use [GitHub's private reporting feature.](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) + +## Acknowledgements + +### sekey +Secretive was inspired by the [sekey project](https://github.com/sekey/sekey). + +### Localization +Secretive is localized to many languages by a generous team of volunteers. To learn more, see [LOCALIZING.md](LOCALIZING.md). Secretive's localization workflow is generously provided by [Crowdin](https://crowdin.com). diff --git a/Sources/Config/Config.xcconfig b/Sources/Config/Config.xcconfig index 9c18c35..e81adaa 100644 --- a/Sources/Config/Config.xcconfig +++ b/Sources/Config/Config.xcconfig @@ -1,2 +1,3 @@ CI_VERSION = GITHUB_CI_VERSION CI_BUILD_NUMBER = GITHUB_BUILD_NUMBER +CI_BUILD_LINK = GITHUB_BUILD_URL diff --git a/Sources/Packages/Resources/Localizable.xcstrings b/Sources/Packages/Resources/Localizable.xcstrings index dbb7599..aca6a13 100644 --- a/Sources/Packages/Resources/Localizable.xcstrings +++ b/Sources/Packages/Resources/Localizable.xcstrings @@ -177,6 +177,1117 @@ "value" : "" } } + }, + "shouldTranslate" : false + }, + "**%@** (%@)" : { + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "ar" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "ca" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "cs" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "da" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "de" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "el" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "he" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "hu" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "ja" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "ko" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "no" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "pl" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "pt" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "ro" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "sr" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "sv" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "tr" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "uk" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "vi" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "new", + "value" : "**%1$@** (%2$@)" + } + } + }, + "shouldTranslate" : false + }, + "about_build_log_button" : { + "extractionState" : "manual", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "ar" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "ca" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "cs" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "da" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "de" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "el" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "he" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "hu" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ビルドログ" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "빌드 로그" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "no" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "pl" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "pt" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "ro" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "sr" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "sv" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "tr" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "uk" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "vi" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "new", + "value" : "Build Log" + } + } + } + }, + "about_menu_bar_title" : { + "extractionState" : "manual", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "ar" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "ca" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "cs" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "da" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "de" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "el" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "he" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "hu" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Secretiveについて" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Secretive에 대해서" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "no" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "pl" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "pt" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "ro" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "sr" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "sv" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "tr" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "uk" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "vi" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关于 Secretive" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "new", + "value" : "About Secretive" + } + } + } + }, + "about_open_source_notice" : { + "extractionState" : "manual", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "ar" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "ca" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "cs" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "da" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "de" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "el" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "he" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "hu" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Secretiveはオープンソースです。MITライセンスで提供されています。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Secretive는 오픈 소스이며 MIT 라이선스입니다" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "no" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "pl" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "pt" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "ro" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "sr" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "sv" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "tr" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "uk" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "vi" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "new", + "value" : "Secretive is Open Source and MIT Licensed" + } + } + } + }, + "about_thanks" : { + "extractionState" : "manual", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "ar" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "ca" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "cs" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "da" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "de" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "el" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "he" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "hu" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "[コントリビューター](%1$(contributorsLink)@)と[スポンサー](%2$(sponsorsLink)@)に感謝します" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "[기여자](%1$(contributorsLink)@)와 [후원자](%2$(sponsorsLink)@)에게 특별히 감사드립니다" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "no" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "pl" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "pt" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "ro" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "sr" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "sv" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "tr" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "uk" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "vi" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "new", + "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + } + } + } + }, + "about_view_on_github_button" : { + "extractionState" : "manual", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "ar" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "ca" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "cs" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "da" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "de" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "el" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "he" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "hu" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "GitHub" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "GitHub에서 보기" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "no" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "pl" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "pt" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "ro" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "sr" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "sv" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "tr" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "uk" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "vi" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "new", + "value" : "View on GitHub" + } + } } }, "agent_details_could_not_start_error" : { @@ -197,7 +1308,7 @@ "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Secretive no ha aconseguit arrancar l'agent segur (SecretAgent). Prova de reiniciar el teu Mac, i si açò no funciona, obri una \"issue\" al GitHub." + "value" : "Secretive no ha aconseguit arrancar l'agent segur (SecretAgent). Prova de reiniciar el teu Mac, i si açò no funciona, obri una “issue” al GitHub." } }, "cs" : { @@ -214,14 +1325,14 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Secretive was unable to get SecretAgent to launch. Please try restarting your Mac, and if that doesn't work, file an issue on GitHub." + "state" : "translated", + "value" : "Secretive konnte den SecretAgent nicht starten. Bitte versuche Deinen Mac neu zu starten und wenn das nicht funktioniert, ein Issue auf GitHub zu erstellen." } }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "Secretive was unable to get SecretAgent to launch. Please try restarting your Mac, and if that doesn't work, file an issue on GitHub." + "state" : "translated", + "value" : "Το Secretive δεν μπόρεσε να εκκινήσει τον SecretAgent. Παρακαλώ δοκιμάστε να επανεκκινήσετε τον Mac σας, και αν αυτό δεν λειτουργήσει, ανοίξτε ένα issue στο GitHub." } }, "en" : { @@ -316,8 +1427,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Secretive was unable to get SecretAgent to launch. Please try restarting your Mac, and if that doesn't work, file an issue on GitHub." + "state" : "translated", + "value" : "Secretive не получлось запустить SecretAgent. Пожалуйста, попробуйте перезапустить Ваш Mac, и если это не поможет – создайте issue на GitHub." } }, "sr" : { @@ -399,14 +1510,14 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Disable Agent" + "state" : "translated", + "value" : "Agent deaktivieren" } }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "Disable Agent" + "state" : "translated", + "value" : "Απενεργοποίηση Πράκτορα" } }, "en" : { @@ -501,8 +1612,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Disable Agent" + "state" : "translated", + "value" : "Отключить агент" } }, "sr" : { @@ -584,14 +1695,14 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Restart Agent" + "state" : "translated", + "value" : "Agent neu starten" } }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "Restart Agent" + "state" : "translated", + "value" : "Επανεκκίνηση Πράκτορα" } }, "en" : { @@ -686,8 +1797,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Restart Agent" + "state" : "translated", + "value" : "Перезапустить агент" } }, "sr" : { @@ -769,14 +1880,14 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Running Since" + "state" : "translated", + "value" : "Läuft seit" } }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "Running Since" + "state" : "translated", + "value" : "Εκτελείται Από" } }, "en" : { @@ -871,8 +1982,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Running Since" + "state" : "translated", + "value" : "Запущено с" } }, "sr" : { @@ -954,13 +2065,13 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Socket Path" + "state" : "translated", + "value" : "Socket-Pfad" } }, "el" : { "stringUnit" : { - "state" : "new", + "state" : "translated", "value" : "Socket Path" } }, @@ -1056,8 +2167,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Socket Path" + "state" : "translated", + "value" : "Путь к сокету" } }, "sr" : { @@ -1139,14 +2250,14 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Start Agent" + "state" : "translated", + "value" : "Agent starten" } }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "Start Agent" + "state" : "translated", + "value" : "Έναρξη SecretAgent" } }, "en" : { @@ -1241,8 +2352,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Start Agent" + "state" : "translated", + "value" : "Запустить агент" } }, "sr" : { @@ -1324,14 +2435,14 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Starting Agent" + "state" : "translated", + "value" : "Agent wird gestartet" } }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "Starting Agent" + "state" : "translated", + "value" : "Έναρξη Πράκτορα" } }, "en" : { @@ -1426,8 +2537,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Starting Agent" + "state" : "translated", + "value" : "Агент запускается" } }, "sr" : { @@ -1509,14 +2620,14 @@ }, "de" : { "stringUnit" : { - "state" : "new", + "state" : "translated", "value" : "Version" } }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "Version" + "state" : "translated", + "value" : "Έκδοση" } }, "en" : { @@ -1611,8 +2722,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Version" + "state" : "translated", + "value" : "Версия" } }, "sr" : { @@ -1694,14 +2805,14 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "SecretAgent is a process that runs in the background to sign requests, so you don't need to keep Secretive open all the time.\n\n**Secretive will not be able to function properly unless the agent is installed and running.**" + "state" : "translated", + "value" : "SecretAgent ist ein Hintergrund-Prozess, der Anfragen signiert, sodass Du Secretive nicht durchgehend geöffnet haben musst.\n\n**Secretive wird nicht richtig funktionieren, wenn der Agent nicht installiert und ausgeführt wird.**" } }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "SecretAgent is a process that runs in the background to sign requests, so you don't need to keep Secretive open all the time.\n\n**Secretive will not be able to function properly unless the agent is installed and running.**" + "state" : "translated", + "value" : "Το SecretAgent είναι μια διεργασία που εκτελείται στο παρασκήνιο για να υπογράφει αιτήματα. Δεν χρειάζεται να κρατάτε παράθυρο του Secretive ανοιχτό συνεχώς.\n\n**Το Secretive δεν θα μπορεί να λειτουργήσει σωστά εκτός και αν o Agent είναι εγκατεστημένος και εκτελείται.**" } }, "en" : { @@ -1796,8 +2907,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "SecretAgent is a process that runs in the background to sign requests, so you don't need to keep Secretive open all the time.\n\n**Secretive will not be able to function properly unless the agent is installed and running.**" + "state" : "translated", + "value" : "SecretAgent это процесс, который работает в фоне чтобы подписывать запросы. Так Вам не придется все время держать Secretive открытым.\n\n**Secretive не сможет нормально функционировать, пока агент не установлен и не запущен.**" } }, "sr" : { @@ -1885,8 +2996,8 @@ }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "Agent Is Not Running" + "state" : "translated", + "value" : "Ο Agent Δεν Εκτελείται" } }, "en" : { @@ -2070,8 +3181,8 @@ }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "SecretAgent is a process that runs in the background to sign requests, so you don't need to keep Secretive open all the time.\n\n**You can close Secretive, and everything will still keep working.**" + "state" : "translated", + "value" : "Το SecretAgent είναι μια διεργασία που εκτελείται στο παρασκήνιο για να υπογράφει αιτήματα. Δεν χρειάζεται να κρατάτε παράθυρο του Secretive ανοιχτό συνεχώς.\n\n**Το Secretive δεν θα μπορεί να λειτουργήσει σωστά εκτός και αν o Agent είναι εγκατεστημένος και εκτελείται.**" } }, "en" : { @@ -2167,7 +3278,7 @@ "ru" : { "stringUnit" : { "state" : "translated", - "value" : "SecretAgent это процесс, который работает в фоне чтобы подписывать запросы – так Вам не нужно держать Secretive открытым все время.\n\n**Вы можете закрыть Secretive, и все продолжит работать штатно.**" + "value" : "SecretAgent это процесс, который работает в фоне, чтобы подписывать запросы – так Вам не нужно держать Secretive открытым все время.\n\n**Вы можете закрыть Secretive, и все продолжит работать штатно.**" } }, "sr" : { @@ -2255,8 +3366,8 @@ }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "Secret Agent is Running" + "state" : "translated", + "value" : "Ο Πράκτορας εκτελείται" } }, "en" : { @@ -2619,14 +3730,14 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Secret Agent Location" + "state" : "translated", + "value" : "SecretAgent-Pfad" } }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "Secret Agent Location" + "state" : "translated", + "value" : "Τοποθεσία Secret Agent" } }, "en" : { @@ -2721,8 +3832,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Secret Agent Location" + "state" : "translated", + "value" : "Расположение Secret Agent" } }, "sr" : { @@ -3139,6 +4250,191 @@ } } }, + "app_not_in_applications_notice_cancel_button" : { + "extractionState" : "manual", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "ar" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "ca" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "cs" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "da" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "de" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "el" : { + "stringUnit" : { + "state" : "translated", + "value" : "Εφαρμογή αργότερα" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "he" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "hu" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "後にする" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "나중에" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "no" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "pl" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "pt" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "ro" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Позже" + } + }, + "sr" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "sv" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "tr" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "uk" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "vi" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "new", + "value" : "Later" + } + } + } + }, "app_not_in_applications_notice_detail_description" : { "extractionState" : "manual", "localizations" : { @@ -3324,6 +4620,191 @@ } } }, + "app_not_in_applications_notice_quit_button" : { + "extractionState" : "manual", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "ar" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "ca" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "cs" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "da" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "de" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "el" : { + "stringUnit" : { + "state" : "translated", + "value" : "Έξοδος" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "he" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "hu" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "終了" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "종료" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "no" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "pl" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "pt" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "ro" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Выйти" + } + }, + "sr" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "sv" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "tr" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "uk" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "vi" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "new", + "value" : "Quit" + } + } + } + }, "app_not_in_applications_notice_title" : { "extractionState" : "manual", "localizations" : { @@ -3510,91 +4991,91 @@ } }, "auth_context_persist_for_duration" : { - "comment" : "When the user clicks the notification to leave a secret unlocked, they are shown a prompt to approve the action. This is the description, showing which secret will used. The first placeholder is the name of the secret. The second placeholder is a localized description of the time period it will remain unlocked for (eg: \"five minutes\")", + "comment" : "When the user clicks the notification to leave a secret unlocked, they are shown a prompt to approve the action. This is the description, showing which secret will used. The first placeholder is the name of the secret. The second placeholder is a localized description of the time period it will remain unlocked for (eg: “five minutes”)", "extractionState" : "manual", "localizations" : { "af" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "ar" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "desbloqueja el secret \"%1$(secretName)@\" per a %2$(duration)@" + "value" : "desbloqueja el secret “%1$(secretName)@” per a %2$(duration)@" } }, "cs" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "da" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Secret \"%1$(secretName)@\" für %2$(duration)@ entsperren" + "value" : "Secret “%1$(secretName)@” für %2$(duration)@ entsperren" } }, "el" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "es" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "avaa salaisuuden \"%1$(secretName)@\" lukitus ajaksi %2$(duration)@" + "value" : "avaa salaisuuden “%1$(secretName)@” lukitus ajaksi %2$(duration)@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "déverrouiller le secret \"%1$(secretName)@\" pendant %2$(duration)@" + "value" : "déverrouiller le secret “%1$(secretName)@” pendant %2$(duration)@" } }, "he" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "hu" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "sblocca il Segreto \"%1$(secretName)@\" per %2$(duration)@" + "value" : "sblocca il Segreto “%1$(secretName)@” per %2$(duration)@" } }, "ja" : { @@ -3606,19 +5087,19 @@ "ko" : { "stringUnit" : { "state" : "translated", - "value" : "비밀 \"%1$(secretName)@\"를 %2$(duration)@ 동안 잠금 해제" + "value" : "비밀 “%1$(secretName)@”를 %2$(duration)@ 동안 잠금 해제" } }, "nl" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "no" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "pl" : { @@ -3630,67 +5111,67 @@ "pt" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "destravar segredo \"%1$(secretName)@\" for %2$(duration)@" + "value" : "destravar segredo “%1$(secretName)@” for %2$(duration)@" } }, "ro" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "разблокировать секрет \"%1$(secretName)@\" на %2$(duration)@" + "value" : "разблокировать секрет “%1$(secretName)@” на %2$(duration)@" } }, "sr" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "sv" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "tr" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "uk" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "vi" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "解锁密钥串 \"%1$(secretName)@\" 给 %2$(duration)@" + "value" : "解锁密钥串 “%1$(secretName)@” 给 %2$(duration)@" } }, "zh-Hant" : { "stringUnit" : { "state" : "new", - "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" + "value" : "unlock secret “%1$(secretName)@” for %2$(duration)@" } } } @@ -3888,85 +5369,85 @@ "af" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "ar" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "signa una petición de \"%1$(appName)@\" usant el secret \"%2$(secretName)@\"" + "value" : "signa una petición de “%1$(appName)@” usant el secret “%2$(secretName)@”" } }, "cs" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "da" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "eine Anfrage von \"%1$(appName)@\" mit dem Secret \"%2$(secretName)@\" signieren" + "value" : "eine Anfrage von “%1$(appName)@” mit dem Secret “%2$(secretName)@” signieren" } }, "el" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "es" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "allekirjoita pyyntö lähteestä \"%1$(appName)@\" käyttäen salaisuutta \"%2$(secretName)@\"" + "value" : "allekirjoita pyyntö lähteestä “%1$(appName)@” käyttäen salaisuutta “%2$(secretName)@”" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "signer une requête de \"%1$(appName)@\" en utilisant le secret \"%2$(secretName)@\"" + "value" : "signer une requête de “%1$(appName)@” en utilisant le secret “%2$(secretName)@”" } }, "he" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "hu" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "firma la richiesta di \"%1$(appName)@\" usando il Segreto \"%2$(secretName)@\"" + "value" : "firma la richiesta di “%1$(appName)@” usando il Segreto “%2$(secretName)@”" } }, "ja" : { @@ -3978,91 +5459,91 @@ "ko" : { "stringUnit" : { "state" : "translated", - "value" : "비밀 \"%2$(secretName)@\"를 사용해서 \"%1$(appName)@\"의 요청에 서명" + "value" : "비밀 “%2$(secretName)@”를 사용해서 “%1$(appName)@”의 요청에 서명" } }, "nl" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "no" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "podpisz zapytanie od “%1$(appName)@\" za pomocą sekretu “%2$(secretName)@”" + "value" : "podpisz zapytanie od “%1$(appName)@” za pomocą sekretu “%2$(secretName)@”" } }, "pt" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "assinar requisição a partir do \"%1$(appName)@\" utilizando o segredo \"%2$(secretName)@\"" + "value" : "assinar requisição a partir do “%1$(appName)@” utilizando o segredo “%2$(secretName)@”" } }, "ro" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "подписать запрос от \"%1$(appName)@\" используя секрет \"%2$(secretName)@\"" + "value" : "подписать запрос от “%1$(appName)@” используя секрет “%2$(secretName)@”" } }, "sr" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "sv" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "tr" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "uk" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "vi" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "使用密钥串 \"%2$(secretName)@\" 认证 \"%1$(appName)@\" " + "value" : "使用密钥串 “%2$(secretName)@” 认证 “%1$(appName)@” " } }, "zh-Hant" : { "stringUnit" : { "state" : "new", - "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" + "value" : "sign a request from “%1$(appName)@” using secret “%2$(secretName)@”" } } } @@ -4472,8 +5953,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Advanced" + "state" : "translated", + "value" : "Erweitert" } }, "el" : { @@ -4574,8 +6055,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Advanced" + "state" : "translated", + "value" : "Расширенное" } }, "sr" : { @@ -4657,8 +6138,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "If you change your biometric settings in _any way_, including adding a new fingerprint, this key will no longer be accessible." + "state" : "translated", + "value" : "Wenn Du Deine Biometrie-Einstellungen _irgendwie_, einschließlich des Hinzufügens eines neuen Fingerabdrucks, änderst, wird dieser Schlüssel nicht mehr zugänglich sein." } }, "el" : { @@ -4759,8 +6240,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "If you change your biometric settings in _any way_, including adding a new fingerprint, this key will no longer be accessible." + "state" : "translated", + "value" : "Если Вы сделаете _любые_ изменения в Ваших настройках биометрии, включая добавление нового отпечатка пальца, этот ключ перестанет быть доступным." } }, "sr" : { @@ -4848,8 +6329,8 @@ }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "Cancel" + "state" : "translated", + "value" : "Άκυρο" } }, "en" : { @@ -5212,14 +6693,14 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "This shows at the end of your public key. It’s usually an email address." + "state" : "translated", + "value" : "Sie wird am Ende Deines öffentlichen Schlüssels angezeigt und ist üblicherweise eine E-Mail-Adresse." } }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "This shows at the end of your public key. It’s usually an email address." + "state" : "translated", + "value" : "Αυτό εμφανίζεται στο τέλος του δημόσιου κλειδιού σας. Είναι συνήθως μια διεύθυνση ηλεκτρονικού ταχυδρομείου." } }, "en" : { @@ -5314,8 +6795,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "This shows at the end of your public key. It’s usually an email address." + "state" : "translated", + "value" : "Это отображается в конце Вашего публичного ключа. Как правило, это email адрес." } }, "sr" : { @@ -5350,8 +6831,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "This shows at the end of your public key. It’s usually an email address." + "state" : "translated", + "value" : "会显示在公钥末尾。通常为电子邮箱地址。" } }, "zh-Hant" : { @@ -5397,14 +6878,14 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Key Attribution" + "state" : "translated", + "value" : "Schlüssel-Zuordnung" } }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "Key Attribution" + "state" : "translated", + "value" : "Απόδοση Κλειδιού" } }, "en" : { @@ -5499,8 +6980,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Key Attribution" + "state" : "translated", + "value" : "Принадлежность ключа" } }, "sr" : { @@ -5535,8 +7016,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "Key Attribution" + "state" : "translated", + "value" : "密钥归属" } }, "zh-Hant" : { @@ -5582,8 +7063,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Key Type" + "state" : "translated", + "value" : "Schlüsseltyp" } }, "el" : { @@ -5684,8 +7165,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Key Type" + "state" : "translated", + "value" : "Тип ключа" } }, "sr" : { @@ -5732,6 +7213,191 @@ } } }, + "create_secret_key_type_macOS_update_required_label" : { + "extractionState" : "manual", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "ar" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "ca" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "cs" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "da" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "de" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "el" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "he" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "hu" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "このバージョンのmacOSには対応していません" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이 버전의 macOS에서는 사용할 수 없습니다" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "no" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "pl" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "pt" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "ro" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "sr" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "sv" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "tr" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "uk" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "vi" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "new", + "value" : "Unavailable on this version of macOS" + } + } + } + }, "create_secret_mldsa_warning" : { "extractionState" : "manual", "localizations" : { @@ -5869,8 +7535,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Warning: ML-DSA keys are very new, and not supported by many servers yet. Please verify the server you'll be using this key for accepts ML-DSA keys." + "state" : "translated", + "value" : "Внимание: Ключи ML-DSA совсем новые, и не пока поддерживаются многими серверами. Пожалуйста удостоверьтесь, что используемый с этим ключом сервер поддерживает ключи ML-DSA." } }, "sr" : { @@ -5952,14 +7618,14 @@ }, "de" : { "stringUnit" : { - "state" : "new", + "state" : "translated", "value" : "Name" } }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "Name" + "state" : "translated", + "value" : "Τίτλος" } }, "en" : { @@ -6425,7 +8091,7 @@ "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Аутентификация не потребуется пока Ваш Mac разблокирован, но Вы получите уведомление, если секрет был использован." + "value" : "Аутентификация не потребуется пока Ваш Mac разблокирован, но Вы получите уведомление, если секрет будет использован." } }, "sr" : { @@ -6794,8 +8460,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Protection Level" + "state" : "translated", + "value" : "Уровень защиты" } }, "sr" : { @@ -6979,8 +8645,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Require authentication with current set of biometrics." + "state" : "translated", + "value" : "Требовать аутентификацию с текущим набором биометрии." } }, "sr" : { @@ -7062,8 +8728,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Current Biometrics" + "state" : "translated", + "value" : "Aktuelle Biometrie" } }, "el" : { @@ -7164,8 +8830,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Current Biometrics" + "state" : "translated", + "value" : "Текущая биометрия" } }, "sr" : { @@ -8143,79 +9809,79 @@ "af" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "ar" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Si esborres %1$(secretName)@, no podràs recuperar-la. Escriu \"%2$(confirmSecretName)@\" per a confirmar." + "value" : "Si esborres %1$(secretName)@, no podràs recuperar-la. Escriu “%2$(confirmSecretName)@” per a confirmar." } }, "cs" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "da" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wenn du %1$(secretName)@ löschst, kannst du es nicht wiederherstellen. Gib zur Bestätigung \"%2$(confirmSecretName)@\" ein." + "value" : "Wenn du %1$(secretName)@ löschst, kannst du es nicht wiederherstellen. Gib zur Bestätigung “%2$(confirmSecretName)@” ein." } }, "el" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "es" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Jos poistat kohteen %1$(secretName)@, sitä ei pysty palauttamaan. Kirjoita \"%2$(confirmSecretName)@\" vahvistaaksesi poiston." + "value" : "Jos poistat kohteen %1$(secretName)@, sitä ei pysty palauttamaan. Kirjoita “%2$(confirmSecretName)@” vahvistaaksesi poiston." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Si vous effacez %1$(secretName)@, vous ne pourrez pas le récupérer. Tapez \"%2$(confirmSecretName)@\" pour confirmer." + "value" : "Si vous effacez %1$(secretName)@, vous ne pourrez pas le récupérer. Tapez “%2$(confirmSecretName)@” pour confirmer." } }, "he" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "hu" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "it" : { @@ -8233,19 +9899,19 @@ "ko" : { "stringUnit" : { "state" : "translated", - "value" : "%1$(secretName)@를 삭제하면 복구할 수 없습니다. 확인하려면 \"%2$(confirmSecretName)@\"를 입력하세요." + "value" : "%1$(secretName)@를 삭제하면 복구할 수 없습니다. 확인하려면 “%2$(confirmSecretName)@”를 입력하세요." } }, "nl" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "no" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "pl" : { @@ -8257,67 +9923,67 @@ "pt" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Se você deletar %1$(secretName)@, você não será permitido recuperá-lo. Digite \"%2$(confirmSecretName)@\" para confirmar." + "value" : "Se você deletar %1$(secretName)@, você não será permitido recuperá-lo. Digite “%2$(confirmSecretName)@” para confirmar." } }, "ro" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Если Вы удалите %1$(secretName)@, вы не сможете его восстановить. Введите \"%2$(confirmSecretName)@\" для подтверждения." + "value" : "Если Вы удалите %1$(secretName)@, вы не сможете его восстановить. Введите “%2$(confirmSecretName)@” для подтверждения." } }, "sr" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "sv" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "tr" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "uk" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "vi" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "如果您删除 %1$(secretName)@ ,您将没有任何方式恢复它。输入 \"%2$(confirmSecretName)@\" 以确认。" + "value" : "如果您删除 %1$(secretName)@ ,您将没有任何方式恢复它。输入 “%2$(confirmSecretName)@” 以确认。" } }, "zh-Hant" : { "stringUnit" : { "state" : "new", - "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@“ to confirm." + "value" : "If you delete %1$(secretName)@, you will not be able to recover it. Type “%2$(confirmSecretName)@” to confirm." } } } @@ -8548,8 +10214,8 @@ }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "Cancel" + "state" : "translated", + "value" : "Άκυρο" } }, "en" : { @@ -8681,7 +10347,7 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "返回" + "value" : "取消" } }, "zh-Hant" : { @@ -8733,8 +10399,8 @@ }, "el" : { "stringUnit" : { - "state" : "new", - "value" : "Save" + "state" : "translated", + "value" : "Αποθήκευση" } }, "en" : { @@ -9384,8 +11050,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "It looks like you may have recently updated macOS. Sometimes this puts the Secure Enclave into a weird state, and you might need to reboot your Mac before things start working again." + "state" : "translated", + "value" : "Похоже, что Вы могли недавно обновить MacOS. Иногда из-за этого возникают проблемы с Secure Enclave, и возможно Вам потребуется перезапустить Ваш Mac, чтобы их исправить." } }, "sr" : { @@ -9467,8 +11133,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Missing Secrets?" + "state" : "translated", + "value" : "Fehlende Secrets?" } }, "el" : { @@ -9569,8 +11235,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Missing Secrets?" + "state" : "translated", + "value" : "Не хватает секретов?" } }, "sr" : { @@ -10565,8 +12231,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Add This:" + "state" : "translated", + "value" : "Folgendes hinzufügen:" } }, "el" : { @@ -10667,8 +12333,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Add This:" + "state" : "translated", + "value" : "Добавить следующее:" } }, "sr" : { @@ -10703,8 +12369,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "Add This:" + "state" : "translated", + "value" : "添加这些内容:" } }, "zh-Hant" : { @@ -10750,7 +12416,7 @@ }, "de" : { "stringUnit" : { - "state" : "new", + "state" : "translated", "value" : "Apps" } }, @@ -10852,8 +12518,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Apps" + "state" : "translated", + "value" : "Приложения" } }, "sr" : { @@ -10888,8 +12554,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "Apps" + "state" : "translated", + "value" : "应用" } }, "zh-Hant" : { @@ -10918,7 +12584,7 @@ "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Existeix una llista d'instruccions per a aplicacions (en anglès) mantinguda per la comunitat a GitHub. Si l'aplicació que cerques no està suportada, crea una \"issue\" i la comunitat podria ajudar-te." + "value" : "Existeix una llista d'instruccions per a aplicacions (en anglès) mantinguda per la comunitat a GitHub. Si l'aplicació que cerques no està suportada, crea una “issue” i la comunitat podria ajudar-te." } }, "cs" : { @@ -11037,8 +12703,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "There's a community-maintained list of instructions for apps on GitHub. If the app you're looking for isn't supported, create an issue and the community may be able to help." + "state" : "translated", + "value" : "Существует список инструкций, поддерживаемый сообществом, к приложениям на GitHub. Если нужное Вам приложение не поддерживается, создайте issue, возможно сообщество сможет помочь Вам." } }, "sr" : { @@ -11073,8 +12739,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "There's a community-maintained list of instructions for apps on GitHub. If the app you're looking for isn't supported, create an issue and the community may be able to help." + "state" : "translated", + "value" : "GitHub 上有份由社区维护的清单介绍如何配置应用。若尚不支持你要找的应用,请创建一个 issue,社区或许能提供帮助。" } }, "zh-Hant" : { @@ -11103,7 +12769,7 @@ "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Existeix una llista d'instruccions per a shells (en anglès) mantinguda per la comunitat a GitHub. Si l'aplicació que cerques no està suportada, crea una \"issue\" i la comunitat podria ajudar-te." + "value" : "Existeix una llista d'instruccions per a shells (en anglès) mantinguda per la comunitat a GitHub. Si l'aplicació que cerques no està suportada, crea una “issue” i la comunitat podria ajudar-te." } }, "cs" : { @@ -11222,8 +12888,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "There's a community-maintained list of shell instructions on GitHub. If the shell you're looking for isn't supported, create an issue and the community may be able to help." + "state" : "translated", + "value" : "Существует список shell-скриптов, поддерживаемый сообществом на GitHub. Если нужный Вам shell-скрипт не поддерживается, создайте issue, возможно сообщество сможет помочь Вам." } }, "sr" : { @@ -11258,8 +12924,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "There's a community-maintained list of shell instructions on GitHub. If the shell you're looking for isn't supported, create an issue and the community may be able to help." + "state" : "translated", + "value" : "GitHub 上有份由社区维护的清单介绍如何配置 shell。若尚不支持你要找的 shell,请创建一个 issue,社区或许能提供帮助。" } }, "zh-Hant" : { @@ -11407,8 +13073,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "You'll need to create a Secret before configuring this action." + "state" : "translated", + "value" : "Вам потребуется создать секрет перед настройкой этого действия." } }, "sr" : { @@ -11490,8 +13156,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Configure Using Secret" + "state" : "translated", + "value" : "Mit Secret konfigurieren" } }, "el" : { @@ -11592,8 +13258,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Configure Using Secret" + "state" : "translated", + "value" : "Настроить используя секрет" } }, "sr" : { @@ -11675,8 +13341,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "No Secret" + "state" : "translated", + "value" : "Kein Secret" } }, "el" : { @@ -11777,8 +13443,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "No Secret" + "state" : "translated", + "value" : "Без секрета" } }, "sr" : { @@ -11860,7 +13526,7 @@ }, "de" : { "stringUnit" : { - "state" : "new", + "state" : "translated", "value" : "Secret" } }, @@ -11962,8 +13628,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Secret" + "state" : "translated", + "value" : "С секретом" } }, "sr" : { @@ -12147,8 +13813,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "You can configure more than one tool, they generally won't interfere with each other." + "state" : "translated", + "value" : "Вы можете настроить более чем один инструмент. Обычно это не вызывает конфликтов при работе." } }, "sr" : { @@ -12230,8 +13896,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Getting Started" + "state" : "translated", + "value" : "Erste Schritte" } }, "el" : { @@ -12332,8 +13998,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Getting Started" + "state" : "translated", + "value" : "Начало работы" } }, "sr" : { @@ -12415,8 +14081,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Integrations" + "state" : "translated", + "value" : "Integrationen" } }, "el" : { @@ -12517,8 +14183,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Integrations" + "state" : "translated", + "value" : "Интеграции" } }, "sr" : { @@ -12702,8 +14368,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "If you're trying to sign your git commits, set up Git Signing." + "state" : "translated", + "value" : "Если Вы хотите подписывать ваши git коммиты, настройте подпись Git." } }, "sr" : { @@ -12887,8 +14553,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "If you're trying to configure anything your command line runs to use Secretive, configure your shell." + "state" : "translated", + "value" : "Если Вы хотите наладить работу Secretive с Вашими скриптами в Терминале, настройте Ваш Терминал." } }, "sr" : { @@ -13072,8 +14738,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "If you don't known what shell you use and haven't changed it, you're probably using `%(shellName)@`." + "state" : "translated", + "value" : "Если Вы не знаете, какой Терминал используете и не меняли его раннее, то скорее всего Вы используете `%(shellName)@`." } }, "sr" : { @@ -13257,8 +14923,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "If you're trying to authenticate with an SSH server or authenticating with a service like GitHub over SSH, configure your SSH client." + "state" : "translated", + "value" : "Если Вы хотите аутентифицироваться с SSH сервером, или с сервисом как GitHub через SSH, настройте Ваш SSH клиент." } }, "sr" : { @@ -13442,8 +15108,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Configuring Tools for Secretive" + "state" : "translated", + "value" : "Настройка инструментов для Secretive" } }, "sr" : { @@ -13627,8 +15293,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Most tools will try and look for SSH keys on disk in `~/.ssh`. To use Secretive, we need to configure those tools to talk to Secretive instead." + "state" : "translated", + "value" : "Большинство инструментов попробуют найти SSH ключи в `~/.ssh`. Для того, чтобы использовать Secretive, нам потребуется настроить эти инструменты, чтобы они работали с Secretive." } }, "sr" : { @@ -13663,8 +15329,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "Most tools will try and look for SSH keys on disk in `~/.ssh`. To use Secretive, we need to configure those tools to talk to Secretive instead." + "state" : "translated", + "value" : "多数工具会在 `~/.ssh` 中尝试并查找磁盘上的 SSH 密钥。要使用 Secretive,我们需要配置这些工具来与 Secretive 通信。" } }, "zh-Hant" : { @@ -13812,8 +15478,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "What Should I Configure?" + "state" : "translated", + "value" : "Что мне следует настроить?" } }, "sr" : { @@ -13848,8 +15514,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "What Should I Configure?" + "state" : "translated", + "value" : "我应该配置什么?" } }, "zh-Hant" : { @@ -13997,8 +15663,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "~/.gitallowedsigners probably does not exist. You'll need to create it." + "state" : "translated", + "value" : "Возможно, директория ~/.gitallowedsigners не существует. Вам потребуется создать ее." } }, "sr" : { @@ -14033,8 +15699,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "~/.gitallowedsigners probably does not exist. You'll need to create it." + "state" : "translated", + "value" : "~/.gitallowedsigners 可能不存在。你需要创建它。" } }, "zh-Hant" : { @@ -14266,8 +15932,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Integrations…" + "state" : "translated", + "value" : "Integrationen…" } }, "el" : { @@ -14368,8 +16034,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Integrations…" + "state" : "translated", + "value" : "Интеграции…" } }, "sr" : { @@ -14404,8 +16070,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "Integrations…" + "state" : "translated", + "value" : "第三方集成…" } }, "zh-Hant" : { @@ -14451,8 +16117,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Other" + "state" : "translated", + "value" : "Sonstige" } }, "el" : { @@ -14553,8 +16219,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Other" + "state" : "translated", + "value" : "Другое" } }, "sr" : { @@ -14589,8 +16255,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "Other" + "state" : "translated", + "value" : "其他" } }, "zh-Hant" : { @@ -14636,8 +16302,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "other" + "state" : "translated", + "value" : "sonstige" } }, "el" : { @@ -14738,8 +16404,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "other" + "state" : "translated", + "value" : "другое" } }, "sr" : { @@ -14774,8 +16440,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "other" + "state" : "translated", + "value" : "其他" } }, "zh-Hant" : { @@ -14821,8 +16487,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Configuration File" + "state" : "translated", + "value" : "Konfigurationsdatei" } }, "el" : { @@ -14923,8 +16589,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Configuration File" + "state" : "translated", + "value" : "Файл конфигурации" } }, "sr" : { @@ -14959,8 +16625,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "Configuration File" + "state" : "translated", + "value" : "配置文件" } }, "zh-Hant" : { @@ -15006,7 +16672,7 @@ }, "de" : { "stringUnit" : { - "state" : "new", + "state" : "translated", "value" : "Shell" } }, @@ -15144,7 +16810,7 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", + "state" : "translated", "value" : "Shell" } }, @@ -15293,8 +16959,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "You can tell SSH to use a specific key for a given host. See the web documentation for more details." + "state" : "translated", + "value" : "Вы можете настроить SSH так, чтобы использовался конкретный ключ для выбранного хоста. Посмотрите документацию в интернете, чтобы узнать подробности." } }, "sr" : { @@ -15329,8 +16995,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "You can tell SSH to use a specific key for a given host. See the web documentation for more details." + "state" : "translated", + "value" : "你可以让 SSH 为指定的主机使用特定密钥。详情请参阅在线文档。" } }, "zh-Hant" : { @@ -15376,7 +17042,7 @@ }, "de" : { "stringUnit" : { - "state" : "new", + "state" : "translated", "value" : "System" } }, @@ -15478,8 +17144,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "System" + "state" : "translated", + "value" : "Система" } }, "sr" : { @@ -15514,8 +17180,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "System" + "state" : "translated", + "value" : "系统" } }, "zh-Hant" : { @@ -16035,8 +17701,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Git Signing" + "state" : "translated", + "value" : "Подпись Git" } }, "sr" : { @@ -16071,8 +17737,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "Git Signing" + "state" : "translated", + "value" : "Git 签名" } }, "zh-Hant" : { @@ -16490,8 +18156,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "View on GitHub" + "state" : "translated", + "value" : "Auf GitHub anzeigen" } }, "el" : { @@ -16592,8 +18258,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "View on GitHub" + "state" : "translated", + "value" : "Посмотреть на GitHub" } }, "sr" : { @@ -16628,8 +18294,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "View on GitHub" + "state" : "translated", + "value" : "在 GitHub 上查看" } }, "zh-Hant" : { @@ -16675,8 +18341,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "View Documentation on Web" + "state" : "translated", + "value" : "Dokumentation im Web ansehen" } }, "el" : { @@ -16777,8 +18443,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "View Documentation on Web" + "state" : "translated", + "value" : "Найти документацию в интернете" } }, "sr" : { @@ -16813,8 +18479,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "View Documentation on Web" + "state" : "translated", + "value" : "查看在线文档" } }, "zh-Hant" : { @@ -16860,8 +18526,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "If any section (like [user]) already exists, just add the entries in the existing section." + "state" : "translated", + "value" : "Wenn ein Abschnitt (wie [user]) bereits existiert, füge die Einträge einfach in dem vorhandenen Abschnitt hinzu." } }, "el" : { @@ -16962,8 +18628,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "If any section (like [user]) already exists, just add the entries in the existing section." + "state" : "translated", + "value" : "Если любой раздел (как [user]) уже существует, просто добавьте новые записи в него." } }, "sr" : { @@ -16998,8 +18664,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "If any section (like [user]) already exists, just add the entries in the existing section." + "state" : "translated", + "value" : "若某小节(比如 [user])已存在,只需在现有小节中添加条目。" } }, "zh-Hant" : { @@ -17890,7 +19556,7 @@ "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не разблокировывать" + "value" : "Не разблокировать" } }, "sr" : { @@ -17972,8 +19638,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Reveal in Finder" + "state" : "translated", + "value" : "In Finder anzeigen" } }, "el" : { @@ -18074,8 +19740,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Reveal in Finder" + "state" : "translated", + "value" : "Открыть в Finder" } }, "sr" : { @@ -18110,8 +19776,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "Reveal in Finder" + "state" : "translated", + "value" : "在“访达”中查看" } }, "zh-Hant" : { @@ -19267,7 +20933,7 @@ }, "de" : { "stringUnit" : { - "state" : "new", + "state" : "translated", "value" : "Secure Enclave" } }, @@ -19734,7 +21400,7 @@ "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Это вспомогательное приложение назвается **Secret Agent**, Вы можете наблюдать его в Activity Monitor время от времени." + "value" : "Это вспомогательное приложение назвается **Secret Agent**, Вы можете видеть его в Activity Monitor время от времени." } }, "sr" : { @@ -20289,7 +21955,7 @@ "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Настроить агента" + "value" : "Настроить агент" } }, "sr" : { @@ -20473,8 +22139,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Done" + "state" : "translated", + "value" : "Готово" } }, "sr" : { @@ -20509,8 +22175,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "Done" + "state" : "translated", + "value" : "完成" } }, "zh-Hant" : { @@ -20556,8 +22222,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Configure" + "state" : "translated", + "value" : "Konfigurieren" } }, "el" : { @@ -20658,8 +22324,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Configure" + "state" : "translated", + "value" : "Настроить" } }, "sr" : { @@ -20694,8 +22360,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "Configure" + "state" : "translated", + "value" : "配置" } }, "zh-Hant" : { @@ -20741,8 +22407,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Tell the tools you use how to talk to Secretive." + "state" : "translated", + "value" : "Sag den Tools, die Du benutzt, wie sie mit Secretive kommunizieren können." } }, "el" : { @@ -20843,8 +22509,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Tell the tools you use how to talk to Secretive." + "state" : "translated", + "value" : "Настройте Ваши инструменты для работы с Secretive" } }, "sr" : { @@ -20879,8 +22545,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "Tell the tools you use how to talk to Secretive." + "state" : "translated", + "value" : "让你用的工具知晓如何与 Secretive 通信。" } }, "zh-Hant" : { @@ -20926,8 +22592,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Configure Integrations" + "state" : "translated", + "value" : "Integration konfigurieren" } }, "el" : { @@ -21028,8 +22694,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Configure Integrations" + "state" : "translated", + "value" : "Настроить интеграции" } }, "sr" : { @@ -21064,8 +22730,8 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "new", - "value" : "Configure Integrations" + "state" : "translated", + "value" : "配置第三方集成" } }, "zh-Hant" : { @@ -21296,7 +22962,7 @@ }, "de" : { "stringUnit" : { - "state" : "new", + "state" : "translated", "value" : "OK" } }, @@ -21481,7 +23147,7 @@ }, "de" : { "stringUnit" : { - "state" : "new", + "state" : "translated", "value" : "Updates" } }, @@ -21666,8 +23332,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Done" + "state" : "translated", + "value" : "Fertig" } }, "el" : { @@ -21768,8 +23434,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Done" + "state" : "translated", + "value" : "Готово" } }, "sr" : { @@ -22881,7 +24547,7 @@ "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пропустить" + "value" : "Игнорировать" } }, "sr" : { @@ -23810,7 +25476,7 @@ "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Кликните чтобы обновить" + "value" : "Кликните, чтобы обновить" } }, "sr" : { @@ -24263,8 +25929,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Test Build" + "state" : "translated", + "value" : "Testversion" } }, "el" : { @@ -24633,7 +26299,7 @@ }, "de" : { "stringUnit" : { - "state" : "new", + "state" : "translated", "value" : "Secretive %1$(updateName)@" } }, @@ -24735,8 +26401,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Secretive %1$(updateName)@" + "state" : "translated", + "value" : "Секретный %1$(updateName)@" } }, "sr" : { @@ -24818,8 +26484,8 @@ }, "de" : { "stringUnit" : { - "state" : "new", - "value" : "Download Latest Nightly Build" + "state" : "translated", + "value" : "Letzte Nightly-Version herunterladen" } }, "el" : { @@ -24920,8 +26586,8 @@ }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "Download Latest Nightly Build" + "state" : "translated", + "value" : "Скачать последнюю ночную сборку" } }, "sr" : { diff --git a/Sources/Packages/Sources/Brief/Release.swift b/Sources/Packages/Sources/Brief/Release.swift index ffc3293..248fcd7 100644 --- a/Sources/Packages/Sources/Brief/Release.swift +++ b/Sources/Packages/Sources/Brief/Release.swift @@ -1,7 +1,8 @@ import Foundation +import SwiftUI /// A release is a representation of a downloadable update. -public struct Release: Codable, Sendable { +public struct Release: Codable, Sendable, Hashable { /// The user-facing name of the release. Typically "Secretive 1.2.3" public let name: String @@ -15,6 +16,8 @@ public struct Release: Codable, Sendable { /// A user-facing description of the contents of the update. public let body: String + public let attributedBody: AttributedString + /// Initializes a Release. /// - Parameters: /// - name: The user-facing name of the release. @@ -26,6 +29,56 @@ public struct Release: Codable, Sendable { self.prerelease = prerelease self.html_url = html_url self.body = body + self.attributedBody = AttributedString(_markdown: body) + } + + public init(_ release: GitHubRelease) { + self.name = release.name + self.prerelease = release.prerelease + self.html_url = release.html_url + self.body = release.body + self.attributedBody = AttributedString(_markdown: release.body) + } + +} + +public struct GitHubRelease: Codable, Sendable { + let name: String + let prerelease: Bool + let html_url: URL + let body: String +} + +fileprivate extension AttributedString { + + init(_markdown markdown: String) { + let split = markdown.split(whereSeparator: \.isNewline) + let lines = split + .compactMap { + try? AttributedString(markdown: String($0), options: .init(allowsExtendedAttributes: true, interpretedSyntax: .full)) + } + .map { (string: AttributedString) in + guard case let .header(level) = string.runs.first?.presentationIntent?.components.first?.kind else { return string } + return AttributedString("\n") + string + .transformingAttributes(\.font) { font in + font.value = switch level { + case 2: .headline.bold() + case 3: .headline + default: .subheadline + } + } + .transformingAttributes(\.underlineStyle) { underline in + underline.value = switch level { + case 2: .single + default: .none + } + } + + AttributedString("\n") + } + self = lines.reduce(into: AttributedString()) { partialResult, next in + partialResult.append(next) + partialResult.append(AttributedString("\n")) + } } } diff --git a/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift b/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift index 08123a1..f163879 100644 --- a/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift +++ b/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift @@ -64,7 +64,7 @@ public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiab private let _create: @Sendable (String, Attributes) async throws -> AnySecret private let _delete: @Sendable (AnySecret) async throws -> Void private let _update: @Sendable (AnySecret, String, Attributes) async throws -> Void - private let _supportedKeyTypes: @Sendable () -> [KeyType] + private let _supportedKeyTypes: @Sendable () -> KeyAvailability public init(_ secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable { _create = { AnySecret(try await secretStore.create(name: $0, attributes: $1)) } @@ -87,7 +87,7 @@ public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiab try await _update(secret, name, attributes) } - public var supportedKeyTypes: [KeyType] { + public var supportedKeyTypes: KeyAvailability { _supportedKeyTypes() } diff --git a/Sources/Packages/Sources/SecretKit/PublicKeyStandinFileController.swift b/Sources/Packages/Sources/SecretKit/PublicKeyStandinFileController.swift index 49983d2..49e417e 100644 --- a/Sources/Packages/Sources/SecretKit/PublicKeyStandinFileController.swift +++ b/Sources/Packages/Sources/SecretKit/PublicKeyStandinFileController.swift @@ -19,9 +19,10 @@ public final class PublicKeyFileStoreController: Sendable { public func generatePublicKeys(for secrets: [AnySecret], clear: Bool = false) throws { logger.log("Writing public keys to disk") if clear { - let validPaths = Set(secrets.map { publicKeyPath(for: $0) }).union(Set(secrets.map { sshCertificatePath(for: $0) })) + let validPaths = Set(secrets.map { publicKeyPath(for: $0) }) + .union(Set(secrets.map { sshCertificatePath(for: $0) })) let contentsOfDirectory = (try? FileManager.default.contentsOfDirectory(atPath: directory.path())) ?? [] - let fullPathContents = contentsOfDirectory.map { "\(directory)/\($0)" } + let fullPathContents = contentsOfDirectory.map { directory.appending(path: $0).path() } let untracked = Set(fullPathContents) .subtracting(validPaths) diff --git a/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift b/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift index 14abc9f..42b4db9 100644 --- a/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift +++ b/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift @@ -62,10 +62,37 @@ public protocol SecretStoreModifiable: SecretStore { /// - attributes: The new attributes for the secret. func update(secret: SecretType, name: String, attributes: Attributes) async throws - var supportedKeyTypes: [KeyType] { get } + var supportedKeyTypes: KeyAvailability { get } } +public struct KeyAvailability: Sendable { + + public let available: [KeyType] + public let unavailable: [UnavailableKeyType] + + public init(available: [KeyType], unavailable: [UnavailableKeyType]) { + self.available = available + self.unavailable = unavailable + } + + public struct UnavailableKeyType: Sendable { + public let keyType: KeyType + public let reason: Reason + + public init(keyType: KeyType, reason: Reason) { + self.keyType = keyType + self.reason = reason + } + + public enum Reason: Sendable { + case macOSUpdateRequired + } + } + +} + + extension NSNotification.Name { // Distributed notification that keys were modified out of process (ie, that the management tool added/removed secrets) diff --git a/Sources/Packages/Sources/SecureEnclaveSecretKit/CryptoKitMigrator.swift b/Sources/Packages/Sources/SecureEnclaveSecretKit/CryptoKitMigrator.swift index cf6b3c0..68c73b2 100644 --- a/Sources/Packages/Sources/SecureEnclaveSecretKit/CryptoKitMigrator.swift +++ b/Sources/Packages/Sources/SecureEnclaveSecretKit/CryptoKitMigrator.swift @@ -50,16 +50,16 @@ extension SecureEnclave { let secret = Secret(id: UUID().uuidString, name: name, publicKey: parsed.publicKey.x963Representation, attributes: Attributes(keyType: .init(algorithm: .ecdsa, size: 256), authentication: auth)) guard !migratedPublicKeys.contains(parsed.publicKey.x963Representation) else { logger.log("Skipping \(name), public key already present. Marking as migrated.") - try markMigrated(secret: secret, oldID: id) + markMigrated(secret: secret, oldID: id) continue } logger.log("Migrating \(name).") try store.saveKey(tokenObjectID, name: name, attributes: secret.attributes) logger.log("Migrated \(name).") - try markMigrated(secret: secret, oldID: id) + markMigrated(secret: secret, oldID: id) migratedAny = true } catch { - logger.error("Failed to migrate \(name): \(error).") + logger.error("Failed to migrate \(name): \(error.localizedDescription).") } } if migratedAny { @@ -69,10 +69,10 @@ extension SecureEnclave { - public func markMigrated(secret: Secret, oldID: Data) throws { + public func markMigrated(secret: Secret, oldID: Data) { let updateQuery = KeychainDictionary([ kSecClass: kSecClassKey, - kSecAttrApplicationLabel: secret.id + kSecAttrApplicationLabel: oldID ]) let newID = oldID + Constants.migrationMagicNumber @@ -82,7 +82,7 @@ extension SecureEnclave { let status = SecItemUpdate(updateQuery, updatedAttributes) if status != errSecSuccess { - throw KeychainError(statusCode: status) + logger.warning("Failed to mark \(secret.name) as migrated: \(status).") } } diff --git a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift index 5156900..7f2fc55 100644 --- a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift +++ b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift @@ -186,17 +186,22 @@ extension SecureEnclave { await reloadSecrets() } - public var supportedKeyTypes: [KeyType] { - if #available(macOS 26, *) { - [ - .ecdsa256, - .mldsa65, - .mldsa87, - ] + public let supportedKeyTypes: KeyAvailability = { + let macOS26Keys: [KeyType] = [.mldsa65, .mldsa87] + let isAtLeastMacOS26 = if #available(macOS 26, *) { + true } else { - [.ecdsa256] + false } - } + return KeyAvailability( + available: [ + .ecdsa256, + ] + (isAtLeastMacOS26 ? macOS26Keys : []), + unavailable: (isAtLeastMacOS26 ? [] : macOS26Keys).map { + KeyAvailability.UnavailableKeyType(keyType: $0, reason: .macOSUpdateRequired) + } + ) + }() } } diff --git a/Sources/Packages/Sources/XPCWrappers/XPCServiceDelegate.swift b/Sources/Packages/Sources/XPCWrappers/XPCServiceDelegate.swift index 5108ed2..9fd9216 100644 --- a/Sources/Packages/Sources/XPCWrappers/XPCServiceDelegate.swift +++ b/Sources/Packages/Sources/XPCWrappers/XPCServiceDelegate.swift @@ -34,7 +34,9 @@ public final class XPCServiceDelegate: NSObject, NSXPCListenerDelegate { if let error = error as? Codable & Error { reply(nil, NSError(error)) } else { - reply(nil, error) + // Sending cast directly tries to serialize it and crashes XPCEncoder. + let cast = error as NSError + reply(nil, NSError(domain: cast.domain, code: cast.code, userInfo: [NSLocalizedDescriptionKey: error.localizedDescription])) } } } diff --git a/Sources/SecretAgent/XPCInputParser.swift b/Sources/SecretAgent/XPCInputParser.swift index 33d179f..b78f316 100644 --- a/Sources/SecretAgent/XPCInputParser.swift +++ b/Sources/SecretAgent/XPCInputParser.swift @@ -2,18 +2,24 @@ import Foundation import SecretAgentKit import Brief import XPCWrappers +import OSLog /// Delegates all agent input parsing to an XPC service which wraps OpenSSH public final class XPCAgentInputParser: SSHAgentInputParserProtocol { + private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "XPCAgentInputParser") private let session: XPCTypedSession public init() async throws { + logger.debug("Creating XPCAgentInputParser") session = try await XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.SecretAgentInputParser", warmup: true) + logger.debug("XPCAgentInputParser is warmed up.") } public func parse(data: Data) async throws -> SSHAgent.Request { - try await session.send(data) + logger.debug("Parsing input") + defer { logger.debug("Parsed input") } + return try await session.send(data) } deinit { diff --git a/Sources/SecretAgent/Base.lproj/Main.storyboard b/Sources/SecretAgent/en.lproj/Main.storyboard similarity index 100% rename from Sources/SecretAgent/Base.lproj/Main.storyboard rename to Sources/SecretAgent/en.lproj/Main.storyboard diff --git a/Sources/Secretive.xcodeproj/project.pbxproj b/Sources/Secretive.xcodeproj/project.pbxproj index ab820fa..44f5ca0 100644 --- a/Sources/Secretive.xcodeproj/project.pbxproj +++ b/Sources/Secretive.xcodeproj/project.pbxproj @@ -19,7 +19,6 @@ 5003EF632780081B00DF2006 /* SecureEnclaveSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF622780081B00DF2006 /* SecureEnclaveSecretKit */; }; 5003EF652780081B00DF2006 /* SmartCardSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF642780081B00DF2006 /* SmartCardSecretKit */; }; 5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; }; - 5008C2402E52792400507AC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8623FCE48E0099B055 /* Assets.xcassets */; }; 5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; }; 501421622781262300BBAA70 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 501421612781262300BBAA70 /* Brief */; }; 501421652781268000BBAA70 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -36,13 +35,11 @@ 50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; }; 50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; }; 50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.swift */; }; - 50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8623FCE48E0099B055 /* Assets.xcassets */; }; 50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */; }; 50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DD123FCEFA90099B055 /* PreviewStore.swift */; }; 5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */; }; 5066A6C22516F303004B5A36 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C12516F303004B5A36 /* SetupView.swift */; }; 5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C72516FE6E004B5A36 /* CopyableView.swift */; }; - 506772C72424784600034DED /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 506772C62424784600034DED /* Credits.rtf */; }; 506772C92425BB8500034DED /* NoStoresView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506772C82425BB8500034DED /* NoStoresView.swift */; }; 50692D1D2E6FDB880043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 50692D282E6FDB8D0043C7BB /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50692D242E6FDB8D0043C7BB /* main.swift */; }; @@ -73,6 +70,10 @@ 50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */; }; 50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; }; 50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; }; + 50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */; }; + 50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4C22E7765DF00C73783 /* AboutView.swift */; }; + 50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; }; + 50E4C4C92E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -194,10 +195,10 @@ 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxBackgroundStyle.swift; sourceTree = ""; }; 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = ""; }; 50571E0424393D1500F76F6C /* LaunchAgentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAgentController.swift; sourceTree = ""; }; + 5059933F2E7A3B5B0092CFFA /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Main.storyboard; sourceTree = ""; }; 50617D7F23FCE48E0099B055 /* Secretive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretive.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50617D8223FCE48E0099B055 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; 50617D8423FCE48E0099B055 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - 50617D8623FCE48E0099B055 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 50617D8E23FCE48E0099B055 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50617D8F23FCE48E0099B055 /* Secretive.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Secretive.entitlements; sourceTree = ""; }; @@ -205,7 +206,6 @@ 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarButtonStyle.swift; sourceTree = ""; }; 5066A6C12516F303004B5A36 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = ""; }; 5066A6C72516FE6E004B5A36 /* CopyableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableView.swift; sourceTree = ""; }; - 506772C62424784600034DED /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; 506772C82425BB8500034DED /* NoStoresView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoStoresView.swift; sourceTree = ""; }; 50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = ""; }; 50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretiveUpdater.xpc; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -228,7 +228,6 @@ 5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSecretView.swift; sourceTree = ""; }; 50A3B78A24026B7500D209EA /* SecretAgent.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SecretAgent.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50A3B79324026B7600D209EA /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 50A3B79624026B7600D209EA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 50A3B79824026B7600D209EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50A3B79924026B7600D209EA /* SecretAgent.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretAgent.entitlements; sourceTree = ""; }; 50AE96FF2E5C1A420018C710 /* IntegrationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationsView.swift; sourceTree = ""; }; @@ -239,6 +238,9 @@ 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationItemView.swift; sourceTree = ""; }; 50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = ""; }; 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = ""; }; + 50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowBackgroundStyle.swift; sourceTree = ""; }; + 50E4C4C22E7765DF00C73783 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; + 50E4C4C72E777E4200C73783 /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -293,15 +295,16 @@ path = Helpers; sourceTree = ""; }; - 504788ED2E681EB200B4556F /* Styles */ = { + 504788ED2E681EB200B4556F /* Modifiers */ = { isa = PBXGroup; children = ( + 50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */, 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */, 50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */, 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */, 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */, ); - path = Styles; + path = Modifiers; sourceTree = ""; }; 504788EE2E681EC300B4556F /* Secrets */ = { @@ -335,6 +338,7 @@ 504788F02E681F0100B4556F /* Views */ = { isa = PBXGroup; children = ( + 50E4C4C22E7765DF00C73783 /* AboutView.swift */, 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */, 50617D8423FCE48E0099B055 /* ContentView.swift */, 5066A6C72516FE6E004B5A36 /* CopyableView.swift */, @@ -375,11 +379,10 @@ 508A58B0241ED1C40069DC07 /* Views */, 508A58B1241ED1EA0069DC07 /* Controllers */, 50033AC427813F1C00253856 /* Helpers */, - 50617D8623FCE48E0099B055 /* Assets.xcassets */, 50617D8E23FCE48E0099B055 /* Info.plist */, 508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */, + 50E4C4C72E777E4200C73783 /* AppIcon.icon */, 50617D8F23FCE48E0099B055 /* Secretive.entitlements */, - 506772C62424784600034DED /* Credits.rtf */, 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */, 50617D8823FCE48E0099B055 /* Preview Content */, ); @@ -432,7 +435,7 @@ children = ( 504788EF2E681ED700B4556F /* Configuration */, 504788EE2E681EC300B4556F /* Secrets */, - 504788ED2E681EB200B4556F /* Styles */, + 504788ED2E681EB200B4556F /* Modifiers */, 504788F02E681F0100B4556F /* Views */, ); path = Views; @@ -613,7 +616,6 @@ hasScannedForEncodings = 0; knownRegions = ( en, - Base, it, fr, de, @@ -643,9 +645,8 @@ buildActionMask = 2147483647; files = ( 50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */, + 50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */, 5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */, - 50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */, - 506772C72424784600034DED /* Credits.rtf in Resources */, 508BF28E25B4F005009EFB7E /* InternetAccessPolicy.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -671,8 +672,8 @@ 50A3B79724026B7600D209EA /* Main.storyboard in Resources */, 5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */, 50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */, + 50E4C4C92E777E4200C73783 /* AppIcon.icon in Resources */, 508BF2AA25B4F1CB009EFB7E /* InternetAccessPolicy.plist in Resources */, - 5008C2402E52792400507AC2 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -685,7 +686,9 @@ files = ( 504788F22E681F3A00B4556F /* Instructions.swift in Sources */, 50BDCB742E6436CA0072D2E7 /* ErrorStyle.swift in Sources */, + 50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */, 2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */, + 50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */, 5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */, 504788EC2E680DC800B4556F /* URLs.swift in Sources */, 504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */, @@ -784,7 +787,7 @@ 50A3B79524026B7600D209EA /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( - 50A3B79624026B7600D209EA /* Base */, + 5059933F2E7A3B5B0092CFFA /* en */, ); name = Main.storyboard; sourceTree = ""; @@ -1045,7 +1048,7 @@ INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 26.0; + MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1085,7 +1088,7 @@ INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 26.0; + MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1127,7 +1130,7 @@ INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 26.0; + MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1159,7 +1162,7 @@ INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 26.0; + MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1189,7 +1192,7 @@ INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 26.0; + MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1221,7 +1224,7 @@ INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 26.0; + MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/Sources/Secretive/App.swift b/Sources/Secretive/App.swift index 64cc298..4c3dff1 100644 --- a/Sources/Secretive/App.swift +++ b/Sources/Secretive/App.swift @@ -4,55 +4,18 @@ import SecureEnclaveSecretKit import SmartCardSecretKit import Brief -extension EnvironmentValues { - - // This is injected through .environment modifier below instead of @Entry for performance reasons (basially, restrictions around init/mainactor causing delay in loading secrets/"empty screen" blip). - @MainActor fileprivate static let _secretStoreList: SecretStoreList = { - let list = SecretStoreList() - let cryptoKit = SecureEnclave.Store() - let migrator = SecureEnclave.CryptoKitMigrator() - try? migrator.migrate(to: cryptoKit) - list.add(store: cryptoKit) - list.add(store: SmartCard.Store()) - return list - }() - - private static let _agentStatusChecker = AgentStatusChecker() - @Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker - private static let _updater: any UpdaterProtocol = { - @AppStorage("defaultsHasRunSetup") var hasRunSetup = false - return Updater(checkOnLaunch: hasRunSetup) - }() - @Entry var updater: any UpdaterProtocol = _updater - - private static let _justUpdatedChecker = JustUpdatedChecker() - @Entry var justUpdatedChecker: any JustUpdatedCheckerProtocol = _justUpdatedChecker - - @MainActor var secretStoreList: SecretStoreList { - EnvironmentValues._secretStoreList - } -} - @main struct Secretive: App { @Environment(\.agentStatusChecker) var agentStatusChecker @Environment(\.justUpdatedChecker) var justUpdatedChecker - @AppStorage("defaultsHasRunSetup") var hasRunSetup = false - @State private var showingSetup = false - @State private var showingIntegrations = false - @State private var showingCreation = false @SceneBuilder var body: some Scene { WindowGroup { - ContentView(showingCreation: $showingCreation, runningSetup: $showingSetup, hasRunSetup: $hasRunSetup) + ContentView() .environment(EnvironmentValues._secretStoreList) - .onAppear { - if !hasRunSetup { - showingSetup = true - } - } .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in + @AppStorage("defaultsHasRunSetup") var hasRunSetup = false guard hasRunSetup else { return } agentStatusChecker.check() if agentStatusChecker.running && justUpdatedChecker.justUpdatedBuild { @@ -62,25 +25,52 @@ struct Secretive: App { forceLaunchAgent() } } - .sheet(isPresented: $showingIntegrations) { - IntegrationsView() - } } .commands { + AppCommands() + } + WindowGroup(id: String(describing: IntegrationsView.self)) { + IntegrationsView() + } + .windowResizability(.contentMinSize) + WindowGroup(id: String(describing: AboutView.self)) { + AboutView() + } + .windowStyle(.hiddenTitleBar) + .windowResizability(.contentSize) + } + +} + +extension Secretive { + + struct AppCommands: Commands { + + @Environment(\.openWindow) var openWindow + @Environment(\.openURL) var openURL + @FocusedValue(\.showCreateSecret) var showCreateSecret + + var body: some Commands { + CommandGroup(replacing: .appInfo) { + Button(.aboutMenuBarTitle, systemImage: "info.circle") { + openWindow(id: String(describing: AboutView.self)) + } + } CommandGroup(before: CommandGroupPlacement.appSettings) { Button(.integrationsMenuBarTitle, systemImage: "app.connected.to.app.below.fill") { - showingIntegrations = true + openWindow(id: String(describing: IntegrationsView.self)) } } CommandGroup(after: CommandGroupPlacement.newItem) { - Button(.appMenuNewSecretButton) { - showingCreation = true + Button(.appMenuNewSecretButton, systemImage: "plus") { + showCreateSecret?() } .keyboardShortcut(KeyboardShortcut(KeyEquivalent("N"), modifiers: [.command, .shift])) + .disabled(showCreateSecret?.isEnabled == false) } CommandGroup(replacing: .help) { Button(.appMenuHelpButton) { - NSWorkspace.shared.open(Constants.helpURL) + openURL(Constants.helpURL) } } SidebarCommands() @@ -113,8 +103,56 @@ extension Secretive { } - private enum Constants { static let helpURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md")! } + +extension EnvironmentValues { + + // This is injected through .environment modifier below instead of @Entry for performance reasons (basially, restrictions around init/mainactor causing delay in loading secrets/"empty screen" blip). + @MainActor fileprivate static let _secretStoreList: SecretStoreList = { + let list = SecretStoreList() + let cryptoKit = SecureEnclave.Store() + let migrator = SecureEnclave.CryptoKitMigrator() + try? migrator.migrate(to: cryptoKit) + list.add(store: cryptoKit) + list.add(store: SmartCard.Store()) + return list + }() + + private static let _agentStatusChecker = AgentStatusChecker() + @Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker + private static let _updater: any UpdaterProtocol = { + @AppStorage("defaultsHasRunSetup") var hasRunSetup = false + return Updater(checkOnLaunch: hasRunSetup) + }() + @Entry var updater: any UpdaterProtocol = _updater + + private static let _justUpdatedChecker = JustUpdatedChecker() + @Entry var justUpdatedChecker: any JustUpdatedCheckerProtocol = _justUpdatedChecker + + @MainActor var secretStoreList: SecretStoreList { + EnvironmentValues._secretStoreList + } +} + +extension FocusedValues { + @Entry var showCreateSecret: OpenSheet? +} + +final class OpenSheet { + + let closure: () -> Void + let isEnabled: Bool + + init(isEnabled: Bool = true, closure: @escaping () -> Void) { + self.isEnabled = isEnabled + self.closure = closure + } + + func callAsFunction() { + closure() + } + +} diff --git a/Design/Icon.icon/Assets/Icon 7.png b/Sources/Secretive/AppIcon.icon/Assets/Icon 7.png similarity index 100% rename from Design/Icon.icon/Assets/Icon 7.png rename to Sources/Secretive/AppIcon.icon/Assets/Icon 7.png diff --git a/Design/Icon.icon/Assets/Rectangle 2 8.png b/Sources/Secretive/AppIcon.icon/Assets/Rectangle 2 8.png similarity index 100% rename from Design/Icon.icon/Assets/Rectangle 2 8.png rename to Sources/Secretive/AppIcon.icon/Assets/Rectangle 2 8.png diff --git a/Design/Icon.icon/Assets/Rectangle Copy 10.png b/Sources/Secretive/AppIcon.icon/Assets/Rectangle Copy 10.png similarity index 100% rename from Design/Icon.icon/Assets/Rectangle Copy 10.png rename to Sources/Secretive/AppIcon.icon/Assets/Rectangle Copy 10.png diff --git a/Design/Icon.icon/icon.json b/Sources/Secretive/AppIcon.icon/icon.json similarity index 100% rename from Design/Icon.icon/icon.json rename to Sources/Secretive/AppIcon.icon/icon.json diff --git a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Contents.json b/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index a78196d..0000000 --- a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "filename" : "Icon-macOS-ClearDark-16x16@1x.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "16x16" - }, - { - "filename" : "Icon-macOS-ClearDark-16x16@2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "16x16" - }, - { - "filename" : "Icon-macOS-ClearDark-32x32@1x.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "32x32" - }, - { - "filename" : "Icon-macOS-ClearDark-32x32@2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "32x32" - }, - { - "filename" : "Icon-macOS-ClearDark-128x128@1x.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "128x128" - }, - { - "filename" : "Icon-macOS-ClearDark-128x128@2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "128x128" - }, - { - "filename" : "Icon-macOS-ClearDark-256x256@1x.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "256x256" - }, - { - "filename" : "Icon-macOS-ClearDark-256x256@2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "256x256" - }, - { - "filename" : "Icon-macOS-ClearDark-512x512@1x.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "512x512" - }, - { - "filename" : "Icon-macOS-ClearDark-1024x1024@1x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "512x512" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-1024x1024@1x.png b/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-1024x1024@1x.png deleted file mode 100644 index d4a5a06..0000000 Binary files a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-1024x1024@1x.png and /dev/null differ diff --git a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-128x128@1x.png b/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-128x128@1x.png deleted file mode 100644 index 639811c..0000000 Binary files a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-128x128@1x.png and /dev/null differ diff --git a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-128x128@2x.png b/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-128x128@2x.png deleted file mode 100644 index 68c79a1..0000000 Binary files a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-128x128@2x.png and /dev/null differ diff --git a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-16x16@1x.png b/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-16x16@1x.png deleted file mode 100644 index 13e16f5..0000000 Binary files a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-16x16@1x.png and /dev/null differ diff --git a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-16x16@2x.png b/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-16x16@2x.png deleted file mode 100644 index 0f9c355..0000000 Binary files a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-16x16@2x.png and /dev/null differ diff --git a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-256x256@1x.png b/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-256x256@1x.png deleted file mode 100644 index 68c79a1..0000000 Binary files a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-256x256@1x.png and /dev/null differ diff --git a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-256x256@2x.png b/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-256x256@2x.png deleted file mode 100644 index e433fab..0000000 Binary files a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-256x256@2x.png and /dev/null differ diff --git a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-32x32@1x.png b/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-32x32@1x.png deleted file mode 100644 index 29ffd13..0000000 Binary files a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-32x32@1x.png and /dev/null differ diff --git a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-32x32@2x.png b/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-32x32@2x.png deleted file mode 100644 index 8b67748..0000000 Binary files a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-32x32@2x.png and /dev/null differ diff --git a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-512x512@1x.png b/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-512x512@1x.png deleted file mode 100644 index e433fab..0000000 Binary files a/Sources/Secretive/Assets.xcassets/AppIcon.appiconset/Icon-macOS-ClearDark-512x512@1x.png and /dev/null differ diff --git a/Sources/Secretive/Assets.xcassets/Contents.json b/Sources/Secretive/Assets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/Sources/Secretive/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Sources/Secretive/Credits.rtf b/Sources/Secretive/Credits.rtf deleted file mode 100644 index 70bdc54..0000000 --- a/Sources/Secretive/Credits.rtf +++ /dev/null @@ -1,36 +0,0 @@ -{\rtf1\ansi\ansicpg1252\cocoartf2580 -\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\margl1440\margr1440\vieww9000\viewh8400\viewkind0 -\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6119\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 -{\field{\*\fldinst{HYPERLINK "https://github.com/maxgoedjen/secretive"}}{\fldrslt -\f0\fs24 \cf0 GitHub Repository}} -\f0\fs24 \ -\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 -\cf0 \ -{\field{\*\fldinst{HYPERLINK "GITHUB_BUILD_URL"}}{\fldrslt Build Log}}\ -\ -Special Thanks To:\ -\ -{\field{\*\fldinst{HYPERLINK "https://github.com/maxgoedjen/secretive/graphs/contributors"}}{\fldrslt Contributors}}:\ -{\field{\*\fldinst{HYPERLINK "https://github.com/0xflotus"}}{\fldrslt 0xflotus}}\ -{\field{\*\fldinst{HYPERLINK "https://github.com/aaron-trout"}}{\fldrslt Aaron Trout}}\ -\pard\pardeftab720\partightenfactor0 -{\field{\*\fldinst{HYPERLINK "https://github.com/EppO"}}{\fldrslt \cf0 Florent Monbillard}}\ -{\field{\*\fldinst{HYPERLINK "https://github.com/vladimyr"}}{\fldrslt Dario Vladovi\uc0\u263 }}\ -{\field{\*\fldinst{HYPERLINK "https://github.com/lavalleeale"}}{\fldrslt Alex Lavallee}}\ -{\field{\*\fldinst{HYPERLINK "https://github.com/joshheyse"}}{\fldrslt Josh}}\ -{\field{\*\fldinst{HYPERLINK "https://github.com/diesal11"}}{\fldrslt Dylan Lundy}}\ -\ -\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 -\cf0 Testers:\ -{\field{\*\fldinst{HYPERLINK "https://github.com/bdash"}}{\fldrslt Mark Rowe}}\ -{\field{\*\fldinst{HYPERLINK "https://github.com/danielctull"}}{\fldrslt Daniel Tull}}\ -{\field{\*\fldinst{HYPERLINK "https://github.com/davedelong"}}{\fldrslt Dave DeLong}}\ -{\field{\*\fldinst{HYPERLINK "https://github.com/esttorhe"}}{\fldrslt Esteban Torres}}\ -{\field{\*\fldinst{HYPERLINK "https://github.com/joeblau"}}{\fldrslt Joe Blau}}\ -{\field{\*\fldinst{HYPERLINK "https://github.com/marksands"}}{\fldrslt Mark Sands}}\ -{\field{\*\fldinst{HYPERLINK "https://github.com/mergesort"}}{\fldrslt Joe Fabisevich}}\ -{\field{\*\fldinst{HYPERLINK "https://github.com/phillco"}}{\fldrslt Phil Cohen}}\ -{\field{\*\fldinst{HYPERLINK "https://github.com/zackdotcomputer"}}{\fldrslt Zack Sheppard}}} \ No newline at end of file diff --git a/Sources/Secretive/Info.plist b/Sources/Secretive/Info.plist index beb76e8..da665c4 100644 --- a/Sources/Secretive/Info.plist +++ b/Sources/Secretive/Info.plist @@ -20,6 +20,8 @@ $(CI_VERSION) CFBundleVersion $(CI_BUILD_NUMBER) + GitHubBuildLog + https://$(CI_BUILD_LINK) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright diff --git a/Sources/Secretive/Preview Content/PreviewStore.swift b/Sources/Secretive/Preview Content/PreviewStore.swift index 8c65f80..63e7507 100644 --- a/Sources/Secretive/Preview Content/PreviewStore.swift +++ b/Sources/Secretive/Preview Content/PreviewStore.swift @@ -60,16 +60,17 @@ extension Preview { let id = UUID() var name: String { "Modifiable Preview Store" } let secrets: [Secret] - var supportedKeyTypes: [KeyType] { - if #available(macOS 26, *) { - [ + var supportedKeyTypes: KeyAvailability { + return KeyAvailability( + available: [ .ecdsa256, .mldsa65, - .mldsa87, + .mldsa87 + ], + unavailable: [ + .init(keyType: .ecdsa384, reason: .macOSUpdateRequired) ] - } else { - [.ecdsa256] - } + ) } init(secrets: [Secret]) { diff --git a/Sources/Secretive/Views/Configuration/ConfigurationItemView.swift b/Sources/Secretive/Views/Configuration/ConfigurationItemView.swift index 77e6a2e..edf9f67 100644 --- a/Sources/Secretive/Views/Configuration/ConfigurationItemView.swift +++ b/Sources/Secretive/Views/Configuration/ConfigurationItemView.swift @@ -32,7 +32,7 @@ struct ConfigurationItemView: View { Spacer() switch action { case .copy(let string): - Button(.copyableClickToCopyButton, systemImage: "document.on.document") { + Button(.copyableClickToCopyButton, systemImage: "doc.on.doc") { NSPasteboard.general.declareTypes([.string], owner: nil) NSPasteboard.general.setString(string, forType: .string) } diff --git a/Sources/Secretive/Views/Configuration/IntegrationsView.swift b/Sources/Secretive/Views/Configuration/IntegrationsView.swift index de6b8a0..ced62d0 100644 --- a/Sources/Secretive/Views/Configuration/IntegrationsView.swift +++ b/Sources/Secretive/Views/Configuration/IntegrationsView.swift @@ -21,47 +21,19 @@ struct IntegrationsView: View { } } } detail: { - IntegrationsDetailView(selectedInstruction: $selectedInstruction) - .fauxToolbar { - Button(.setupDoneButton) { - dismiss() - } - .normalButton() - } + IntegrationsDetailView(selectedInstruction: $selectedInstruction) } + .toolbar { + Button(.setupDoneButton) { + dismiss() + } + } + .hiddenToolbar() + .windowBackgroundStyle(.thinMaterial) .onAppear { selectedInstruction = instructions.gettingStarted } - .frame(minHeight: 500) - } - -} - -extension View { - - func fauxToolbar(content: () -> Content) -> some View { - modifier(FauxToolbarModifier(toolbarContent: content())) - } - -} - -struct FauxToolbarModifier: ViewModifier { - - var toolbarContent: ToolbarContent - - func body(content: Content) -> some View { - VStack(alignment: .leading, spacing: 0) { - content - Divider() - HStack { - Spacer() - toolbarContent - .padding(.top, 8) - .padding(.trailing, 16) - .padding(.bottom, 16) - } - } - + .frame(minWidth: 400, minHeight: 400) } } diff --git a/Sources/Secretive/Views/Configuration/SetupView.swift b/Sources/Secretive/Views/Configuration/SetupView.swift index 2578c28..df55855 100644 --- a/Sources/Secretive/Views/Configuration/SetupView.swift +++ b/Sources/Secretive/Views/Configuration/SetupView.swift @@ -85,7 +85,10 @@ struct SetupView: View { integrations = true }, content: { IntegrationsView() + .frame(minWidth: 500, minHeight: 400) }) + .frame(idealWidth: 600) + .fixedSize(horizontal: false, vertical: true) } } @@ -172,10 +175,13 @@ struct StepView: View { .frame(width: 20) VStack(alignment: .leading, spacing: 4) { Text(title) + .fixedSize(horizontal: false, vertical: true) .bold() Text(description) + .fixedSize(horizontal: false, vertical: true) if let detail { Text(detail) + .fixedSize(horizontal: false, vertical: true) .font(.callout) .italic() } diff --git a/Sources/Secretive/Views/Configuration/ToolConfigurationView.swift b/Sources/Secretive/Views/Configuration/ToolConfigurationView.swift index cd1bc69..d23679a 100644 --- a/Sources/Secretive/Views/Configuration/ToolConfigurationView.swift +++ b/Sources/Secretive/Views/Configuration/ToolConfigurationView.swift @@ -32,6 +32,7 @@ struct ToolConfigurationView: View { selectedSecret = created } } + .fixedSize() } } } diff --git a/Sources/Secretive/Views/Styles/ActionButtonStyle.swift b/Sources/Secretive/Views/Modifiers/ActionButtonStyle.swift similarity index 92% rename from Sources/Secretive/Views/Styles/ActionButtonStyle.swift rename to Sources/Secretive/Views/Modifiers/ActionButtonStyle.swift index 74284a7..70ab463 100644 --- a/Sources/Secretive/Views/Styles/ActionButtonStyle.swift +++ b/Sources/Secretive/Views/Modifiers/ActionButtonStyle.swift @@ -24,7 +24,7 @@ extension View { } -struct MenuButtonModifier: ViewModifier { +struct ToolbarCircleButtonModifier: ViewModifier { func body(content: Content) -> some View { if #available(macOS 26.0, *) { @@ -40,8 +40,8 @@ struct MenuButtonModifier: ViewModifier { extension View { - func menuButton() -> some View { - modifier(MenuButtonModifier()) + func toolbarCircleButton() -> some View { + modifier(ToolbarCircleButtonModifier()) } } diff --git a/Sources/Secretive/Views/Styles/BoxBackgroundStyle.swift b/Sources/Secretive/Views/Modifiers/BoxBackgroundStyle.swift similarity index 100% rename from Sources/Secretive/Views/Styles/BoxBackgroundStyle.swift rename to Sources/Secretive/Views/Modifiers/BoxBackgroundStyle.swift diff --git a/Sources/Secretive/Views/Styles/ErrorStyle.swift b/Sources/Secretive/Views/Modifiers/ErrorStyle.swift similarity index 100% rename from Sources/Secretive/Views/Styles/ErrorStyle.swift rename to Sources/Secretive/Views/Modifiers/ErrorStyle.swift diff --git a/Sources/Secretive/Views/Styles/ToolbarButtonStyle.swift b/Sources/Secretive/Views/Modifiers/ToolbarButtonStyle.swift similarity index 69% rename from Sources/Secretive/Views/Styles/ToolbarButtonStyle.swift rename to Sources/Secretive/Views/Modifiers/ToolbarButtonStyle.swift index 7dd1f65..99ada75 100644 --- a/Sources/Secretive/Views/Styles/ToolbarButtonStyle.swift +++ b/Sources/Secretive/Views/Modifiers/ToolbarButtonStyle.swift @@ -1,6 +1,6 @@ import SwiftUI -struct ToolbarButtonStyle: ButtonStyle { +struct ToolbarStatusButtonStyle: ButtonStyle { private let lightColor: Color private let darkColor: Color @@ -39,6 +39,7 @@ struct ToolbarButtonStyle: ButtonStyle { } else { configuration .label + .padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8)) .background(colorScheme == .light ? lightColor : darkColor) .foregroundColor(.white) .clipShape(RoundedRectangle(cornerRadius: 5)) @@ -55,3 +56,24 @@ struct ToolbarButtonStyle: ButtonStyle { } } } + +struct ToolbarButtonStyle: PrimitiveButtonStyle { + + var tint: Color = .white.opacity(0.1) + + func makeBody(configuration: Configuration) -> some View { + if #available(macOS 26.0, *) { + configuration + .label + .padding(.vertical, 10) + .padding(.horizontal, 12) + .glassEffect(.regular.interactive().tint(tint)) + } else { + BorderedButtonStyle().makeBody(configuration: configuration) + .padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8)) + .foregroundColor(.white) + .clipShape(RoundedRectangle(cornerRadius: 5)) + } + } +} + diff --git a/Sources/Secretive/Views/Modifiers/WindowBackgroundStyle.swift b/Sources/Secretive/Views/Modifiers/WindowBackgroundStyle.swift new file mode 100644 index 0000000..809d5e3 --- /dev/null +++ b/Sources/Secretive/Views/Modifiers/WindowBackgroundStyle.swift @@ -0,0 +1,47 @@ +import SwiftUI + +struct WindowBackgroundStyleModifier: ViewModifier { + + let shapeStyle: any ShapeStyle + + func body(content: Content) -> some View { + if #available(macOS 15.0, *) { + content + .containerBackground( + shapeStyle, for: .window + ) + } else { + content + } + } + +} + +extension View { + + func windowBackgroundStyle(_ style: some ShapeStyle) -> some View { + modifier(WindowBackgroundStyleModifier(shapeStyle: style)) + } + +} + +struct HiddenToolbarModifier: ViewModifier { + + func body(content: Content) -> some View { + if #available(macOS 15.0, *) { + content + .toolbarBackgroundVisibility(.hidden, for: .automatic) + } else { + content + } + } + +} + +extension View { + + func hiddenToolbar() -> some View { + modifier(HiddenToolbarModifier()) + } + +} diff --git a/Sources/Secretive/Views/Secrets/CreateSecretView.swift b/Sources/Secretive/Views/Secrets/CreateSecretView.swift index 192c3dc..ca77124 100644 --- a/Sources/Secretive/Views/Secrets/CreateSecretView.swift +++ b/Sources/Secretive/Views/Secrets/CreateSecretView.swift @@ -75,10 +75,23 @@ struct CreateSecretView: View { Section { VStack { Picker(.createSecretKeyTypeLabel, selection: $keyType) { - ForEach(store.supportedKeyTypes, id: \.self) { option in + ForEach(store.supportedKeyTypes.available, id: \.self) { option in Text(String(describing: option)) .tag(option) - .font(.caption) + } + Divider() + ForEach(store.supportedKeyTypes.unavailable, id: \.keyType) { option in + VStack { + Button { + } label: { + Text(String(describing: option.keyType)) + switch option.reason { + case .macOSUpdateRequired: + Text(.createSecretKeyTypeMacOSUpdateRequiredLabel) + } + } + } + .selectionDisabled() } } if keyType?.algorithm == .mldsa { @@ -119,7 +132,7 @@ struct CreateSecretView: View { .padding() } .onAppear { - keyType = store.supportedKeyTypes.first + keyType = store.supportedKeyTypes.available.first } .formStyle(.grouped) } diff --git a/Sources/Secretive/Views/Views/AboutView.swift b/Sources/Secretive/Views/Views/AboutView.swift new file mode 100644 index 0000000..415f202 --- /dev/null +++ b/Sources/Secretive/Views/Views/AboutView.swift @@ -0,0 +1,69 @@ +import SwiftUI + +struct AboutView: View { + var body: some View { + if #available(macOS 15.0, *) { + AboutViewContent() + .containerBackground( + .thinMaterial, for: .window + ) + } else { + AboutViewContent() + } + } +} + +struct AboutViewContent: View { + + @Environment(\.openURL) var openURL + var body: some View { + VStack(spacing: 10) { + HStack { + Image(nsImage: NSApplication.shared.applicationIconImage) + VStack(alignment: .leading) { + Text(verbatim: "Secretive") + .font(.system(.largeTitle, weight: .bold)) + Text("**\(Bundle.main.versionNumber)** (\(Bundle.main.buildNumber))") + .fixedSize(horizontal: true, vertical: false) + HStack { + Button(.aboutViewOnGithubButton) { + openURL(URL(string: "https://github.com/maxgoedjen/secretive")!) + } + .normalButton() + Button(.aboutBuildLogButton) { + openURL(Bundle.main.buildLog) + } + .normalButton() + } + } + } + Text(.aboutThanks(contributorsLink: "https://github.com/maxgoedjen/secretive/graphs/contributors", sponsorsLink: "https://github.com/sponsors/maxgoedjen")) + .font(.headline) + Text(.aboutOpenSourceNotice) + .font(.subheadline) + } + .padding(EdgeInsets(top: 10, leading: 30, bottom: 30, trailing: 30)) + } + +} + +private extension Bundle { + + var buildLog: URL { + URL(string: infoDictionary!["GitHubBuildLog"] as! String)! + } + + var versionNumber: String { + infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0" + } + + var buildNumber: String { + infoDictionary?["CFBundleVersion"] as? String ?? "0.0" + } + +} + +#Preview { + AboutView() + .frame(width: 500, height: 250) +} diff --git a/Sources/Secretive/Views/Views/ContentView.swift b/Sources/Secretive/Views/Views/ContentView.swift index 117d0d8..7c395df 100644 --- a/Sources/Secretive/Views/Views/ContentView.swift +++ b/Sources/Secretive/Views/Views/ContentView.swift @@ -6,19 +6,21 @@ import Brief struct ContentView: View { - @Binding var showingCreation: Bool - @Binding var runningSetup: Bool - @Binding var hasRunSetup: Bool - @State var showingAgentInfo = false @State var activeSecret: AnySecret? - @Environment(\.colorScheme) var colorScheme - - @Environment(\.secretStoreList) private var storeList - @Environment(\.updater) private var updater: any UpdaterProtocol - @Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol @State private var selectedUpdate: Release? + + @Environment(\.colorScheme) private var colorScheme + @Environment(\.openWindow) private var openWindow + @Environment(\.secretStoreList) private var storeList + @Environment(\.updater) private var updater + @Environment(\.agentStatusChecker) private var agentStatusChecker + + @AppStorage("defaultsHasRunSetup") private var hasRunSetup = false + @State private var showingCreation = false @State private var showingAppPathNotice = false + @State private var runningSetup = false + @State private var showingAgentInfo = false var body: some View { VStack { @@ -35,6 +37,23 @@ struct ContentView: View { toolbarItem(appPathNoticeView, id: "appPath") toolbarItem(newItemView, id: "new") } + .onAppear { + if !hasRunSetup { + runningSetup = true + } + } + .focusedSceneValue(\.showCreateSecret, .init(isEnabled: !runningSetup) { + showingCreation = true + }) + .sheet(isPresented: $showingCreation) { + if let modifiable = storeList.modifiableStore { + CreateSecretView(store: modifiable) { created in + if let created { + activeSecret = created + } + } + } + } .sheet(isPresented: $runningSetup) { SetupView(setupComplete: $hasRunSetup) } @@ -85,24 +104,9 @@ extension ContentView { .font(.headline) .foregroundColor(.white) }) - .buttonStyle(ToolbarButtonStyle(color: color)) + .buttonStyle(ToolbarStatusButtonStyle(color: color)) .sheet(item: $selectedUpdate) { update in - VStack { - if updater.currentVersion.isTestBuild { - VStack { - if let description = updater.currentVersion.previewDescription { - Text(description) - } - Link(destination: URL(string: "https://github.com/maxgoedjen/secretive/actions/workflows/nightly.yml")!) { - Button(.updaterDownloadLatestNightlyButton) {} - .frame(maxWidth: .infinity) - .primaryButton() - } - } - .padding() - } - UpdateDetailView(update: update) - } + UpdateDetailView(update: update) } } } @@ -113,16 +117,7 @@ extension ContentView { Button(.appMenuNewSecretButton, systemImage: "plus") { showingCreation = true } - .menuButton() - .sheet(isPresented: $showingCreation) { - if let modifiable = storeList.modifiableStore { - CreateSecretView(store: modifiable) { created in - if let created { - activeSecret = created - } - } - } - } + .toolbarCircleButton() } } @@ -149,7 +144,7 @@ extension ContentView { } }) .buttonStyle( - ToolbarButtonStyle( + ToolbarStatusButtonStyle( lightColor: agentStatusChecker.running ? .black.opacity(0.05) : .red.opacity(0.75), darkColor: agentStatusChecker.running ? .white.opacity(0.05) : .red.opacity(0.5), ) @@ -171,18 +166,18 @@ extension ContentView { .font(.headline) .foregroundColor(.white) }) - .buttonStyle(ToolbarButtonStyle(color: .orange)) - .popover(isPresented: $showingAppPathNotice, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) { - VStack { - Image(systemName: "exclamationmark.triangle") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 64) - Text(.appNotInApplicationsNoticeDetailDescription) - .frame(maxWidth: 300) + .buttonStyle(ToolbarStatusButtonStyle(color: .orange)) + .confirmationDialog(.appNotInApplicationsNoticeTitle, isPresented: $showingAppPathNotice) { + Button(.appNotInApplicationsNoticeCancelButton, role: .cancel) { } - .padding() + Button(.appNotInApplicationsNoticeQuitButton) { + NSWorkspace.shared.selectFile(Bundle.main.bundlePath, inFileViewerRootedAtPath: Bundle.main.bundlePath) + NSApplication.shared.terminate(nil) + } + } message: { + Text(.appNotInApplicationsNoticeDetailDescription) } + .dialogIcon(Image(systemName: "folder.fill.badge.questionmark")) } } diff --git a/Sources/Secretive/Views/Views/CopyableView.swift b/Sources/Secretive/Views/Views/CopyableView.swift index 5d5b431..e56ab20 100644 --- a/Sources/Secretive/Views/Views/CopyableView.swift +++ b/Sources/Secretive/Views/Views/CopyableView.swift @@ -11,7 +11,7 @@ struct CopyableView: View { @State private var interactionState: InteractionState = .normal var content: some View { - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 15) { HStack { image .renderingMode(.template) @@ -28,20 +28,19 @@ struct CopyableView: View { } copyButton } - .foregroundColor(secondaryTextColor) - .transition(.opacity) + .foregroundColor(secondaryTextColor) + .transition(.opacity) } - } - .padding(EdgeInsets(top: 20, leading: 20, bottom: 10, trailing: 20)) Divider() + .ignoresSafeArea() Text(text) .fixedSize(horizontal: false, vertical: true) .foregroundColor(primaryTextColor) - .padding(EdgeInsets(top: 10, leading: 20, bottom: 20, trailing: 20)) .multilineTextAlignment(.leading) .font(.system(.body, design: .monospaced)) } + .safeAreaPadding(20) ._background(interactionState: interactionState) .frame(minWidth: 150, maxWidth: .infinity) } @@ -53,12 +52,12 @@ struct CopyableView: View { interactionState = hovering ? .hovering : .normal } } - .onDrag({ - NSItemProvider(item: NSData(data: text.data(using: .utf8)!), typeIdentifier: UTType.utf8PlainText.identifier) - }, preview: { - content + .draggable(text) { + content + .lineLimit(3) + .frame(maxWidth: 300) ._background(interactionState: .dragging) - }) + } .onTapGesture { copy() withAnimation { @@ -79,7 +78,7 @@ struct CopyableView: View { var copyButton: some View { switch interactionState { case .hovering: - Button(.copyableClickToCopyButton, systemImage: "document.on.document") { + Button(.copyableClickToCopyButton, systemImage: "doc.on.doc") { withAnimation { // Button will eat the click, so we set interaction state manually. interactionState = .clicking @@ -159,7 +158,8 @@ fileprivate struct BackgroundViewModifier: ViewModifier { // Very thin opacity lets user hover anywhere over the view, glassEffect doesn't allow. .background(.white.opacity(0.01), in: RoundedRectangle(cornerRadius: 15)) .glassEffect(.regular.tint(backgroundColor(interactionState: interactionState)), in: RoundedRectangle(cornerRadius: 15)) - + .mask(RoundedRectangle(cornerRadius: 15)) + .shadow(color: .black.opacity(0.1), radius: 5) } else { content .background(backgroundColor(interactionState: interactionState)) @@ -170,13 +170,25 @@ fileprivate struct BackgroundViewModifier: ViewModifier { func backgroundColor(interactionState: InteractionState) -> Color { guard appearsActive else { return Color.clear } - switch interactionState { - case .normal: - return colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.885) - case .hovering, .dragging: - return colorScheme == .dark ? Color(white: 0.275) : Color(white: 0.82) - case .clicking: - return .accentColor + if #available(macOS 26.0, *) { + let base = colorScheme == .dark ? Color(white: 0.2) : Color(white: 1) + switch interactionState { + case .normal: + return base + case .hovering: + return base.mix(with: .accentColor, by: colorScheme == .dark ? 0.2 : 0.1) + case .clicking, .dragging: + return base.mix(with: .accentColor, by: 0.8) + } + } else { + switch interactionState { + case .normal: + return colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.885) + case .hovering: + return colorScheme == .dark ? Color(white: 0.275) : Color(white: 0.82) + case .clicking, .dragging: + return .accentColor + } } } @@ -184,7 +196,10 @@ fileprivate struct BackgroundViewModifier: ViewModifier { } #Preview { - CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "figure.wave"), text: "Hello world.") + VStack { + CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "figure.wave"), text: "Hello world.") + CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "figure.wave"), text: "Hello world.") + } .padding() } diff --git a/Sources/Secretive/Views/Views/UpdateView.swift b/Sources/Secretive/Views/Views/UpdateView.swift index 810e0e8..91f8513 100644 --- a/Sources/Secretive/Views/Views/UpdateView.swift +++ b/Sources/Secretive/Views/Views/UpdateView.swift @@ -3,61 +3,50 @@ import Brief struct UpdateDetailView: View { - @Environment(\.updater) var updater: any UpdaterProtocol + @Environment(\.updater) var updater + @Environment(\.openURL) var openURL let update: Release var body: some View { - VStack { - Text(.updateVersionName(updateName: update.name)).font(.title) - GroupBox(label: Text(.updateReleaseNotesTitle)) { - ScrollView { - attributedBody - } - } - HStack { - if !update.critical { - Button(.updateIgnoreButton) { - Task { - await updater.ignore(release: update) + VStack(spacing: 0) { + HStack { + if !update.critical { + Button(.updateIgnoreButton) { + Task { + await updater.ignore(release: update) + } } + .buttonStyle(ToolbarButtonStyle()) } Spacer() + if updater.currentVersion.isTestBuild { + Button(.updaterDownloadLatestNightlyButton) { + openURL(URL(string: "https://github.com/maxgoedjen/secretive/actions/workflows/nightly.yml")!) + } + .buttonStyle(ToolbarButtonStyle(tint: .accentColor)) + } + Button(.updateUpdateButton) { + openURL(update.html_url) + } + .buttonStyle(ToolbarButtonStyle(tint: .accentColor)) + .keyboardShortcut(.defaultAction) } - Button(.updateUpdateButton) { - NSWorkspace.shared.open(update.html_url) + .padding() + Divider() + Form { + Section { + Text(update.attributedBody) + } header: { + Text(.updateVersionName(updateName: update.name)) .headerProminence(.increased) + } } - .keyboardShortcut(.defaultAction) - } - + .formStyle(.grouped) } - .padding() - .frame(maxWidth: 500) - } - - var attributedBody: Text { - var text = Text(verbatim: "") - for line in update.body.split(whereSeparator: \.isNewline) { - let attributed: Text - let split = line.split(separator: " ") - let unprefixed = split.dropFirst().joined(separator: " ") - if let prefix = split.first { - switch prefix { - case "#": - attributed = Text(unprefixed).font(.title) + Text(verbatim: "\n") - case "##": - attributed = Text(unprefixed).font(.title2) + Text(verbatim: "\n") - case "###": - attributed = Text(unprefixed).font(.title3) + Text(verbatim: "\n") - default: - attributed = Text(line) + Text(verbatim: "\n\n") - } - } else { - attributed = Text(line) + Text(verbatim: "\n\n") - } - text = text + attributed - } - return text } } + +#Preview { + UpdateDetailView(update: .init(name: "3.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Hello")) +} diff --git a/Sources/SecretiveUpdater/SecretiveUpdater.swift b/Sources/SecretiveUpdater/SecretiveUpdater.swift index 998fd31..eab2587 100644 --- a/Sources/SecretiveUpdater/SecretiveUpdater.swift +++ b/Sources/SecretiveUpdater/SecretiveUpdater.swift @@ -11,7 +11,9 @@ final class SecretiveUpdater: NSObject, XPCProtocol { func process(_: Data) async throws -> [Release] { let (data, _) = try await URLSession.shared.data(from: Constants.updateURL) - return try JSONDecoder().decode([Release].self, from: data) + return try JSONDecoder() + .decode([GitHubRelease].self, from: data) + .map(Release.init) } }