diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index be8cc51..7f3ef37 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,10 +23,7 @@ jobs: - name: Set Environment run: sudo xcrun xcode-select -s /Applications/Xcode_26_beta_5.app - name: Test - run: | - pushd Sources/Packages - swift test - popd + run: swift build --build-system swiftbuild --package-path Sources/Packages build: # runs-on: macOS-latest runs-on: macos-15 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f9d9048..f471bc6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,4 @@ jobs: - name: Set Environment run: sudo xcrun xcode-select -s /Applications/Xcode_26_beta_5.app - name: Test - run: | - pushd Sources/Packages - swift test - popd + run: swift build --build-system swiftbuild --package-path Sources/Packages diff --git a/Sources/Secretive/Localizable.xcstrings b/Sources/Packages/Localizable.xcstrings similarity index 88% rename from Sources/Secretive/Localizable.xcstrings rename to Sources/Packages/Localizable.xcstrings index 063d2fc..2fba308 100644 --- a/Sources/Secretive/Localizable.xcstrings +++ b/Sources/Packages/Localizable.xcstrings @@ -2,6 +2,7 @@ "sourceLanguage" : "en", "strings" : { "agent_not_running_notice_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -78,6 +79,7 @@ } }, "agent_running_notice_detail_description" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -154,6 +156,7 @@ } }, "agent_running_notice_detail_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -230,6 +233,7 @@ } }, "agent_running_notice_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -306,6 +310,7 @@ } }, "agent_setup_notice_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -382,6 +387,7 @@ } }, "app_menu_help_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -458,6 +464,7 @@ } }, "app_menu_new_secret_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -534,6 +541,7 @@ } }, "app_menu_setup_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -610,6 +618,7 @@ } }, "app_not_in_applications_notice_detail_description" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -686,6 +695,7 @@ } }, "app_not_in_applications_notice_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -761,236 +771,236 @@ } } }, - "auth_context_persist_for_duration_%@_%@" : { + "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\")", "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { - "state" : "translated", - "value" : "desbloqueja el secret \"%1$@\" per a %2$@" + "state" : "needs_review", + "value" : "desbloqueja el secret \"%1$(secretName)@\" per a %2$(duration)@" } }, "de" : { "stringUnit" : { - "state" : "translated", - "value" : "Secret \"%1$@\" für %2$@ entsperren" + "state" : "needs_review", + "value" : "Secret \"%1$(secretName)@\" für %2$(duration)@ entsperren" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "unlock secret \"%1$@\" for %2$@" + "value" : "unlock secret \"%1$(secretName)@“ for %2$(duration)@" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "avaa salaisuuden \"%1$@\" lukitus ajaksi %2$@" + "state" : "needs_review", + "value" : "avaa salaisuuden \"%1$(secretName)@\" lukitus ajaksi %2$(duration)@" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "déverrouiller le secret \"%1$@\" pendant %2$@" + "state" : "needs_review", + "value" : "déverrouiller le secret \"%1$(secretName)@\" pendant %2$(duration)@" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "sblocca il Segreto \"%1$@\" per %2$@" + "state" : "needs_review", + "value" : "sblocca il Segreto \"%1$(secretName)@\" per %2$(duration)@" } }, "ja" : { "stringUnit" : { - "state" : "translated", - "value" : "シークレット“%1$@”のロックを解除します (%2$@間)" + "state" : "needs_review", + "value" : "シークレット“%1$(secretName)@”のロックを解除します (%2$(duration)@間)" } }, "ko" : { "stringUnit" : { - "state" : "translated", - "value" : "비밀 \"%1$@\"를 %2$@ 동안 잠금 해제" + "state" : "needs_review", + "value" : "비밀 \"%1$(secretName)@\"를 %2$(duration)@ 동안 잠금 해제" } }, "pl" : { "stringUnit" : { - "state" : "translated", - "value" : "odblokuj sekret “%1$@” dla %2$@" + "state" : "needs_review", + "value" : "odblokuj sekret “%1$(secretName)@” dla %2$(duration)@" } }, "pt-BR" : { "stringUnit" : { - "state" : "translated", - "value" : "destravar segredo \"%1$@\" for %2$@" + "state" : "needs_review", + "value" : "destravar segredo \"%1$(secretName)@\" for %2$(duration)@" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "разблокировать секрет \"%1$@\" на %2$@" + "state" : "needs_review", + "value" : "разблокировать секрет \"%1$(secretName)@\" на %2$(duration)@" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "解锁密钥串 \"%1$@\" 给 %2$@" + "state" : "needs_review", + "value" : "解锁密钥串 \"%1$(secretName)@\" 给 %2$(duration)@" } } } }, - "auth_context_persist_for_duration_unknown_%@" : { + "auth_context_persist_for_duration_unknown" : { "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 placeholder is the name of the secret. This is a fallback used when a duration is unable to be specified.", "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { - "state" : "translated", - "value" : "desbloqueja el secret \"%1$@\"" + "state" : "needs_review", + "value" : "desbloqueja el secret \"%1$(secretName)@\"" } }, "de" : { "stringUnit" : { - "state" : "translated", - "value" : "Secret \"%1$@\" entsperren" + "state" : "needs_review", + "value" : "Secret \"%1$(secretName)@\" entsperren" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "unlock secret \"%1$@\"" + "value" : "unlock secret “%1$(secretName)@\"" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "avaa salaisuuden \"%1$@\" lukitus" + "state" : "needs_review", + "value" : "avaa salaisuuden \"%1$(secretName)@\" lukitus" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "déverrouiller le secret \"%1$@\"" + "state" : "needs_review", + "value" : "déverrouiller le secret \"%1$(secretName)@\"" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "sblocca il Segreto \"%1$@\"" + "state" : "needs_review", + "value" : "sblocca il Segreto \"%1$(secretName)@\"" } }, "ja" : { "stringUnit" : { - "state" : "translated", - "value" : "シークレット“%1$@”のロックを解除します" + "state" : "needs_review", + "value" : "シークレット“%1$(secretName)@”のロックを解除します" } }, "ko" : { "stringUnit" : { - "state" : "translated", - "value" : "비밀 \"%1$@\" 잠금 해제" + "state" : "needs_review", + "value" : "비밀 \"%1$(secretName)@\" 잠금 해제" } }, "pl" : { "stringUnit" : { - "state" : "translated", - "value" : "odblokuj sekret “%1$@”" + "state" : "needs_review", + "value" : "odblokuj sekret “%1$(secretName)@”" } }, "pt-BR" : { "stringUnit" : { - "state" : "translated", - "value" : "destravar secreto \"%1$@\"" + "state" : "needs_review", + "value" : "destravar secreto \"%1$(secretName)@\"" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "разблокировать секрет \"%1$@\"" + "state" : "needs_review", + "value" : "разблокировать секрет \"%1$(secretName)@\"" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "解锁密钥串 \"%1$@\"" + "state" : "needs_review", + "value" : "解锁密钥串 \"%1$(secretName)@\"" } } } }, - "auth_context_request_decrypt_description_%@" : { + "auth_context_request_decrypt_description" : { "comment" : "When the user performs a decryption action using a secret, they are shown a prompt to approve the action. This is the description, showing which secret will be used. The placeholder is the name of the secret. NOTE: This is currently not exposed in UI.", "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { - "state" : "translated", - "value" : "desencripta dades usant el secret \"%1$@\" " + "state" : "needs_review", + "value" : "desencripta dades usant el secret \"%1$(secretName)@\" " } }, "de" : { "stringUnit" : { - "state" : "translated", - "value" : "Daten mit dem Secret \"%1$@\" entschlüsseln" + "state" : "needs_review", + "value" : "Daten mit dem Secret \"%1$(secretName)@\" entschlüsseln" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "decrypt data using secret \"%1$@\"" + "value" : "decrypt data using secret \"%1$(secretName)@“" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "pura salaus käyttäen salaisuutta \"%1$@\"" + "state" : "needs_review", + "value" : "pura salaus käyttäen salaisuutta \"%1$(secretName)@\"" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "déchiffrer les données en utilisant le secret \"%1$@\"." + "state" : "needs_review", + "value" : "déchiffrer les données en utilisant le secret \"%1$(secretName)@\"." } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "decifra i dati usando il Segreto \"%1$@\"" + "state" : "needs_review", + "value" : "decifra i dati usando il Segreto \"%1$(secretName)@\"" } }, "ja" : { "stringUnit" : { - "state" : "translated", - "value" : "シークレット“%1$@”を使って復号化します" + "state" : "needs_review", + "value" : "シークレット“%1$(secretName)@”を使って復号化します" } }, "ko" : { "stringUnit" : { - "state" : "translated", - "value" : "비밀 \"%1$@\"를 사용해서 데이터 복호화" + "state" : "needs_review", + "value" : "비밀 \"%1$(secretName)@\"를 사용해서 데이터 복호화" } }, "pl" : { "stringUnit" : { - "state" : "translated", - "value" : "odszyfruj dane używając sekretu “%1$@”" + "state" : "needs_review", + "value" : "odszyfruj dane używając sekretu “%1$(secretName)@”" } }, "pt-BR" : { "stringUnit" : { - "state" : "translated", - "value" : "decriptar o dado utilizando segredo \"%1$@\"" + "state" : "needs_review", + "value" : "decriptar o dado utilizando segredo \"%1$(secretName)@\"" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "расшифровать данные используя секрет \"%1$@\"" + "state" : "needs_review", + "value" : "расшифровать данные используя секрет \"%1$(secretName)@\"" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "使用密钥串 \"%1$@\" 解密数据" + "state" : "needs_review", + "value" : "使用密钥串 \"%1$(secretName)@\" 解密数据" } } } @@ -1073,241 +1083,242 @@ } } }, - "auth_context_request_encrypt_description_%@" : { + "auth_context_request_encrypt_description" : { "comment" : "When the user performs an encryption action using a secret, they are shown a prompt to approve the action. This is the description, showing which secret will be used. The placeholder is the name of the secret. NOTE: This is currently not exposed in UI.", "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { - "state" : "translated", - "value" : "encripta dades usant el secret \"%1$@\"" + "state" : "needs_review", + "value" : "encripta dades usant el secret \"%1$(secretName)@\"" } }, "de" : { "stringUnit" : { - "state" : "translated", - "value" : "Daten mit dem Secret \"%1$@\" verschlüsseln" + "state" : "needs_review", + "value" : "Daten mit dem Secret \"%1$(secretName)@\" verschlüsseln" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "encrypt data using secret \"%1$@\"" + "value" : "encrypt data using secret \"%1$(secretName)@“" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "salaa käyttäen salaisuutta \"%1$@\"" + "state" : "needs_review", + "value" : "salaa käyttäen salaisuutta \"%1$(secretName)@\"" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "chiffrer les données en utilisant le secret \"%1$@\"" + "state" : "needs_review", + "value" : "chiffrer les données en utilisant le secret \"%1$(secretName)@\"" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "cifra i dati usando il Segreto \"%1$@\"" + "state" : "needs_review", + "value" : "cifra i dati usando il Segreto \"%1$(secretName)@\"" } }, "ja" : { "stringUnit" : { - "state" : "translated", - "value" : "シークレット“%1$@”を使って暗号化します" + "state" : "needs_review", + "value" : "シークレット“%1$(secretName)@”を使って暗号化します" } }, "ko" : { "stringUnit" : { - "state" : "translated", - "value" : "비밀 \"%1$@\"를 사용해서 데이터 암호화" + "state" : "needs_review", + "value" : "비밀 \"%1$(secretName)@\"를 사용해서 데이터 암호화" } }, "pl" : { "stringUnit" : { - "state" : "translated", - "value" : "zaszyfruj dane używając sekretu “%1$@”" + "state" : "needs_review", + "value" : "zaszyfruj dane używając sekretu “%1$(secretName)@”" } }, "pt-BR" : { "stringUnit" : { - "state" : "translated", - "value" : "encriptar dado utilizando o segredo \"%1$@\"" + "state" : "needs_review", + "value" : "encriptar dado utilizando o segredo \"%1$(secretName)@\"" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "зашифровать данные используя секрет \"%1$@\"" + "state" : "needs_review", + "value" : "зашифровать данные используя секрет \"%1$(secretName)@\"" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "使用密钥串 \"%1$@\" 加密数据" + "state" : "needs_review", + "value" : "使用密钥串 \"%1$(secretName)@\" 加密数据" } } } }, - "auth_context_request_signature_description_%@_%@" : { + "auth_context_request_signature_description" : { "comment" : "When the user performs a signature action using a secret, they are shown a prompt to approve the action. This is the description, showing which secret will be used, and where the request is coming from. The first placeholder is the name of the app requesting the operation. The second placeholder is the name of the secret.", "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { - "state" : "translated", - "value" : "signa una petición de \"%1$@\" usant el secret \"%2$@\"" + "state" : "needs_review", + "value" : "signa una petición de \"%1$(appName)@\" usant el secret \"%2$(secretName)@\"" } }, "de" : { "stringUnit" : { - "state" : "translated", - "value" : "eine Anfrage von \"%1$@\" mit dem Secret \"%2$@\" signieren" + "state" : "needs_review", + "value" : "eine Anfrage von \"%1$(appName)@\" mit dem Secret \"%2$(secretName)@\" signieren" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "sign a request from \"%1$@\" using secret \"%2$@\"" + "value" : "sign a request from \"%1$(appName)@“ using secret \"%2$(secretName)@“" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "allekirjoita pyyntö lähteestä \"%1$@\" käyttäen salaisuutta \"%2$@\"" + "state" : "needs_review", + "value" : "allekirjoita pyyntö lähteestä \"%1$(appName)@\" käyttäen salaisuutta \"%2$(secretName)@\"" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "signer une requête de \"%1$@\" en utilisant le secret \"%2$@\"" + "state" : "needs_review", + "value" : "signer une requête de \"%1$(appName)@\" en utilisant le secret \"%2$(secretName)@\"" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "firma la richiesta di \"%1$@\" usando il Segreto \"%2$@\"" + "state" : "needs_review", + "value" : "firma la richiesta di \"%1$(appName)@\" usando il Segreto \"%2$(secretName)@\"" } }, "ja" : { "stringUnit" : { - "state" : "translated", - "value" : "シークレット“%2$@”を使って“%1$@”の要求に署名します" + "state" : "needs_review", + "value" : "シークレット“%2$(secretName)@”を使って“%1$(appName)@”の要求に署名します" } }, "ko" : { "stringUnit" : { - "state" : "translated", - "value" : "비밀 \"%2$@\"를 사용해서 \"%1$@\"의 요청에 서명" + "state" : "needs_review", + "value" : "비밀 \"%2$(secretName)@\"를 사용해서 \"%1$(appName)@\"의 요청에 서명" } }, "pl" : { "stringUnit" : { - "state" : "translated", - "value" : "podpisz zapytanie od “%1$@\" za pomocą sekretu “%2$@”" + "state" : "needs_review", + "value" : "podpisz zapytanie od “%1$(appName)@\" za pomocą sekretu “%2$(secretName)@”" } }, "pt-BR" : { "stringUnit" : { - "state" : "translated", - "value" : "assinar requisição a partir do \"%1$@\" utilizando o segredo \"%2$@\"" + "state" : "needs_review", + "value" : "assinar requisição a partir do \"%1$(appName)@\" utilizando o segredo \"%2$(secretName)@\"" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "подписать запрос от \"%1$@\" используя секрет \"%2$@\"" + "state" : "needs_review", + "value" : "подписать запрос от \"%1$(appName)@\" используя секрет \"%2$(secretName)@\"" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "使用密钥串 \"%2$@\" 认证 \"%1$@\" " + "state" : "needs_review", + "value" : "使用密钥串 \"%2$(secretName)@\" 认证 \"%1$(appName)@\" " } } } }, - "auth_context_request_verify_description_%@" : { + "auth_context_request_verify_description" : { "comment" : "When the user performs a signature verification action using a secret, they are shown a prompt to approve the action. This is the description, showing which secret will be used. The placeholder is the name of the secret. NOTE: This is currently not exposed in UI.", "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { - "state" : "translated", - "value" : "verifica una signatura usant el secret \"%1$@\"" + "state" : "needs_review", + "value" : "verifica una signatura usant el secret \"%1$(secretName)@\"" } }, "de" : { "stringUnit" : { - "state" : "translated", - "value" : "eine Signatur mit dem Secret \"%1$@\" verifizieren" + "state" : "needs_review", + "value" : "eine Signatur mit dem Secret \"%1$(secretName)@\" verifizieren" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "verify a signature using secret \"%1$@\"" + "value" : "verify a signature using secret \"%1$(secretName)@“" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "varmista allekirjoitus käyttäen salaisuutta \"%1$@\"" + "state" : "needs_review", + "value" : "varmista allekirjoitus käyttäen salaisuutta \"%1$(secretName)@\"" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "vérifier une signature en utilisant le secret \"%1$@\"" + "state" : "needs_review", + "value" : "vérifier une signature en utilisant le secret \"%1$(secretName)@\"" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "verifica una firma usando il segreto \"%1$@\"" + "state" : "needs_review", + "value" : "verifica una firma usando il segreto \"%1$(secretName)@\"" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "シークレット“%2$@”を使って署名を検証します" + "value" : "シークレット“%1$(secretName)@”を使って署名を検証します" } }, "ko" : { "stringUnit" : { - "state" : "translated", - "value" : "비밀 \"%1$@\"를 사용하여 서명 검증" + "state" : "needs_review", + "value" : "비밀 \"%1$(secretName)@\"를 사용하여 서명 검증" } }, "pl" : { "stringUnit" : { - "state" : "translated", - "value" : "zweryfikuj sygnaturę za pomocą sekretu “%1$@”" + "state" : "needs_review", + "value" : "zweryfikuj sygnaturę za pomocą sekretu “%1$(secretName)@”" } }, "pt-BR" : { "stringUnit" : { - "state" : "translated", - "value" : "verificar a assinatura utilizando o segredo \"%1$@\"" + "state" : "needs_review", + "value" : "verificar a assinatura utilizando o segredo \"%1$(secretName)@\"" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "верифицировать подпись используя секрет \"%1$@\"" + "state" : "needs_review", + "value" : "верифицировать подпись используя секрет \"%1$(secretName)@\"" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "使用密钥串 \"%1$@\" 认证" + "state" : "needs_review", + "value" : "使用密钥串 \"%1$(secretName)@\" 认证" } } } }, "copyable_click_to_copy_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -1384,6 +1395,7 @@ } }, "copyable_copied" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -1460,6 +1472,7 @@ } }, "create_secret_cancel_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -1536,6 +1549,7 @@ } }, "create_secret_create_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -1612,6 +1626,7 @@ } }, "create_secret_name_label" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -1688,6 +1703,7 @@ } }, "create_secret_name_placeholder" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -1764,6 +1780,7 @@ } }, "create_secret_notify_description" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -1840,6 +1857,7 @@ } }, "create_secret_notify_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -1916,6 +1934,7 @@ } }, "create_secret_require_authentication_description" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -1992,6 +2011,7 @@ } }, "create_secret_require_authentication_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -2068,6 +2088,7 @@ } }, "create_secret_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -2144,6 +2165,7 @@ } }, "delete_confirmation_cancel_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -2220,6 +2242,7 @@ } }, "delete_confirmation_confirm_name_label" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -2296,6 +2319,7 @@ } }, "delete_confirmation_delete_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -2371,159 +2395,162 @@ } } }, - "delete_confirmation_description_%@_%@" : { + "delete_confirmation_description" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { - "state" : "translated", - "value" : "Si esborres %1$@, no podràs recuperar-la. Escriu \"%2$@\" per a confirmar." + "state" : "needs_review", + "value" : "Si esborres %1$(secretName)@, no podràs recuperar-la. Escriu \"%2$(confirmSecretName)@\" per a confirmar." } }, "de" : { "stringUnit" : { - "state" : "translated", - "value" : "Wenn du %1$@ löschst, kannst du es nicht wiederherstellen. Gib zur Bestätigung \"%2$@\" ein." + "state" : "needs_review", + "value" : "Wenn du %1$(secretName)@ löschst, kannst du es nicht wiederherstellen. Gib zur Bestätigung \"%2$(confirmSecretName)@\" ein." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "If you delete %1$@, you will not be able to recover it. Type \"%2$@\" 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$@, sitä ei pysty palauttamaan. Kirjoita \"%2$@\" vahvistaaksesi poiston." + "state" : "needs_review", + "value" : "Jos poistat kohteen %1$(secretName)@, sitä ei pysty palauttamaan. Kirjoita \"%2$(confirmSecretName)@\" vahvistaaksesi poiston." } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Si vous effacez %1$@, vous ne pourrez pas le récupérer. Tapez \"%2$@\" pour confirmer." + "state" : "needs_review", + "value" : "Si vous effacez %1$(secretName)@, vous ne pourrez pas le récupérer. Tapez \"%2$(confirmSecretName)@\" pour confirmer." } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Se elimini %1$@, non sarai più in grado di recuperarlo. Digita “%1$@” per confermare." + "state" : "needs_review", + "value" : "Se elimini %1$(secretName)@, non sarai più in grado di recuperarlo. Digita “%1$(secretName)@” per confermare." } }, "ja" : { "stringUnit" : { - "state" : "translated", - "value" : "一旦%1$@を削除すると二度と元には戻せません。“%2$@”と入力して確認してください。" + "state" : "needs_review", + "value" : "一旦%1$(secretName)@を削除すると二度と元には戻せません。“%2$(confirmSecretName)@”と入力して確認してください。" } }, "ko" : { "stringUnit" : { - "state" : "translated", - "value" : "%1$@를 삭제하면 복구할 수 없습니다. 확인하려면 \"%2$@\"를 입력하세요." + "state" : "needs_review", + "value" : "%1$(secretName)@를 삭제하면 복구할 수 없습니다. 확인하려면 \"%2$(confirmSecretName)@\"를 입력하세요." } }, "pl" : { "stringUnit" : { - "state" : "translated", - "value" : "Jeżeli usuniesz %1$@, nie będziesz w stanie go odzyskać. Napisz “%2$@” aby potwierdzić." + "state" : "needs_review", + "value" : "Jeżeli usuniesz %1$(secretName)@, nie będziesz w stanie go odzyskać. Napisz “%2$(confirmSecretName)@” aby potwierdzić." } }, "pt-BR" : { "stringUnit" : { - "state" : "translated", - "value" : "Se você deletar %1$@, você não será permitido recuperá-lo. Digite \"%2$@\" para confirmar." + "state" : "needs_review", + "value" : "Se você deletar %1$(secretName)@, você não será permitido recuperá-lo. Digite \"%2$(confirmSecretName)@\" para confirmar." } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Если Вы удалите %1$@, вы не сможете его восстановить. Введите \"%2$@\" для подтверждения." + "state" : "needs_review", + "value" : "Если Вы удалите %1$(secretName)@, вы не сможете его восстановить. Введите \"%2$(confirmSecretName)@\" для подтверждения." } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "如果您删除 %1$@ ,您将没有任何方式恢复它。输入 \"%2$@\" 以确认。" + "state" : "needs_review", + "value" : "如果您删除 %1$(secretName)@ ,您将没有任何方式恢复它。输入 \"%2$(confirmSecretName)@\" 以确认。" } } } }, - "delete_confirmation_title_%@" : { + "delete_confirmation_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { - "state" : "translated", - "value" : "Esborrar %1$@?" + "state" : "needs_review", + "value" : "Esborrar %1$(secretName)@?" } }, "de" : { "stringUnit" : { - "state" : "translated", - "value" : "%1$@ Löschen?" + "state" : "needs_review", + "value" : "%1$(secretName)@ Löschen?" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Delete %1$@?" + "value" : "Delete %1$(secretName)@?" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Poista %1$@?" + "state" : "needs_review", + "value" : "Poista %1$(secretName)@?" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Supprimer %1$@?" + "state" : "needs_review", + "value" : "Supprimer %1$(secretName)@?" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Eliminare %1$@?" + "state" : "needs_review", + "value" : "Eliminare %1$(secretName)@?" } }, "ja" : { "stringUnit" : { - "state" : "translated", - "value" : "%1$@を削除しますか?" + "state" : "needs_review", + "value" : "%1$(secretName)@を削除しますか?" } }, "ko" : { "stringUnit" : { - "state" : "translated", - "value" : "%1$@를 지우겠습니까?" + "state" : "needs_review", + "value" : "%1$(secretName)@를 지우겠습니까?" } }, "pl" : { "stringUnit" : { - "state" : "translated", - "value" : "Usunąć %1$@?" + "state" : "needs_review", + "value" : "Usunąć %1$(secretName)@?" } }, "pt-BR" : { "stringUnit" : { - "state" : "translated", - "value" : "Deletar %1$@?" + "state" : "needs_review", + "value" : "Deletar %1$(secretName)@?" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Удалить %1$@?" + "state" : "needs_review", + "value" : "Удалить %1$(secretName)@?" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "删除 %1$@ 吗?" + "state" : "needs_review", + "value" : "删除 %1$(secretName)@ 吗?" } } } }, "empty_store_modifiable_click_here_description" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -2600,83 +2627,7 @@ } }, "empty_store_modifiable_click_here_title" : { - "localizations" : { - "ca" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sense secrets" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Secrets" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No Secrets" - } - }, - "fi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ei salaisuuksia" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aucun secret" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nessun Segreto" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "シークレットがありません" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "비밀이 없음" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Brak sekretów" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sem Segredos" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Нет секретов" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "没有密钥串" - } - } - } - }, - "empty_store_modifiable_title" : { - "extractionState" : "stale", + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -2753,6 +2704,7 @@ } }, "empty_store_nonmodifiable_description" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -2829,6 +2781,7 @@ } }, "empty_store_nonmodifiable_supported_key_types" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -2905,6 +2858,7 @@ } }, "empty_store_nonmodifiable_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -2981,6 +2935,7 @@ } }, "no_secure_storage_description" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -3057,6 +3012,7 @@ } }, "no_secure_storage_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -3133,6 +3089,7 @@ } }, "no_secure_storage_yubico_link" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -3365,6 +3322,7 @@ } }, "rename_cancel_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -3435,6 +3393,7 @@ } }, "rename_rename_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -3504,77 +3463,79 @@ } } }, - "rename_title_%@" : { + "rename_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { - "state" : "translated", - "value" : "Escriu el nou nom per a %1$@ baix." + "state" : "needs_review", + "value" : "Escriu el nou nom per a %1$(secretName)@ baix." } }, "de" : { "stringUnit" : { - "state" : "translated", - "value" : "Gib einen neuen Namen für %1$@ ein." + "state" : "needs_review", + "value" : "Gib einen neuen Namen für %1$(secretName)@ ein." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Type your new name for %1$@ below." + "value" : "Type your new name for %1$(secretName)@ below." } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Saisissez votre nouveau nom pour %1$@ ci-dessous." + "state" : "needs_review", + "value" : "Saisissez votre nouveau nom pour %1$(secretName)@ ci-dessous." } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Digita qui sotto il nuovo nome per %1$@." + "state" : "needs_review", + "value" : "Digita qui sotto il nuovo nome per %1$(secretName)@." } }, "ja" : { "stringUnit" : { - "state" : "translated", - "value" : "%1$@の新しい名前を入力してください。" + "state" : "needs_review", + "value" : "%1$(secretName)@の新しい名前を入力してください。" } }, "ko" : { "stringUnit" : { - "state" : "translated", - "value" : "아래에 %1$@의 새 이름을 입력하세요." + "state" : "needs_review", + "value" : "아래에 %1$(secretName)@의 새 이름을 입력하세요." } }, "pl" : { "stringUnit" : { - "state" : "translated", - "value" : "Wprowadź nową nazwę dla %1$@ poniżej." + "state" : "needs_review", + "value" : "Wprowadź nową nazwę dla %1$(secretName)@ poniżej." } }, "pt-BR" : { "stringUnit" : { - "state" : "translated", - "value" : "Digite o novo nome para %1$@ abaixo." + "state" : "needs_review", + "value" : "Digite o novo nome para %1$(secretName)@ abaixo." } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Введите новое название для \"%1$@\" ниже." + "state" : "needs_review", + "value" : "Введите новое название для \"%1$(secretName)@\" ниже." } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "在此输入密钥串 %1$@ 的新名字。" + "state" : "needs_review", + "value" : "在此输入密钥串 %1$(secretName)@ 的新名字。" } } } }, "secret_detail_md5_fingerprint_label" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -3645,6 +3606,7 @@ } }, "secret_detail_public_key_label" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -3715,6 +3677,7 @@ } }, "secret_detail_public_key_path_label" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -3785,6 +3748,7 @@ } }, "secret_detail_sha256_fingerprint_label" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -3855,6 +3819,7 @@ } }, "secret_list_delete_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -3925,6 +3890,7 @@ } }, "secret_list_rename_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -4066,6 +4032,7 @@ } }, "setup_agent_activity_monitor_description" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -4136,6 +4103,7 @@ } }, "setup_agent_description" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -4206,6 +4174,7 @@ } }, "setup_agent_install_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -4276,6 +4245,7 @@ } }, "setup_agent_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -4346,6 +4316,7 @@ } }, "setup_ssh_add_for_me_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -4415,77 +4386,79 @@ } } }, - "setup_ssh_add_to_config_button_%@" : { + "setup_ssh_add_to_config_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { - "state" : "translated", - "value" : "Afegeix a %1$@" + "state" : "needs_review", + "value" : "Afegeix a %1$(configPath)@" } }, "de" : { "stringUnit" : { - "state" : "translated", - "value" : "In %1$@ einfügen" + "state" : "needs_review", + "value" : "In %1$(configPath)@ einfügen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Add to %1$@" + "value" : "Add to %1$(configPath)@" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Ajouter à %1$@" + "state" : "needs_review", + "value" : "Ajouter à %1$(configPath)@" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Aggiungi a %1$@" + "state" : "needs_review", + "value" : "Aggiungi a %1$(configPath)@" } }, "ja" : { "stringUnit" : { - "state" : "translated", - "value" : "%1$@に追加" + "state" : "needs_review", + "value" : "%1$(configPath)@に追加" } }, "ko" : { "stringUnit" : { - "state" : "translated", - "value" : "%1$@에 추가" + "state" : "needs_review", + "value" : "%1$(configPath)@에 추가" } }, "pl" : { "stringUnit" : { - "state" : "translated", - "value" : "Dodaj do %1$@" + "state" : "needs_review", + "value" : "Dodaj do %1$(configPath)@" } }, "pt-BR" : { "stringUnit" : { - "state" : "translated", - "value" : "Adicionar para %1$@" + "state" : "needs_review", + "value" : "Adicionar para %1$(configPath)@" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Добавить к %1$@" + "state" : "needs_review", + "value" : "Добавить к %1$(configPath)@" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "添加到 %1$@" + "state" : "needs_review", + "value" : "添加到 %1$(configPath)@" } } } }, "setup_ssh_added_manually_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -4556,6 +4529,7 @@ } }, "setup_ssh_description" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -4626,6 +4600,7 @@ } }, "setup_ssh_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -4696,6 +4671,7 @@ } }, "setup_step_complete_symbol" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -4766,6 +4742,7 @@ } }, "setup_third_party_faq_link" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -4836,6 +4813,7 @@ } }, "setup_updates_description" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -4906,6 +4884,7 @@ } }, "setup_updates_ok" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -4976,6 +4955,7 @@ } }, "setup_updates_readmore" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -5046,6 +5026,7 @@ } }, "setup_updates_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -5115,146 +5096,146 @@ } } }, - "signed_notification_description_%@" : { + "signed_notification_description" : { "comment" : "When the user performs an action using a secret, they're shown a notification describing what happened. This is the description, showing which secret was used. The placeholder is the name of the secret.", "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { - "state" : "translated", - "value" : "Usant secret %1$@" + "state" : "needs_review", + "value" : "Usant secret %1$(secretName)@" } }, "de" : { "stringUnit" : { - "state" : "translated", - "value" : "Benutze Secret %1$@" + "state" : "needs_review", + "value" : "Benutze Secret %1$(secretName)@" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Using secret %1$@" + "value" : "Using secret %1$(secretName)@" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Utilisation du secret %1$@" + "state" : "needs_review", + "value" : "Utilisation du secret %1$(secretName)@" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Usato il Segreto %1$@" + "state" : "needs_review", + "value" : "Usato il Segreto %1$(secretName)@" } }, "ja" : { "stringUnit" : { - "state" : "translated", - "value" : "%1$@を使用中" + "state" : "needs_review", + "value" : "%1$(secretName)@を使用中" } }, "ko" : { "stringUnit" : { - "state" : "translated", - "value" : "비밀 %1$@ 사용됨" + "state" : "needs_review", + "value" : "비밀 %1$(secretName)@ 사용됨" } }, "pl" : { "stringUnit" : { - "state" : "translated", - "value" : "Użyto sekretu %1$@" + "state" : "needs_review", + "value" : "Użyto sekretu %1$(secretName)@" } }, "pt-BR" : { "stringUnit" : { - "state" : "translated", - "value" : "Utilizando o segredo %1$@" + "state" : "needs_review", + "value" : "Utilizando o segredo %1$(secretName)@" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Используя секрет %1$@" + "state" : "needs_review", + "value" : "Используя секрет %1$(secretName)@" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "使用密钥串 %1$@" + "state" : "needs_review", + "value" : "使用密钥串 %1$(secretName)@" } } } }, - "signed_notification_title_%@" : { + "signed_notification_title" : { "comment" : "When the user performs an action using a secret, they're shown a notification describing what happened. This is the title, showing which app requested the action. The placeholder is the name of the app.", "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { - "state" : "translated", - "value" : "Petició signada de %1$@" + "state" : "needs_review", + "value" : "Petició signada de %1$(appName)@" } }, "de" : { "stringUnit" : { - "state" : "translated", - "value" : "Signierte Anfrage von %1$@" + "state" : "needs_review", + "value" : "Signierte Anfrage von %1$(appName)@" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Signed Request from %1$@" + "value" : "Signed Request from %1$(appName)@" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Requête signée de %1$@" + "state" : "needs_review", + "value" : "Requête signée de %1$(appName)@" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Firmata la richiesta da %1$@" + "state" : "needs_review", + "value" : "Firmata la richiesta da %1$(appName)@" } }, "ja" : { "stringUnit" : { - "state" : "translated", - "value" : "%1$@の要求に署名しました" + "state" : "needs_review", + "value" : "%1$(appName)@の要求に署名しました" } }, "ko" : { "stringUnit" : { - "state" : "translated", - "value" : "%1$@에서 서명 요청" + "state" : "needs_review", + "value" : "%1$(appName)@에서 서명 요청" } }, "pl" : { "stringUnit" : { - "state" : "translated", - "value" : "Podpisano żądanie od %1$@" + "state" : "needs_review", + "value" : "Podpisano żądanie od %1$(appName)@" } }, "pt-BR" : { "stringUnit" : { - "state" : "translated", - "value" : "Requisição Assinada fr %1$@" + "state" : "needs_review", + "value" : "Requisição Assinada fr %1$(appName)@" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Подписан запрос от %1$@" + "state" : "needs_review", + "value" : "Подписан запрос от %1$(appName)@" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "已认证来自 %1$@ 的请求" + "state" : "needs_review", + "value" : "已认证来自 %1$(appName)@ 的请求" } } } @@ -5402,6 +5383,7 @@ } }, "update_critical_notice_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -5472,6 +5454,7 @@ } }, "update_ignore_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -5542,6 +5525,7 @@ } }, "update_normal_notice_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -5755,74 +5739,74 @@ } } }, - "update_notification_update_critical_title_%@" : { + "update_notification_update_critical_title" : { "comment" : "When an update is available, a notification is shown. This is the title for a very high priority update with security fixes. The placeholder is for the application version, eg \"Critical Security Update - 2.0\"", "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { - "state" : "translated", - "value" : "Actualització de seguretat crítica - %1$@" + "state" : "needs_review", + "value" : "Actualització de seguretat crítica - %1$(updateName)@" } }, "de" : { "stringUnit" : { - "state" : "translated", - "value" : "Kritisches Sicherheitsupdate - %1$@" + "state" : "needs_review", + "value" : "Kritisches Sicherheitsupdate - %1$(updateName)@" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Critical Security Update - %1$@" + "value" : "Critical Security Update - %1$(updateName)@" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Mise à jour critique de sécurité - %1$@" + "state" : "needs_review", + "value" : "Mise à jour critique de sécurité - %1$(updateName)@" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Aggiornamento di sicurezza critico - %1$@" + "state" : "needs_review", + "value" : "Aggiornamento di sicurezza critico - %1$(updateName)@" } }, "ja" : { "stringUnit" : { - "state" : "translated", - "value" : "重要なセキュリティアップデート - %1$@" + "state" : "needs_review", + "value" : "重要なセキュリティアップデート - %1$(updateName)@" } }, "ko" : { "stringUnit" : { - "state" : "translated", - "value" : "치명적 보안 업데이트 - %1$@" + "state" : "needs_review", + "value" : "치명적 보안 업데이트 - %1$(updateName)@" } }, "pl" : { "stringUnit" : { - "state" : "translated", - "value" : "Krytyczna aktualizacja bezpieczeństwa - %1$@" + "state" : "needs_review", + "value" : "Krytyczna aktualizacja bezpieczeństwa - %1$(updateName)@" } }, "pt-BR" : { "stringUnit" : { - "state" : "translated", - "value" : "Atualização de Segurança Crítica - %1$@" + "state" : "needs_review", + "value" : "Atualização de Segurança Crítica - %1$(updateName)@" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Критическое обновление безопасности - %1$@" + "state" : "needs_review", + "value" : "Критическое обновление безопасности - %1$(updateName)@" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "重要安全更新 - %1$@" + "state" : "needs_review", + "value" : "重要安全更新 - %1$(updateName)@" } } } @@ -5899,79 +5883,80 @@ } } }, - "update_notification_update_normal_title_%@" : { + "update_notification_update_normal_title" : { "comment" : "When an update is available, a notification is shown. This is the title for a normal priority update. The placeholder is for the application version, eg \"Update Available - 2.0\"", "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { - "state" : "translated", - "value" : "Actualizació disponible - %1$@" + "state" : "needs_review", + "value" : "Actualizació disponible - %1$(updateName)@" } }, "de" : { "stringUnit" : { - "state" : "translated", - "value" : "Update Verfügbar - %1$@" + "state" : "needs_review", + "value" : "Update Verfügbar - %1$(updateName)@" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Update Available - %1$@" + "value" : "Update Available - %1$(updateName)@" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Mise à jour disponible - %1$@" + "state" : "needs_review", + "value" : "Mise à jour disponible - %1$(updateName)@" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Aggiornamento disponibile - %1$@" + "state" : "needs_review", + "value" : "Aggiornamento disponibile - %1$(updateName)@" } }, "ja" : { "stringUnit" : { - "state" : "translated", - "value" : "アップデートがあります - %1$@" + "state" : "needs_review", + "value" : "アップデートがあります - %1$(updateName)@" } }, "ko" : { "stringUnit" : { - "state" : "translated", - "value" : "사용 가능한 업데이트 - %1$@" + "state" : "needs_review", + "value" : "사용 가능한 업데이트 - %1$(updateName)@" } }, "pl" : { "stringUnit" : { - "state" : "translated", - "value" : "Aktualizacja dostępna - %1$@" + "state" : "needs_review", + "value" : "Aktualizacja dostępna - %1$(updateName)@" } }, "pt-BR" : { "stringUnit" : { - "state" : "translated", - "value" : "Atualização disponível - %1$@" + "state" : "needs_review", + "value" : "Atualização disponível - %1$(updateName)@" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Доступно обновление - %1$@" + "state" : "needs_review", + "value" : "Доступно обновление - %1$(updateName)@" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "更新可用 - %1$@" + "state" : "needs_review", + "value" : "更新可用 - %1$(updateName)@" } } } }, "update_release_notes_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -6042,6 +6027,7 @@ } }, "update_test_notice_title" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -6112,6 +6098,7 @@ } }, "update_update_button" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { @@ -6181,72 +6168,73 @@ } } }, - "update_version_name_%@" : { + "update_version_name" : { + "extractionState" : "manual", "localizations" : { "ca" : { "stringUnit" : { - "state" : "translated", - "value" : "Secretive %1$@" + "state" : "needs_review", + "value" : "Secretive %1$(updateName)@" } }, "de" : { "stringUnit" : { - "state" : "translated", - "value" : "Secretive %1$@" + "state" : "needs_review", + "value" : "Secretive %1$(updateName)@" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Secretive %1$@" + "value" : "Secretive %1$(updateName)@" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Secretive %1$@" + "state" : "needs_review", + "value" : "Secretive %1$(updateName)@" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Secretive %1$@" + "state" : "needs_review", + "value" : "Secretive %1$(updateName)@" } }, "ja" : { "stringUnit" : { - "state" : "translated", - "value" : "Secretive %1$@" + "state" : "needs_review", + "value" : "Secretive %1$(updateName)@" } }, "ko" : { "stringUnit" : { - "state" : "translated", - "value" : "Secretive %1$@" + "state" : "needs_review", + "value" : "Secretive %1$(updateName)@" } }, "pl" : { "stringUnit" : { - "state" : "translated", - "value" : "Secretive %1$@" + "state" : "needs_review", + "value" : "Secretive %1$(updateName)@" } }, "pt-BR" : { "stringUnit" : { - "state" : "translated", - "value" : "Secretive %1$@" + "state" : "needs_review", + "value" : "Secretive %1$(updateName)@" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Secretive %1$@" + "state" : "needs_review", + "value" : "Secretive %1$(updateName)@" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "Secretive %1$@" + "state" : "needs_review", + "value" : "Secretive %1$(updateName)@" } } } diff --git a/Sources/Packages/Package.swift b/Sources/Packages/Package.swift index 5dc484a..820da97 100644 --- a/Sources/Packages/Package.swift +++ b/Sources/Packages/Package.swift @@ -5,6 +5,7 @@ import PackageDescription let package = Package( name: "SecretivePackages", + defaultLocalization: "en", platforms: [ .macOS(.v14) ], @@ -34,6 +35,7 @@ let package = Package( .target( name: "SecretKit", dependencies: [], + resources: [localization], swiftSettings: swiftSettings ), .testTarget( @@ -44,16 +46,19 @@ let package = Package( .target( name: "SecureEnclaveSecretKit", dependencies: ["SecretKit"], + resources: [localization], swiftSettings: swiftSettings ), .target( name: "SmartCardSecretKit", dependencies: ["SecretKit"], + resources: [localization], swiftSettings: swiftSettings ), .target( name: "SecretAgentKit", dependencies: ["SecretKit", "SecretAgentKitHeaders"], + resources: [localization], swiftSettings: swiftSettings ), .systemLibrary( @@ -66,6 +71,7 @@ let package = Package( .target( name: "Brief", dependencies: [], + resources: [localization], swiftSettings: swiftSettings ), .testTarget( @@ -75,6 +81,10 @@ let package = Package( ] ) +var localization: Resource { + .process("../../Localizable.xcstrings") +} + var swiftSettings: [PackageDescription.SwiftSetting] { [ .swiftLanguageMode(.v6), diff --git a/Sources/Packages/Sources/Localization/Stub.swift b/Sources/Packages/Sources/Localization/Stub.swift new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Sources/Packages/Sources/Localization/Stub.swift @@ -0,0 +1 @@ + diff --git a/Sources/Packages/Sources/SecureEnclaveSecretKit/PersistentAuthenticationHandler.swift b/Sources/Packages/Sources/SecureEnclaveSecretKit/PersistentAuthenticationHandler.swift index d42e051..cc10c7f 100644 --- a/Sources/Packages/Sources/SecureEnclaveSecretKit/PersistentAuthenticationHandler.swift +++ b/Sources/Packages/Sources/SecureEnclaveSecretKit/PersistentAuthenticationHandler.swift @@ -50,16 +50,16 @@ extension SecureEnclave { func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) async throws { let newContext = LAContext() newContext.touchIDAuthenticationAllowableReuseDuration = duration - newContext.localizedCancelTitle = String(localized: "auth_context_request_deny_button") + newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton) let formatter = DateComponentsFormatter() formatter.unitsStyle = .spellOut formatter.allowedUnits = [.hour, .minute, .day] if let durationString = formatter.string(from: duration) { - newContext.localizedReason = String(localized: "auth_context_persist_for_duration_\(secret.name)_\(durationString)") + newContext.localizedReason = String(localized: .authContextPersistForDuration(secretName: secret.name, duration: durationString)) } else { - newContext.localizedReason = String(localized: "auth_context_persist_for_duration_unknown_\(secret.name)") + newContext.localizedReason = String(localized: .authContextPersistForDurationUnknown(secretName: secret.name)) } let success = try await newContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: newContext.localizedReason) guard success else { return } diff --git a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift index 2c756be..f999b7e 100644 --- a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift +++ b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift @@ -15,7 +15,7 @@ extension SecureEnclave { CryptoKit.SecureEnclave.isAvailable } public let id = UUID() - public let name = String(localized: "secure_enclave") + public let name = String(localized: .secureEnclave) private let persistentAuthenticationHandler = PersistentAuthenticationHandler() /// Initializes a Store. @@ -105,10 +105,10 @@ extension SecureEnclave { context = existing.context } else { let newContext = LAContext() - newContext.localizedCancelTitle = String(localized: "auth_context_request_deny_button") + newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton) context = newContext } - context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)") + context.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name)) let attributes = KeychainDictionary([ kSecClass: kSecClassKey, kSecAttrKeyClass: kSecAttrKeyClassPrivate, @@ -138,8 +138,8 @@ extension SecureEnclave { public func verify(signature: Data, for data: Data, with secret: Secret) throws -> Bool { let context = LAContext() - context.localizedReason = String(localized: "auth_context_request_verify_description_\(secret.name)") - context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") + context.localizedReason = String(localized: .authContextRequestVerifyDescription(secretName: secret.name)) + context.localizedCancelTitle = String(localized: .authContextRequestDenyButton) let attributes = KeychainDictionary([ kSecClass: kSecClassKey, kSecAttrKeyClass: kSecAttrKeyClassPrivate, @@ -240,7 +240,7 @@ extension SecureEnclave.Store { nil)! let wrapped: [SecureEnclave.Secret] = publicTyped.map { - let name = $0[kSecAttrLabel] as? String ?? String(localized: "unnamed_secret") + let name = $0[kSecAttrLabel] as? String ?? String(localized: .unnamedSecret) let id = $0[kSecAttrApplicationLabel] as! Data let publicKeyRef = $0[kSecValueRef] as! SecKey let publicKeyAttributes = SecKeyCopyAttributes(publicKeyRef) as! [CFString: Any] diff --git a/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift b/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift index f9ed8f8..3f06773 100644 --- a/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift +++ b/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift @@ -9,7 +9,7 @@ extension SmartCard { @MainActor @Observable fileprivate final class State { var isAvailable = false - var name = String(localized: "smart_card") + var name = String(localized: .smartCard) var secrets: [Secret] = [] let watcher = TKTokenWatcher() var tokenID: String? = nil @@ -63,8 +63,8 @@ extension SmartCard { public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data { guard let tokenID = await state.tokenID else { fatalError() } let context = LAContext() - context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)") - context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") + context.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name)) + context.localizedCancelTitle = String(localized: .authContextRequestDenyButton) let attributes = KeychainDictionary([ kSecClass: kSecClassKey, kSecAttrKeyClass: kSecAttrKeyClassPrivate, @@ -162,7 +162,7 @@ extension SmartCard.Store { @MainActor private func loadSecrets() { guard let tokenID = state.tokenID else { return } - let fallbackName = String(localized: "smart_card") + let fallbackName = String(localized: .smartCard) if let driverName = state.watcher.tokenInfo(forTokenID: tokenID)?.driverName { state.name = driverName } else { @@ -180,7 +180,7 @@ extension SmartCard.Store { SecItemCopyMatching(attributes, &untyped) guard let typed = untyped as? [[CFString: Any]] else { return } let wrapped = typed.map { - let name = $0[kSecAttrLabel] as? String ?? String(localized: "unnamed_secret") + let name = $0[kSecAttrLabel] as? String ?? String(localized: .unnamedSecret) let tokenID = $0[kSecAttrApplicationLabel] as! Data let algorithm = Algorithm(secAttr: $0[kSecAttrKeyType] as! NSNumber) let keySize = $0[kSecAttrKeySizeInBits] as! Int @@ -207,8 +207,8 @@ extension SmartCard.Store { /// - Warning: Encryption functions are deliberately only exposed on a library level, and are not exposed in Secretive itself to prevent users from data loss. Any pull requests which expose this functionality in the app will not be merged. public func encrypt(data: Data, with secret: SecretType) throws -> Data { let context = LAContext() - context.localizedReason = String(localized: "auth_context_request_encrypt_description_\(secret.name)") - context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") + context.localizedReason = String(localized: .authContextRequestEncryptDescription(secretName: secret.name)) + context.localizedCancelTitle = String(localized: .authContextRequestDenyButton) let attributes = KeychainDictionary([ kSecAttrKeyType: secret.algorithm.secAttrKeyType, kSecAttrKeySizeInBits: secret.keySize, @@ -236,8 +236,8 @@ extension SmartCard.Store { public func decrypt(data: Data, with secret: SecretType) async throws -> Data { guard let tokenID = await state.tokenID else { fatalError() } let context = LAContext() - context.localizedReason = String(localized: "auth_context_request_decrypt_description_\(secret.name)") - context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") + context.localizedReason = String(localized: .authContextRequestDecryptDescription(secretName: secret.name)) + context.localizedCancelTitle = String(localized: .authContextRequestDenyButton) let attributes = KeychainDictionary([ kSecClass: kSecClassKey, kSecAttrKeyClass: kSecAttrKeyClassPrivate, diff --git a/Sources/SecretAgent/Notifier.swift b/Sources/SecretAgent/Notifier.swift index 2e53260..62540b8 100644 --- a/Sources/SecretAgent/Notifier.swift +++ b/Sources/SecretAgent/Notifier.swift @@ -10,8 +10,8 @@ final class Notifier: Sendable { private let notificationDelegate = NotificationDelegate() init() { - let updateAction = UNNotificationAction(identifier: Constants.updateActionIdentitifier, title: String(localized: "update_notification_update_button"), options: []) - let ignoreAction = UNNotificationAction(identifier: Constants.ignoreActionIdentitifier, title: String(localized: "update_notification_ignore_button"), options: []) + let updateAction = UNNotificationAction(identifier: Constants.updateActionIdentitifier, title: String(localized: .updateNotificationUpdateButton), options: []) + let ignoreAction = UNNotificationAction(identifier: Constants.ignoreActionIdentitifier, title: String(localized: .updateNotificationIgnoreButton), options: []) let updateCategory = UNNotificationCategory(identifier: Constants.updateCategoryIdentitifier, actions: [updateAction, ignoreAction], intentIdentifiers: [], options: []) let criticalUpdateCategory = UNNotificationCategory(identifier: Constants.criticalUpdateCategoryIdentitifier, actions: [updateAction], intentIdentifiers: [], options: []) @@ -22,7 +22,7 @@ final class Notifier: Sendable { Measurement(value: 24, unit: UnitDuration.hours) ] - let doNotPersistAction = UNNotificationAction(identifier: Constants.doNotPersistActionIdentitifier, title: String(localized: "persist_authentication_decline_button"), options: []) + let doNotPersistAction = UNNotificationAction(identifier: Constants.doNotPersistActionIdentitifier, title: String(localized: .persistAuthenticationDeclineButton), options: []) var allPersistenceActions = [doNotPersistAction] let formatter = DateComponentsFormatter() @@ -41,7 +41,7 @@ final class Notifier: Sendable { let persistAuthenticationCategory = UNNotificationCategory(identifier: Constants.persistAuthenticationCategoryIdentitifier, actions: allPersistenceActions, intentIdentifiers: [], options: []) if persistAuthenticationCategory.responds(to: Selector(("actionsMenuTitle"))) { - persistAuthenticationCategory.setValue(String(localized: "persist_authentication_accept_button"), forKey: "_actionsMenuTitle") + persistAuthenticationCategory.setValue(String(localized: .persistAuthenticationAcceptButton), forKey: "_actionsMenuTitle") } UNUserNotificationCenter.current().setNotificationCategories([updateCategory, criticalUpdateCategory, persistAuthenticationCategory]) UNUserNotificationCenter.current().delegate = notificationDelegate @@ -64,8 +64,8 @@ final class Notifier: Sendable { await notificationDelegate.state.setPending(secret: secret, store: store) let notificationCenter = UNUserNotificationCenter.current() let notificationContent = UNMutableNotificationContent() - notificationContent.title = String(localized: "signed_notification_title_\(provenance.origin.displayName)") - notificationContent.subtitle = String(localized: "signed_notification_description_\(secret.name)") + notificationContent.title = String(localized: .signedNotificationTitle(appName: provenance.origin.displayName)) + notificationContent.subtitle = String(localized: .signedNotificationDescription(secretName: secret.name)) notificationContent.userInfo[Constants.persistSecretIDKey] = secret.id.description notificationContent.userInfo[Constants.persistStoreIDKey] = store.id.description notificationContent.interruptionLevel = .timeSensitive @@ -85,11 +85,11 @@ final class Notifier: Sendable { let notificationContent = UNMutableNotificationContent() if update.critical { notificationContent.interruptionLevel = .critical - notificationContent.title = String(localized: "update_notification_update_critical_title_\(update.name)") + notificationContent.title = String(localized: .updateNotificationUpdateCriticalTitle(updateName: update.name)) } else { - notificationContent.title = String(localized: "update_notification_update_normal_title_\(update.name)") + notificationContent.title = String(localized: .updateNotificationUpdateNormalTitle(updateName: update.name)) } - notificationContent.subtitle = String(localized: "update_notification_update_description") + notificationContent.subtitle = String(localized: .updateNotificationUpdateDescription) notificationContent.body = update.body notificationContent.categoryIdentifier = update.critical ? Constants.criticalUpdateCategoryIdentitifier : Constants.updateCategoryIdentitifier let request = UNNotificationRequest(identifier: UUID().uuidString, content: notificationContent, trigger: nil) diff --git a/Sources/Secretive.xcodeproj/project.pbxproj b/Sources/Secretive.xcodeproj/project.pbxproj index ad6a0b9..0c85007 100644 --- a/Sources/Secretive.xcodeproj/project.pbxproj +++ b/Sources/Secretive.xcodeproj/project.pbxproj @@ -18,8 +18,9 @@ 5003EF612780081600DF2006 /* SmartCardSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF602780081600DF2006 /* SmartCardSecretKit */; }; 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 */; }; - 500B93C32B478D8400E157DE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 500B93C22B478D8400E157DE /* Localizable.xcstrings */; }; + 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, ); }; }; 50153E20250AFCB200525160 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E1F250AFCB200525160 /* UpdateView.swift */; }; @@ -51,7 +52,6 @@ 50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8550C24138C4F009958AC /* DeleteSecretView.swift */; }; 50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; }; 50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; }; - 50E9CF422B51D596004AB36D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 500B93C22B478D8400E157DE /* Localizable.xcstrings */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -102,7 +102,7 @@ 50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 50033AC227813F1700253856 /* BundleIDs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleIDs.swift; sourceTree = ""; }; 5003EF39278005C800DF2006 /* Packages */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Packages; sourceTree = ""; }; - 500B93C22B478D8400E157DE /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; + 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = Packages/Localizable.xcstrings; sourceTree = SOURCE_ROOT; }; 50153E1F250AFCB200525160 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = ""; }; 50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = ""; }; 5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = ""; }; @@ -210,7 +210,7 @@ 508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */, 50617D8F23FCE48E0099B055 /* Secretive.entitlements */, 506772C62424784600034DED /* Credits.rtf */, - 500B93C22B478D8400E157DE /* Localizable.xcstrings */, + 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */, 50617D8823FCE48E0099B055 /* Preview Content */, ); path = Secretive; @@ -404,7 +404,7 @@ buildActionMask = 2147483647; files = ( 50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */, - 500B93C32B478D8400E157DE /* Localizable.xcstrings in Resources */, + 5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */, 50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */, 506772C72424784600034DED /* Credits.rtf in Resources */, 508BF28E25B4F005009EFB7E /* InternetAccessPolicy.plist in Resources */, @@ -416,7 +416,7 @@ buildActionMask = 2147483647; files = ( 50A3B79724026B7600D209EA /* Main.storyboard in Resources */, - 50E9CF422B51D596004AB36D /* Localizable.xcstrings in Resources */, + 5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */, 50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */, 508BF2AA25B4F1CB009EFB7E /* InternetAccessPolicy.plist in Resources */, 5008C2402E52792400507AC2 /* Assets.xcassets in Resources */, diff --git a/Sources/Secretive/App.swift b/Sources/Secretive/App.swift index 5505964..2c1ec9d 100644 --- a/Sources/Secretive/App.swift +++ b/Sources/Secretive/App.swift @@ -59,18 +59,18 @@ struct Secretive: App { } .commands { CommandGroup(after: CommandGroupPlacement.newItem) { - Button("app_menu_new_secret_button") { + Button(.appMenuNewSecretButton) { showingCreation = true } .keyboardShortcut(KeyboardShortcut(KeyEquivalent("N"), modifiers: [.command, .shift])) } CommandGroup(replacing: .help) { - Button("app_menu_help_button") { + Button(.appMenuHelpButton) { NSWorkspace.shared.open(Constants.helpURL) } } CommandGroup(after: .help) { - Button("app_menu_setup_button") { + Button(.appMenuSetupButton) { showingSetup = true } } diff --git a/Sources/Secretive/Views/ContentView.swift b/Sources/Secretive/Views/ContentView.swift index 7965341..e57f5d8 100644 --- a/Sources/Secretive/Views/ContentView.swift +++ b/Sources/Secretive/Views/ContentView.swift @@ -70,15 +70,15 @@ extension ContentView { } } - var updateNoticeContent: (LocalizedStringKey, Color)? { + var updateNoticeContent: (LocalizedStringResource, Color)? { guard let update = updater.update else { return nil } if update.critical { - return ("update_critical_notice_title", .red) + return (.updateCriticalNoticeTitle, .red) } else { if updater.testBuild { - return ("update_test_notice_title", .blue) + return (.updateTestNoticeTitle, .blue) } else { - return ("update_normal_notice_title", .orange) + return (.updateNormalNoticeTitle, .orange) } } } @@ -127,13 +127,13 @@ extension ContentView { }, label: { Group { if hasRunSetup && !agentStatusChecker.running { - Text("agent_not_running_notice_title") + Text(.agentNotRunningNoticeTitle) } else { - Text("agent_setup_notice_title") + Text(.agentSetupNoticeTitle) } } .font(.headline) - .foregroundColor(.white) + }) .buttonStyle(ToolbarButtonStyle(color: .orange)) } @@ -144,7 +144,7 @@ extension ContentView { showingAgentInfo = true }, label: { HStack { - Text("agent_running_notice_title") + Text(.agentRunningNoticeTitle) .font(.headline) .foregroundColor(colorScheme == .light ? Color(white: 0.3) : .white) Circle() @@ -155,10 +155,10 @@ extension ContentView { .buttonStyle(ToolbarButtonStyle(lightColor: .black.opacity(0.05), darkColor: .white.opacity(0.05))) .popover(isPresented: $showingAgentInfo, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) { VStack { - Text("agent_running_notice_detail_title") + Text(.agentRunningNoticeDetailTitle) .font(.title) .padding(5) - Text("agent_running_notice_detail_description") + Text(.agentRunningNoticeDetailDescription) .frame(width: 300) } .padding() @@ -172,7 +172,7 @@ extension ContentView { showingAppPathNotice = true }, label: { Group { - Text("app_not_in_applications_notice_title") + Text(.appNotInApplicationsNoticeTitle) } .font(.headline) .foregroundColor(.white) @@ -184,7 +184,7 @@ extension ContentView { .resizable() .aspectRatio(contentMode: .fit) .frame(width: 64) - Text("app_not_in_applications_notice_detail_description") + Text(.appNotInApplicationsNoticeDetailDescription) .frame(maxWidth: 300) } .padding() diff --git a/Sources/Secretive/Views/CopyableView.swift b/Sources/Secretive/Views/CopyableView.swift index a1b224b..d1be4be 100644 --- a/Sources/Secretive/Views/CopyableView.swift +++ b/Sources/Secretive/Views/CopyableView.swift @@ -3,7 +3,7 @@ import UniformTypeIdentifiers struct CopyableView: View { - var title: LocalizedStringKey + var title: LocalizedStringResource var image: Image var text: String diff --git a/Sources/Secretive/Views/CreateSecretView.swift b/Sources/Secretive/Views/CreateSecretView.swift index 9bddcd2..7900f16 100644 --- a/Sources/Secretive/Views/CreateSecretView.swift +++ b/Sources/Secretive/Views/CreateSecretView.swift @@ -14,30 +14,30 @@ struct CreateSecretView: View { HStack { VStack { HStack { - Text("create_secret_title") + Text(.createSecretTitle) .font(.largeTitle) Spacer() } HStack { - Text("create_secret_name_label") - TextField("create_secret_name_placeholder", text: $name) + Text(.createSecretNameLabel) + TextField(String(localized: .createSecretNamePlaceholder), text: $name) .focusable() } ThumbnailPickerView(items: [ - ThumbnailPickerView.Item(value: true, name: "create_secret_require_authentication_title", description: "create_secret_require_authentication_description", thumbnail: AuthenticationView()), - ThumbnailPickerView.Item(value: false, name: "create_secret_notify_title", - description: "create_secret_notify_description", + ThumbnailPickerView.Item(value: true, name: .createSecretRequireAuthenticationTitle, description: .createSecretRequireAuthenticationDescription, thumbnail: AuthenticationView()), + ThumbnailPickerView.Item(value: false, name: .createSecretNotifyTitle, + description: .createSecretNotifyDescription, thumbnail: NotificationView()) ], selection: $requiresAuthentication) } } HStack { Spacer() - Button("create_secret_cancel_button") { + Button(.createSecretCancelButton) { showing = false } .keyboardShortcut(.cancelAction) - Button("create_secret_create_button", action: save) + Button(.createSecretCreateButton, action: save) .disabled(name.isEmpty) .keyboardShortcut(.defaultAction) } @@ -98,11 +98,11 @@ extension ThumbnailPickerView { struct Item: Identifiable { let id = UUID() let value: InnerValueType - let name: LocalizedStringKey - let description: LocalizedStringKey + let name: LocalizedStringResource + let description: LocalizedStringResource let thumbnail: AnyView - init(value: InnerValueType, name: LocalizedStringKey, description: LocalizedStringKey, thumbnail: ViewType) { + init(value: InnerValueType, name: LocalizedStringResource, description: LocalizedStringResource, thumbnail: ViewType) { self.value = value self.name = name self.description = description diff --git a/Sources/Secretive/Views/DeleteSecretView.swift b/Sources/Secretive/Views/DeleteSecretView.swift index 3be07c2..00063d2 100644 --- a/Sources/Secretive/Views/DeleteSecretView.swift +++ b/Sources/Secretive/Views/DeleteSecretView.swift @@ -18,24 +18,24 @@ struct DeleteSecretView: View { .padding() VStack { HStack { - Text("delete_confirmation_title_\(secret.name)").bold() + Text(.deleteConfirmationTitle(secretName: secret.name)).bold() Spacer() } HStack { - Text("delete_confirmation_description_\(secret.name)_\(secret.name)") + Text(.deleteConfirmationDescription(secretName: secret.name, confirmSecretName: secret.name)) Spacer() } HStack { - Text("delete_confirmation_confirm_name_label") + Text(.deleteConfirmationConfirmNameLabel) TextField(secret.name, text: $confirm) } } } HStack { Spacer() - Button("delete_confirmation_delete_button", action: delete) + Button(.deleteConfirmationDeleteButton, action: delete) .disabled(confirm != secret.name) - Button("delete_confirmation_cancel_button") { + Button(.deleteConfirmationCancelButton) { dismissalBlock(false) } .keyboardShortcut(.cancelAction) diff --git a/Sources/Secretive/Views/EmptyStoreView.swift b/Sources/Secretive/Views/EmptyStoreView.swift index b6593c0..3f0bd81 100644 --- a/Sources/Secretive/Views/EmptyStoreView.swift +++ b/Sources/Secretive/Views/EmptyStoreView.swift @@ -18,9 +18,9 @@ struct EmptyStoreImmutableView: View { var body: some View { VStack { - Text("empty_store_nonmodifiable_title").bold() - Text("empty_store_nonmodifiable_description") - Text("empty_store_nonmodifiable_supported_key_types") + Text(.emptyStoreNonmodifiableTitle).bold() + Text(.emptyStoreNonmodifiableDescription) + Text(.emptyStoreNonmodifiableSupportedKeyTypes) }.frame(maxWidth: .infinity, maxHeight: .infinity) } @@ -49,8 +49,8 @@ struct EmptyStoreModifiableView: View { path.addLine(to: CGPoint(x: g.size.width - 3, y: 0)) }.fill() }.frame(height: (windowGeometry.size.height/2) - 20).padding() - Text("empty_store_modifiable_click_here_title").bold() - Text("empty_store_modifiable_click_here_description") + Text(.emptyStoreModifiableClickHereTitle).bold() + Text(.emptyStoreModifiableClickHereDescription) Spacer() }.frame(maxWidth: .infinity, maxHeight: .infinity) } diff --git a/Sources/Secretive/Views/NoStoresView.swift b/Sources/Secretive/Views/NoStoresView.swift index 3ac4841..497138d 100644 --- a/Sources/Secretive/Views/NoStoresView.swift +++ b/Sources/Secretive/Views/NoStoresView.swift @@ -4,10 +4,10 @@ struct NoStoresView: View { var body: some View { VStack { - Text("no_secure_storage_title") + Text(.noSecureStorageTitle) .bold() - Text("no_secure_storage_description") - Link("no_secure_storage_yubico_link", destination: URL(string: "https://www.yubico.com/products/compare-yubikey-5-series/")!) + Text(.noSecureStorageDescription) + Link(.noSecureStorageYubicoLink, destination: URL(string: "https://www.yubico.com/products/compare-yubikey-5-series/")!) }.padding() } diff --git a/Sources/Secretive/Views/RenameSecretView.swift b/Sources/Secretive/Views/RenameSecretView.swift index 0a417cb..edd3a61 100644 --- a/Sources/Secretive/Views/RenameSecretView.swift +++ b/Sources/Secretive/Views/RenameSecretView.swift @@ -18,7 +18,7 @@ struct RenameSecretView: View { .padding() VStack { HStack { - Text("rename_title_\(secret.name)") + Text(.renameTitle(secretName: secret.name)) Spacer() } HStack { @@ -28,10 +28,10 @@ struct RenameSecretView: View { } HStack { Spacer() - Button("rename_rename_button", action: rename) + Button(.renameRenameButton, action: rename) .disabled(newName.count == 0) .keyboardShortcut(.return) - Button("rename_cancel_button") { + Button(.renameCancelButton) { dismissalBlock(false) }.keyboardShortcut(.cancelAction) } diff --git a/Sources/Secretive/Views/SecretDetailView.swift b/Sources/Secretive/Views/SecretDetailView.swift index 75dde47..a361bbc 100644 --- a/Sources/Secretive/Views/SecretDetailView.swift +++ b/Sources/Secretive/Views/SecretDetailView.swift @@ -12,16 +12,16 @@ struct SecretDetailView: View { ScrollView { Form { Section { - CopyableView(title: "secret_detail_sha256_fingerprint_label", image: Image(systemName: "touchid"), text: keyWriter.openSSHSHA256Fingerprint(secret: secret)) + CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "touchid"), text: keyWriter.openSSHSHA256Fingerprint(secret: secret)) Spacer() .frame(height: 20) - CopyableView(title: "secret_detail_md5_fingerprint_label", image: Image(systemName: "touchid"), text: keyWriter.openSSHMD5Fingerprint(secret: secret)) + CopyableView(title: .secretDetailMd5FingerprintLabel, image: Image(systemName: "touchid"), text: keyWriter.openSSHMD5Fingerprint(secret: secret)) Spacer() .frame(height: 20) - CopyableView(title: "secret_detail_public_key_label", image: Image(systemName: "key"), text: keyString) + CopyableView(title: .secretDetailPublicKeyLabel, image: Image(systemName: "key"), text: keyString) Spacer() .frame(height: 20) - CopyableView(title: "secret_detail_public_key_path_label", image: Image(systemName: "lock.doc"), text: publicKeyFileStoreController.publicKeyPath(for: secret)) + CopyableView(title: .secretDetailPublicKeyPathLabel, image: Image(systemName: "lock.doc"), text: publicKeyFileStoreController.publicKeyPath(for: secret)) Spacer() } } diff --git a/Sources/Secretive/Views/SecretListItemView.swift b/Sources/Secretive/Views/SecretListItemView.swift index 38a6f36..58b0f32 100644 --- a/Sources/Secretive/Views/SecretListItemView.swift +++ b/Sources/Secretive/Views/SecretListItemView.swift @@ -39,10 +39,10 @@ struct SecretListItemView: View { .contextMenu { if store is AnySecretStoreModifiable { Button(action: { isRenaming = true }) { - Text("secret_list_rename_button") + Text(.secretListRenameButton) } Button(action: { isDeleting = true }) { - Text("secret_list_delete_button") + Text(.secretListDeleteButton) } } } diff --git a/Sources/Secretive/Views/SetupView.swift b/Sources/Secretive/Views/SetupView.swift index 8f71bc0..e0a2560 100644 --- a/Sources/Secretive/Views/SetupView.swift +++ b/Sources/Secretive/Views/SetupView.swift @@ -61,7 +61,7 @@ struct StepView: View { Circle() .foregroundColor(.green) .frame(width: Constants.circleWidth, height: Constants.circleWidth) - Text("setup_step_complete_symbol") + Text(.setupStepCompleteSymbol) .foregroundColor(.white) .bold() } else { @@ -101,14 +101,14 @@ extension StepView { struct SetupStepView : View where Content : View { - let title: LocalizedStringKey + let title: LocalizedStringResource let image: Image - let bodyText: LocalizedStringKey - let buttonTitle: LocalizedStringKey + let bodyText: LocalizedStringResource + let buttonTitle: LocalizedStringResource let buttonAction: () -> Void let content: Content - init(title: LocalizedStringKey, image: Image, bodyText: LocalizedStringKey, buttonTitle: LocalizedStringKey, buttonAction: @escaping () -> Void = {}, @ViewBuilder content: () -> Content) { + init(title: LocalizedStringResource, image: Image, bodyText: LocalizedStringResource, buttonTitle: LocalizedStringResource, buttonAction: @escaping () -> Void = {}, @ViewBuilder content: () -> Content) { self.title = title self.image = image self.bodyText = bodyText @@ -145,12 +145,12 @@ struct SecretAgentSetupView: View { let buttonAction: () -> Void var body: some View { - SetupStepView(title: "setup_agent_title", + SetupStepView(title: .setupAgentTitle, image: Image(nsImage: NSApplication.shared.applicationIconImage), - bodyText: "setup_agent_description", - buttonTitle: "setup_agent_install_button", + bodyText: .setupAgentDescription, + buttonTitle: .setupAgentInstallButton, buttonAction: install) { - Text("setup_agent_activity_monitor_description") + Text(.setupAgentActivityMonitorDescription) .multilineTextAlignment(.center) } } @@ -172,12 +172,12 @@ struct SSHAgentSetupView: View { @State private var selectedShellInstruction: ShellConfigInstruction = controller.shellInstructions.first! var body: some View { - SetupStepView(title: "setup_ssh_title", + SetupStepView(title: .setupSshTitle, image: Image(systemName: "terminal"), - bodyText: "setup_ssh_description", - buttonTitle: "setup_ssh_added_manually_button", + bodyText: .setupSshDescription, + buttonTitle: .setupSshAddedManuallyButton, buttonAction: buttonAction) { - Link("setup_third_party_faq_link", destination: URL(string: "https://github.com/maxgoedjen/secretive/blob/main/APP_CONFIG.md")!) + Link(.setupThirdPartyFaqLink, destination: URL(string: "https://github.com/maxgoedjen/secretive/blob/main/APP_CONFIG.md")!) Picker(selection: $selectedShellInstruction, label: EmptyView()) { ForEach(SSHAgentSetupView.controller.shellInstructions) { instruction in Text(instruction.shell) @@ -185,8 +185,8 @@ struct SSHAgentSetupView: View { .padding() } }.pickerStyle(SegmentedPickerStyle()) - CopyableView(title: "setup_ssh_add_to_config_button_\(selectedShellInstruction.shellConfigPath)", image: Image(systemName: "greaterthan.square"), text: selectedShellInstruction.text) - Button("setup_ssh_add_for_me_button") { + CopyableView(title: .setupSshAddToConfigButton(configPath: selectedShellInstruction.shellConfigPath), image: Image(systemName: "greaterthan.square"), text: selectedShellInstruction.text) + Button(.setupSshAddForMeButton) { let controller = ShellConfigurationController() if controller.addToShell(shellInstructions: selectedShellInstruction) { buttonAction() @@ -216,12 +216,12 @@ struct UpdaterExplainerView: View { let buttonAction: () -> Void var body: some View { - SetupStepView(title: "setup_updates_title", + SetupStepView(title: .setupUpdatesTitle, image: Image(systemName: "dot.radiowaves.left.and.right"), - bodyText: "setup_updates_description", - buttonTitle: "setup_updates_ok", + bodyText: .setupUpdatesDescription, + buttonTitle: .setupUpdatesOk, buttonAction: buttonAction) { - Link("setup_updates_readmore", destination: SetupView.Constants.updaterFAQURL) + Link(.setupUpdatesReadmore, destination: SetupView.Constants.updaterFAQURL) } } diff --git a/Sources/Secretive/Views/UpdateView.swift b/Sources/Secretive/Views/UpdateView.swift index bebbcc5..810e0e8 100644 --- a/Sources/Secretive/Views/UpdateView.swift +++ b/Sources/Secretive/Views/UpdateView.swift @@ -9,22 +9,22 @@ struct UpdateDetailView: View { var body: some View { VStack { - Text("update_version_name_\(update.name)").font(.title) - GroupBox(label: Text("update_release_notes_title")) { + Text(.updateVersionName(updateName: update.name)).font(.title) + GroupBox(label: Text(.updateReleaseNotesTitle)) { ScrollView { attributedBody } } HStack { if !update.critical { - Button("update_ignore_button") { + Button(.updateIgnoreButton) { Task { await updater.ignore(release: update) } } Spacer() } - Button("update_update_button") { + Button(.updateUpdateButton) { NSWorkspace.shared.open(update.html_url) } .keyboardShortcut(.defaultAction)