diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2665f345568..187f500828c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -143,6 +143,7 @@ apps/desktop/macos/autofill-extension @bitwarden/team-autofill-dev apps/desktop/src/app/components/fido2placeholder.component.ts @bitwarden/team-autofill-dev apps/desktop/desktop_native/windows_plugin_authenticator @bitwarden/team-autofill-dev apps/desktop/desktop_native/autotype @bitwarden/team-autofill-dev +.github/workflows/test-browser-interactions.yml @bitwarden/team-autofill-dev # DuckDuckGo integration apps/desktop/native-messaging-test-runner @bitwarden/team-autofill-dev apps/desktop/src/services/duckduckgo-message-handler.service.ts @bitwarden/team-autofill-dev diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index b31b22b926e..73b765f207a 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -71,6 +71,7 @@ jobs: - name: Get Node Version id: retrieve-node-version + working-directory: ./ run: | NODE_NVMRC=$(cat .nvmrc) NODE_VERSION=${NODE_NVMRC/v/''} @@ -104,7 +105,7 @@ jobs: env: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} - _WIN_PKG_FETCH_VERSION: 20.11.1 + _WIN_PKG_FETCH_VERSION: 22.15.1 _WIN_PKG_VERSION: 3.5 permissions: contents: read @@ -283,7 +284,7 @@ jobs: env: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} - _WIN_PKG_FETCH_VERSION: 20.11.1 + _WIN_PKG_FETCH_VERSION: 22.15.1 _WIN_PKG_VERSION: 3.5 steps: - name: Check out repo diff --git a/.github/workflows/test-browser-interactions.yml b/.github/workflows/test-browser-interactions.yml new file mode 100644 index 00000000000..872b4c35264 --- /dev/null +++ b/.github/workflows/test-browser-interactions.yml @@ -0,0 +1,74 @@ +name: Autofill BIT checks + +on: + workflow_run: + workflows: ["Build Browser"] + types: + - completed + +jobs: + check-files: + name: Check files + runs-on: ubuntu-22.04 + permissions: + actions: read + contents: read + id-token: write + steps: + - name: Log in to Azure + uses: bitwarden/gh-actions/azure-login@main + with: + subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + tenant_id: ${{ secrets.AZURE_TENANT_ID }} + client_id: ${{ secrets.AZURE_CLIENT_ID }} + + - name: Get Azure Key Vault secrets + id: get-kv-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: gh-org-bitwarden + secrets: "BW-GHAPP-ID,BW-GHAPP-KEY" + + - name: Log out from Azure + uses: bitwarden/gh-actions/azure-logout@main + + - name: Generate GH App token + uses: actions/create-github-app-token@30bf6253fa41bdc8d1501d202ad15287582246b4 # v2.0.3 + id: app-token + with: + app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }} + private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} + owner: bitwarden + repositories: browser-interactions-testing + permission-actions: write + + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + + - name: Get changed files + id: changed-files + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + with: + list-files: shell + ref: ${{ github.event.workflow_run.head_branch }} + token: ${{ secrets.GITHUB_TOKEN }} + filters: | + monitored: + - 'apps/browser/src/autofill/**' + - 'apps/browser/src/background/**' + - 'apps/browser/src/platform/services/browser-script-injector.service.ts' + + - name: Trigger test-all workflow in browser-interactions-testing + if: steps.changed-files.outputs.monitored == 'true' + uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 + with: + token: ${{ steps.app-token.outputs.token }} + repository: "bitwarden/browser-interactions-testing" + event-type: trigger-bit-tests + client-payload: |- + { + "origin_issue": ${{ github.event.workflow_run.pull_requests[0].number }}, + "origin_branch": "${{ github.event.workflow_run.pull_requests[0].head.ref }}" + } diff --git a/apps/browser/package.json b/apps/browser/package.json index 70dd0d7a241..e75d2b235db 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2025.7.0", + "version": "2025.8.0", "scripts": { "build": "npm run build:chrome", "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 65ee9cab458..ee723217f2a 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 16b74ffe175..1d26f50bca6 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -14,7 +14,7 @@ "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { - "message": "Güvənli seyfinizə müraciət etmək üçün giriş edin və ya yeni bir hesab yaradın." + "message": "Güvənli seyfinizə erişmək üçün giriş edin və ya yeni bir hesab yaradın." }, "inviteAccepted": { "message": "Dəvət qəbul edildi" @@ -59,7 +59,7 @@ "message": "Ana parol" }, "masterPassDesc": { - "message": "Ana parol, seyfinizə müraciət etmək üçün istifadə edəcəyiniz paroldur. Ana parolu yadda saxlamaq çox vacibdir. Unutsanız, parolu bərpa etməyin heç bir yolu yoxdur." + "message": "Ana parol, seyfinizə erişmək üçün istifadə edəcəyiniz paroldur. Ana parolu yadda saxlamaq çox vacibdir. Unutsanız, parolu bərpa etməyin heç bir yolu yoxdur." }, "masterPassHintDesc": { "message": "Ana parol məsləhəti, unutduğunuz parolunuzu xatırlamağınıza kömək edir." @@ -350,7 +350,7 @@ "message": "Bitwarden Sirr Meneceri" }, "continueToSecretsManagerPageDesc": { - "message": "Bitwarden Sirr Meneceri ilə developer sirlərini güvənli bir şəkildə saxlayın, idarə edin və paylaşın. Daha ətraflı bitwarden.com veb saytında öyrənə bilərsiniz." + "message": "Bitwarden Sirr Meneceri ilə gəlişdirici sirlərini güvənli bir şəkildə saxlayın, idarə edin və paylaşın. Daha ətraflı bitwarden.com veb saytında öyrənə bilərsiniz." }, "passwordlessDotDev": { "message": "Passwordless.dev" @@ -548,7 +548,7 @@ "message": "Seyfdə axtar" }, "resetSearch": { - "message": "Reset search" + "message": "Axtarışı sıfırla" }, "edit": { "message": "Düzəliş et" @@ -1216,10 +1216,10 @@ "message": "Konteks menyu seçimlərini göstər" }, "contextMenuItemDesc": { - "message": "Veb sayt üçün parol yaratmaq və uyuşan giriş məlumatlarına müraciət etmək üçün sağ klikləməni istifadə edin." + "message": "Veb sayt üçün parol yaradılmasına və uyuşan giriş məlumatlarına erişmək üçün sağ klikləməni istifadə edin." }, "contextMenuItemDescAlt": { - "message": "Veb sayt üçün parol yaratmaq və uyuşan giriş məlumatlarına müraciət etmək üçün sağ klikləməni istifadə edin. Giriş etmiş bütün hesablara aiddir." + "message": "Veb sayt üçün parol yaradılmasına və uyuşan giriş məlumatlarına erişmək üçün sağ klikləməni istifadə edin. Giriş etmiş bütün hesablara aiddir." }, "defaultUriMatchDetection": { "message": "İlkin URI uyuşma aşkarlaması", @@ -1290,16 +1290,16 @@ "message": "Seyfi xaricə köçürməyi təsdiqlə" }, "exportWarningDesc": { - "message": "Xaricə köçürdüyünüz bu fayldakı datanız şifrələnməmiş formatdadır. Bu faylı güvənli olmayan kanallar (e-poçt kimi) üzərində saxlamamalı və ya göndərməməlisiniz. İşiniz bitdikdən sonra faylı dərhal silin." + "message": "Bu xaricə köçürmədəki seyf veriləriniz şifrələnməmiş formatdadır. Bu xaricə köçürülmüş faylı güvənli olmayan kanallar (e-poçt kimi) üzərində saxlamamalı və ya göndərməməlisiniz. İşiniz bitdikdən sonra faylı dərhal silin." }, "encExportKeyWarningDesc": { - "message": "Xaricə köçürdüyünüz bu fayldakı data, hesabınızın şifrələmə açarı istifadə edilərək şifrələnir. Hesabınızın şifrələmə açarını dəyişdirsəniz, bu faylın şifrəsini aça bilməyəcəksiniz və onu yenidən xaricə köçürməli olacaqsınız." + "message": "Bu xaricə köçürmə, verilərinizi hesabınızın şifrələmə açarını istifadə edərək şifrələyir. Hesabınızın şifrələmə açarını dəyişdirsəniz, bu faylın şifrəsini aça bilməyəcəksiniz və onu yenidən xaricə köçürməli olacaqsınız." }, "encExportAccountWarningDesc": { "message": "Hesab şifrələmə açarları, hər Bitwarden istifadəçi hesabı üçün unikaldır, buna görə də şifrələnmiş bir xaricə köçürməni, fərqli bir hesaba köçürə bilməzsiniz." }, "exportMasterPassword": { - "message": "Seyf datanızı xaricə köçürmək üçün ana parolunuzu daxil edin." + "message": "Seyf verilərinizi xaricə köçürmək üçün ana parolunuzu daxil edin." }, "shared": { "message": "Paylaşılan" @@ -1399,13 +1399,13 @@ "message": "Fayl qoşmaları üçün 1 GB şifrələnmiş anbar sahəsi" }, "premiumSignUpEmergency": { - "message": "Fövqəladə hal müraciəti" + "message": "Fövqəladə hal erişimi." }, "premiumSignUpTwoStepOptions": { "message": "YubiKey və Duo kimi mülkiyyətçi iki addımlı giriş seçimləri." }, "ppremiumSignUpReports": { - "message": "Seyfinizi güvəndə saxlamaq üçün parol gigiyenası, hesab sağlamlığı və data pozuntusu hesabatları." + "message": "Seyfinizi güvəndə saxlamaq üçün parol gigiyenası, hesab sağlamlığı və veri pozuntusu hesabatları." }, "ppremiumSignUpTotp": { "message": "Seyfinizdəki girişlər üçün TOTP doğrulama kodu (2FA) yaradıcısı." @@ -1523,7 +1523,7 @@ "message": "İki addımlı giriş üsulunu seçin" }, "recoveryCodeDesc": { - "message": "İki faktorlu provayderlərinə müraciəti itirmisiniz? Geri qaytarma kodunuzu istifadə edərək hesabınızdakı bütün iki faktorlu provayderləri sıradan çıxarda bilərsiniz." + "message": "İki faktorlu provayderlərinizə erişə bilmirsiniz? Geri qaytarma kodunuzu istifadə edərək hesabınızdakı bütün iki faktorlu provayderləri söndürün." }, "recoveryCodeTitle": { "message": "Geri qaytarma kodu" @@ -1539,7 +1539,7 @@ "message": "Yubico OTP Güvənlik Açarı" }, "yubiKeyDesc": { - "message": "Hesabınıza müraciət etmək üçün bir YubiKey istifadə edin. YubiKey 4, 4 Nano, 4C və NEO cihazları ilə işləyir." + "message": "Hesabınıza erişmək üçün bir YubiKey istifadə edin. YubiKey 4, 4 Nano, 4C və NEO cihazları ilə işləyir." }, "duoDescV2": { "message": "Duo Security tərəfindən yaradılan kodu daxil edin.", @@ -1553,7 +1553,7 @@ "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "Hesabınıza müraciət etmək üçün WebAuthn özəllikli istənilən bir güvənlik açarı istifadə edin." + "message": "Hesabınıza erişmək üçün istənilən WebAuthn uyumlu güvənlik açarını istifadə edin." }, "emailTitle": { "message": "E-poçt" @@ -2023,7 +2023,7 @@ "message": "Parolun ifşalanıb ifşalanmadığını yoxlayın." }, "passwordExposed": { - "message": "Bu parol, məlumat pozuntularında $VALUE$ dəfə üzə çıxıb. Dəyişdirməyi məsləhət görürük.", + "message": "Bu parol, veri pozuntularında $VALUE$ dəfə üzə çıxıb. Dəyişdirməyi məsləhət görürük.", "placeholders": { "value": { "content": "$1", @@ -2032,7 +2032,7 @@ } }, "passwordSafe": { - "message": "Bu parol, məlumat pozuntularında qeydə alınmayıb. Rahatlıqla istifadə edə bilərsiniz." + "message": "Bu parol, veri pozuntularında qeydə alınmayıb. Rahatlıqla istifadə edə bilərsiniz." }, "baseDomain": { "message": "Baza domeni", @@ -2275,7 +2275,7 @@ "message": "Artıq hesabınız var?" }, "vaultTimeoutLogOutConfirmation": { - "message": "Çıxış etdikdə, seyfinizə bütün müraciətiniz dayanacaq və vaxt bitməsindən sonra onlayn kimlik doğrulaması tələb olunacaq. Bu ayarı istifadə etmək istədiyinizə əminsiniz?" + "message": "Çıxış etdikdə, seyfinizə erişiminiz tamamilə dayanacaq və vaxt bitdikdən sonra onlayn kimlik doğrulaması tələb olunacaq. Bu ayarı istifadə etmək istədiyinizə əminsiniz?" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Vaxt bitmə əməliyyat təsdiqi" @@ -2401,7 +2401,7 @@ "message": "Oldu" }, "errorRefreshingAccessToken": { - "message": "Müraciət tokeni təzələmə xətası" + "message": "Erişim tokeni təzələmə xətası" }, "errorRefreshingAccessTokenDesc": { "message": "Təzələmə tokeni və ya API açarlar tapılmadı. Lütfən çıxış edib yenidən giriş etməyə çalışın." @@ -2726,7 +2726,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "maxAccessCountReached": { - "message": "Maksimal müraciət sayına çatıldı", + "message": "Maksimum erişim sayına çatıldı", "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "hideTextByDefault": { @@ -2810,7 +2810,7 @@ "message": "Özəl" }, "sendPasswordDescV3": { - "message": "Alıcıların bu \"Send\"ə müraciət etməsi üçün ixtiyari bir parol əlavə edin.", + "message": "Alıcıların bu \"Send\"ə erişməsi üçün ixtiyari bir parol əlavə edin.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { @@ -2941,13 +2941,13 @@ "message": "Ana parolu güncəllə" }, "updateMasterPasswordWarning": { - "message": "Ana parolunuz təzəlikcə təşkilatınızdakı bir administrator tərəfindən dəyişdirildi. Seyfə müraciət etmək üçün onu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış edəcəksiniz və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." + "message": "Ana parolunuz təzəlikcə təşkilatınızdakı bir inzibatçı tərəfindən dəyişdirildi. Seyfə erişmək üçün onu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış edəcəksiniz və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." }, "updateWeakMasterPasswordWarning": { - "message": "Ana parolunuz təşkilatınızdakı siyasətlərdən birinə və ya bir neçəsinə uyğun gəlmir. Seyfə müraciət üçün ana parolunuzu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış etmiş və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." + "message": "Ana parolunuz təşkilatınızdakı siyasətlərdən birinə və ya bir neçəsinə uyğun gəlmir. Seyfə erişmək üçün ana parolunuzu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış etmiş və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." }, "tdeDisabledMasterPasswordRequired": { - "message": "Təşkilatınız, güvənli cihaz şifrələməsini sıradan çıxartdı. Seyfinizə müraciət etmək üçün lütfən ana parol təyin edin." + "message": "Təşkilatınız, güvənli cihaz şifrələməsini sıradan çıxartdı. Seyfinizə erişmək üçün lütfən ana parol təyin edin." }, "resetPasswordPolicyAutoEnroll": { "message": "Avtomatik yazılma" @@ -3145,7 +3145,7 @@ "message": "Bitwarden, aşağıda sadalanan seyf element(lər)inin şifrəsini aça bilmədi." }, "contactCSToAvoidDataLossPart1": { - "message": "Əlavə data itkisini önləmək üçün", + "message": "Əlavə veri itkisini önləmək üçün", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { @@ -3362,7 +3362,7 @@ "description": "Part of a URL." }, "apiAccessToken": { - "message": "API müraciət tokeni" + "message": "API erişim tokeni" }, "apiKey": { "message": "API açar" @@ -3377,7 +3377,7 @@ "message": "Təşkilat dayandırıldı." }, "disabledOrganizationFilterError": { - "message": "Dayandırılmış Təşkilatlardakı elementlərə müraciət edilə bilmir. Kömək üçün Təşkilatınızın sahibi ilə əlaqə saxlayın." + "message": "Fəaliyyəti dayandırılmış Təşkilatlardakı elementlərə erişilə bilməz. Kömək üçün Təşkilatınızın sahibi ilə əlaqə saxlayın." }, "loggingInTo": { "message": "$DOMAIN$ domeninə giriş edilir", @@ -3495,16 +3495,16 @@ "message": "İfşa olunmuş ana parol" }, "exposedMasterPasswordDesc": { - "message": "Parol, məlumat pozuntusunda tapıldı. Hesabınızı qorumaq üçün unikal bir parol istifadə edin. İfşa olunmuş bir parol istifadə etmək istədiyinizə əminsiniz?" + "message": "Parol, veri pozuntusunda tapıldı. Hesabınızı qorumaq üçün unikal bir parol istifadə edin. İfşa olunmuş bir parol istifadə etmək istədiyinizə əminsiniz?" }, "weakAndExposedMasterPassword": { "message": "Zəif və ifşa olunmuş ana parol" }, "weakAndBreachedMasterPasswordDesc": { - "message": "Zəif parol məlumat pozuntusunda aşkarlandı və tapıldı. Hesabınızı qorumaq üçün güclü və unikal bir parol istifadə edin. Bu parolu istifadə etmək istədiyinizə əminsiniz?" + "message": "Zəif parol, veri pozuntusunda aşkarlandı və tapıldı. Hesabınızı qorumaq üçün güclü və unikal bir parol istifadə edin. Bu parolu istifadə etmək istədiyinizə əminsiniz?" }, "checkForBreaches": { - "message": "Bu parol üçün bilinən məlumat pozuntularını yoxlayın" + "message": "Bu parol üçün bilinən veri pozuntularını yoxlayın" }, "important": { "message": "Vacib:" @@ -3631,7 +3631,7 @@ "message": "Cihazlar" }, "accessAttemptBy": { - "message": "$EMAIL$ ilə müraciət cəhdi", + "message": "$EMAIL$ ilə erişim cəhdi", "placeholders": { "email": { "content": "$1", @@ -3640,10 +3640,10 @@ } }, "confirmAccess": { - "message": "Müraciəti təsdiqlə" + "message": "Erişimi təsdiqlə" }, "denyAccess": { - "message": "Müraciətə rədd cavabı ver" + "message": "Erişimə rədd cavabı ver" }, "time": { "message": "Vaxt" @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Bu tələb artıq yararsızdır." }, - "areYouTryingToAccessYourAccount": { - "message": "Hesabınıza müraciət etməyə çalışırsınız?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "$DEVICE$ cihazında $EMAIL$ üçün giriş təsdiqləndi", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Başqa bir cihazdan giriş cəhdinə rədd cavabı verdiniz. Bu həqiqətən siz idinizsə, cihazla yenidən giriş etməyə çalışın." - }, "loginRequestHasAlreadyExpired": { "message": "Giriş tələbinin müddəti artıq bitib." }, @@ -3747,7 +3728,7 @@ "description": "European Union" }, "accessDenied": { - "message": "Müraciət rədd edildi. Bu səhifəyə baxmaq üçün icazəniz yoxdur." + "message": "Erişim rədd edildi. Bu səhifəyə baxmaq üçün icazəniz yoxdur." }, "general": { "message": "Ümumi" @@ -3792,7 +3773,7 @@ "message": "Təşkilata güvənilmir" }, "emergencyAccessTrustWarning": { - "message": "Hesabınızın təhlükəsizliyi üçün yalnız bu istifadəçiyə fövqəladə hal müraciəti icazəsini verdiyinizi və onun barmaq izinin hesabında görünən barmaq izi ilə uyuşduğunu təsdiqləyin" + "message": "Hesabınızın təhlükəsizliyi üçün yalnız bu istifadəçiyə fövqəladə hal erişim icazəsini verdiyinizi və onun barmaq izinin hesabında görünən barmaq izi ilə uyuşduğunu təsdiqləyin" }, "orgTrustWarning": { "message": "Hesabınızın təhlükəsizliyi üçün yalnız bu təşkilatın üzvüsünüzsə, hesab geri qaytarma fəaldırsa və aşağıda görünən barmaq izi təşkilatın barmaq izi ilə uyuşursa davam edin." @@ -3808,7 +3789,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsBodyNoItems": { - "message": "İstənilən platformada faylları və dataları hər kəslə paylaşın. İfşa olunmağı məhdudlaşdıraraq məlumatlarınız ucdan-uca şifrələnmiş qalacaq.", + "message": "İstənilən platformada faylları və veriləri hər kəslə güvənli şəkildə paylaşın. Məlumatlarınız, ifşa olunmamaq üçün ucdan-uca şifrələnmiş qalacaq.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4039,14 +4020,14 @@ "message": "Yox say" }, "importData": { - "message": "Datanı daxilə köçür", + "message": "Veriləri daxilə köçür", "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" }, "importError": { "message": "Daxilə köçürmə xətası" }, "importErrorDesc": { - "message": "Daxilə köçürməyə çalışdığınız data ilə bağlı bir problem var. Lütfən mənbə faylınızda aşağıda sadalanan xətaları həll edib yenidən sınayın." + "message": "Daxilə köçürməyə çalışdığınız verilərlə bağlı bir problem var. Lütfən mənbə faylınızda aşağıda sadalanan xətaları həll edib yenidən sınayın." }, "resolveTheErrorsBelowAndTryAgain": { "message": "Aşağıdakı xətaları həll edin və yenidən sınayın." @@ -4055,7 +4036,7 @@ "message": "Açıqlama" }, "importSuccess": { - "message": "Data uğurla daxilə köçürüldü" + "message": "Verilər uğurla daxilə köçürüldü" }, "importSuccessNumberOfItems": { "message": "Cəmi $AMOUNT$ element daxilə köçürüldü.", @@ -4106,7 +4087,7 @@ "message": "Cəmi" }, "importWarning": { - "message": "Datanı $ORGANIZATION$ təşkilatına köçürürsünüz. Datanızı bu təşkilatın üzvləri ilə paylaşa bilərsiniz. Davam etmək istəyirsiniz?", + "message": "Veriləri $ORGANIZATION$ təşkilatına köçürürsünüz. Veriləriniz, bu təşkilatın üzvləri ilə paylaşıla bilər. Davam etmək istəyirsiniz?", "placeholders": { "organization": { "content": "$1", @@ -4127,13 +4108,13 @@ "message": "Duo-nu başlat" }, "importFormatError": { - "message": "Data doğru format edilməyib. Lütfən daxilə köçürmə faylınızı yoxlayıb yenidən sınayın." + "message": "Verilər düzgün format olunmayıb. Lütfən daxilə köçürmə faylınızı yoxlayıb yenidən sınayın." }, "importNothingError": { "message": "Heç nə daxilə köçürülmədi." }, "importEncKeyError": { - "message": "Xaricə köçürülən faylın şifrəsi açılarkən xəta baş verdi. Şifrələmə açarınız, datanı xaricə köçürmək üçün istifadə edilən şifrələmə açarı ilə uyuşmur." + "message": "Xaricə köçürülən faylın şifrəsi açılarkən xəta baş verdi. Şifrələmə açarınız, veriləri xaricə köçürmək üçün istifadə edilən şifrələmə açarı ilə uyuşmur." }, "invalidFilePassword": { "message": "Yararsız fayl parolu, lütfən xaricə köçürmə faylını yaradarkən daxil etdiyiniz parolu istifadə edin." @@ -4192,19 +4173,19 @@ "message": "Seyfi daxilə köçürməyi təsdiqlə" }, "confirmVaultImportDesc": { - "message": "Bu fayl parolla qorunur. Məlumatları daxilə köçürmək üçün fayl parolunu daxil edin." + "message": "Bu fayl parolla qorunur. Veriləri daxilə köçürmək üçün fayl parolunu daxil edin." }, "confirmFilePassword": { "message": "Fayl parolunu təsdiqlə" }, "exportSuccess": { - "message": "Seyf datası xaricə köçürüldü" + "message": "Seyf veriləri xaricə köçürüldü" }, "typePasskey": { "message": "Keçid açarı" }, "accessing": { - "message": "Müraciət edilir" + "message": "Erişilir" }, "loggedInExclamation": { "message": "Giriş edildi!" @@ -4270,7 +4251,7 @@ "message": "Çox faktorlu kimlik doğrulama ləğv edildi" }, "noLastPassDataFound": { - "message": "LastPass datası tapılmadı" + "message": "LastPass veriləri tapılmadı" }, "incorrectUsernameOrPassword": { "message": "Yanlış istifadəçi adı və ya parol" @@ -4376,7 +4357,7 @@ "message": "sahiblik edən" }, "useDeviceOrHardwareKey": { - "message": "Cihazınızı və ya avadanlıq açarınızı istifadə edin" + "message": "Cihazınızı və ya donanım açarınızı istifadə edin" }, "justOnce": { "message": "Yalnız bir dəfə" @@ -4700,7 +4681,7 @@ "description": "Used as a label to indicate that the user is the owner of an item." }, "contactYourOrgAdmin": { - "message": "Deaktiv edilmiş təşkilatlardakı elementlərə müraciət edilə bilməz. Kömək üçün təşkilatınızın sahibi ilə əlaqə saxlayın." + "message": "Deaktiv edilmiş təşkilatlardakı elementlərə erişilə bilməz. Kömək üçün təşkilatınızın sahibi ilə əlaqə saxlayın." }, "additionalInformation": { "message": "Əlavə məlumat" @@ -4757,13 +4738,13 @@ "message": "Mobil tətbiqi əldə et" }, "getTheMobileAppDesc": { - "message": "Bitwarden mobil tətbiqi ilə parollarınıza hər yerdən müraciət edin." + "message": "Bitwarden mobil tətbiqi ilə parollarınıza hər yerdən erişin." }, "getTheDesktopApp": { "message": "Masaüstü tətbiqi əldə et" }, "getTheDesktopAppDesc": { - "message": "Seyfinizə brauzer olmadan müraciət edin, sonra həm masaüstü tətbiqində, həm də brauzer uzantısında kilid açma prosesini sürətləndirmək üçün biometrik ilə kilid açma prosesini qurun." + "message": "Seyfinizə brauzer olmadan erişin, sonra həm masaüstü tətbiqində, həm də brauzer uzantısında kilid açma prosesini sürətləndirmək üçün biometrik ilə kilid açma prosesini qurun." }, "downloadFromBitwardenNow": { "message": "İndi bitwarden.com saytından endir" @@ -4916,7 +4897,7 @@ "message": "Yüklənir" }, "data": { - "message": "Data" + "message": "Veri" }, "passkeys": { "message": "Keçid açarı", @@ -4934,10 +4915,10 @@ "message": "Təyin et" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Yalnız bu kolleksiyalara müraciəti olan təşkilat üzvləri bu elementi görə biləcək." + "message": "Yalnız bu kolleksiyalara erişimi olan təşkilat üzvləri bu elementi görə biləcək." }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Yalnız bu kolleksiyalara müraciəti olan təşkilat üzvləri bu elementləri görə biləcək." + "message": "Yalnız bu kolleksiyalara erişimi olan təşkilat üzvləri bu elementləri görə biləcək." }, "bulkCollectionAssignmentWarning": { "message": "$TOTAL_COUNT$ element seçmisiniz. Düzəliş icazəniz olmadığı üçün $READONLY_COUNT$ elementi güncəlləyə bilməzsiniz.", @@ -4964,10 +4945,10 @@ "message": "Xana etiketi" }, "textHelpText": { - "message": "Təhlükəsizlik sualları kimi datalar üçün mətn xanalarını istifadə edin" + "message": "Təhlükəsizlik sualları kimi verilər üçün mətn xanalarını istifadə edin" }, "hiddenHelpText": { - "message": "Parol kimi həssas datalar üçün gizli xanaları istifadə edin" + "message": "Parol kimi həssas verilər üçün gizli xanaları istifadə edin" }, "checkBoxHelpText": { "message": "\"E-poçtu xatırla\" kimi formun təsdiq qutusunu avto-doldurmaq istəyirsinizsə təsdiq qutularını istifadə edin" @@ -5239,7 +5220,7 @@ "message": "Seyfinizin kilidini saniyələr ərzində açın" }, "unlockVaultDesc": { - "message": "Seyfinizə daha cəld müraciət etmək üçün kilid açma və bitmə vaxtı ayarlarını özəlləşdirə bilərsiniz." + "message": "Seyfinizə daha cəld erişmək üçün kilid açma və bitmə vaxtı ayarlarını özəlləşdirə bilərsiniz." }, "unlockPinSet": { "message": "PIN ilə kilid açma təyini" @@ -5491,7 +5472,7 @@ "message": "Bütün hesablarınız üçün güclü, unikal parollar yaratmaq və saxlamaq üçün yaradıcını istifadə edin." }, "secureDevices": { - "message": "Datanız, ehtiyacınız olan vaxt yanınızdadır" + "message": "Veriləriniz, ehtiyacınız olan vaxt yanınızdadır" }, "secureDevicesBody": { "message": "Bitwarden mobil, brauzer və masaüstü tətbiqləri ilə limitsiz cihaz arasında limitsiz parol saxlayın." @@ -5515,7 +5496,7 @@ "message": "Hazırkı səhifə üçün elementləri avto-doldur" }, "hasItemsVaultNudgeBodyTwo": { - "message": "Asan müraciət üçün elementləri sevimlilərə əlavə et" + "message": "Asan erişmək üçün elementləri sevimlilərə əlavə et" }, "hasItemsVaultNudgeBodyThree": { "message": "Seyfinizdə başqa bir şey axtarın" @@ -5551,13 +5532,13 @@ "message": "Kimliklərinizlə, uzun qeydiyyat və ya əlaqə xanalarını daha tez avtomatik doldurun." }, "newNoteNudgeTitle": { - "message": "Həssas datalarınızı güvənli şəkildə saxlayın" + "message": "Həssas verilərinizi güvənli şəkildə saxlayın" }, "newNoteNudgeBody": { - "message": "Notlarla, bankçılıq və ya sığorta təfsilatları kimi həssas dataları təhlükəsiz saxlayın." + "message": "Notlarla, bankçılıq və ya sığorta təfsilatları kimi həssas veriləri təhlükəsiz saxlayın." }, "newSshNudgeTitle": { - "message": "Gəlişdirici dostu SSH müraciəti" + "message": "Gəlişdirici dostu SSH erişimi" }, "newSshNudgeBodyOne": { "message": "Açarlarınızı saxlayın və sürətli, şifrələnmiş kimlik doğrulama üçün SSH agentinə bağlayın.", @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly brauzerinizdə dəstəklənmir və ya fəal deyil. WebAssembly, Bitwarden tətbiqini istifadə etmək üçün tələb olunur.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Daha çox göstər" + }, + "showLess": { + "message": "Daha az göstər" } } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index d7a1db3adc8..d6090123878 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 79e13cdb677..3b411cd088c 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Тази заявка вече не е активна." }, - "areYouTryingToAccessYourAccount": { - "message": "Опитвате ли се да получите достъп до акаунта си?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Вписването за $EMAIL$ на $DEVICE$ е одобрено", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Вие отказахте опит за вписване от друго устройство. Ако това наистина сте били Вие, опитайте да се впишете от устройството отново." - }, "loginRequestHasAlreadyExpired": { "message": "Заявката за вписване вече е изтекла." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly не е включено или не се поддържа от Вашия браузър. За ползването на приложението на Битуорден е необходимо WebAssembly да работи.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Показване на повече" + }, + "showLess": { + "message": "Показване на по-малко" } } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index a3c029fb963..55baca0dad2 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 8a94ba3e9e9..7ad375dabf0 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 42fb9c24003..3edfb98eba2 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 3f8dd2e2b48..916f765ea21 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -338,7 +338,7 @@ "message": "Pokračovat na bitwarden.com?" }, "bitwardenForBusiness": { - "message": "Bitwarden pro byznys" + "message": "Bitwarden pro podnikání" }, "bitwardenAuthenticator": { "message": "Autentifikátor Bitwarden" @@ -398,7 +398,7 @@ "message": "Název složky" }, "folderHintText": { - "message": "Vnořit složku přidáním názvu nadřazené složky následovaného znakem \"/\". Příklad: Sociální/Fóra" + "message": "Složku vnoříte přidáním názvu nadřazené složky následovaného znakem \"/\". Příklad: Sociální/Fóra" }, "noFoldersAdded": { "message": "Nebyly přidány žádné složky" @@ -407,7 +407,7 @@ "message": "Vytvořte složky pro organizaci Vašich položek trezoru" }, "deleteFolderPermanently": { - "message": "Opravdu chcete trvale smazat tuto složku?" + "message": "Opravdu chcete tuto složku trvale smazat?" }, "deleteFolder": { "message": "Smazat složku" @@ -531,10 +531,10 @@ "message": "Zahrnout číslice" }, "minNumbers": { - "message": "Minimální počet číslic" + "message": "Minimálně číslic" }, "minSpecial": { - "message": "Minimální počet speciálních znaků" + "message": "Minimálně speciálních znaků" }, "avoidAmbiguous": { "message": "Nepoužívat zaměnitelné znaky", @@ -1016,7 +1016,7 @@ "description": "This is the folder for uncategorized items" }, "enableAddLoginNotification": { - "message": "Ptát se na přidání přihlášení" + "message": "Zeptat se na přidání přihlášení" }, "vaultSaveOptionsTitle": { "message": "Uložit do voleb trezoru" @@ -1049,7 +1049,7 @@ "message": "Klepněte na položky pro automatické vyplnění v zobrazení trezoru" }, "clickToAutofill": { - "message": "Klepněte na položky v návrhu automatického vyplňování pro vyplnění" + "message": "Klepnout na položky v návrhu automatického vyplňování pro vyplnění" }, "clearClipboard": { "message": "Vymazat schránku", @@ -1305,7 +1305,7 @@ "message": "Sdílené" }, "bitwardenForBusinessPageDesc": { - "message": "Bitwarden pro bvyznys Vám umožňuje sdílet položky v trezoru s ostatními prostřednictvím organizace. Více informací naleznete na bitwarden.com." + "message": "Bitwarden pro podnikání Vám umožňuje sdílet položky v trezoru s ostatními prostřednictvím organizace. Více informací naleznete na bitwarden.com." }, "moveToOrganization": { "message": "Přesunout do organizace" @@ -1660,7 +1660,7 @@ "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Automaticky vyplnit údaje při načtení stránky" + "message": "Automatické vyplnění údajů při načtení stránky" }, "enableAutoFillOnPageLoad": { "message": "Automaticky vyplnit údaje při načtení stránky" @@ -1678,7 +1678,7 @@ "message": "Více informací o automatickém vyplňování" }, "defaultAutoFillOnPageLoad": { - "message": "Výchozí nastavení automatického vyplňování pro položky přihlášení" + "message": "Výchozí nastavení autom. vyplňování" }, "defaultAutoFillOnPageLoadDesc": { "message": "Můžete vypnout automatické vyplňování při načtení stránky pro jednotlivé přihlašovací položky v zobrazení pro úpravu položky." @@ -1690,10 +1690,10 @@ "message": "Použít výchozí nastavení" }, "autoFillOnPageLoadYes": { - "message": "Automatické vyplnění při načtení stránky" + "message": "Automatické vyplnění při načtení" }, "autoFillOnPageLoadNo": { - "message": "Nevyplňovat automaticky při načtení stránky" + "message": "Nevyplňovat automaticky při načtení" }, "commandOpenPopup": { "message": "Otevřít vyskakovací okno trezoru" @@ -1851,7 +1851,7 @@ "message": "Slečna" }, "dr": { - "message": "MUDr." + "message": "Dr." }, "mx": { "message": "Neutrální" @@ -1875,7 +1875,7 @@ "message": "Společnost" }, "ssn": { - "message": "Číslo sociálního pojištění" + "message": "Rodné číslo" }, "passportNumber": { "message": "Číslo cestovního pasu" @@ -2099,7 +2099,7 @@ "message": "Nic k zobrazení" }, "nothingGeneratedRecently": { - "message": "Nedávno jste nic nevygenerovali" + "message": "Ještě jste nic nevygenerovali" }, "remove": { "message": "Odebrat" @@ -2233,7 +2233,7 @@ "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "pro vytvoření silného jedinečného hesla", + "message": "pro vytvoření silného jedinečného hesla.", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "vaultCustomization": { @@ -2685,7 +2685,7 @@ "message": "Změny v zablokovaných doménách byly uloženy" }, "excludedDomainsSavedSuccess": { - "message": "Vyloučené změny domény byly uloženy" + "message": "Změny vyloučené domény byly uloženy" }, "limitSendViews": { "message": "Omezit zobrazení" @@ -2911,7 +2911,7 @@ "message": "Došlo k chybě při ukládání datumu smzání a vypršení platnosti." }, "hideYourEmail": { - "message": "Skryje Vaši e-mailovou adresu před zobrazením." + "message": "Skrýt Vaši e-mailovou adresu" }, "passwordPrompt": { "message": "Zeptat se znovu na hlavní heslo" @@ -3228,7 +3228,7 @@ "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Vyberte doménu, která je podporována vybranou službou", + "message": "Vyberte doménu, která je podporována vybranou službou.", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Tento požadavek již není platný." }, - "areYouTryingToAccessYourAccount": { - "message": "Pokoušíte se získat přístup k Vašemu účtu?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Přihlášení bylo potvrzeno z $EMAIL$ pro $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Pokus o přihlášení byl zamítnut z jiného zařízení. Pokud jste to opravdu Vy, zkuste se znovu přihlásit do zařízení." - }, "loginRequestHasAlreadyExpired": { "message": "Požadavek na přihlášení již vypršel." }, @@ -4151,7 +4132,7 @@ "message": "Zvolte sbírku" }, "importTargetHint": { - "message": "Pokud chcete obsah importovaného souboru přesunout do složky $DESTINATION$, vyberte tuto volbu", + "message": "Pokud chcete obsah importovaného souboru přesunout do: \"$DESTINATION$\", vyberte tuto volbu", "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", "placeholders": { "destination": { @@ -4426,7 +4407,7 @@ "description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page" }, "confirmContinueToHelpCenterPasswordManagementContent": { - "message": "Změňte nastavení automatického vyplňování a správy hesel.", + "message": "Změna nastavení automatického vyplňování a nastavení správy hesel.", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser password management settings" }, "confirmContinueToHelpCenterKeyboardShortcutsContent": { @@ -4434,7 +4415,7 @@ "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser keyboard shortcut settings" }, "confirmContinueToBrowserPasswordManagementSettingsContent": { - "message": "Změňte nastavení automatického vyplňování a správy hesel.", + "message": "Změna nastavení automatického vyplňování a nastavení správy hesel.", "description": "Body content for dialog which asks if the user wants to proceed to the browser's password management settings page" }, "confirmContinueToBrowserKeyboardShortcutSettingsContent": { @@ -4964,13 +4945,13 @@ "message": "Popis pole" }, "textHelpText": { - "message": "Použijte textová pole pro data jako bezpečnostní otázky" + "message": "Použijte textová pole pro data (jako např. bezpečnostní otázky)." }, "hiddenHelpText": { - "message": "Použijte skrytá pole pro citlivá data, jako je heslo" + "message": "Použijte skrytá pole pro citlivá data, jako je heslo." }, "checkBoxHelpText": { - "message": "Použijte zaškrtávací políčka, pokud chcete automaticky vyplnit zaškrtávací políčko formuláře (např. pro zapamatování e-mailu)" + "message": "Použijte zaškrtávací políčka, pokud chcete automaticky zvolit zaškrtávací políčko formuláře (např. pro zapamatování e-mailu)." }, "linkedHelpText": { "message": "Použijte propojené pole, pokud máte problémy s automatickým vyplňováním na konkrétní webové stránce." @@ -5140,7 +5121,7 @@ "message": "Zobrazit akce rychlé kopie v trezoru" }, "systemDefault": { - "message": "Systémový výchozí" + "message": "Výchozí systémový" }, "enterprisePolicyRequirementsApplied": { "message": "Na toto nastavení byly uplatněny požadavky podnikových zásad" @@ -5197,7 +5178,7 @@ "message": "Položky, které smažete, se zde zobrazí a budou trvale smazány po 30 dnech." }, "trashWarning": { - "message": "Položky, které byly v koši déle než 30 dní, budou automaticky smazány." + "message": "Položky, které byly v koši déle než 30 dnů, budou automaticky smazány." }, "restore": { "message": "Obnovit" @@ -5413,10 +5394,10 @@ "message": "Šířka rozšíření" }, "wide": { - "message": "Šířka" + "message": "Široké" }, "extraWide": { - "message": "Extra široký" + "message": "Extra široké" }, "sshKeyWrongPassword": { "message": "Zadané heslo není správné." @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly není ve Vašem prohlížeči podporováno nebo není povoleno. WebAssembly je vyžadováno pro použití aplikace Bitwarden.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Zobrazit více" + }, + "showLess": { + "message": "Zobrazit méně" } } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 307373da9aa..8307b994fe0 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 4b6da81a994..0ff87e6e14f 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 9ef82a6d5ae..ffaf490fbf8 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Diese Anfrage ist nicht mehr gültig." }, - "areYouTryingToAccessYourAccount": { - "message": "Versuchst du auf dein Konto zuzugreifen?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Anmeldung von $EMAIL$ auf $DEVICE$ bestätigt", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Du hast einen Anmeldeversuch von einem anderen Gerät abgelehnt. Wenn du das wirklich warst, versuche dich erneut mit dem Gerät anzumelden." - }, "loginRequestHasAlreadyExpired": { "message": "Anmeldeanfrage ist bereits abgelaufen." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly wird von deinem Browser nicht unterstützt oder ist nicht aktiviert. WebAssembly wird benötigt, um die Bitwarden-App nutzen zu können.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Mehr anzeigen" + }, + "showLess": { + "message": "Weniger anzeigen" } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index fa4f3ac0f3c..3d46b63ec45 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -84,7 +84,7 @@ "message": "Υπόδειξη κύριου κωδικού πρόσβασης (προαιρετικό)" }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "Βαθμολογία ισχύς κωδικού πρόσβασης $SCORE$", "placeholders": { "score": { "content": "$1", @@ -468,13 +468,13 @@ "message": "Ο κωδικός δημιουργήθηκε" }, "passphraseGenerated": { - "message": "Passphrase generated" + "message": "Το συνθηματικό δημιουργήθηκε" }, "usernameGenerated": { - "message": "Username generated" + "message": "Το όνομα χρήστη δημιουργήθηκε" }, "emailGenerated": { - "message": "Email generated" + "message": "Το email δημιουργήθηκε" }, "regeneratePassword": { "message": "Επαναδημιουργία κωδικού πρόσβασης" @@ -548,7 +548,7 @@ "message": "Αναζήτηση στο vault" }, "resetSearch": { - "message": "Reset search" + "message": "Επαναφορά αναζήτησης" }, "edit": { "message": "Επεξεργασία" @@ -659,10 +659,10 @@ "message": "Το πρόγραμμα περιήγησης ιστού δεν υποστηρίζει εύκολη αντιγραφή πρόχειρου. Αντιγράψτε το με το χέρι αντ'αυτού." }, "verifyYourIdentity": { - "message": "Verify your identity" + "message": "Επαλήθευση ταυτότητας" }, "weDontRecognizeThisDevice": { - "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + "message": "Δεν αναγνωρίζουμε αυτή τη συσκευή. Εισάγετε τον κωδικό που στάλθηκε στο email σας για να επαληθεύσετε την ταυτότητά σας." }, "continueLoggingIn": { "message": "Continue logging in" @@ -875,22 +875,22 @@ "message": "Σύνδεση στο Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "Εισάγετε τον κωδικό που στάλθηκε στο email σας" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "Εισαγάγετε τον κωδικό μιας χρήσης από την εφαρμογή αυθεντικοποίησης" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "Πιέστε το YubiKey σας για ταυτοποίηση" }, "duoTwoFactorRequiredPageSubtitle": { - "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + "message": "Απαιτείται σύνδεση Duo δύο βημάτων για το λογαριασμό σας. Ακολουθήστε τα παρακάτω βήματα για να ολοκληρώσετε τη σύνδεση." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Follow the steps below to finish logging in." + "message": "Ακολουθήστε τα παρακάτω βήματα για να ολοκληρώσετε τη σύνδεση." }, "followTheStepsBelowToFinishLoggingInWithSecurityKey": { - "message": "Follow the steps below to finish logging in with your security key." + "message": "Ακολουθήστε τα παρακάτω βήματα για να ολοκληρώσετε τη σύνδεση με το κλειδί ασφαλείας σας." }, "restartRegistration": { "message": "Επανεκκίνηση εγγραφής" @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index ad933c24875..f8dde376b35 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5573,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index a70fbd85123..8acf75e2ee5 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 39de06249fc..281caeac0d5 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 3b681054abc..0e1185148df 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -197,7 +197,7 @@ "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { - "message": "Autorrellenar" + "message": "Autocompletar" }, "autoFillLogin": { "message": "Autocompletar inicio de sesión" @@ -548,7 +548,7 @@ "message": "Buscar en caja fuerte" }, "resetSearch": { - "message": "Reset search" + "message": "Restablecer búsqueda" }, "edit": { "message": "Editar" @@ -1456,7 +1456,7 @@ "message": "Copiar TOTP automáticamente" }, "disableAutoTotpCopyDesc": { - "message": "Si tu entrada tiene una clave de autenticación adjunta, el código de verificación TOTP es copiado automáticamente al portapapeles cuando autorellenas una entrada." + "message": "Si tu entrada tiene una clave de autenticación, copiar el código de verificación TOTP a tu portapapeles cuando autocompletes el inicio de sesión." }, "enableAutoBiometricsPrompt": { "message": "Pedir datos biométricos al ejecutar" @@ -1663,25 +1663,25 @@ "message": "Autocompletar al cargar la página" }, "enableAutoFillOnPageLoad": { - "message": "Habilitar autorrellenar al cargar la página" + "message": "Autocompletar al cargar la página" }, "enableAutoFillOnPageLoadDesc": { - "message": "Si se detecta un formulario, realizar automáticamente un autorellenado cuando la web cargue." + "message": "Si se detecta un formulario de inicio de sesión, autocompletar cuando cargue la página web." }, "experimentalFeature": { - "message": "Los sitios web vulnerados o no confiables pueden explotar el autorelleno al cargar la página." + "message": "Los sitios web vulnerados o no confiables pueden explotar el autocompletado al cargar la página." }, "learnMoreAboutAutofillOnPageLoadLinkText": { "message": "Más información sobre riesgos" }, "learnMoreAboutAutofill": { - "message": "Más información sobre el relleno automático" + "message": "Más información sobre el autocompletado" }, "defaultAutoFillOnPageLoad": { "message": "Configuración de autorrelleno por defecto para elementos de inicio de sesión" }, "defaultAutoFillOnPageLoadDesc": { - "message": "Después de activar el autorelleno en Carga de página, puede activar o desactivar la función para entradas individuales. Esta es la configuración predeterminada para elementos de inicio de sesión que no están configurados por separado." + "message": "Puedes desactivar el autocompletado al cargar la página para elementos individuales de inicio de sesión desde la vista de Edición del elemento." }, "itemAutoFillOnPageLoad": { "message": "Auto-relleno en carga de página (si está habilitado en opciones)" @@ -2281,16 +2281,16 @@ "message": "Confirmación de la acción del tiempo de espera" }, "autoFillAndSave": { - "message": "Autorellenar y guardar" + "message": "Autocompletar y guardar" }, "fillAndSave": { "message": "Rellenar y guardar" }, "autoFillSuccessAndSavedUri": { - "message": "Objeto autorellenado y URI guardada" + "message": "Elemento autocompletado y URI guardada" }, "autoFillSuccess": { - "message": "Objeto autorellenado" + "message": "Elemento autocompletado " }, "insecurePageWarning": { "message": "Atención: Esta es una página HTTP no segura, y cualquier información que envíes puede ser vista y cambiada por otros. Este inicio de sesión fue guardado originalmente en una página segura (HTTPS)." @@ -2528,7 +2528,7 @@ "message": "El autorrelleno y otras funcionalidades relacionadas no se ofrecerán para estos sitios web. Debe actualizar la página para que los cambios surtan efecto." }, "autofillBlockedNoticeV2": { - "message": "Autorelleno está desactivado para este sitio web." + "message": "Autocompletado está bloqueado para este sitio web." }, "autofillBlockedNoticeGuidance": { "message": "Cambia esto en los ajustes" @@ -2709,7 +2709,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Send details", + "message": "Detalles del Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { @@ -2932,7 +2932,7 @@ "message": "Debes verificar tu correo electrónico para usar esta función. Puedes verificar tu correo electrónico en la caja fuerte web." }, "masterPasswordSuccessfullySet": { - "message": "Master password successfully set" + "message": "Contraseña maestra establecida correctamente" }, "updatedMasterPassword": { "message": "Contraseña maestra actualizada" @@ -3106,7 +3106,7 @@ "message": "Exportando caja fuerte personal" }, "exportingIndividualVaultDescription": { - "message": "Solo se exportarán los elementos individuales de la caja fuerte asociados con $EMAIL$. Los elementos de la bóveda de la organización no se incluirán. Solo se exportará la información de los elementos individuales y no incluirá adjuntos asociados.", + "message": "Solo se exportarán los elementos individuales de la caja fuerte asociados a $EMAIL$. Los elementos de la caja fuerte de la organización no se incluirán. Solo se exportará la información de los elementos de la caja fuerte y no se incluirá adjuntos asociados.", "placeholders": { "email": { "content": "$1", @@ -3483,13 +3483,13 @@ "message": "You denied a login attempt from another device. If this was you, try to log in with the device again." }, "device": { - "message": "Device" + "message": "Dispositivo" }, "loginStatus": { "message": "Login status" }, "masterPasswordChanged": { - "message": "Master password saved" + "message": "Contraseña maestra guardada" }, "exposedMasterPassword": { "message": "Contraseña maestra comprometida" @@ -3525,7 +3525,7 @@ "message": "Las políticas de su organización han activado autocompletar al cargar la página." }, "howToAutofill": { - "message": "Cómo autorellenar" + "message": "Cómo autocompletar" }, "autofillSelectInfoWithCommand": { "message": "Selecciona un elemento de esta pantalla, usa el acceso directo $COMMAND$ o explora otras opciones en ajustes.", @@ -3585,28 +3585,28 @@ "message": "Remember this device to make future logins seamless" }, "manageDevices": { - "message": "Manage devices" + "message": "Gestionar dispositivos" }, "currentSession": { - "message": "Current session" + "message": "Sesión actual" }, "mobile": { - "message": "Mobile", + "message": "Móvil", "description": "Mobile app" }, "extension": { - "message": "Extension", + "message": "Extensión", "description": "Browser extension/addon" }, "desktop": { - "message": "Desktop", + "message": "Escritorio", "description": "Desktop app" }, "webVault": { - "message": "Web vault" + "message": "Caja fuerte Web" }, "webApp": { - "message": "Web app" + "message": "Aplicación Web" }, "cli": { "message": "CLI" @@ -3616,22 +3616,22 @@ "description": "Software Development Kit" }, "requestPending": { - "message": "Request pending" + "message": "Solicitud pendiente" }, "firstLogin": { - "message": "First login" + "message": "Primer inicio de sesión" }, "trusted": { - "message": "Trusted" + "message": "De confianza" }, "needsApproval": { - "message": "Needs approval" + "message": "Necesita aprobación" }, "devices": { - "message": "Devices" + "message": "Dispositivos" }, "accessAttemptBy": { - "message": "Access attempt by $EMAIL$", + "message": "Intento de acceso de $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -3640,16 +3640,16 @@ } }, "confirmAccess": { - "message": "Confirm access" + "message": "Confirmar acceso" }, "denyAccess": { - "message": "Deny access" + "message": "Denegar acceso" }, "time": { "message": "Time" }, "deviceType": { - "message": "Device Type" + "message": "Tipo de Dispositivo" }, "loginRequest": { "message": "Login request" @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5583,14 +5564,20 @@ "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyAria": { - "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "message": "Crea fácilmente contraseñas seguras y únicas haciendo clic en el botón Generar contraseña para ayudarte a mantener tus inicios de sesión seguros.", "description": "Aria label for the body content of the generator nudge" }, "noPermissionsViewPage": { "message": "No tienes permisos para ver esta página. Intenta iniciar sesión con otra cuenta." }, "wasmNotSupported": { - "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", + "message": "WebAssembly no está soportado por tu navegador o no está habilitado. WebAssembly es necesario para usar la aplicación de Bitwarden.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 8b61aa70a60..09e77bd361f 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 73bd992dacb..1cd1439e972 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 18afcb775f9..cfae87fd1a5 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly در مرورگر شما پشتیبانی نمی‌شود یا فعال نیست. برای استفاده از برنامه Bitwarden، فعال بودن WebAssembly الزامی است.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 894b50b5273..ffd5caea1d3 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Tämä pyyntö ei ole enää voimassa." }, - "areYouTryingToAccessYourAccount": { - "message": "Yritätkö kirjautua tilillesi?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Kirjautuminen vahvistettu tunnuksella $EMAIL$ laitteella $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Estit toisen laitteen lähettämän kirjautumispyynnön. Jos kuitenkin tunnistit kirjautumisyrityksen, suorita kirjautuminen uudelleen." - }, "loginRequestHasAlreadyExpired": { "message": "Kirjautumispyyntö on jo vanhentunut." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly ei ole tuettu selaimessasi tai se ei ole käytössä. WebAssembly vaaditaan, jotta voi käyttää Bitwardenia.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index dfc8d65cfd9..1682d21aa7f 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index c35f4ca4bb9..1a4f466a41c 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Cette demande n'est plus valide." }, - "areYouTryingToAccessYourAccount": { - "message": "Essayez-vous d'accéder à votre compte ?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Connexion confirmée pour $EMAIL$ sur $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Vous avez refusé une tentative de connexion depuis un autre appareil. Si c'était vraiment vous, essayez de vous reconnecter avec l'appareil." - }, "loginRequestHasAlreadyExpired": { "message": "La demande de connexion a déjà expiré." }, @@ -4601,7 +4582,7 @@ } }, "copyFieldCipherName": { - "message": "Copiez $FIELD$, $CIPHERNAME$", + "message": "Copier $FIELD$, $CIPHERNAME$", "description": "Title for a button that copies a field value to the clipboard.", "placeholders": { "field": { @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly n'est pas pris en charge sur votre navigateur ou n'est pas activé. WebAssembly est requis pour utiliser l'application Bitwarden.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index c7d47b61209..0698191f53e 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index c13bcad10c6..96dc5215051 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index cc89f9cc2dd..35d7e8b2911 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 97bd79f2950..d38461ab553 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Ovaj zahtjev više nije valjan." }, - "areYouTryingToAccessYourAccount": { - "message": "Pokušavaš li pristupiti svom računu?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Prijava za $EMAIL$ potvrđena na uređaju $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Odbijena je prijava na drugom uređaju. Ako si ovo stvarno ti, pokušaj se ponovno prijaviti uređajem." - }, "loginRequestHasAlreadyExpired": { "message": "Zahtjev za prijavu je već istekao." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly ili nije podržan ili nije omogućen u tvom pregledniku. WebAssembly je potreban za korištenje Bitwarden aplikacije.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Prikaži više" + }, + "showLess": { + "message": "Pokaži manje" } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index cfe79ea4b20..a0fc88e047a 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "A kérés a továbbiakban már nem érvényes." }, - "areYouTryingToAccessYourAccount": { - "message": "A fiókhoz próbálunk hozzáférni?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "A bejelelentketés $EMAIL$ email címmel megerősítésre került $DEVICE$ eszközön.", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Megtagadásra került egy bejelentkezési kísérletet egy másik eszközről. Ha valóban mi voltunk, próbáljunk meg újra bejelentkezni az eszközzel." - }, "loginRequestHasAlreadyExpired": { "message": "A bejelentkezési kérés már lejárt." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "A WebAssembly nem támogatott a böngészőben vagy nincs engedélyezve. A WebAssembly szükséges a Bitwarden alkalmazás használatához.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Több megjelenítése" + }, + "showLess": { + "message": "Kevesebb megjelenítése" } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 4ce58ee7618..297b2d458e1 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -20,7 +20,7 @@ "message": "Undangan diterima" }, "createAccount": { - "message": "Buat Akun" + "message": "Buat akun" }, "newToBitwarden": { "message": "Baru menggunakan Bitwarden?" @@ -548,7 +548,7 @@ "message": "Cari brankas" }, "resetSearch": { - "message": "Reset search" + "message": "Atur ulang pencarian" }, "edit": { "message": "Edit" @@ -1177,10 +1177,10 @@ "description": "Detailed error message shown when saving login details fails." }, "changePasswordWarning": { - "message": "After changing your password, you will need to log in with your new password. Active sessions on other devices will be logged out within one hour." + "message": "Setelah mengubah kata sandi, Anda perlu masuk lagi dengan kata sandi baru. Sesi aktif di perangkat lain akan keluar dalam satu jam." }, "accountRecoveryUpdateMasterPasswordSubtitle": { - "message": "Change your master password to complete account recovery." + "message": "Ubah kata sandi utama Anda untuk menyelesaikan pemulihan akun." }, "enableChangedPasswordNotification": { "message": "Tanyakan untuk memperbarui masuk yang sudah ada" @@ -1833,7 +1833,7 @@ "message": "Kode Keamanan" }, "cardNumber": { - "message": "card number" + "message": "nomor kartu" }, "ex": { "message": "mis." @@ -2500,10 +2500,10 @@ "message": "Sebuah kebijakan organisasi telah menghalangi mengimpor benda-benda ke brankas pribadi Anda." }, "restrictCardTypeImport": { - "message": "Cannot import card item types" + "message": "Tidak dapat meingpor jenis item kartu" }, "restrictCardTypeImportDesc": { - "message": "A policy set by 1 or more organizations prevents you from importing cards to your vaults." + "message": "Kebijakan yang diatur 1 atau lebih organisasi tidak mengizinkan Anda mengimpor kartu ke brankas." }, "domainsTitle": { "message": "Domain", @@ -2932,7 +2932,7 @@ "message": "Anda harus memverifikasi email Anda untuk menggunakan fitur ini. Anda dapat memverifikasi email Anda di brankas web." }, "masterPasswordSuccessfullySet": { - "message": "Master password successfully set" + "message": "Kata sandi utama berhasil diatur" }, "updatedMasterPassword": { "message": "Kata Sandi Utama Telah Diperbarui" @@ -3467,7 +3467,7 @@ "message": "Permintaan terkirim" }, "loginRequestApprovedForEmailOnDevice": { - "message": "Login request approved for $EMAIL$ on $DEVICE$", + "message": "Permintaan masuk untuk $EMAIL$ di $DEVICE$ diizinkan", "placeholders": { "email": { "content": "$1", @@ -3480,16 +3480,16 @@ } }, "youDeniedLoginAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this was you, try to log in with the device again." + "message": "Anda menolak percobaan masuk dari perangkat lain. Jika itu adalah Anda, mohon coba masuk kembali di perangkat tersebut." }, "device": { - "message": "Device" + "message": "Perangkat" }, "loginStatus": { - "message": "Login status" + "message": "Status masuk" }, "masterPasswordChanged": { - "message": "Master password saved" + "message": "Sandi utama disimpan" }, "exposedMasterPassword": { "message": "Kata Sandi Utama yang Terpapar" @@ -3585,17 +3585,17 @@ "message": "Ingat perangkat ini untuk membuat login berikutnya lebih lancar" }, "manageDevices": { - "message": "Manage devices" + "message": "Kelola perangkat" }, "currentSession": { - "message": "Current session" + "message": "Sesi saat ini" }, "mobile": { - "message": "Mobile", + "message": "Seluler", "description": "Mobile app" }, "extension": { - "message": "Extension", + "message": "Pengaya", "description": "Browser extension/addon" }, "desktop": { @@ -3603,35 +3603,35 @@ "description": "Desktop app" }, "webVault": { - "message": "Web vault" + "message": "Brankas web" }, "webApp": { - "message": "Web app" + "message": "Aplikasi web" }, "cli": { - "message": "CLI" + "message": "Antarmuka Baris Perintah" }, "sdk": { "message": "SDK", "description": "Software Development Kit" }, "requestPending": { - "message": "Request pending" + "message": "Permintaan tertunda" }, "firstLogin": { - "message": "First login" + "message": "Masuk pertama" }, "trusted": { - "message": "Trusted" + "message": "Terpercaya" }, "needsApproval": { - "message": "Needs approval" + "message": "Perlu persetujuan" }, "devices": { - "message": "Devices" + "message": "Perangkat" }, "accessAttemptBy": { - "message": "Access attempt by $EMAIL$", + "message": "Percobaan akses oleh $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -3640,50 +3640,31 @@ } }, "confirmAccess": { - "message": "Confirm access" + "message": "Izinkan akses" }, "denyAccess": { - "message": "Deny access" + "message": "Tolak akses" }, "time": { - "message": "Time" + "message": "Waktu" }, "deviceType": { - "message": "Device Type" + "message": "Jenis Perangkat" }, "loginRequest": { - "message": "Login request" + "message": "Permintaan masuk" }, "thisRequestIsNoLongerValid": { - "message": "This request is no longer valid." - }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + "message": "Permintaan ini tidak lagi valid." }, "loginRequestHasAlreadyExpired": { - "message": "Login request has already expired." + "message": "Permintaan masuk kedaluwarsa." }, "justNow": { - "message": "Just now" + "message": "Baru saja" }, "requestedXMinutesAgo": { - "message": "Requested $MINUTES$ minutes ago", + "message": "Diminta $MINUTES$ menit yang lalu", "placeholders": { "minutes": { "content": "$1", @@ -3713,10 +3694,10 @@ "message": "Minta persetujuan admin" }, "unableToCompleteLogin": { - "message": "Unable to complete login" + "message": "Gagal menyelesaikan proses masuk" }, "loginOnTrustedDeviceOrAskAdminToAssignPassword": { - "message": "You need to log in on a trusted device or ask your administrator to assign you a password." + "message": "Anda harus masuk di perangkat terpercaya atau meminta administrator untuk membuat kata sandi Anda." }, "ssoIdentifierRequired": { "message": "Pengenal SSO organisasi diperlukan." @@ -4398,23 +4379,23 @@ "description": "Label indicating the most common import formats" }, "uriMatchDefaultStrategyHint": { - "message": "URI match detection is how Bitwarden identifies autofill suggestions.", + "message": "Bitwarden mengenali saran isi otomatis menggunakan deteksi kecocokan URI.", "description": "Explains to the user that URI match detection determines how Bitwarden suggests autofill options, and clarifies that this default strategy applies when no specific match detection is set for a login item." }, "regExAdvancedOptionWarning": { - "message": "\"Regular expression\" is an advanced option with increased risk of exposing credentials.", + "message": "\"Ekspresi regular\" adalah opsi lanjutan dengan risiko lebih besar untuk menyingkapkan identitas anda.", "description": "Content for dialog which warns a user when selecting 'regular expression' matching strategy as a cipher match strategy" }, "startsWithAdvancedOptionWarning": { - "message": "\"Starts with\" is an advanced option with increased risk of exposing credentials.", + "message": "\"Bermula dengan\" adalah opsi lanjutan dengan risiko lebih besar untuk menyingkapkan identitas anda.", "description": "Content for dialog which warns a user when selecting 'starts with' matching strategy as a cipher match strategy" }, "uriMatchWarningDialogLink": { - "message": "More about match detection", + "message": "Ketahui lebih tentang deteksi kecocokan", "description": "Link to match detection docs on warning dialog for advance match strategy" }, "uriAdvancedOption": { - "message": "Advanced options", + "message": "Opsi lanjutan", "description": "Advanced option placeholder for uri option component" }, "confirmContinueToBrowserSettingsTitle": { @@ -4601,7 +4582,7 @@ } }, "copyFieldCipherName": { - "message": "Copy $FIELD$, $CIPHERNAME$", + "message": "Salin $FIELD$, $CIPHERNAME$", "description": "Title for a button that copies a field value to the clipboard.", "placeholders": { "field": { @@ -5245,7 +5226,7 @@ "message": "PIN untuk membuka telah diatur" }, "unlockWithBiometricSet": { - "message": "Unlock with biometrics set" + "message": "Biometrik untuk membuka telah diatur" }, "authenticating": { "message": "Sedang memeriksa keaslian" @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly tidak didukung atau tidak dinyalakan oleh peramban Anda. Web Assembly diperlukan untuk menggunakan aplikasi Bitwarden.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 164d46bcec5..4e1c1d63e50 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -548,7 +548,7 @@ "message": "Cerca nella cassaforte" }, "resetSearch": { - "message": "Reset search" + "message": "Svuota ricerca" }, "edit": { "message": "Modifica" @@ -1177,10 +1177,10 @@ "description": "Detailed error message shown when saving login details fails." }, "changePasswordWarning": { - "message": "After changing your password, you will need to log in with your new password. Active sessions on other devices will be logged out within one hour." + "message": "Dopo aver cambiato la password, dovrai accedere con quella nuova. Le sessioni attive su altri dispositivi saranno disconnesse entro un'ora." }, "accountRecoveryUpdateMasterPasswordSubtitle": { - "message": "Change your master password to complete account recovery." + "message": "Cambia la password principale per completare il recupero dell'account." }, "enableChangedPasswordNotification": { "message": "Chiedi di aggiornare il login esistente" @@ -1833,7 +1833,7 @@ "message": "Codice di sicurezza" }, "cardNumber": { - "message": "card number" + "message": "numero carta" }, "ex": { "message": "es." @@ -2932,7 +2932,7 @@ "message": "Devi verificare la tua email per usare questa funzionalità. Puoi verificare la tua email nella cassaforte web." }, "masterPasswordSuccessfullySet": { - "message": "Master password successfully set" + "message": "Password principale impostata correttamente" }, "updatedMasterPassword": { "message": "Password principale aggiornata" @@ -3467,7 +3467,7 @@ "message": "Richiesta inviata" }, "loginRequestApprovedForEmailOnDevice": { - "message": "Login request approved for $EMAIL$ on $DEVICE$", + "message": "Richiesta di accesso approvata per $EMAIL$ su $DEVICE$", "placeholders": { "email": { "content": "$1", @@ -3480,16 +3480,16 @@ } }, "youDeniedLoginAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this was you, try to log in with the device again." + "message": "Hai negato un tentativo di accesso da un altro dispositivo. Se eri davvero tu, prova di nuovo ad accedere con il dispositivo." }, "device": { - "message": "Device" + "message": "Dispositivo" }, "loginStatus": { - "message": "Login status" + "message": "Stato login" }, "masterPasswordChanged": { - "message": "Master password saved" + "message": "Password principale salvata" }, "exposedMasterPassword": { "message": "Password principale violata" @@ -3585,17 +3585,17 @@ "message": "Ricorda questo dispositivo per rendere immediati i futuri accessi" }, "manageDevices": { - "message": "Manage devices" + "message": "Gestisci dispositivi" }, "currentSession": { - "message": "Current session" + "message": "Sessione in corso" }, "mobile": { "message": "Mobile", "description": "Mobile app" }, "extension": { - "message": "Extension", + "message": "Estensione", "description": "Browser extension/addon" }, "desktop": { @@ -3603,35 +3603,35 @@ "description": "Desktop app" }, "webVault": { - "message": "Web vault" + "message": "Cassaforte Web" }, "webApp": { - "message": "Web app" + "message": "Bitwarden Web" }, "cli": { - "message": "CLI" + "message": "Linea di comando" }, "sdk": { "message": "SDK", "description": "Software Development Kit" }, "requestPending": { - "message": "Request pending" + "message": "Richiesta in attesa" }, "firstLogin": { - "message": "First login" + "message": "Primo accesso" }, "trusted": { - "message": "Trusted" + "message": "Attendibile" }, "needsApproval": { - "message": "Needs approval" + "message": "In attesa di approvazione" }, "devices": { - "message": "Devices" + "message": "Dispositivi" }, "accessAttemptBy": { - "message": "Access attempt by $EMAIL$", + "message": "Tentativo di accesso da $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -3640,50 +3640,31 @@ } }, "confirmAccess": { - "message": "Confirm access" + "message": "Conferma l'accesso" }, "denyAccess": { - "message": "Deny access" + "message": "Nega l'accesso" }, "time": { - "message": "Time" + "message": "Ora" }, "deviceType": { - "message": "Device Type" + "message": "Tipo di dispositivo" }, "loginRequest": { - "message": "Login request" + "message": "Richiesta di accesso" }, "thisRequestIsNoLongerValid": { - "message": "This request is no longer valid." - }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + "message": "Questa richiesta non è più valida." }, "loginRequestHasAlreadyExpired": { - "message": "Login request has already expired." + "message": "La richiesta di accesso è scaduta." }, "justNow": { - "message": "Just now" + "message": "Pochi secondi fa" }, "requestedXMinutesAgo": { - "message": "Requested $MINUTES$ minutes ago", + "message": "Richiesta: $MINUTES$ minuti fa", "placeholders": { "minutes": { "content": "$1", @@ -3713,10 +3694,10 @@ "message": "Richiedi approvazione dell'amministratore" }, "unableToCompleteLogin": { - "message": "Unable to complete login" + "message": "Impossibile completare l'accesso" }, "loginOnTrustedDeviceOrAskAdminToAssignPassword": { - "message": "You need to log in on a trusted device or ask your administrator to assign you a password." + "message": "È necessario accedere da un dispositivo attendibile oppure chiedere all'amministratore l'assegnazione di una password." }, "ssoIdentifierRequired": { "message": "Identificatore SSO dell'organizzazione obbligatorio." @@ -4398,23 +4379,23 @@ "description": "Label indicating the most common import formats" }, "uriMatchDefaultStrategyHint": { - "message": "URI match detection is how Bitwarden identifies autofill suggestions.", + "message": "La corrispondenza URL è il metodo predefinito per identificare i suggerimenti di riempimento automatico.", "description": "Explains to the user that URI match detection determines how Bitwarden suggests autofill options, and clarifies that this default strategy applies when no specific match detection is set for a login item." }, "regExAdvancedOptionWarning": { - "message": "\"Regular expression\" is an advanced option with increased risk of exposing credentials.", + "message": "'Espressione regolare' è un'opzione avanzata con un maggior rischio di esporre le credenziali.", "description": "Content for dialog which warns a user when selecting 'regular expression' matching strategy as a cipher match strategy" }, "startsWithAdvancedOptionWarning": { - "message": "\"Starts with\" is an advanced option with increased risk of exposing credentials.", + "message": "'Inizia con' è un'opzione con un maggior rischio di esporre le credenziali.", "description": "Content for dialog which warns a user when selecting 'starts with' matching strategy as a cipher match strategy" }, "uriMatchWarningDialogLink": { - "message": "More about match detection", + "message": "Ulteriori informazioni sulla corrispondenza degli URL", "description": "Link to match detection docs on warning dialog for advance match strategy" }, "uriAdvancedOption": { - "message": "Advanced options", + "message": "Opzioni avanzate", "description": "Advanced option placeholder for uri option component" }, "confirmContinueToBrowserSettingsTitle": { @@ -4601,7 +4582,7 @@ } }, "copyFieldCipherName": { - "message": "Copy $FIELD$, $CIPHERNAME$", + "message": "Copia $FIELD$, $CIPHERNAME$", "description": "Title for a button that copies a field value to the clipboard.", "placeholders": { "field": { @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly non è supportato dal browser o non è abilitato. WebAssembly è richiesto per utilizzare l'app Bitwarden.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Mostra di più" + }, + "showLess": { + "message": "Mostra di meno" } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 977d04ca28a..9370a3ae6b1 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index da70c255803..6bbdb6f67f3 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index f4c58318bc1..d076ef01ef2 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 31b285f6780..f4ab488884a 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 89d59488aaf..50725e73064 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -747,7 +747,7 @@ "message": "시스템 잠금 시" }, "onRestart": { - "message": "브라우저 다시 시작 시" + "message": "브라우저 재시작 시" }, "never": { "message": "안함" @@ -1016,7 +1016,7 @@ "description": "This is the folder for uncategorized items" }, "enableAddLoginNotification": { - "message": "로그인을 추가할 건지 물어보기" + "message": "로그인 추가 확인" }, "vaultSaveOptionsTitle": { "message": "보관함 옵션들을 저장하기" @@ -1028,7 +1028,7 @@ "message": "보관함에 항목이 없을 경우 추가하라는 메시지를 표시합니다. 모든 로그인된 계정에 적용됩니다." }, "showCardsInVaultViewV2": { - "message": "보관함 보기에서 언제나 카드 자동 완성 제안을 표시" + "message": "보관함 보기에서 자동 완성 제안으로 카드를 항상 표시" }, "showCardsCurrentTab": { "message": "탭 페이지에 카드 표시" @@ -1037,7 +1037,7 @@ "message": "간편한 자동완성을 위해 탭에 카드 항목들을 나열" }, "showIdentitiesInVaultViewV2": { - "message": "보관함 보기에서 언제나 신원의 자동 완성 제안을 표시" + "message": "보관함 보기에서 자동 채우기 제안으로 신원을 항상 표시" }, "showIdentitiesCurrentTab": { "message": "탭 페이지에 신원들을 표시" @@ -1049,7 +1049,7 @@ "message": "보관함 보기에서 항목을 클릭하여 자동 완성" }, "clickToAutofill": { - "message": "Click items in autofill suggestion to fill" + "message": "자동 완성 제안에서 항목 클릭으로 채우기" }, "clearClipboard": { "message": "클립보드 비우기", @@ -1183,7 +1183,7 @@ "message": "Change your master password to complete account recovery." }, "enableChangedPasswordNotification": { - "message": "현재 로그인으로 업데이트할 건지 묻기" + "message": "기존 로그인 정보 업데이트 확인" }, "changedPasswordNotificationDesc": { "message": "웹사이트에서 변경 사항이 감지되면 로그인 비밀번호를 업데이트하라는 메시지를 표시합니다." @@ -1213,7 +1213,7 @@ "message": "추가 옵션" }, "enableContextMenuItem": { - "message": "문맥 매뉴 옵션 표시" + "message": "컨텍스트 메뉴 옵션 표시" }, "contextMenuItemDesc": { "message": "우클릭을 사용하여, 비밀번호 생성과 웹사이트 로그인 매칭에 접근하세요" @@ -1264,10 +1264,10 @@ "message": "이 비밀번호는 이 파일을 파일 내보내거나, 가져오는데 사용됩니다." }, "accountRestrictedOptionDescription": { - "message": "내보내기를 당신의 계정의 사용자이름과 마스터비밀번호로부터 파생된 계정 암호화 키를 사용하여 암호화하고, 현재의 Bitwarden 계정으로만 가져오도록 제한합니다." + "message": "당신 계정의 사용자 이름과 마스터 비밀번호를 통한 암호화 키로 내보내기 파일을 암호화 하고, 현재의 Bitwarden 계정으로만 가져올 수 있도록 제한합니다." }, "passwordProtectedOptionDescription": { - "message": "파일에 비밀번호를 설정하여 내보내기를 암호화하고, 어느 Bitwarden 계정으로든 그 비밀번호로 해독하여 가져오기 합니다." + "message": "파일에 비밀번호를 설정하여 내보내기 파일을 암호화하며, 그 비밀번호만 입력하면 어느 Bitwarden 계정으로든 가져올 수 있습니다." }, "exportTypeHeading": { "message": "내보내기 유형" @@ -1630,13 +1630,13 @@ "message": "양식 필드에 자동 완성 제안 표시" }, "showInlineMenuIdentitiesLabel": { - "message": "신원를 제안으로 표시" + "message": "신원을 제안으로 표시" }, "showInlineMenuCardsLabel": { "message": "카드를 제안으로 표시" }, "showInlineMenuOnIconSelectionLabel": { - "message": "아이콘을 선택할 때 제안을 표시" + "message": "아이콘 선택 시 제안 표시" }, "showInlineMenuOnFormFieldsDescAlt": { "message": "로그인한 모든 계정에 적용" @@ -2196,7 +2196,7 @@ "message": "브라우저 다시 시작 시 마스터 비밀번호로 잠금" }, "lockWithMasterPassOnRestart1": { - "message": "브라우저 다시 시작 시 마스터 비밀번호가 필요합니다" + "message": "브라우저 재시작 시 마스터 비밀번호 요구" }, "selectOneCollection": { "message": "반드시 하나 이상의 컬렉션을 선택해야 합니다." @@ -3115,7 +3115,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included", + "message": "$EMAIL$ 계정과 관련된 개인 보관함 항목과 첨부 파일만 내보내집니다. 조직 보관함 항목은 포함되지 않습니다.", "placeholders": { "email": { "content": "$1", @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -3808,7 +3789,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsBodyNoItems": { - "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "message": "플랫폼에 구애받지 않고 누구에게나 파일과 데이터를 안전하게 공유하세요. 전송하는 정보는 종단 간 암호화로 보호되어 외부 노출이 최소화됩니다.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4398,7 +4379,7 @@ "description": "Label indicating the most common import formats" }, "uriMatchDefaultStrategyHint": { - "message": "URI match detection is how Bitwarden identifies autofill suggestions.", + "message": "Bitwarden에서 자동 채우기 제안을 식별하는 방법은 URI 일치 감지입니다.", "description": "Explains to the user that URI match detection determines how Bitwarden suggests autofill options, and clarifies that this default strategy applies when no specific match detection is set for a login item." }, "regExAdvancedOptionWarning": { @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 5a4f0d8c419..fa53a42a4b7 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 81447182f26..68d5a0db2e3 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -605,7 +605,7 @@ "message": "Izdzēst vienumu" }, "viewItem": { - "message": "Skatīt vienumu" + "message": "Apskatīt vienumu" }, "launch": { "message": "Palaist" @@ -1180,7 +1180,7 @@ "message": "Pēc savas paroles nomainīšanas būs nepieciešams pieteikties ar jauno paroli. Spēkā esošajās sesijās citās ierīcēs stundas laikā notiks atteikšanās." }, "accountRecoveryUpdateMasterPasswordSubtitle": { - "message": "Jānomaina sava galvenā'parole, lai pabeigtu konta atkopi." + "message": "Jānomaina sava galvenā parole, lai pabeigtu konta atkopi." }, "enableChangedPasswordNotification": { "message": "Vaicāt atjaunināt esošu pieteikšanās vienumu" @@ -1490,7 +1490,7 @@ "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Izmantot savu atkopes kodu" + "message": "Izmanto savu atkopes kodu" }, "insertU2f": { "message": "Ievieto savu drošības atslēgu datora USB ligzdā! Ja tai ir poga, pieskaries tai!" @@ -1523,10 +1523,10 @@ "message": "Atlasīt divpakāpju pieteikšanās veidu" }, "recoveryCodeDesc": { - "message": "Zaudēta piekļuve visiem divpakāpju nodrošinātājiem? Izmanto atkopšanas kodus, lai atspējotu visus sava konta divpakāpju nodrošinātājus!" + "message": "Zaudēta piekļuve visiem divpakāpju nodrošinātājiem? Izmanto atkopes kodus, lai atspējotu visus sava konta divpakāpju nodrošinātājus!" }, "recoveryCodeTitle": { - "message": "Atgūšanas kods" + "message": "Atkopes kods" }, "authenticatorAppTitle": { "message": "Autentificētāja lietotne" @@ -3437,7 +3437,7 @@ "message": "Atkārtoti nosūtīt paziņojumu" }, "viewAllLogInOptions": { - "message": "Skatīt visas pieteikšanās iespējas" + "message": "Apskatīt visas pieteikšanās iespējas" }, "notificationSentDevice": { "message": "Uz ierīci ir nosūtīts paziņojums." @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Šis pieprasījums vairs nav derīgs." }, - "areYouTryingToAccessYourAccount": { - "message": "Vai mēģini piekļūt savam kontam?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "$EMAIL$ pieteikšanās apstiprināta ierīcē $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Tu noraidīji pieteikšanās mēģinājumu no citas ierīces. Ja tas tiešām biji Tu, mēģini pieteikties no ierīces vēlreiz!" - }, "loginRequestHasAlreadyExpired": { "message": "Pieteikšanās pieprasījuma derīgums jau ir beidzies." }, @@ -3747,7 +3728,7 @@ "description": "European Union" }, "accessDenied": { - "message": "Piekļuve liegta. Nav nepieciešamo atļauju, lai skatītu šo lapu." + "message": "Piekļuve liegta. Nav nepieciešamo atļauju, lai apskatītu šo lapu." }, "general": { "message": "Vispārīgi" @@ -4406,7 +4387,7 @@ "description": "Content for dialog which warns a user when selecting 'regular expression' matching strategy as a cipher match strategy" }, "startsWithAdvancedOptionWarning": { - "message": "\"Sākas ar' ir lietpratējiem paredzēta iespēja ar paaugstinātu piekļuves datu atklāšanas bīstamību.", + "message": "“Sākas ar” ir lietpratējiem paredzēta iespēja ar paaugstinātu piekļuves datu atklāšanas bīstamību.", "description": "Content for dialog which warns a user when selecting 'starts with' matching strategy as a cipher match strategy" }, "uriMatchWarningDialogLink": { @@ -4430,7 +4411,7 @@ "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser password management settings" }, "confirmContinueToHelpCenterKeyboardShortcutsContent": { - "message": "Paplašinājuma īsinājumtaustiņus skatīt un iestatīt var pārlūka iestatījumos.", + "message": "Paplašinājuma īsinājumtaustiņus apskatīt un iestatīt var pārlūka iestatījumos.", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser keyboard shortcut settings" }, "confirmContinueToBrowserPasswordManagementSettingsContent": { @@ -4438,7 +4419,7 @@ "description": "Body content for dialog which asks if the user wants to proceed to the browser's password management settings page" }, "confirmContinueToBrowserKeyboardShortcutSettingsContent": { - "message": "Paplašinājuma īsinājumtaustiņus skatīt un iestatīt var pārlūka iestatījumos.", + "message": "Paplašinājuma īsinājumtaustiņus apskatīt un iestatīt var pārlūka iestatījumos.", "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { @@ -4553,7 +4534,7 @@ } }, "viewItemTitle": { - "message": "Skatīt vienumu - $ITEMNAME$", + "message": "Apskatīt vienumu - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4648,7 +4629,7 @@ "message": "Kļūda mērķa mapes piešķiršanā." }, "viewItemsIn": { - "message": "Skatīt $NAME$ vienumus", + "message": "Apskatīt $NAME$ vienumus", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly šajā pārlūkā netiek atbalstīts vai nav iespējots. WebAssebly ir nepieciešams, lai izmantotu Bitwarden lietotni.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Rādīt vairāk" + }, + "showLess": { + "message": "Rādīt mazāk" } } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 11709a4611b..ca1bbc69297 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index e7d06e4d5f9..70059b1552e 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index f4c58318bc1..d076ef01ef2 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 7cbe8747d3b..9e617957dc8 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Denne forespørselen er ikke lenger gyldig." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Innloggingsforespørselen har allerede utløpt." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index f4c58318bc1..d076ef01ef2 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 0c2bd31935f..4b38d8491aa 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -3073,13 +3073,13 @@ "message": "Geen unieke id gevonden." }, "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + "message": "Voor leden van de volgende organisatie is een hoofdwachtwoord niet langer nodig. Bevestig het domein hieronder met de beheerder van je organisatie." }, "organizationName": { - "message": "Organization name" + "message": "Organisatienaam" }, "keyConnectorDomain": { - "message": "Key Connector domain" + "message": "Key Connector domein" }, "leaveOrganization": { "message": "Organisatie verlaten" @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Dit verzoek is niet langer geldig." }, - "areYouTryingToAccessYourAccount": { - "message": "Probeer je toegang te krijgen tot je account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Inloggen voor $EMAIL$ bevestigd op $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Je hebt een inlogpoging vanaf een ander apparaat geweigerd. Als je dit toch echt zelf was, probeer dan opnieuw in te loggen met het apparaat." - }, "loginRequestHasAlreadyExpired": { "message": "Inlogverzoek is al verlopen." }, @@ -4772,7 +4753,7 @@ "message": "Download op Google Play" }, "downloadOnTheAppStore": { - "message": "Download on the App Store" + "message": "Te downloaden in de App Store" }, "permanentlyDeleteAttachmentConfirmation": { "message": "Weet je zeker dat je deze bijlage definitief wilt verwijderen?" @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly wordt niet ondersteund in je browser of is niet ingeschakeld. WebAssembly is vereist om de Bitwarden-app te gebruiken.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Meer weergeven" + }, + "showLess": { + "message": "Minder weergeven" } } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index f4c58318bc1..d076ef01ef2 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index f4c58318bc1..d076ef01ef2 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 04112b08219..97875409529 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -1153,7 +1153,7 @@ "description": "Shown to user after login is updated." }, "loginUpdateTaskSuccessAdditional": { - "message": "Dziękujemy za dbanie o bezpieczeństwo $ORGANIZATION$. Pozostało $TASK_COUNT$ haseł do zaktualizowania.", + "message": "Dziękujemy za zwiększenie bezpieczeństwa organizacji $ORGANIZATION$. Zaktualizuj hasła dla jeszcze $TASK_COUNT$ danych logowania.", "placeholders": { "organization": { "content": "$1" @@ -1468,7 +1468,7 @@ "message": "Konto premium jest wymagane, aby skorzystać z tej funkcji." }, "authenticationTimeout": { - "message": "Limit czasu uwierzytelniania" + "message": "Przekroczono limit czasu uwierzytelniania" }, "authenticationSessionTimedOut": { "message": "Upłynął limit czasu uwierzytelniania. Zaloguj się ponownie." @@ -1523,7 +1523,7 @@ "message": "Wybierz metodę logowania dwustopniowego" }, "recoveryCodeDesc": { - "message": "Utraciłeś dostęp do wszystkich swoich mechanizmów dwustopniowego logowania? Użyj kodów odzyskiwania, aby wyłączyć dwustopniowe logowanie na Twoim koncie." + "message": "Nie masz dostępu do logowania dwustopniowego? Użyj kodu odzyskiwania, aby je wyłączyć." }, "recoveryCodeTitle": { "message": "Kod odzyskiwania" @@ -1681,7 +1681,7 @@ "message": "Domyślne ustawienie autouzupełniania" }, "defaultAutoFillOnPageLoadDesc": { - "message": "Po włączeniu autouzupełnianiu po załadowaniu strony możesz włączyć lub wyłączyć tę funkcję dla poszczególnych wpisów." + "message": "Możesz wyłączyć autouzupełnianie po załadowaniu strony dla poszczególnych elementów w opcjach konkretnych elementów." }, "itemAutoFillOnPageLoad": { "message": "Automatycznie uzupełniaj po załadowaniu strony (jeśli włączono w opcjach)" @@ -1773,7 +1773,7 @@ "message": "Pokaż licznik na ikonie" }, "badgeCounterDesc": { - "message": "Wskaż, ile masz danych logowania do bieżącej strony internetowej." + "message": "Pokazuje liczbę danych logowania dla obecnej strony internetowej." }, "cardholderName": { "message": "Właściciel karty" @@ -2178,7 +2178,7 @@ "message": "Kod PIN jest nieprawidłowy." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Zbyt wiele nieprawidłowych prób wpisywania PIN. Wylogowywanie." + "message": "Zbyt wiele nieprawidłowych prób wpisywania kodu PIN. Trwa wylogowanie" }, "unlockWithBiometrics": { "message": "Odblokuj biometrią" @@ -2476,13 +2476,13 @@ "message": "Logowanie biometrią nie powiodło się" }, "biometricsFailedDesc": { - "message": "Dane biometryczne nie mogę być użyte, rozważ użycie hasła głównego lub wylogowanie. Jeśli się to powtarza, skontaktuj się z pomocą techniczną Bitwarden." + "message": "Weryfikacja biometryczna nie powiodła się. Zaloguj się za pomocą hasła głównego. Jeśli problem się powtarza, skontaktuj się z pomocą techniczną Bitwarden." }, "nativeMessaginPermissionErrorTitle": { "message": "Uprawnienie nie zostało przyznane" }, "nativeMessaginPermissionErrorDesc": { - "message": "Bez uprawnienia do komunikowania się z aplikacją desktopową Bitwarden nie możemy dostarczyć obsługi danych biometrycznych w rozszerzeniu przeglądarki. Spróbuj ponownie." + "message": "Odblokowanie biometrią jest dostępne dopiero po połączeniu rozszerzenia przeglądarki z aplikacją desktopową Bitwarden. Spróbuj ponownie." }, "nativeMessaginPermissionSidebarTitle": { "message": "Wystąpił błąd żądania uprawnienia" @@ -2503,7 +2503,7 @@ "message": "Nie można zaimportować karty" }, "restrictCardTypeImportDesc": { - "message": "Polityka ustawiona przez 1 lub więcej organizacji uniemożliwia importowanie kart do sejfów." + "message": "Zasada ustawiona przez co najmniej 1 organizację uniemożliwia importowanie kart do sejfów." }, "domainsTitle": { "message": "Domeny", @@ -2610,7 +2610,7 @@ "message": "Sprawdź i zmień 1 zagrożone hasło" }, "reviewAndChangeAtRiskPasswordsPlural": { - "message": "Przejrzyj i zmień $COUNT$ zagrożonych haseł ", + "message": "Sprawdź i zmień zagrożone hasła ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -2631,14 +2631,14 @@ "message": "Sprawdź zagrożone hasła" }, "reviewAtRiskLoginsSlideDesc": { - "message": "Twoje hasła organizacji są zagrożone, ponieważ są słabe, ponownie używane i/lub narażone.", + "message": "Hasła w Twojej organizacji są zagrożone, ponieważ są słabe, identyczne lub ujawnione.", "description": "Description of the review at-risk login slide on the at-risk password page carousel" }, "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Ilustracja listy danych logowania, które są zagrożone." }, "generatePasswordSlideDesc": { - "message": "Szybko wygeneruj silne, unikalne hasło z menu autouzupełniania Bitwarden na stronie narażonej na ryzyko.", + "message": "Wygeneruj silne i unikalne hasło dla zagrożonej strony internetowej za pomocą autouzupełniania Bitwarden.", "description": "Description of the generate password slide on the at-risk password page carousel" }, "generatePasswordSlideImgAltPeriod": { @@ -3115,7 +3115,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Tylko poszczególne elementy sejfu łącznie z załącznikami powiązanymi z $EMAIL$ zostaną wyeksportowane. Elementy sejfu organizacji nie będą dołączone", + "message": "Tylko osobisty sejf $EMAIL$ zostanie wyeksportowany. Elementy organizacji nie zostaną uwzględnione.", "placeholders": { "email": { "content": "$1", @@ -3127,7 +3127,7 @@ "message": "Eksportowanie sejfu organizacji" }, "exportingOrganizationVaultDesc": { - "message": "Tylko sejf organizacji powiązany z $ORGANIZATION$ zostanie wyeksportowany. Pozycje w poszczególnych sejfach lub innych organizacji nie będą uwzględnione.", + "message": "Tylko sejf organizacji $ORGANIZATION$ zostanie wyeksportowany. Elementy innych sejfów nie zostaną uwzględnione.", "placeholders": { "organization": { "content": "$1", @@ -3348,7 +3348,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Nieznana usługa przekierowania: '$SERVICENAME$'.", + "message": "Usługa „$SERVICENAME$” jest nieznana.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -3380,7 +3380,7 @@ "message": "Nie można uzyskać dostępu do elementów w zawieszonych organizacjach. Skontaktuj się z właścicielem organizacji, aby uzyskać pomoc." }, "loggingInTo": { - "message": "Logowanie na $DOMAIN$", + "message": "Serwer: $DOMAIN$", "placeholders": { "domain": { "content": "$1", @@ -3522,10 +3522,10 @@ } }, "autofillPageLoadPolicyActivated": { - "message": "Twoja organizacja włączyła autouzupełnianie podczas wczytywania strony." + "message": "Twoja organizacja włączyła autouzupełnianie po załadowaniu strony." }, "howToAutofill": { - "message": "Jak autouzupełniać" + "message": "Jak uzupełniać dane" }, "autofillSelectInfoWithCommand": { "message": "Wybierz element, użyj skrótu $COMMAND$ lub zobacz inne opcje w ustawieniach.", @@ -3616,7 +3616,7 @@ "description": "Software Development Kit" }, "requestPending": { - "message": "Zapytanie oczekuje" + "message": "Oczekująca prośba" }, "firstLogin": { "message": "Pierwsze logowanie" @@ -3652,30 +3652,11 @@ "message": "Rodzaj urządzenia" }, "loginRequest": { - "message": "Żądanie logowania" + "message": "Prośba logowania" }, "thisRequestIsNoLongerValid": { "message": "Prośba nie jest już ważna." }, - "areYouTryingToAccessYourAccount": { - "message": "Czy próbujesz uzyskać dostęp do swojego konta?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Logowanie potwierdzone dla $EMAIL$ na $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Odrzucono próby logowania z innego urządzenia. Jeśli to naprawdę Ty, spróbuj ponownie zalogować się za pomocą urządzenia." - }, "loginRequestHasAlreadyExpired": { "message": "Prośba logowania wygasła." }, @@ -3713,7 +3694,7 @@ "message": "Poproś administratora o potwierdzenie" }, "unableToCompleteLogin": { - "message": "Nie można ukończyć logowania" + "message": "Logowanie nie powiodło się" }, "loginOnTrustedDeviceOrAskAdminToAssignPassword": { "message": "Musisz zalogować się na zaufanym urządzeniu lub poprosić administratora o przypisanie hasła." @@ -3747,7 +3728,7 @@ "description": "European Union" }, "accessDenied": { - "message": "Odmowa dostępu. Nie masz uprawnień do przeglądania tej strony." + "message": "Odmowa dostępu. Nie masz uprawnień do wyświetlenia tej strony." }, "general": { "message": "Ogólne" @@ -3777,7 +3758,7 @@ "message": "Nie znaleziono aktywnego adresu e-mail. Trwa wylogowanie." }, "deviceTrusted": { - "message": "Zaufano urządzeniu" + "message": "Urządzenie zostało zaufane" }, "trustOrganization": { "message": "Zaufaj organizacji" @@ -3935,7 +3916,7 @@ "description": "Toast message for describing that master password re-prompt cannot be autofilled on page load." }, "autofillOnPageLoadSetToDefault": { - "message": "Automatyczne wypełnianie przy wczytywaniu strony zostało ustawione, aby używać ustawień domyślnych.", + "message": "Autouzupełnianie po załadowaniu strony zostało ustawione do domyślnych ustawień.", "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "turnOffMasterPasswordPromptToEditField": { @@ -4046,7 +4027,7 @@ "message": "Błąd importowania" }, "importErrorDesc": { - "message": "Wystąpił problem z danymi, które chcesz zaimportować. Rozwiąż poniższe problemy w Twoim pliku i spróbuj ponownie." + "message": "Wystąpił problem podczas importowania danych. Usuń poniższe błędy w pliku źródłowym i spróbuj ponownie." }, "resolveTheErrorsBelowAndTryAgain": { "message": "Rozwiąż poniższe błędy i spróbuj ponownie." @@ -4082,7 +4063,7 @@ "message": "Oczekiwanie na potwierdzenie" }, "couldNotCompleteBiometrics": { - "message": "Nie można ukończyć z użyciem biometrii." + "message": "Logowanie biometrią nie powiodło się" }, "needADifferentMethod": { "message": "Potrzebujesz innej metody?" @@ -4303,7 +4284,7 @@ "message": "Wpisz jednorazowy kod z aplikacji uwierzytelniającej" }, "lastPassOOBDesc": { - "message": "Zatwierdź żądanie logowania w aplikacji uwierzytelniającej lub wprowadź jednorazowe hasło." + "message": "Potwierdź logowanie w aplikacji uwierzytelniającej lub wpisz jednorazowy kod." }, "passcode": { "message": "Kod" @@ -4373,7 +4354,7 @@ "message": "serwer" }, "hostedAt": { - "message": "hostowany w" + "message": "serwer" }, "useDeviceOrHardwareKey": { "message": "Użyj urządzenia lub klucza sprzętowego" @@ -4398,7 +4379,7 @@ "description": "Label indicating the most common import formats" }, "uriMatchDefaultStrategyHint": { - "message": "Wykrywanie URI polega na tym, jak Bitwarden identyfikuje sugestie autouzupełniania.", + "message": "Wykrywanie dopasowania to sposób, w jaki Bitwarden identyfikuje sugestie autouzupełniania.", "description": "Explains to the user that URI match detection determines how Bitwarden suggests autofill options, and clarifies that this default strategy applies when no specific match detection is set for a login item." }, "regExAdvancedOptionWarning": { @@ -5044,7 +5025,7 @@ "message": "Element zostanie przeniesiony do organizacji. Nie będziesz już właścicielem elementu." }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ elementów zostanie trwale przeniesionych do wybranej organizacji. Nie będziesz już posiadać tych elementów.", + "message": "Nie będziesz już właścicielem $PERSONAL_ITEMS_COUNT$ elementów przeniesionych do organizacji.", "placeholders": { "personal_items_count": { "content": "$1", @@ -5062,7 +5043,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ elementy zostaną trwale przeniesione do $ORG$. Nie będziesz już posiadać tych elementów.", + "message": "Nie będziesz już właścicielem $PERSONAL_ITEMS_COUNT$ elementów przeniesionych do organizacji $ORG$.", "placeholders": { "personal_items_count": { "content": "$1", @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly nie jest obsługiwany w przeglądarce lub jest wyłączony. WebAssembly jest wymagany do korzystania z aplikacji Bitwarden.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Pokaż więcej" + }, + "showLess": { + "message": "Pokaż mniej" } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 46fbb9beca3..ac5afdc9fce 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -1935,7 +1935,7 @@ "message": "Chave SSH" }, "typeNote": { - "message": "Note" + "message": "Nota" }, "newItemHeader": { "message": "Nova $TYPE$", @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -3804,11 +3785,11 @@ "message": "Trust user" }, "sendsTitleNoItems": { - "message": "Send sensitive information safely", + "message": "Envie informações sensíveis com segurança", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsBodyNoItems": { - "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "message": "Compartilhe arquivos e dados com segurança, com qualquer pessoa e em qualquer plataforma. Suas informações ficarão sempre criptografadas de ponta a ponta, garantindo exposição mínima.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4748,25 +4729,25 @@ } }, "downloadBitwarden": { - "message": "Download Bitwarden" + "message": "Baixar o Bitwarden" }, "downloadBitwardenOnAllDevices": { - "message": "Download Bitwarden on all devices" + "message": "Baixar o Bitwarden em todos os dispositivos" }, "getTheMobileApp": { - "message": "Get the mobile app" + "message": "Baixar o aplicativo para dispositivos móveis" }, "getTheMobileAppDesc": { - "message": "Access your passwords on the go with the Bitwarden mobile app." + "message": "Acesse as suas senhas em qualquer lugar com o aplicativo móvel Bitwarden." }, "getTheDesktopApp": { "message": "Get the desktop app" }, "getTheDesktopAppDesc": { - "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + "message": "Acesse o seu cofre sem um navegador e, em seguida, configure o desbloqueio com dados biométricos para facilitar o desbloqueio tanto no aplicativo desktop quanto na extensão do navegador." }, "downloadFromBitwardenNow": { - "message": "Download from bitwarden.com now" + "message": "Baixar em bitwarden.com agora" }, "getItOnGooglePlay": { "message": "Get it on Google Play" @@ -5464,7 +5445,7 @@ "message": "Alterar senhas vulneráveis" }, "settingsVaultOptions": { - "message": "Vault options" + "message": "Opções do cofre" }, "emptyVaultDescription": { "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 3cd813847d1..d3ce5ec3e48 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Este pedido já não é válido." }, - "areYouTryingToAccessYourAccount": { - "message": "Está a tentar aceder à sua conta?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Início de sessão confirmado para $EMAIL$ no $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Recusou uma tentativa de início de sessão de outro dispositivo. Se foi realmente o caso, tente iniciar sessão com o dispositivo novamente." - }, "loginRequestHasAlreadyExpired": { "message": "O pedido de início de sessão já expirou." }, @@ -3747,7 +3728,7 @@ "description": "European Union" }, "accessDenied": { - "message": "Acesso negado. Não tem permissão para visualizar esta página." + "message": "Acesso negado. Não tem permissão para ver esta página." }, "general": { "message": "Geral" @@ -4402,7 +4383,7 @@ "description": "Explains to the user that URI match detection determines how Bitwarden suggests autofill options, and clarifies that this default strategy applies when no specific match detection is set for a login item." }, "regExAdvancedOptionWarning": { - "message": "A \"expressão regular\" é uma opção avançada com um risco acrescido de exposição de credenciais.", + "message": "A \"Expressão regular\" é uma opção avançada com um risco acrescido de exposição de credenciais.", "description": "Content for dialog which warns a user when selecting 'regular expression' matching strategy as a cipher match strategy" }, "startsWithAdvancedOptionWarning": { @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "O WebAssembly não é suportado no seu navegador ou não está ativado. O WebAssembly é necessário para utilizar a app Bitwarden.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Mostrar mais" + }, + "showLess": { + "message": "Mostrar menos" } } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 13fe8aa9482..741b491da13 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index a5a6809a059..88fa89ac73e 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Этот запрос больше не действителен." }, - "areYouTryingToAccessYourAccount": { - "message": "Вы пытаетесь получить доступ к своему аккаунту?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Вход подтвержден для $EMAIL$ на $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Вы отклонили попытку авторизации с другого устройства. Если это действительно были вы, попробуйте авторизоваться с этого устройства еще раз." - }, "loginRequestHasAlreadyExpired": { "message": "Запрос на вход истек." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly не поддерживается вашим браузером или не включен. WebAssembly необходим для использования приложения Bitwarden.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Больше" + }, + "showLess": { + "message": "Меньше" } } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 7bc0ba2694a..dd2df6f309c 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 28a687be339..e12a0c97ac4 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -216,7 +216,7 @@ "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { - "message": "Vygenerovať heslo (skopírované)" + "message": "Vygenerovať heslo (+skopírovať)" }, "copyElementIdentifier": { "message": "Kopírovať názov vlastného poľa" @@ -979,7 +979,7 @@ "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." }, "addedItem": { - "message": "Pridaná položka" + "message": "Položka bola pridaná" }, "editedItem": { "message": "Položka upravená" @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Táto žiadosť už nie je platná." }, - "areYouTryingToAccessYourAccount": { - "message": "Snažíte sa získať prístup k svojmu účtu?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Potvrdené prihlásenie pre $EMAIL$ na $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Odmietli ste pokus o prihlásenie z iného zariadenia. Ak ste to boli naozaj vy, skúste sa prihlásiť pomocou zariadenia znova." - }, "loginRequestHasAlreadyExpired": { "message": "Platnosť žiadosti o prihlásenie už vypršala." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly nie je vo vašom prehliadači podporovaný alebo nie je povolený. Na používanie Bitwardenu sa vyžaduje WebAssembly.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Zobraziť viac" + }, + "showLess": { + "message": "Zobraziť menej" } } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 2db40266cd7..3af24b85889 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 3ee18612553..12d0c5b015e 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Овај захтев више није важећи." }, - "areYouTryingToAccessYourAccount": { - "message": "Да ли покушавате да приступите вашем налогу?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Пријава потврђена за $EMAIL$ на $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Одбили сте покушај пријаве са другог уређаја. Ако сте то заиста били ви, покушајте поново да се пријавите помоћу уређаја." - }, "loginRequestHasAlreadyExpired": { "message": "Захтев за пријаву је већ истекао." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly није подржано или није уапљено на вашем прегледачу. WebAssembly је потребно да би се користила апликација Bitwarden.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 49d2765bf6e..1174b6ac077 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Denna begäran är inte längre giltig." }, - "areYouTryingToAccessYourAccount": { - "message": "Försöker du komma åt ditt konto?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Inloggning bekräftad för $EMAIL$ på $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Du har avvisat ett inloggningsförsök från en annan enhet. Om det verkligen var du, försök logga in med enheten igen." - }, "loginRequestHasAlreadyExpired": { "message": "Inloggningsbegäran har redan gått ut." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly stöds inte av din webbläsare eller är inte aktiverat. WebAssembly krävs för att använda Bitwarden-appen.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Visa mer" + }, + "showLess": { + "message": "Visa mindre" } } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index f4c58318bc1..d076ef01ef2 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index fd92c71c200..2cea91a424e 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 83265497ddf..22a083959a9 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -84,7 +84,7 @@ "message": "Ana parola ipucu (isteğe bağlı)" }, "passwordStrengthScore": { - "message": "Parola Güvenlik Puanı $SCORE$", + "message": "Parola gücü puanı: $SCORE$", "placeholders": { "score": { "content": "$1", @@ -982,7 +982,7 @@ "message": "Kayıt eklendi" }, "editedItem": { - "message": "Kayıt kaydedildi" + "message": "Hesap kaydedildi" }, "deleteItemConfirmation": { "message": "Çöp kutusuna göndermek istediğinizden emin misiniz?" @@ -2500,7 +2500,7 @@ "message": "Bir kuruluş ilkesi, kayıtları kişisel kasanıza içe aktarmayı engelledi." }, "restrictCardTypeImport": { - "message": "Cannot import card item types" + "message": "Kart öge türleri içe aktarılamıyor" }, "restrictCardTypeImportDesc": { "message": "A policy set by 1 or more organizations prevents you from importing cards to your vaults." @@ -2625,7 +2625,7 @@ "message": "Ayarlarınızı güncelleyin, böylece parolalarınızı hızlıca otomatik doldurabilir ve yeni parolalar oluşturabilirsiniz" }, "reviewAtRiskLogins": { - "message": "Risk altındaki girişleri inceleyin" + "message": "Risk altındaki hesapları inceleyin" }, "reviewAtRiskPasswords": { "message": "Risk altındaki parolaları inceleyin" @@ -3392,7 +3392,7 @@ "message": "Sunucu sürümü" }, "selfHostedServer": { - "message": "şirket içinde barındırılan" + "message": "şirket içi" }, "thirdParty": { "message": "Üçüncü taraf" @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Bu istek artık geçerli değil." }, - "areYouTryingToAccessYourAccount": { - "message": "Hesabınıza erişmeye mi çalışıyorsunuz?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "$DEVICE$ cihazında $EMAIL$ girişi onaylandı", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Başka bir cihazdan giriş isteğini reddettiniz. Yanlışlıkla yaptıysanız aynı cihazdan yeniden giriş yapmayı deneyin." - }, "loginRequestHasAlreadyExpired": { "message": "Giriş isteğinin süresi doldu." }, @@ -4009,7 +3990,7 @@ "description": "Button text to display within inline menu when there are no matching items on a login field" }, "addNewLoginItemAria": { - "message": "Add new vault login item, opens in a new window", + "message": "Yeni kasa giriş ögesini yeni bir açılır pencerede ekleyin", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { @@ -4017,7 +3998,7 @@ "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { - "message": "Add new vault card item, opens in a new window", + "message": "Yeni kasa kartı ögesini yeni bir açılır pencerede ekleyin", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { @@ -4204,7 +4185,7 @@ "message": "Geçiş anahtarı" }, "accessing": { - "message": "Erişilen konum:" + "message": "Erişilen konum" }, "loggedInExclamation": { "message": "Giriş yapıldı!" @@ -4398,7 +4379,7 @@ "description": "Label indicating the most common import formats" }, "uriMatchDefaultStrategyHint": { - "message": "URI match detection is how Bitwarden identifies autofill suggestions.", + "message": "URl eşleştirme tespiti, Bitwarden'ın otomatik doldurma önerilerini nasıl tanımladığıdır.", "description": "Explains to the user that URI match detection determines how Bitwarden suggests autofill options, and clarifies that this default strategy applies when no specific match detection is set for a login item." }, "regExAdvancedOptionWarning": { @@ -4410,7 +4391,7 @@ "description": "Content for dialog which warns a user when selecting 'starts with' matching strategy as a cipher match strategy" }, "uriMatchWarningDialogLink": { - "message": "More about match detection", + "message": "Eşleşme tespiti hakkında daha fazla bilgi", "description": "Link to match detection docs on warning dialog for advance match strategy" }, "uriAdvancedOption": { @@ -5209,22 +5190,22 @@ "message": "Bu kaydı düzenleme yetkisine sahip değilsiniz" }, "biometricsStatusHelptextUnlockNeeded": { - "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + "message": "Önce PIN veya parola gerektiğinden biyometrik kilit açılamıyor." }, "biometricsStatusHelptextHardwareUnavailable": { - "message": "Biometric unlock is currently unavailable." + "message": "Biyometrik kilit açma şu anda kullanılamıyor." }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "Biyometrik kilit açma, yanlış yapılandırılmış sistem dosyaları nedeniyle kullanılamıyor." }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "Biyometrik kilit açma, yanlış yapılandırılmış sistem dosyaları nedeniyle kullanılamıyor." }, "biometricsStatusHelptextDesktopDisconnected": { - "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + "message": "Bitwarden masaüstü uygulaması kapalı olduğu için biyometrik kilit açılamıyor." }, "biometricsStatusHelptextNotEnabledInDesktop": { - "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "message": "Biyometrik kilit açma, Bitwarden masaüstü uygulamasında $EMAIL$ etkin olmadığı için kullanılamıyor.", "placeholders": { "email": { "content": "$1", @@ -5233,7 +5214,7 @@ } }, "biometricsStatusHelptextUnavailableReasonUnknown": { - "message": "Biometric unlock is currently unavailable for an unknown reason." + "message": "Biyometrik kilit açma şu anda bilinmeyen bir nedenden dolayı kullanılamıyor." }, "unlockVault": { "message": "Kasanızın kilidini saniyeler içinde açın" @@ -5560,20 +5541,20 @@ "message": "Developer-friendly SSH access" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "Anahtarlarınızı saklayın ve hızlı, şifreli kimlik doğrulama için SSH aracısıyla bağlantı kurun.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Learn more about SSH agent", + "message": "SSH agent hakkında daha fazla bilgi edinin", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "generatorNudgeTitle": { - "message": "Quickly create passwords" + "message": "Hızlı bir şekilde parolalar oluşturun" }, "generatorNudgeBodyOne": { - "message": "Easily create strong and unique passwords by clicking on", + "message": "Tıklayarak güçlü ve benzersiz parolaları kolayca oluşturun", "description": "Two part message", "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, @@ -5583,7 +5564,7 @@ "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyAria": { - "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "message": "Güvenli oturumlar açmaya yardımcı olmak için Parola Oluştur düğmesine tıklayarak güçlü ve benzersiz parolalar oluşturun.", "description": "Aria label for the body content of the generator nudge" }, "noPermissionsViewPage": { @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "Tarayıcınızda WebAssembly desteklenmiyor veya etkinleştirilmemişt. Bitwarden uygulamasını kullanmak için WebAssembly gereklidir.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Daha fazla göster" + }, + "showLess": { + "message": "Daha az göster" } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index a03440efe02..537a9bfd5cf 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Цей запит більше недійсний." }, - "areYouTryingToAccessYourAccount": { - "message": "Ви намагаєтесь отримати доступ до свого облікового запису?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Підтверджено вхід для $EMAIL$ на $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Ви відхилили спробу входу з іншого пристрою. Якщо це були дійсно ви, спробуйте ввійти з пристроєм знову." - }, "loginRequestHasAlreadyExpired": { "message": "Термін дії запиту на вхід завершився." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly не підтримується або не ввімкнено у вашому браузері. WebAssembly є обов'язковою вимогою для програми Bitwarden.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Розгорнути" + }, + "showLess": { + "message": "Згорнути" } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index f25cc9c51dc..7a596d7c23d 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -1836,7 +1836,7 @@ "message": "số thẻ" }, "ex": { - "message": "Ví dụ:" + "message": "ví dụ." }, "title": { "message": "Tiêu đề" @@ -2458,7 +2458,7 @@ "message": "Nhận dạng sinh trắc học không được hỗ trợ" }, "biometricsNotSupportedDesc": { - "message": "Nhận dạng sinh trắc học trên trình duyệt không được hỗ trợ trên thiết bị này" + "message": "Nhận dạng sinh trắc học trên trình duyệt không được hỗ trợ trên thiết bị này." }, "biometricsNotUnlockedTitle": { "message": "Người dùng đã khoá hoặc đã đăng xuất" @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "Yêu cầu này không còn hiệu lực." }, - "areYouTryingToAccessYourAccount": { - "message": "Bạn đang cố gắng truy cập tài khoản của mình?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Đã xác nhận đăng nhập cho $EMAIL$ trên $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Bạn đã từ chối một lần đăng nhập từ thiết bị khác. Nếu thực sự là bạn, hãy thử đăng nhập lại bằng thiết bị đó." - }, "loginRequestHasAlreadyExpired": { "message": "Yêu cầu đăng nhập đã hết hạn." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly không được hỗ trợ trên trình duyệt của bạn hoặc chưa được kích hoạt. WebAssembly là yêu cầu bắt buộc để sử dụng ứng dụng Bitwarden.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Xem thêm" + }, + "showLess": { + "message": "Thu gọn" } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 2382e6fc971..fff87a949ce 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "此请求已失效。" }, - "areYouTryingToAccessYourAccount": { - "message": "您正在尝试访问您的账户吗?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "已确认 $EMAIL$ 在 $DEVICE$ 上的登录", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "您拒绝了一个来自其他设备的登录尝试。若确实是您本人,请尝试再次发起设备登录。" - }, "loginRequestHasAlreadyExpired": { "message": "登录请求已过期。" }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "您的浏览器不支持 WebAssembly 或 WebAssembly 未启用。使用 Bitwarden App 需要 WebAssembly。", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "显示更多" + }, + "showLess": { + "message": "显示更少" } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index d2776cb227d..e8873d74e92 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -3657,25 +3657,6 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" - }, - "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", - "placeholders": { - "email": { - "content": "$1", - "example": "name@example.com" - }, - "device": { - "content": "$2", - "example": "iOS" - } - } - }, - "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." - }, "loginRequestHasAlreadyExpired": { "message": "Login request has already expired." }, @@ -5592,5 +5573,11 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "showMore": { + "message": "Show more" + }, + "showLess": { + "message": "Show less" } } diff --git a/apps/browser/src/auth/popup/account-switching/current-account.component.html b/apps/browser/src/auth/popup/account-switching/current-account.component.html index 09342c58756..c16abdadf29 100644 --- a/apps/browser/src/auth/popup/account-switching/current-account.component.html +++ b/apps/browser/src/auth/popup/account-switching/current-account.component.html @@ -2,7 +2,7 @@ `, diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index f01809433e3..e2f4561e86c 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -2,11 +2,7 @@ import { Injectable, NgModule } from "@angular/core"; import { ActivatedRouteSnapshot, RouteReuseStrategy, RouterModule, Routes } from "@angular/router"; import { AuthenticationTimeoutComponent } from "@bitwarden/angular/auth/components/authentication-timeout.component"; -import { - EnvironmentSelectorComponent, - EnvironmentSelectorRouteData, - ExtensionDefaultOverlayPosition, -} from "@bitwarden/angular/auth/components/environment-selector.component"; +import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/environment-selector/environment-selector.component"; import { activeAuthGuard, authGuard, @@ -400,9 +396,6 @@ const routes: Routes = [ path: "", component: EnvironmentSelectorComponent, outlet: "environment-selector", - data: { - overlayPosition: ExtensionDefaultOverlayPosition, - } satisfies EnvironmentSelectorRouteData, }, ], }, @@ -425,9 +418,6 @@ const routes: Routes = [ path: "", component: EnvironmentSelectorComponent, outlet: "environment-selector", - data: { - overlayPosition: ExtensionDefaultOverlayPosition, - } satisfies EnvironmentSelectorRouteData, }, ], }, @@ -474,9 +464,6 @@ const routes: Routes = [ path: "", component: EnvironmentSelectorComponent, outlet: "environment-selector", - data: { - overlayPosition: ExtensionDefaultOverlayPosition, - } satisfies EnvironmentSelectorRouteData, }, ], }, diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 5150c51d765..2aacd3d3632 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -9,7 +9,6 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; -import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe"; import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe"; @@ -96,7 +95,6 @@ import "../platform/popup/locales"; TabsV2Component, UserVerificationComponent, RemovePasswordComponent, - EnvironmentSelectorComponent, ], exports: [], providers: [CurrencyPipe, DatePipe], diff --git a/apps/browser/src/popup/scss/tailwind.css b/apps/browser/src/popup/scss/tailwind.css index b49fe912861..54139990356 100644 --- a/apps/browser/src/popup/scss/tailwind.css +++ b/apps/browser/src/popup/scss/tailwind.css @@ -1,9 +1,5 @@ @import "../../../../../libs/components/src/tw-theme.css"; -@tailwind base; -@tailwind components; -@tailwind utilities; - @layer components { /** Safari Support */ html.browser_safari .tw-styled-scrollbar::-webkit-scrollbar { diff --git a/apps/browser/src/popup/tabs-v2.component.ts b/apps/browser/src/popup/tabs-v2.component.ts index 3d93f5d4e04..860b71794ff 100644 --- a/apps/browser/src/popup/tabs-v2.component.ts +++ b/apps/browser/src/popup/tabs-v2.component.ts @@ -1,11 +1,9 @@ import { Component } from "@angular/core"; -import { combineLatest, map, Observable, startWith, switchMap } from "rxjs"; +import { map, Observable, startWith, switchMap } from "rxjs"; import { NudgesService } from "@bitwarden/angular/vault"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { Icons } from "@bitwarden/components"; import { NavButton } from "../platform/popup/layout/popup-tab-navigation.component"; @@ -19,12 +17,9 @@ export class TabsV2Component { private hasActiveBadges$ = this.accountService.activeAccount$ .pipe(getUserId) .pipe(switchMap((userId) => this.nudgesService.hasActiveBadges$(userId))); - protected navButtons$: Observable = combineLatest([ - this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge), - this.hasActiveBadges$, - ]).pipe( - startWith([false, false]), - map(([onboardingFeatureEnabled, hasBadges]) => { + protected navButtons$: Observable = this.hasActiveBadges$.pipe( + startWith(false), + map((hasBadges) => { return [ { label: "vault", @@ -49,7 +44,7 @@ export class TabsV2Component { page: "/tabs/settings", icon: Icons.SettingsInactive, iconActive: Icons.SettingsActive, - showBerry: onboardingFeatureEnabled && hasBadges, + showBerry: hasBadges, }, ]; }), @@ -57,6 +52,5 @@ export class TabsV2Component { constructor( private nudgesService: NudgesService, private accountService: AccountService, - private readonly configService: ConfigService, ) {} } diff --git a/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.html b/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.html index 839681889a8..9bba3994357 100644 --- a/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.html +++ b/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.html @@ -23,12 +23,6 @@ - - - {{ "moreFromBitwarden" | i18n }} - - - - +  {{ organization.name }} - + { // Arrange const collections: CollectionView[] = []; - const parentCollection = new CollectionView(); - parentCollection.name = "Parent"; + const parentCollection = new CollectionView({ + name: "Parent", + organizationId: "orgId" as OrganizationId, + id: newGuid() as CollectionId, + }); - const childCollection = new CollectionView(); - childCollection.name = "Parent/Child"; + const childCollection = new CollectionView({ + name: "Parent/Child", + organizationId: "orgId" as OrganizationId, + id: newGuid() as CollectionId, + }); collections.push(childCollection); collections.push(parentCollection); @@ -41,12 +49,14 @@ describe("CollectionUtils Service", () => { describe("getFlatCollectionTree", () => { it("should flatten a tree node with no children", () => { // Arrange - const collection = new CollectionView(); - collection.name = "Test Collection"; - collection.id = "test-id"; + const collection = new CollectionView({ + name: "Test Collection", + id: "test-id" as CollectionId, + organizationId: "orgId" as OrganizationId, + }); const treeNodes: TreeNode[] = [ - new TreeNode(collection, null), + new TreeNode(collection, {} as TreeNode), ]; // Act @@ -59,23 +69,34 @@ describe("CollectionUtils Service", () => { it("should flatten a tree node with children", () => { // Arrange - const parentCollection = new CollectionView(); - parentCollection.name = "Parent"; - parentCollection.id = "parent-id"; + const parentCollection = new CollectionView({ + name: "Parent", + id: "parent-id" as CollectionId, + organizationId: "orgId" as OrganizationId, + }); - const child1Collection = new CollectionView(); - child1Collection.name = "Child 1"; - child1Collection.id = "child1-id"; + const child1Collection = new CollectionView({ + name: "Child 1", + id: "child1-id" as CollectionId, + organizationId: "orgId" as OrganizationId, + }); - const child2Collection = new CollectionView(); - child2Collection.name = "Child 2"; - child2Collection.id = "child2-id"; + const child2Collection = new CollectionView({ + name: "Child 2", + id: "child2-id" as CollectionId, + organizationId: "orgId" as OrganizationId, + }); - const grandchildCollection = new CollectionView(); - grandchildCollection.name = "Grandchild"; - grandchildCollection.id = "grandchild-id"; + const grandchildCollection = new CollectionView({ + name: "Grandchild", + id: "grandchild-id" as CollectionId, + organizationId: "orgId" as OrganizationId, + }); - const parentNode = new TreeNode(parentCollection, null); + const parentNode = new TreeNode( + parentCollection, + {} as TreeNode, + ); const child1Node = new TreeNode(child1Collection, parentNode); const child2Node = new TreeNode(child2Collection, parentNode); const grandchildNode = new TreeNode(grandchildCollection, child1Node); diff --git a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts index 0697659c976..8a0fab520e3 100644 --- a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts +++ b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts @@ -22,7 +22,7 @@ export function getNestedCollectionTree( // Collections need to be cloned because ServiceUtils.nestedTraverse actively // modifies the names of collections. // These changes risk affecting collections store in StateService. - const clonedCollections = collections + const clonedCollections: CollectionView[] | CollectionAdminView[] = collections .sort((a, b) => a.name.localeCompare(b.name)) .map(cloneCollection); @@ -37,6 +37,21 @@ export function getNestedCollectionTree( return nodes; } +export function cloneCollection(collection: CollectionView): CollectionView; +export function cloneCollection(collection: CollectionAdminView): CollectionAdminView; +export function cloneCollection( + collection: CollectionView | CollectionAdminView, +): CollectionView | CollectionAdminView { + let cloned; + + if (collection instanceof CollectionAdminView) { + cloned = Object.assign(new CollectionAdminView({ ...collection }), collection); + } else { + cloned = Object.assign(new CollectionView({ ...collection }), collection); + } + return cloned; +} + export function getFlatCollectionTree( nodes: TreeNode[], ): CollectionAdminView[]; @@ -57,32 +72,3 @@ export function getFlatCollectionTree( return [node.node, ...children]; }); } - -function cloneCollection(collection: CollectionView): CollectionView; -function cloneCollection(collection: CollectionAdminView): CollectionAdminView; -function cloneCollection( - collection: CollectionView | CollectionAdminView, -): CollectionView | CollectionAdminView { - let cloned; - - if (collection instanceof CollectionAdminView) { - cloned = new CollectionAdminView(); - cloned.groups = [...collection.groups]; - cloned.users = [...collection.users]; - cloned.assigned = collection.assigned; - cloned.unmanaged = collection.unmanaged; - } else { - cloned = new CollectionView(); - } - - cloned.id = collection.id; - cloned.externalId = collection.externalId; - cloned.hidePasswords = collection.hidePasswords; - cloned.name = collection.name; - cloned.organizationId = collection.organizationId; - cloned.readOnly = collection.readOnly; - cloned.manage = collection.manage; - cloned.type = collection.type; - - return cloned; -} diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts index 121a5c03ffe..1be16c65cb8 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts @@ -5,7 +5,7 @@ import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; import { Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, switchMap } from "rxjs"; import { CollectionAdminService, @@ -14,6 +14,8 @@ import { } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -99,6 +101,7 @@ export class VaultHeaderComponent { private dialogService: DialogService, private collectionAdminService: CollectionAdminService, private router: Router, + private accountService: AccountService, ) {} get title() { @@ -199,7 +202,14 @@ export class VaultHeaderComponent { async addCollection() { if (this.organization.productTierType === ProductTierType.Free) { - const collections = await this.collectionAdminService.getAll(this.organization.id); + const collections = await firstValueFrom( + this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => + this.collectionAdminService.collectionAdminViews$(this.organization.id, userId), + ), + ), + ); if (collections.length === this.organization.maxCollections) { this.showFreeOrgUpgradeDialog(); return; diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts index 9653c405490..5d2460abdc1 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts @@ -363,7 +363,12 @@ export class VaultComponent implements OnInit, OnDestroy { this.allCollectionsWithoutUnassigned$ = this.refresh$.pipe( switchMap(() => organizationId$), - switchMap((orgId) => this.collectionAdminService.getAll(orgId)), + switchMap((orgId) => + this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => this.collectionAdminService.collectionAdminViews$(orgId, userId)), + ), + ), shareReplay({ refCount: false, bufferSize: 1 }), ); @@ -386,11 +391,13 @@ export class VaultComponent implements OnInit, OnDestroy { // FIXME: We should not assert that the Unassigned type is a CollectionId. // Instead we should consider representing the Unassigned collection as a different object, given that // it is not actually a collection. - const noneCollection = new CollectionAdminView(); - noneCollection.name = this.i18nService.t("unassigned"); - noneCollection.id = Unassigned as CollectionId; - noneCollection.organizationId = organizationId; - return allCollections.concat(noneCollection); + return allCollections.concat( + new CollectionAdminView({ + name: this.i18nService.t("unassigned"), + id: Unassigned as CollectionId, + organizationId, + }), + ); }), ); diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts index ca7d07220b2..9b9be4e50b3 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts @@ -28,6 +28,7 @@ import { } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -156,7 +157,11 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); - private orgCollections$ = from(this.collectionAdminService.getAll(this.organizationId)).pipe( + private orgCollections$ = this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => + this.collectionAdminService.collectionAdminViews$(this.organizationId, userId), + ), shareReplay({ refCount: true, bufferSize: 1 }), ); diff --git a/apps/web/src/app/admin-console/organizations/manage/groups.component.ts b/apps/web/src/app/admin-console/organizations/manage/groups.component.ts index f48860c69a6..23e92056c95 100644 --- a/apps/web/src/app/admin-console/organizations/manage/groups.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/groups.component.ts @@ -253,8 +253,8 @@ export class GroupsComponent { private toCollectionMap( response: ListResponse, ): Observable> { - const collections = response.data.map( - (r) => new Collection(new CollectionData(r as CollectionDetailsResponse)), + const collections = response.data.map((r) => + Collection.fromCollectionData(new CollectionData(r as CollectionDetailsResponse)), ); return this.accountService.activeAccount$.pipe( diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index c6a60165fe1..b951f73d953 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -32,6 +32,7 @@ import { import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -276,9 +277,16 @@ export class MemberDialogComponent implements OnDestroy { ), ); + const collections = this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => + this.collectionAdminService.collectionAdminViews$(this.params.organizationId, userId), + ), + ); + combineLatest({ organization: this.organization$, - collections: this.collectionAdminService.getAll(this.params.organizationId), + collections, userDetails: userDetails$, groups: groups$, }) diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index 2aac4d0a5c8..b4542be8d26 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -200,7 +200,14 @@ export class MembersComponent extends BaseMembersComponent this.organization.canManageUsersPassword && !this.organization.hasPublicAndPrivateKeys ) { - const orgShareKey = await this.keyService.getOrgKey(this.organization.id); + const orgShareKey = await firstValueFrom( + this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => this.keyService.orgKeys$(userId)), + map((orgKeys) => orgKeys[this.organization.id] ?? null), + ), + ); + const orgKeys = await this.keyService.makeKeyPair(orgShareKey); const request = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString); const response = await this.organizationApiService.updateKeys( @@ -305,7 +312,9 @@ export class MembersComponent extends BaseMembersComponent async getCollectionNameMap() { const response = from(this.apiService.getCollections(this.organization.id)).pipe( map((res) => - res.data.map((r) => new Collection(new CollectionData(r as CollectionDetailsResponse))), + res.data.map((r) => + Collection.fromCollectionData(new CollectionData(r as CollectionDetailsResponse)), + ), ), ); @@ -353,7 +362,13 @@ export class MembersComponent extends BaseMembersComponent this.organizationUserService.confirmUser(this.organization, user, publicKey), ); } else { - const orgKey = await this.keyService.getOrgKey(this.organization.id); + const orgKey = await firstValueFrom( + this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => this.keyService.orgKeys$(userId)), + map((orgKeys) => orgKeys[this.organization.id] ?? null), + ), + ); const key = await this.encryptService.encapsulateKeyUnsigned(orgKey, publicKey); const request = new OrganizationUserConfirmRequest(); request.key = key.encryptedString; diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts index db94eb2535f..afc16e72373 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts @@ -17,8 +17,9 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { EncryptionType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { CsprngArray } from "@bitwarden/common/types/csprng"; -import { UserId } from "@bitwarden/common/types/guid"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { UserKey, OrgKey, MasterKey } from "@bitwarden/common/types/key"; import { KdfType, KeyService } from "@bitwarden/key-management"; @@ -36,6 +37,8 @@ describe("OrganizationUserResetPasswordService", () => { let organizationUserApiService: MockProxy; let organizationApiService: MockProxy; let i18nService: MockProxy; + const mockUserId = Utils.newGuid() as UserId; + let accountService: FakeAccountService; beforeAll(() => { keyService = mock(); @@ -44,6 +47,7 @@ describe("OrganizationUserResetPasswordService", () => { organizationUserApiService = mock(); organizationApiService = mock(); i18nService = mock(); + accountService = mockAccountServiceWith(mockUserId); sut = new OrganizationUserResetPasswordService( keyService, @@ -52,6 +56,7 @@ describe("OrganizationUserResetPasswordService", () => { organizationUserApiService, organizationApiService, i18nService, + accountService, ); }); @@ -142,7 +147,10 @@ describe("OrganizationUserResetPasswordService", () => { const mockRandomBytes = new Uint8Array(64) as CsprngArray; const mockOrgKey = new SymmetricCryptoKey(mockRandomBytes) as OrgKey; - keyService.getOrgKey.mockResolvedValue(mockOrgKey); + keyService.orgKeys$.mockReturnValue( + of({ [mockOrgId]: mockOrgKey } as Record), + ); + encryptService.decryptToBytes.mockResolvedValue(mockRandomBytes); encryptService.rsaDecrypt.mockResolvedValue(mockRandomBytes); @@ -170,7 +178,7 @@ describe("OrganizationUserResetPasswordService", () => { }); it("should throw an error if the org key is null", async () => { - keyService.getOrgKey.mockResolvedValue(null); + keyService.orgKeys$.mockReturnValue(of(null)); await expect( sut.resetMasterPassword(mockNewMP, mockEmail, mockOrgUserId, mockOrgId), ).rejects.toThrow(); diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts index a1727a8cc59..df5e7e8a25c 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Injectable } from "@angular/core"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map, switchMap } from "rxjs"; import { OrganizationUserApiService, @@ -10,6 +10,8 @@ import { } from "@bitwarden/admin-console/common"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncryptedString, @@ -47,6 +49,7 @@ export class OrganizationUserResetPasswordService private organizationUserApiService: OrganizationUserApiService, private organizationApiService: OrganizationApiServiceAbstraction, private i18nService: I18nService, + private accountService: AccountService, ) {} /** @@ -111,7 +114,14 @@ export class OrganizationUserResetPasswordService } // Decrypt Organization's encrypted Private Key with org key - const orgSymKey = await this.keyService.getOrgKey(orgId); + const orgSymKey = await firstValueFrom( + this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => this.keyService.orgKeys$(userId)), + map((orgKeys) => orgKeys[orgId as OrganizationId] ?? null), + ), + ); + if (orgSymKey == null) { throw new Error("No org key found"); } diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.ts b/apps/web/src/app/admin-console/organizations/settings/account.component.ts index ecb2dbc54a2..21424e86521 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.ts @@ -8,6 +8,7 @@ import { firstValueFrom, from, lastValueFrom, + map, of, Subject, switchMap, @@ -28,6 +29,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; @@ -179,7 +181,13 @@ export class AccountComponent implements OnInit, OnDestroy { // Backfill pub/priv key if necessary if (!this.org.hasPublicAndPrivateKeys) { - const orgShareKey = await this.keyService.getOrgKey(this.organizationId); + const orgShareKey = await firstValueFrom( + this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => this.keyService.orgKeys$(userId)), + map((orgKeys) => orgKeys[this.organizationId as OrganizationId] ?? null), + ), + ); const orgKeys = await this.keyService.makeKeyPair(orgShareKey); request.keys = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString); } diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html index e9b7ba39aa5..e0ffc9a4bce 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html @@ -50,7 +50,7 @@ >
- +
{{ item.labelName }} @@ -58,7 +58,10 @@ {{ "invited" | i18n }}
-
+
{{ $any(item).email }}
@@ -77,10 +80,10 @@ - {{ item.labelName }} {{ "permission" | i18n }} diff --git a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts index a0964a90fca..59d042cae52 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts @@ -240,9 +240,15 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { return this.groupService.getAll(orgId); }), ); + + const collections = this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => this.collectionAdminService.collectionAdminViews$(orgId, userId)), + ); + combineLatest({ organization: organization$, - collections: this.collectionAdminService.getAll(orgId), + collections, groups: groups$, users: this.organizationUserApiService.getAllMiniUserDetails(orgId), }) @@ -393,9 +399,14 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { return; } - const collectionView = new CollectionAdminView(); - collectionView.id = this.params.collectionId; - collectionView.organizationId = this.formGroup.controls.selectedOrg.value; + const parent = this.formGroup.controls.parent?.value; + const collectionView = new CollectionAdminView({ + id: this.params.collectionId as CollectionId, + organizationId: this.formGroup.controls.selectedOrg.value, + name: parent + ? `${parent}/${this.formGroup.controls.name.value}` + : this.formGroup.controls.name.value, + }); collectionView.externalId = this.formGroup.controls.externalId.value; collectionView.groups = this.formGroup.controls.access.value .filter((v) => v.type === AccessItemType.Group) @@ -404,13 +415,6 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { .filter((v) => v.type === AccessItemType.Member) .map(convertToSelectionView); - const parent = this.formGroup.controls.parent.value; - if (parent) { - collectionView.name = `${parent}/${this.formGroup.controls.name.value}`; - } else { - collectionView.name = this.formGroup.controls.name.value; - } - const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const savedCollection = await this.collectionAdminService.save(collectionView, userId); diff --git a/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts b/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts index 799e10bc15c..8edf98e569e 100644 --- a/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts +++ b/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts @@ -9,6 +9,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { ResetPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/reset-password-policy-options"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; +import { OrganizationInvite } from "@bitwarden/common/auth/services/organization-invite/organization-invite"; import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -86,26 +87,29 @@ describe("WebLoginComponentService", () => { }); describe("getOrgPoliciesFromOrgInvite", () => { + const mockEmail = "test@example.com"; + const orgInvite: OrganizationInvite = { + organizationId: "org-id", + token: "token", + email: mockEmail, + organizationUserId: "org-user-id", + initOrganization: false, + orgSsoIdentifier: "sso-id", + orgUserHasExistingUser: false, + organizationName: "org-name", + }; + it("returns undefined if organization invite is null", async () => { organizationInviteService.getOrganizationInvite.mockResolvedValue(null); - const result = await service.getOrgPoliciesFromOrgInvite(); + const result = await service.getOrgPoliciesFromOrgInvite(mockEmail); expect(result).toBeUndefined(); }); it("logs an error if getPoliciesByToken throws an error", async () => { const error = new Error("Test error"); - organizationInviteService.getOrganizationInvite.mockResolvedValue({ - organizationId: "org-id", - token: "token", - email: "email", - organizationUserId: "org-user-id", - initOrganization: false, - orgSsoIdentifier: "sso-id", - orgUserHasExistingUser: false, - organizationName: "org-name", - }); + organizationInviteService.getOrganizationInvite.mockResolvedValue(orgInvite); policyApiService.getPoliciesByToken.mockRejectedValue(error); - await service.getOrgPoliciesFromOrgInvite(); + await service.getOrgPoliciesFromOrgInvite(mockEmail); expect(logService.error).toHaveBeenCalledWith(error); }); @@ -120,16 +124,7 @@ describe("WebLoginComponentService", () => { const resetPasswordPolicyOptions = new ResetPasswordPolicyOptions(); resetPasswordPolicyOptions.autoEnrollEnabled = autoEnrollEnabled; - organizationInviteService.getOrganizationInvite.mockResolvedValue({ - organizationId: "org-id", - token: "token", - email: "email", - organizationUserId: "org-user-id", - initOrganization: false, - orgSsoIdentifier: "sso-id", - orgUserHasExistingUser: false, - organizationName: "org-name", - }); + organizationInviteService.getOrganizationInvite.mockResolvedValue(orgInvite); policyApiService.getPoliciesByToken.mockResolvedValue(policies); internalPolicyService.getResetPasswordPolicyOptions.mockReturnValue([ @@ -141,7 +136,7 @@ describe("WebLoginComponentService", () => { masterPasswordPolicyOptions, ); - const result = await service.getOrgPoliciesFromOrgInvite(); + const result = await service.getOrgPoliciesFromOrgInvite(mockEmail); expect(result).toEqual({ policies: policies, @@ -151,5 +146,40 @@ describe("WebLoginComponentService", () => { }); }, ); + + describe("given the orgInvite email does not match the provided email", () => { + const mockMismatchedEmail = "mismatched@example.com"; + it("should clear the login redirect URL and organization invite", async () => { + // Arrange + organizationInviteService.getOrganizationInvite.mockResolvedValue({ + ...orgInvite, + email: mockMismatchedEmail, + }); + + // Act + await service.getOrgPoliciesFromOrgInvite(mockEmail); + + // Assert + expect(routerService.getAndClearLoginRedirectUrl).toHaveBeenCalledTimes(1); + expect(organizationInviteService.clearOrganizationInvitation).toHaveBeenCalledTimes(1); + }); + + it("should log an error and return undefined", async () => { + // Arrange + organizationInviteService.getOrganizationInvite.mockResolvedValue({ + ...orgInvite, + email: mockMismatchedEmail, + }); + + // Act + const result = await service.getOrgPoliciesFromOrgInvite(mockEmail); + + // Assert + expect(logService.error).toHaveBeenCalledWith( + `WebLoginComponentService.getOrgPoliciesFromOrgInvite: Email mismatch. Expected: ${mockMismatchedEmail}, Received: ${mockEmail}`, + ); + expect(result).toBeUndefined(); + }); + }); }); }); diff --git a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts index 4ee84ecfde2..5bea0908b0a 100644 --- a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts +++ b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts @@ -66,10 +66,27 @@ export class WebLoginComponentService return; } - async getOrgPoliciesFromOrgInvite(): Promise { + async getOrgPoliciesFromOrgInvite(email: string): Promise { const orgInvite = await this.organizationInviteService.getOrganizationInvite(); if (orgInvite != null) { + /** + * Check if the email on the org invite matches the email submitted in the login form. This is + * important because say userA at "userA@mail.com" clicks an emailed org invite link, but then + * on the login page form they change the email to "userB@mail.com". We don't want to apply the org + * invite in state to userB. Therefore we clear the login redirect url as well as the org invite, + * allowing userB to login as normal. + */ + if (orgInvite.email !== email.toLowerCase()) { + await this.routerService.getAndClearLoginRedirectUrl(); + await this.organizationInviteService.clearOrganizationInvitation(); + + this.logService.error( + `WebLoginComponentService.getOrgPoliciesFromOrgInvite: Email mismatch. Expected: ${orgInvite.email}, Received: ${email}`, + ); + return undefined; + } + let policies: Policy[]; try { diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts index b341fc4f8e4..f0ecca1686d 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts @@ -2,11 +2,14 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -60,6 +63,11 @@ describe("EmergencyViewDialogComponent", () => { { provide: AccountService, useValue: accountService }, { provide: TaskService, useValue: mock() }, { provide: LogService, useValue: mock() }, + { + provide: EnvironmentService, + useValue: { environment$: of({ getIconsUrl: () => "https://icons.example.com" }) }, + }, + { provide: DomainSettingsService, useValue: { showFavicons$: of(true) } }, ], }) .overrideComponent(EmergencyViewDialogComponent, { diff --git a/apps/web/src/app/billing/payment/components/add-account-credit-dialog.component.ts b/apps/web/src/app/billing/payment/components/add-account-credit-dialog.component.ts index 2030d0e73ec..90705abddd3 100644 --- a/apps/web/src/app/billing/payment/components/add-account-credit-dialog.component.ts +++ b/apps/web/src/app/billing/payment/components/add-account-credit-dialog.component.ts @@ -145,13 +145,13 @@ export class AddAccountCreditDialogComponent { map((cloudRegion) => { switch (this.dialogParams.owner.type) { case "account": { - return `user_id=${this.dialogParams.owner.data.id},account_credit=1,region=${cloudRegion}`; + return `user_id:${this.dialogParams.owner.data.id},account_credit:1,region:${cloudRegion}`; } case "organization": { - return `organization_id=${this.dialogParams.owner.data.id},account_credit=1,region=${cloudRegion}`; + return `organization_id:${this.dialogParams.owner.data.id},account_credit:1,region:${cloudRegion}`; } case "provider": { - return `provider_id=${this.dialogParams.owner.data.id},account_credit=1,region=${cloudRegion}`; + return `provider_id:${this.dialogParams.owner.data.id},account_credit:1,region:${cloudRegion}`; } } }), diff --git a/apps/web/src/app/layouts/header/web-header.component.html b/apps/web/src/app/layouts/header/web-header.component.html index 4b0da4ae569..91c31853648 100644 --- a/apps/web/src/app/layouts/header/web-header.component.html +++ b/apps/web/src/app/layouts/header/web-header.component.html @@ -2,7 +2,7 @@ *ngIf="routeData$ | async as routeData" class="-tw-m-6 tw-mb-3 tw-flex tw-flex-col tw-p-6" [ngClass]="{ - 'tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300 tw-bg-background-alt tw-pb-0': + 'tw-border-0 tw-border-b tw-border-solid tw-border-secondary-100 tw-bg-background-alt tw-pb-0': tabsContainer.childElementCount !== 0, }" > @@ -30,7 +30,7 @@ diff --git a/apps/web/src/app/tools/import/import-collection-admin.service.ts b/apps/web/src/app/tools/import/import-collection-admin.service.ts index 64050eb9c06..b63cd15047b 100644 --- a/apps/web/src/app/tools/import/import-collection-admin.service.ts +++ b/apps/web/src/app/tools/import/import-collection-admin.service.ts @@ -1,13 +1,20 @@ import { Injectable } from "@angular/core"; +import { firstValueFrom } from "rxjs"; import { CollectionAdminService, CollectionAdminView } from "@bitwarden/admin-console/common"; import { ImportCollectionServiceAbstraction } from "@bitwarden/importer-core"; +import { UserId } from "@bitwarden/user-core"; @Injectable() export class ImportCollectionAdminService implements ImportCollectionServiceAbstraction { constructor(private collectionAdminService: CollectionAdminService) {} - async getAllAdminCollections(organizationId: string): Promise { - return await this.collectionAdminService.getAll(organizationId); + async getAllAdminCollections( + organizationId: string, + userId: UserId, + ): Promise { + return await firstValueFrom( + this.collectionAdminService.collectionAdminViews$(organizationId, userId), + ); } } diff --git a/apps/web/src/app/tools/send/send-access/authentication-flow.md b/apps/web/src/app/tools/send/send-access/authentication-flow.md new file mode 100644 index 00000000000..f39b43fcd41 --- /dev/null +++ b/apps/web/src/app/tools/send/send-access/authentication-flow.md @@ -0,0 +1,75 @@ +# Send Authentication Flows + +In the below diagrams, activations represent client control flow. + +## Public Sends + +Anyone can access a public send. The token endpoint automatically issues a token. It never issues a challenge. + +```mermaid +sequenceDiagram + participant Visitor + participant TryAccess as try-send-access.guard + participant SendToken as send-token API + participant ViewContent as view-content.component + participant SendAccess as send-access API + + Visitor->>TryAccess: Navigate to send URL + activate TryAccess + TryAccess->>SendToken: Request anonymous access token + SendToken-->>TryAccess: OK + Security token + TryAccess->>ViewContent: Redirect with token + deactivate TryAccess + activate ViewContent + ViewContent->>SendAccess: Request send content (with token and key) + SendAccess-->>ViewContent: Return send content + ViewContent->>Visitor: Display send content + deactivate ViewContent +``` + +## Password Protected Sends + +Password protected sends redirect to a password challenge prompt. + +```mermaid +sequenceDiagram + participant Visitor + participant TryAccess as try-send-access.guard + participant PasswordAuth as password-authentication.component + participant SendToken as send-token API + participant ViewContent as view-content.component + participant SendAccess as send-access API + + Visitor->>TryAccess: Navigate to send URL + activate TryAccess + TryAccess->>SendToken: Request anonymous access token + SendToken-->>TryAccess: Unauthorized + Password challenge + TryAccess->>PasswordAuth: Redirect with send ID and key + deactivate TryAccess + activate PasswordAuth + PasswordAuth->>Visitor: Request password + Visitor-->>PasswordAuth: Enter password + PasswordAuth->>SendToken: Request access token (with password) + SendToken-->>PasswordAuth: OK + Security token + deactivate PasswordAuth + activate ViewContent + PasswordAuth->>ViewContent: Redirect with token and send key + ViewContent->>SendAccess: Request send content (with token) + SendAccess-->>ViewContent: Return send content + ViewContent->>Visitor: Display send content + deactivate ViewContent +``` + +## Send Access without token + +Visiting the view page without a token redirects to a try-access flow, above. + +```mermaid +sequenceDiagram + participant Visitor + participant ViewContent as view-content.component + participant TryAccess as try-send-access.guard + + Visitor->>ViewContent: Navigate to send URL (with id and key) + ViewContent->>TryAccess: Redirect to try-access (with id and key) +``` diff --git a/apps/web/src/app/tools/send/send-access/default-send-access-service.spec.ts b/apps/web/src/app/tools/send/send-access/default-send-access-service.spec.ts new file mode 100644 index 00000000000..cd07d3684fb --- /dev/null +++ b/apps/web/src/app/tools/send/send-access/default-send-access-service.spec.ts @@ -0,0 +1,175 @@ +import { TestBed, fakeAsync, tick } from "@angular/core/testing"; +import { Router, UrlTree } from "@angular/router"; +import { mock, MockProxy } from "jest-mock-extended"; +import { firstValueFrom, NEVER } from "rxjs"; + +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { StateProvider } from "@bitwarden/common/platform/state"; +import { mockAccountServiceWith, FakeStateProvider } from "@bitwarden/common/spec"; +import { SemanticLogger } from "@bitwarden/common/tools/log"; +import { SystemServiceProvider } from "@bitwarden/common/tools/providers"; +import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; +import { UserId } from "@bitwarden/common/types/guid"; +import { SYSTEM_SERVICE_PROVIDER } from "@bitwarden/generator-components"; + +import { DefaultSendAccessService } from "./default-send-access-service"; +import { SEND_RESPONSE_KEY, SEND_CONTEXT_KEY } from "./send-access-memory"; + +describe("DefaultSendAccessService", () => { + let service: DefaultSendAccessService; + let stateProvider: FakeStateProvider; + let sendApiService: MockProxy; + let router: MockProxy; + let logger: MockProxy; + let systemServiceProvider: MockProxy; + + beforeEach(() => { + const accountService = mockAccountServiceWith("user-id" as UserId); + stateProvider = new FakeStateProvider(accountService); + sendApiService = mock(); + router = mock(); + logger = mock(); + systemServiceProvider = mock(); + + systemServiceProvider.log.mockReturnValue(logger); + + TestBed.configureTestingModule({ + providers: [ + DefaultSendAccessService, + { provide: StateProvider, useValue: stateProvider }, + { provide: SendApiService, useValue: sendApiService }, + { provide: Router, useValue: router }, + { provide: SYSTEM_SERVICE_PROVIDER, useValue: systemServiceProvider }, + ], + }); + + service = TestBed.inject(DefaultSendAccessService); + }); + + describe("constructor", () => { + it("creates logger with type 'SendAccessAuthenticationService' when initialized", () => { + expect(systemServiceProvider.log).toHaveBeenCalledWith({ + type: "SendAccessAuthenticationService", + }); + }); + }); + + describe("redirect$", () => { + const sendId = "test-send-id"; + + it("returns content page UrlTree and logs info when API returns success", async () => { + const expectedUrlTree = { toString: () => "/send/content/test-send-id" } as UrlTree; + sendApiService.postSendAccess.mockResolvedValue({} as any); + router.createUrlTree.mockReturnValue(expectedUrlTree); + + const result = await firstValueFrom(service.redirect$(sendId)); + + expect(result).toBe(expectedUrlTree); + expect(logger.info).toHaveBeenCalledWith( + "public send detected; redirecting to send access with token.", + ); + }); + + describe("given error responses", () => { + it("returns password flow UrlTree and logs debug when 401 received", async () => { + const expectedUrlTree = { toString: () => "/send/test-send-id" } as UrlTree; + const errorResponse = new ErrorResponse([], 401); + sendApiService.postSendAccess.mockRejectedValue(errorResponse); + router.createUrlTree.mockReturnValue(expectedUrlTree); + + const result = await firstValueFrom(service.redirect$(sendId)); + + expect(result).toBe(expectedUrlTree); + expect(logger.debug).toHaveBeenCalledWith(errorResponse, "redirecting to password flow"); + }); + + it("returns 404 page UrlTree and logs debug when 404 received", async () => { + const expectedUrlTree = { toString: () => "/404.html" } as UrlTree; + const errorResponse = new ErrorResponse([], 404); + sendApiService.postSendAccess.mockRejectedValue(errorResponse); + router.parseUrl.mockReturnValue(expectedUrlTree); + + const result = await firstValueFrom(service.redirect$(sendId)); + + expect(result).toBe(expectedUrlTree); + expect(logger.debug).toHaveBeenCalledWith(errorResponse, "redirecting to unavailable page"); + }); + + it("logs warning and throws error when 500 received", async () => { + const errorResponse = new ErrorResponse([], 500); + sendApiService.postSendAccess.mockRejectedValue(errorResponse); + + await expect(firstValueFrom(service.redirect$(sendId))).rejects.toBe(errorResponse); + expect(logger.warn).toHaveBeenCalledWith( + errorResponse, + "received unexpected error response", + ); + }); + + it("throws error when unexpected error code received", async () => { + const errorResponse = new ErrorResponse([], 403); + sendApiService.postSendAccess.mockRejectedValue(errorResponse); + + await expect(firstValueFrom(service.redirect$(sendId))).rejects.toBe(errorResponse); + expect(logger.warn).toHaveBeenCalledWith( + errorResponse, + "received unexpected error response", + ); + }); + }); + + it("throws error when non-ErrorResponse error occurs", async () => { + const regularError = new Error("Network error"); + sendApiService.postSendAccess.mockRejectedValue(regularError); + + await expect(firstValueFrom(service.redirect$(sendId))).rejects.toThrow("Network error"); + expect(logger.warn).not.toHaveBeenCalled(); + }); + + it("emits timeout error when API response exceeds 10 seconds", fakeAsync(() => { + // Mock API to never resolve (simulating a hung request) + sendApiService.postSendAccess.mockReturnValue(firstValueFrom(NEVER)); + + const result$ = service.redirect$(sendId); + let error: any; + + result$.subscribe({ + error: (err: unknown) => (error = err), + }); + + // Advance time past 10 seconds + tick(10001); + + expect(error).toBeDefined(); + expect(error.name).toBe("TimeoutError"); + })); + }); + + describe("setContext", () => { + it("updates global state with send context when called with sendId and key", async () => { + const sendId = "test-send-id"; + const key = "test-key"; + + await service.setContext(sendId, key); + + const context = await firstValueFrom(stateProvider.getGlobal(SEND_CONTEXT_KEY).state$); + expect(context).toEqual({ id: sendId, key }); + }); + }); + + describe("clear", () => { + it("sets both SEND_RESPONSE_KEY and SEND_CONTEXT_KEY to null when called", async () => { + // Set initial values + await stateProvider.getGlobal(SEND_RESPONSE_KEY).update(() => ({ some: "response" }) as any); + await stateProvider.getGlobal(SEND_CONTEXT_KEY).update(() => ({ id: "test", key: "test" })); + + await service.clear(); + + const response = await firstValueFrom(stateProvider.getGlobal(SEND_RESPONSE_KEY).state$); + const context = await firstValueFrom(stateProvider.getGlobal(SEND_CONTEXT_KEY).state$); + + expect(response).toBeNull(); + expect(context).toBeNull(); + }); + }); +}); diff --git a/apps/web/src/app/tools/send/send-access/default-send-access-service.ts b/apps/web/src/app/tools/send/send-access/default-send-access-service.ts new file mode 100644 index 00000000000..732303ce25a --- /dev/null +++ b/apps/web/src/app/tools/send/send-access/default-send-access-service.ts @@ -0,0 +1,96 @@ +import { Injectable, Inject } from "@angular/core"; +import { Router, UrlTree } from "@angular/router"; +import { map, of, from, catchError, timeout } from "rxjs"; + +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { StateProvider } from "@bitwarden/common/platform/state"; +import { SemanticLogger } from "@bitwarden/common/tools/log"; +import { SystemServiceProvider } from "@bitwarden/common/tools/providers"; +import { SendAccessRequest } from "@bitwarden/common/tools/send/models/request/send-access.request"; +import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; +import { SYSTEM_SERVICE_PROVIDER } from "@bitwarden/generator-components"; + +import { SEND_RESPONSE_KEY, SEND_CONTEXT_KEY } from "./send-access-memory"; +import { SendAccessService } from "./send-access-service.abstraction"; +import { isErrorResponse } from "./util"; + +const TEN_SECONDS = 10_000; + +@Injectable({ providedIn: "root" }) +export class DefaultSendAccessService implements SendAccessService { + private readonly logger: SemanticLogger; + + constructor( + private readonly state: StateProvider, + private readonly api: SendApiService, + private readonly router: Router, + @Inject(SYSTEM_SERVICE_PROVIDER) system: SystemServiceProvider, + ) { + this.logger = system.log({ type: "SendAccessAuthenticationService" }); + } + + redirect$(sendId: string) { + // FIXME: when the send authentication APIs become available, this method + // should delegate to the API + const response$ = from(this.api.postSendAccess(sendId, new SendAccessRequest())); + + const redirect$ = response$.pipe( + timeout({ first: TEN_SECONDS }), + map((_response) => { + this.logger.info("public send detected; redirecting to send access with token."); + const url = this.toViewRedirect(sendId); + + return url; + }), + catchError((error: unknown) => { + let processed: UrlTree | undefined = undefined; + + if (isErrorResponse(error)) { + processed = this.toErrorRedirect(sendId, error); + } + + if (processed) { + return of(processed); + } + + throw error; + }), + ); + + return redirect$; + } + + private toViewRedirect(sendId: string) { + return this.router.createUrlTree(["send", "content", sendId]); + } + + private toErrorRedirect(sendId: string, response: ErrorResponse) { + let url: UrlTree | undefined = undefined; + + switch (response.statusCode) { + case 401: + this.logger.debug(response, "redirecting to password flow"); + url = this.router.createUrlTree(["send/password", sendId]); + break; + + case 404: + this.logger.debug(response, "redirecting to unavailable page"); + url = this.router.parseUrl("/404.html"); + break; + + default: + this.logger.warn(response, "received unexpected error response"); + } + + return url; + } + + async setContext(sendId: string, key: string) { + await this.state.getGlobal(SEND_CONTEXT_KEY).update(() => ({ id: sendId, key })); + } + + async clear(): Promise { + await this.state.getGlobal(SEND_RESPONSE_KEY).update(() => null); + await this.state.getGlobal(SEND_CONTEXT_KEY).update(() => null); + } +} diff --git a/apps/web/src/app/tools/send/send-access/index.ts b/apps/web/src/app/tools/send/send-access/index.ts index c9df5ce5193..4bef65f468b 100644 --- a/apps/web/src/app/tools/send/send-access/index.ts +++ b/apps/web/src/app/tools/send/send-access/index.ts @@ -1,2 +1,4 @@ export { AccessComponent } from "./access.component"; export { SendAccessExplainerComponent } from "./send-access-explainer.component"; + +export { SendAccessRoutes } from "./routes"; diff --git a/apps/web/src/app/tools/send/send-access/routes.ts b/apps/web/src/app/tools/send/send-access/routes.ts new file mode 100644 index 00000000000..4f794aecd23 --- /dev/null +++ b/apps/web/src/app/tools/send/send-access/routes.ts @@ -0,0 +1,60 @@ +import { Routes } from "@angular/router"; + +import { AnonLayoutWrapperData } from "@bitwarden/components"; +import { ActiveSendIcon } from "@bitwarden/send-ui"; + +import { RouteDataProperties } from "../../../core"; + +import { SendAccessExplainerComponent } from "./send-access-explainer.component"; +import { SendAccessPasswordComponent } from "./send-access-password.component"; +import { trySendAccess } from "./try-send-access.guard"; + +/** Routes to reach send access screens */ +export const SendAccessRoutes: Routes = [ + { + path: "send/:sendId", + // there are no child pages because `trySendAccess` always performs a redirect + canActivate: [trySendAccess], + }, + { + path: "send/password/:sendId", + data: { + pageTitle: { + key: "sendAccessPasswordTitle", + }, + pageIcon: ActiveSendIcon, + showReadonlyHostname: true, + } satisfies RouteDataProperties & AnonLayoutWrapperData, + children: [ + { + path: "", + component: SendAccessPasswordComponent, + }, + { + path: "", + outlet: "secondary", + component: SendAccessExplainerComponent, + }, + ], + }, + { + path: "send/content/:sendId", + data: { + pageTitle: { + key: "sendAccessContentTitle", + }, + pageIcon: ActiveSendIcon, + showReadonlyHostname: true, + } satisfies RouteDataProperties & AnonLayoutWrapperData, + children: [ + { + path: "send/password/:sendId", + }, + { + path: "", + outlet: "secondary", + component: SendAccessExplainerComponent, + }, + ], + }, +]; diff --git a/apps/web/src/app/tools/send/send-access/send-access-memory.spec.ts b/apps/web/src/app/tools/send/send-access/send-access-memory.spec.ts new file mode 100644 index 00000000000..8d7fe9cd380 --- /dev/null +++ b/apps/web/src/app/tools/send/send-access/send-access-memory.spec.ts @@ -0,0 +1,50 @@ +import { KeyDefinition, SEND_ACCESS_AUTH_MEMORY } from "@bitwarden/common/platform/state"; +import { SendAccessResponse } from "@bitwarden/common/tools/send/models/response/send-access.response"; + +import { SEND_CONTEXT_KEY, SEND_RESPONSE_KEY } from "./send-access-memory"; +import { SendContext } from "./types"; + +describe("send-access-memory", () => { + describe("SEND_CONTEXT_KEY", () => { + it("has correct state definition properties", () => { + expect(SEND_CONTEXT_KEY).toBeInstanceOf(KeyDefinition); + expect(SEND_CONTEXT_KEY.stateDefinition).toBe(SEND_ACCESS_AUTH_MEMORY); + expect(SEND_CONTEXT_KEY.key).toBe("sendContext"); + }); + + it("deserializes data as-is", () => { + const testContext: SendContext = { id: "test-id", key: "test-key" }; + const deserializer = SEND_CONTEXT_KEY.deserializer; + expect(deserializer(testContext)).toBe(testContext); + }); + + it("deserializes null as null", () => { + const deserializer = SEND_CONTEXT_KEY.deserializer; + expect(deserializer(null)).toBe(null); + }); + }); + + describe("SEND_RESPONSE_KEY", () => { + it("has correct state definition properties", () => { + expect(SEND_RESPONSE_KEY).toBeInstanceOf(KeyDefinition); + expect(SEND_RESPONSE_KEY.stateDefinition).toBe(SEND_ACCESS_AUTH_MEMORY); + expect(SEND_RESPONSE_KEY.key).toBe("sendResponse"); + }); + + it("deserializes data into SendAccessResponse instance", () => { + const mockData = { id: "test-id", name: "test-send" } as any; + const deserializer = SEND_RESPONSE_KEY.deserializer; + const result = deserializer(mockData); + + expect(result).toBeInstanceOf(SendAccessResponse); + }); + + it.each([ + [null, "null"], + [undefined, "undefined"], + ])("deserializes %s as null", (value, _) => { + const deserializer = SEND_RESPONSE_KEY.deserializer; + expect(deserializer(value!)).toBe(null); + }); + }); +}); diff --git a/apps/web/src/app/tools/send/send-access/send-access-memory.ts b/apps/web/src/app/tools/send/send-access/send-access-memory.ts new file mode 100644 index 00000000000..4f67cf43b37 --- /dev/null +++ b/apps/web/src/app/tools/send/send-access/send-access-memory.ts @@ -0,0 +1,25 @@ +import { KeyDefinition, SEND_ACCESS_AUTH_MEMORY } from "@bitwarden/common/platform/state"; +import { SendAccessResponse } from "@bitwarden/common/tools/send/models/response/send-access.response"; + +import { SendContext } from "./types"; + +export const SEND_CONTEXT_KEY = new KeyDefinition( + SEND_ACCESS_AUTH_MEMORY, + "sendContext", + { + deserializer: (data) => data, + }, +); + +/** When send authentication succeeds, this stores the result so that + * multiple access attempts don't accrue due to the send workflow. + */ +// FIXME: replace this with the send authentication token once it's +// available +export const SEND_RESPONSE_KEY = new KeyDefinition( + SEND_ACCESS_AUTH_MEMORY, + "sendResponse", + { + deserializer: (data) => (data ? new SendAccessResponse(data) : null), + }, +); diff --git a/apps/web/src/app/tools/send/send-access/send-access-service.abstraction.ts b/apps/web/src/app/tools/send/send-access/send-access-service.abstraction.ts new file mode 100644 index 00000000000..66fc87fe802 --- /dev/null +++ b/apps/web/src/app/tools/send/send-access/send-access-service.abstraction.ts @@ -0,0 +1,10 @@ +import { UrlTree } from "@angular/router"; +import { Observable } from "rxjs"; + +export abstract class SendAccessService { + abstract redirect$: (sendId: string) => Observable; + + abstract setContext: (sendId: string, key: string) => Promise; + + abstract clear: () => Promise; +} diff --git a/apps/web/src/app/tools/send/send-access/try-send-access.guard.spec.ts b/apps/web/src/app/tools/send/send-access/try-send-access.guard.spec.ts new file mode 100644 index 00000000000..267de83db9f --- /dev/null +++ b/apps/web/src/app/tools/send/send-access/try-send-access.guard.spec.ts @@ -0,0 +1,426 @@ +import { TestBed } from "@angular/core/testing"; +import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from "@angular/router"; +import { firstValueFrom, Observable, of } from "rxjs"; + +import { SemanticLogger } from "@bitwarden/common/tools/log"; +import { SystemServiceProvider } from "@bitwarden/common/tools/providers"; +import { SYSTEM_SERVICE_PROVIDER } from "@bitwarden/generator-components"; + +import { SendAccessService } from "./send-access-service.abstraction"; +import { trySendAccess } from "./try-send-access.guard"; + +function createMockRoute(params: Record): ActivatedRouteSnapshot { + return { params } as ActivatedRouteSnapshot; +} + +function createMockLogger(): SemanticLogger { + return { + warn: jest.fn(), + panic: jest.fn().mockImplementation(() => { + throw new Error("Logger panic called"); + }), + } as any as SemanticLogger; +} + +function createMockSystemServiceProvider(): SystemServiceProvider { + return { + log: jest.fn().mockReturnValue(createMockLogger()), + } as any as SystemServiceProvider; +} + +function createMockSendAccessService() { + return { + setContext: jest.fn().mockResolvedValue(undefined), + redirect$: jest.fn().mockReturnValue(of({} as UrlTree)), + clear: jest.fn().mockResolvedValue(undefined), + }; +} + +describe("trySendAccess", () => { + let mockSendAccessService: ReturnType; + let mockSystemServiceProvider: SystemServiceProvider; + let mockRouterState: RouterStateSnapshot; + + beforeEach(() => { + mockSendAccessService = createMockSendAccessService(); + mockSystemServiceProvider = createMockSystemServiceProvider(); + mockRouterState = {} as RouterStateSnapshot; + + TestBed.configureTestingModule({ + providers: [ + { provide: SendAccessService, useValue: mockSendAccessService }, + { provide: SYSTEM_SERVICE_PROVIDER, useValue: mockSystemServiceProvider }, + ], + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe("canActivate", () => { + describe("given valid route parameters", () => { + it("extracts sendId and key from route params when both are valid strings", async () => { + const sendId = "test-send-id"; + const key = "test-key"; + const mockRoute = createMockRoute({ sendId, key }); + const expectedUrlTree = { toString: () => "/test-url" } as UrlTree; + mockSendAccessService.redirect$.mockReturnValue(of(expectedUrlTree)); + + // need to cast the result because `CanActivateFn` performs type erasure + const result$ = TestBed.runInInjectionContext(() => + trySendAccess(mockRoute, mockRouterState), + ) as unknown as Observable; + + expect(mockSendAccessService.setContext).toHaveBeenCalledWith(sendId, key); + expect(mockSendAccessService.setContext).toHaveBeenCalledTimes(1); + await expect(firstValueFrom(result$)).resolves.toEqual(expectedUrlTree); + }); + + it("does not throw validation errors when sendId and key are valid strings", async () => { + const sendId = "valid-send-id"; + const key = "valid-key"; + const mockRoute = createMockRoute({ sendId, key }); + const expectedUrlTree = { toString: () => "/test-url" } as UrlTree; + mockSendAccessService.redirect$.mockReturnValue(of(expectedUrlTree)); + + // Should not throw any errors during guard execution + let guardResult: Observable | undefined; + expect(() => { + guardResult = TestBed.runInInjectionContext(() => + trySendAccess(mockRoute, mockRouterState), + ) as unknown as Observable; + }).not.toThrow(); + + // Verify the observable can be subscribed to without errors + expect(guardResult).toBeDefined(); + await expect(firstValueFrom(guardResult!)).resolves.toEqual(expectedUrlTree); + + // Logger methods should not be called for warnings or panics + const mockLogger = (mockSystemServiceProvider.log as jest.Mock).mock.results[0].value; + expect(mockLogger.warn).not.toHaveBeenCalled(); + expect(mockLogger.panic).not.toHaveBeenCalled(); + }); + }); + + describe("given invalid route parameters", () => { + describe("given invalid sendId", () => { + it.each([ + ["undefined", undefined], + ["null", null], + ])( + "logs warning with correct message when sendId is %s", + async (description, sendIdValue) => { + const key = "valid-key"; + const mockRoute = createMockRoute( + sendIdValue === undefined ? { key } : { sendId: sendIdValue, key }, + ); + const mockLogger = createMockLogger(); + (mockSystemServiceProvider.log as jest.Mock).mockReturnValue(mockLogger); + + await expect(async () => { + const result$ = TestBed.runInInjectionContext(() => + trySendAccess(mockRoute, mockRouterState), + ) as unknown as Observable; + await firstValueFrom(result$); + }).rejects.toThrow("Logger panic called"); + + expect(mockSystemServiceProvider.log).toHaveBeenCalledWith({ + function: "trySendAccess", + }); + expect(mockLogger.warn).toHaveBeenCalledWith( + "sendId missing from the route parameters; redirecting to 404", + ); + }, + ); + + it.each([ + ["number", 123], + ["object", {}], + ["boolean", true], + ])("logs panic with expected/actual type info when sendId is %s", async (type, value) => { + const key = "valid-key"; + const mockRoute = createMockRoute({ sendId: value, key }); + const mockLogger = createMockLogger(); + (mockSystemServiceProvider.log as jest.Mock).mockReturnValue(mockLogger); + + await expect(async () => { + const result$ = TestBed.runInInjectionContext(() => + trySendAccess(mockRoute, mockRouterState), + ) as unknown as Observable; + await firstValueFrom(result$); + }).rejects.toThrow("Logger panic called"); + + expect(mockSystemServiceProvider.log).toHaveBeenCalledWith({ function: "trySendAccess" }); + expect(mockLogger.panic).toHaveBeenCalledWith( + { expected: "string", actual: type }, + "sendId has invalid type", + ); + }); + + it("throws when sendId is not a string", async () => { + const key = "valid-key"; + const invalidSendIdValues = [123, {}, true, null, undefined]; + + for (const invalidSendId of invalidSendIdValues) { + const mockRoute = createMockRoute( + invalidSendId === undefined ? { key } : { sendId: invalidSendId, key }, + ); + const mockLogger = createMockLogger(); + (mockSystemServiceProvider.log as jest.Mock).mockReturnValue(mockLogger); + + await expect(async () => { + const result$ = TestBed.runInInjectionContext(() => + trySendAccess(mockRoute, mockRouterState), + ) as unknown as Observable; + await firstValueFrom(result$); + }).rejects.toThrow("Logger panic called"); + } + }); + }); + + describe("given invalid key", () => { + it.each([ + ["undefined", undefined], + ["null", null], + ])("logs panic with correct message when key is %s", async (description, keyValue) => { + const sendId = "valid-send-id"; + const mockRoute = createMockRoute( + keyValue === undefined ? { sendId } : { sendId, key: keyValue }, + ); + const mockLogger = createMockLogger(); + (mockSystemServiceProvider.log as jest.Mock).mockReturnValue(mockLogger); + + await expect(async () => { + const result$ = TestBed.runInInjectionContext(() => + trySendAccess(mockRoute, mockRouterState), + ) as unknown as Observable; + await firstValueFrom(result$); + }).rejects.toThrow("Logger panic called"); + + expect(mockSystemServiceProvider.log).toHaveBeenCalledWith({ function: "trySendAccess" }); + expect(mockLogger.panic).toHaveBeenCalledWith("key missing from the route parameters"); + }); + + it.each([ + ["number", 123], + ["object", {}], + ["boolean", true], + ])("logs panic with expected/actual type info when key is %s", async (type, value) => { + const sendId = "valid-send-id"; + const mockRoute = createMockRoute({ sendId, key: value }); + const mockLogger = createMockLogger(); + (mockSystemServiceProvider.log as jest.Mock).mockReturnValue(mockLogger); + + await expect(async () => { + const result$ = TestBed.runInInjectionContext(() => + trySendAccess(mockRoute, mockRouterState), + ) as unknown as Observable; + await firstValueFrom(result$); + }).rejects.toThrow("Logger panic called"); + + expect(mockSystemServiceProvider.log).toHaveBeenCalledWith({ function: "trySendAccess" }); + expect(mockLogger.panic).toHaveBeenCalledWith( + { expected: "string", actual: type }, + "key has invalid type", + ); + }); + + it("throws when key is not a string", async () => { + const sendId = "valid-send-id"; + const invalidKeyValues = [123, {}, true, null, undefined]; + + for (const invalidKey of invalidKeyValues) { + const mockRoute = createMockRoute( + invalidKey === undefined ? { sendId } : { sendId, key: invalidKey }, + ); + const mockLogger = createMockLogger(); + (mockSystemServiceProvider.log as jest.Mock).mockReturnValue(mockLogger); + + await expect(async () => { + const result$ = TestBed.runInInjectionContext(() => + trySendAccess(mockRoute, mockRouterState), + ) as unknown as Observable; + await firstValueFrom(result$); + }).rejects.toThrow("Logger panic called"); + } + }); + }); + }); + + describe("given service interactions", () => { + it("calls setContext with extracted sendId and key when parameters are valid", async () => { + const sendId = "test-send-id"; + const key = "test-key"; + const mockRoute = createMockRoute({ sendId, key }); + const expectedUrlTree = { toString: () => "/test-url" } as UrlTree; + mockSendAccessService.redirect$.mockReturnValue(of(expectedUrlTree)); + + const result$ = TestBed.runInInjectionContext(() => + trySendAccess(mockRoute, mockRouterState), + ) as unknown as Observable; + + await firstValueFrom(result$); + + expect(mockSendAccessService.setContext).toHaveBeenCalledWith(sendId, key); + expect(mockSendAccessService.setContext).toHaveBeenCalledTimes(1); + }); + + it("calls redirect$ with extracted sendId when setContext completes", async () => { + const sendId = "test-send-id"; + const key = "test-key"; + const mockRoute = createMockRoute({ sendId, key }); + const expectedUrlTree = { toString: () => "/test-url" } as UrlTree; + mockSendAccessService.redirect$.mockReturnValue(of(expectedUrlTree)); + + const result$ = TestBed.runInInjectionContext(() => + trySendAccess(mockRoute, mockRouterState), + ) as unknown as Observable; + + await firstValueFrom(result$); + + expect(mockSendAccessService.redirect$).toHaveBeenCalledWith(sendId); + expect(mockSendAccessService.redirect$).toHaveBeenCalledTimes(1); + }); + }); + + describe("given observable behavior", () => { + it("returns redirect$ emissions when setContext completes successfully", async () => { + const sendId = "test-send-id"; + const key = "test-key"; + const mockRoute = createMockRoute({ sendId, key }); + const expectedUrlTree = { toString: () => "/test-url" } as UrlTree; + mockSendAccessService.redirect$.mockReturnValue(of(expectedUrlTree)); + + const result$ = TestBed.runInInjectionContext(() => + trySendAccess(mockRoute, mockRouterState), + ) as unknown as Observable; + + const actualResult = await firstValueFrom(result$); + + expect(actualResult).toEqual(expectedUrlTree); + expect(mockSendAccessService.redirect$).toHaveBeenCalledWith(sendId); + }); + + it("does not emit setContext values when using ignoreElements", async () => { + const sendId = "test-send-id"; + const key = "test-key"; + const mockRoute = createMockRoute({ sendId, key }); + const expectedUrlTree = { toString: () => "/test-url" } as UrlTree; + const setContextValue = "should-not-be-emitted"; + + // Mock setContext to return a value + mockSendAccessService.setContext.mockResolvedValue(setContextValue); + mockSendAccessService.redirect$.mockReturnValue(of(expectedUrlTree)); + + const result$ = TestBed.runInInjectionContext(() => + trySendAccess(mockRoute, mockRouterState), + ) as unknown as Observable; + + const actualResult = await firstValueFrom(result$); + + // Should only emit the redirect$ value, not the setContext value + expect(actualResult).toEqual(expectedUrlTree); + expect(actualResult).not.toEqual(setContextValue); + }); + + it("ensures setContext completes before redirect$ executes (sequencing)", async () => { + const sendId = "test-send-id"; + const key = "test-key"; + const mockRoute = createMockRoute({ sendId, key }); + const expectedUrlTree = { toString: () => "/test-url" } as UrlTree; + + let setContextResolved = false; + + // Mock setContext to track when it resolves + mockSendAccessService.setContext.mockImplementation(async () => { + await new Promise((resolve) => setTimeout(resolve, 10)); // Small delay + setContextResolved = true; + }); + + // Mock redirect$ to return a delayed observable and check if setContext resolved + mockSendAccessService.redirect$.mockImplementation((id) => { + return new Observable((subscriber) => { + // Check if setContext has resolved when redirect$ subscription starts + setTimeout(() => { + expect(setContextResolved).toBe(true); + subscriber.next(expectedUrlTree); + subscriber.complete(); + }, 0); + }); + }); + + const result$ = TestBed.runInInjectionContext(() => + trySendAccess(mockRoute, mockRouterState), + ) as unknown as Observable; + + await firstValueFrom(result$); + }); + }); + + describe("given error scenarios", () => { + it("does not call redirect$ when setContext rejects", async () => { + const sendId = "test-send-id"; + const key = "test-key"; + const mockRoute = createMockRoute({ sendId, key }); + const setContextError = new Error("setContext failed"); + + // Reset mocks to ensure clean state + jest.clearAllMocks(); + + // Mock setContext to reject + mockSendAccessService.setContext.mockRejectedValue(setContextError); + + // Create a mock observable that we can spy on subscription + const mockRedirectObservable = of({} as UrlTree); + const subscribeSpy = jest.spyOn(mockRedirectObservable, "subscribe"); + mockSendAccessService.redirect$.mockReturnValue(mockRedirectObservable); + + const result$ = TestBed.runInInjectionContext(() => + trySendAccess(mockRoute, mockRouterState), + ) as unknown as Observable; + + // Expect the observable to reject when setContext fails + await expect(firstValueFrom(result$)).rejects.toThrow("setContext failed"); + + // The redirect$ method will be called (since it's called synchronously) + expect(mockSendAccessService.redirect$).toHaveBeenCalledWith(sendId); + + // But the returned observable should not be subscribed to due to the error + // Note: This test verifies the error propagation behavior + expect(subscribeSpy).not.toHaveBeenCalled(); + }); + + it("propagates error to guard return value when redirect$ throws", async () => { + const sendId = "test-send-id"; + const key = "test-key"; + const mockRoute = createMockRoute({ sendId, key }); + const redirectError = new Error("redirect$ failed"); + + // Reset mocks to ensure clean state + jest.clearAllMocks(); + + // Mock setContext to succeed and redirect$ to throw + mockSendAccessService.setContext.mockResolvedValue(undefined); + mockSendAccessService.redirect$.mockReturnValue( + new Observable((subscriber) => { + subscriber.error(redirectError); + }), + ); + + const result$ = TestBed.runInInjectionContext(() => + trySendAccess(mockRoute, mockRouterState), + ) as unknown as Observable; + + // Expect the observable to propagate the redirect$ error + await expect(firstValueFrom(result$)).rejects.toThrow("redirect$ failed"); + + // Verify that setContext was called (should succeed) + expect(mockSendAccessService.setContext).toHaveBeenCalledWith(sendId, key); + + // Verify that redirect$ was called (but it throws) + expect(mockSendAccessService.redirect$).toHaveBeenCalledWith(sendId); + }); + }); + }); +}); diff --git a/apps/web/src/app/tools/send/send-access/try-send-access.guard.ts b/apps/web/src/app/tools/send/send-access/try-send-access.guard.ts new file mode 100644 index 00000000000..51941bf8e74 --- /dev/null +++ b/apps/web/src/app/tools/send/send-access/try-send-access.guard.ts @@ -0,0 +1,38 @@ +import { inject } from "@angular/core"; +import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from "@angular/router"; +import { from, ignoreElements, concat } from "rxjs"; + +import { SystemServiceProvider } from "@bitwarden/common/tools/providers"; +import { SYSTEM_SERVICE_PROVIDER } from "@bitwarden/generator-components"; + +import { SendAccessService } from "./send-access-service.abstraction"; + +export const trySendAccess: CanActivateFn = ( + route: ActivatedRouteSnapshot, + _state: RouterStateSnapshot, +) => { + const sendAccess = inject(SendAccessService); + const system = inject(SYSTEM_SERVICE_PROVIDER); + const logger = system.log({ function: "trySendAccess" }); + + const { sendId, key } = route.params; + if (!sendId) { + logger.warn("sendId missing from the route parameters; redirecting to 404"); + } + if (typeof sendId !== "string") { + logger.panic({ expected: "string", actual: typeof sendId }, "sendId has invalid type"); + } + + if (!key) { + logger.panic("key missing from the route parameters"); + } + if (typeof key !== "string") { + logger.panic({ expected: "string", actual: typeof key }, "key has invalid type"); + } + + const contextUpdated$ = from(sendAccess.setContext(sendId, key)).pipe(ignoreElements()); + const redirect$ = sendAccess.redirect$(sendId); + + // ensure the key has loaded before redirecting + return concat(contextUpdated$, redirect$); +}; diff --git a/apps/web/src/app/tools/send/send-access/types.ts b/apps/web/src/app/tools/send/send-access/types.ts new file mode 100644 index 00000000000..03e058ca681 --- /dev/null +++ b/apps/web/src/app/tools/send/send-access/types.ts @@ -0,0 +1,8 @@ +/** global contextual information for the current send access page. */ +export type SendContext = { + /** identifies the send */ + id: string; + + /** decrypts the send content */ + key: string; +}; diff --git a/apps/web/src/app/tools/send/send-access/util.spec.ts b/apps/web/src/app/tools/send/send-access/util.spec.ts new file mode 100644 index 00000000000..45502ee2509 --- /dev/null +++ b/apps/web/src/app/tools/send/send-access/util.spec.ts @@ -0,0 +1,69 @@ +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; + +import { isErrorResponse, isSendContext } from "./util"; + +describe("util", () => { + describe("isErrorResponse", () => { + it("returns true when value is an ErrorResponse instance", () => { + const error = new ErrorResponse(["Error message"], 400); + expect(isErrorResponse(error)).toBe(true); + }); + + it.each([ + [null, "null"], + [undefined, "undefined"], + ])("returns false when value is %s", (value, description) => { + expect(isErrorResponse(value)).toBe(false); + }); + + it.each([ + ["string", "string"], + [123, "number"], + [true, "boolean"], + [{}, "plain object"], + [[], "array"], + ])("returns false when value is not an ErrorResponse (%s)", (value, description) => { + expect(isErrorResponse(value)).toBe(false); + }); + + it("returns false when value is a different Error type", () => { + const error = new Error("test"); + expect(isErrorResponse(error)).toBe(false); + }); + }); + + describe("isSendContext", () => { + it("returns true when value has id and key properties", () => { + const validContext = { id: "test-id", key: "test-key" }; + expect(isSendContext(validContext)).toBe(true); + }); + + it("returns true even with additional properties", () => { + const contextWithExtras = { id: "test-id", key: "test-key", extra: "data" }; + expect(isSendContext(contextWithExtras)).toBe(true); + }); + + it.each([ + [null, "null"], + [undefined, "undefined"], + ])("returns false when value is %s", (value, _) => { + expect(isSendContext(value)).toBe(false); + }); + + it.each([ + ["string", "string"], + [123, "number"], + [true, "boolean"], + ])("returns false when value is not an object (%s)", (value, _) => { + expect(isSendContext(value)).toBe(false); + }); + + it.each([ + [{ key: "test-key" }, "missing id"], + [{ id: "test-id" }, "missing key"], + [{}, "empty object"], + ])("returns false when value is %s", (value, _) => { + expect(isSendContext(value)).toBe(false); + }); + }); +}); diff --git a/apps/web/src/app/tools/send/send-access/util.ts b/apps/web/src/app/tools/send/send-access/util.ts new file mode 100644 index 00000000000..d9cbef0d337 --- /dev/null +++ b/apps/web/src/app/tools/send/send-access/util.ts @@ -0,0 +1,13 @@ +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; + +import { SendContext } from "./types"; + +/** narrows a type to an `ErrorResponse` */ +export function isErrorResponse(value: unknown): value is ErrorResponse { + return value instanceof ErrorResponse; +} + +/** narrows a type to a `SendContext` */ +export function isSendContext(value: unknown): value is SendContext { + return !!value && typeof value === "object" && "id" in value && "key" in value; +} diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts index a8dd0056806..8fde2eb44e4 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts @@ -224,7 +224,7 @@ export class VaultItemsComponent { } protected canEditCollection(collection: CollectionView): boolean { - // Only allow allow deletion if collection editing is enabled and not deleting "Unassigned" + // Only allow deletion if collection editing is enabled and not deleting "Unassigned" if (collection.id === Unassigned) { return false; } @@ -235,7 +235,7 @@ export class VaultItemsComponent { } protected canDeleteCollection(collection: CollectionView): boolean { - // Only allow allow deletion if collection editing is enabled and not deleting "Unassigned" + // Only allow deletion if collection editing is enabled and not deleting "Unassigned" if (collection.id === Unassigned) { return false; } diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts index c114cb6d7c2..043ae900b40 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts @@ -262,9 +262,11 @@ export const OrganizationTrash: Story = { }, }; -const unassignedCollection = new CollectionAdminView(); -unassignedCollection.id = Unassigned as CollectionId; -unassignedCollection.name = "Unassigned"; +const unassignedCollection = new CollectionAdminView({ + id: Unassigned as CollectionId, + name: "Unassigned", + organizationId: "org id" as OrganizationId, +}); export const OrganizationTopLevelCollection: Story = { args: { ciphers: [], @@ -327,11 +329,11 @@ function createCipherView(i: number, deleted = false): CipherView { function createCollectionView(i: number): CollectionAdminView { const organization = organizations[i % (organizations.length + 1)]; const group = groups[i % (groups.length + 1)]; - const view = new CollectionAdminView(); - view.id = `collection-${i}` as CollectionId; - view.name = `Collection ${i}`; - view.organizationId = organization?.id; - view.manage = true; + const view = new CollectionAdminView({ + id: `collection-${i}` as CollectionId, + name: `Collection ${i}`, + organizationId: organization?.id ?? ("orgId" as OrganizationId), + }); if (group !== undefined) { view.groups = [ @@ -344,6 +346,7 @@ function createCollectionView(i: number): CollectionAdminView { ]; } + view.manage = true; return view; } diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.html b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.html index 3ac5e708e8c..7722ba1ad86 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.html @@ -9,6 +9,7 @@ > {{ "filters" | i18n }}
{ orgId: string, type?: CollectionType, ): CollectionView { - const collection = new CollectionView(); - collection.id = id; - collection.name = name; - collection.organizationId = orgId; - collection.type = type || CollectionTypes.SharedCollection; + const collection = new CollectionView({ + id: id as CollectionId, + name, + organizationId: orgId as OrganizationId, + }); + + if (type) { + collection.type = type; + } + return collection; } }); diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts index 11e074db985..ec77ff97a11 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts @@ -12,11 +12,7 @@ import { switchMap, } from "rxjs"; -import { - CollectionAdminView, - CollectionService, - CollectionView, -} from "@bitwarden/admin-console/common"; +import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { sortDefaultCollections } from "@bitwarden/angular/vault/vault-filter/services/vault-filter.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -38,6 +34,7 @@ import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; import { COLLAPSED_GROUPINGS } from "@bitwarden/common/vault/services/key-state/collapsed-groupings.state"; import { CipherListView } from "@bitwarden/sdk-internal"; +import { cloneCollection } from "@bitwarden/web-vault/app/admin-console/organizations/collections"; import { CipherTypeFilter, @@ -253,14 +250,8 @@ export class VaultFilterService implements VaultFilterServiceAbstraction { } collections.forEach((c) => { - const collectionCopy = new CollectionView() as CollectionFilter; - collectionCopy.id = c.id; - collectionCopy.organizationId = c.organizationId; + const collectionCopy = cloneCollection(new CollectionView({ ...c })) as CollectionFilter; collectionCopy.icon = "bwi-collection-shared"; - if (c instanceof CollectionAdminView) { - collectionCopy.groups = c.groups; - collectionCopy.assigned = c.assigned; - } const parts = c.name != null ? c.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : []; ServiceUtils.nestedTraverse(nodes, 0, parts, collectionCopy, null, NestingDelimiter); }); @@ -274,7 +265,7 @@ export class VaultFilterService implements VaultFilterServiceAbstraction { } protected getCollectionFilterHead(): TreeNode { - const head = new CollectionView() as CollectionFilter; + const head = CollectionView.vaultFilterHead() as CollectionFilter; return new TreeNode(head, null, "collections", "AllCollections"); } diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.html b/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.html index 01a38a02d51..f7078d2a67a 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.html @@ -84,7 +84,7 @@ >  {{ f.node.name }} - + -
- - -
- -
-
- diff --git a/libs/angular/src/auth/components/environment-selector.component.ts b/libs/angular/src/auth/components/environment-selector.component.ts deleted file mode 100644 index 1831e513301..00000000000 --- a/libs/angular/src/auth/components/environment-selector.component.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { animate, state, style, transition, trigger } from "@angular/animations"; -import { ConnectedPosition } from "@angular/cdk/overlay"; -import { Component, EventEmitter, Output, Input, OnInit, OnDestroy } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { Observable, map, Subject, takeUntil } from "rxjs"; - -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports -import { SelfHostedEnvConfigDialogComponent } from "@bitwarden/auth/angular"; -import { - EnvironmentService, - Region, - RegionConfig, -} from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { DialogService, ToastService } from "@bitwarden/components"; - -export const ExtensionDefaultOverlayPosition: ConnectedPosition[] = [ - { - originX: "start", - originY: "top", - overlayX: "start", - overlayY: "bottom", - }, -]; -export const DesktopDefaultOverlayPosition: ConnectedPosition[] = [ - { - originX: "start", - originY: "top", - overlayX: "start", - overlayY: "bottom", - }, -]; - -export interface EnvironmentSelectorRouteData { - overlayPosition?: ConnectedPosition[]; -} - -@Component({ - selector: "environment-selector", - templateUrl: "environment-selector.component.html", - animations: [ - trigger("transformPanel", [ - state( - "void", - style({ - opacity: 0, - }), - ), - transition( - "void => open", - animate( - "100ms linear", - style({ - opacity: 1, - }), - ), - ), - transition("* => void", animate("100ms linear", style({ opacity: 0 }))), - ]), - ], - standalone: false, -}) -export class EnvironmentSelectorComponent implements OnInit, OnDestroy { - @Output() onOpenSelfHostedSettings = new EventEmitter(); - @Input() overlayPosition: ConnectedPosition[] = [ - { - originX: "start", - originY: "bottom", - overlayX: "start", - overlayY: "top", - }, - ]; - - protected isOpen = false; - protected ServerEnvironmentType = Region; - protected availableRegions = this.environmentService.availableRegions(); - protected selectedRegion$: Observable = - this.environmentService.environment$.pipe( - map((e) => e.getRegion()), - map((r) => this.availableRegions.find((ar) => ar.key === r)), - ); - - private destroy$ = new Subject(); - - constructor( - protected environmentService: EnvironmentService, - private route: ActivatedRoute, - private dialogService: DialogService, - private toastService: ToastService, - private i18nService: I18nService, - ) {} - - ngOnInit() { - this.route.data.pipe(takeUntil(this.destroy$)).subscribe((data) => { - if (data && data["overlayPosition"]) { - this.overlayPosition = data["overlayPosition"]; - } - }); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - async toggle(option: Region) { - this.isOpen = !this.isOpen; - if (option === null) { - return; - } - - /** - * Opens the self-hosted settings dialog when the self-hosted option is selected. - */ - if (option === Region.SelfHosted) { - const dialogResult = await SelfHostedEnvConfigDialogComponent.open(this.dialogService); - if (dialogResult) { - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("environmentSaved"), - }); - } - // Don't proceed to setEnvironment when the self-hosted dialog is cancelled - return; - } - - await this.environmentService.setEnvironment(option); - } - - close() { - this.isOpen = false; - } -} diff --git a/libs/angular/src/auth/device-management/device-management-item-group.component.html b/libs/angular/src/auth/device-management/device-management-item-group.component.html index b47408059a2..b6a3ea2d8f8 100644 --- a/libs/angular/src/auth/device-management/device-management-item-group.component.html +++ b/libs/angular/src/auth/device-management/device-management-item-group.component.html @@ -6,8 +6,7 @@ bit-item-content type="button" [attr.tabindex]="device.pendingAuthRequest != null ? 0 : null" - (click)="approveOrDenyAuthRequest(device.pendingAuthRequest)" - (keydown.enter)="approveOrDenyAuthRequest(device.pendingAuthRequest)" + (click)="answerAuthRequest(device.pendingAuthRequest)" > {{ device.displayName }} @@ -21,7 +20,7 @@ - {{ "needsApproval" | i18n }} +
{{ "firstLogin" | i18n }}: {{ device.firstLogin | date: "medium" }} diff --git a/libs/angular/src/auth/device-management/device-management-item-group.component.ts b/libs/angular/src/auth/device-management/device-management-item-group.component.ts index 864712ceb78..71e343e734f 100644 --- a/libs/angular/src/auth/device-management/device-management-item-group.component.ts +++ b/libs/angular/src/auth/device-management/device-management-item-group.component.ts @@ -1,15 +1,11 @@ import { CommonModule } from "@angular/common"; -import { Component, Input } from "@angular/core"; -import { firstValueFrom } from "rxjs"; +import { Component, EventEmitter, Input, Output } from "@angular/core"; import { DevicePendingAuthRequest } from "@bitwarden/common/auth/abstractions/devices/responses/device.response"; -import { BadgeModule, DialogService, ItemModule } from "@bitwarden/components"; +import { BadgeModule, ItemModule } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; -import { LoginApprovalDialogComponent } from "../login-approval/login-approval-dialog.component"; - import { DeviceDisplayData } from "./device-management.component"; -import { clearAuthRequestAndResortDevices } from "./resort-devices.helper"; /** Displays user devices in an item list view */ @Component({ @@ -20,24 +16,12 @@ import { clearAuthRequestAndResortDevices } from "./resort-devices.helper"; }) export class DeviceManagementItemGroupComponent { @Input() devices: DeviceDisplayData[] = []; + @Output() onAuthRequestAnswered = new EventEmitter(); - constructor(private dialogService: DialogService) {} - - protected async approveOrDenyAuthRequest(pendingAuthRequest: DevicePendingAuthRequest | null) { + protected answerAuthRequest(pendingAuthRequest: DevicePendingAuthRequest | null) { if (pendingAuthRequest == null) { return; } - - const loginApprovalDialog = LoginApprovalDialogComponent.open(this.dialogService, { - notificationId: pendingAuthRequest.id, - }); - - const result = await firstValueFrom(loginApprovalDialog.closed); - - if (result !== undefined && typeof result === "boolean") { - // Auth request was approved or denied, so clear the - // pending auth request and re-sort the device array - this.devices = clearAuthRequestAndResortDevices(this.devices, pendingAuthRequest); - } + this.onAuthRequestAnswered.emit(pendingAuthRequest); } } diff --git a/libs/angular/src/auth/device-management/device-management-table.component.html b/libs/angular/src/auth/device-management/device-management-table.component.html index febb0a96a4e..72187b2a2fc 100644 --- a/libs/angular/src/auth/device-management/device-management-table.component.html +++ b/libs/angular/src/auth/device-management/device-management-table.component.html @@ -1,4 +1,4 @@ - + @@ -17,24 +16,17 @@ - +
@if (device.pendingAuthRequest) { - + {{ device.displayName }} -
- {{ "needsApproval" | i18n }} -
+
} @else { {{ device.displayName }}
diff --git a/libs/angular/src/auth/device-management/device-management-table.component.ts b/libs/angular/src/auth/device-management/device-management-table.component.ts index c3c835f05ed..d663e28b9e4 100644 --- a/libs/angular/src/auth/device-management/device-management-table.component.ts +++ b/libs/angular/src/auth/device-management/device-management-table.component.ts @@ -1,6 +1,5 @@ import { CommonModule } from "@angular/common"; -import { Component, Input, OnChanges, SimpleChanges } from "@angular/core"; -import { firstValueFrom } from "rxjs"; +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { DevicePendingAuthRequest } from "@bitwarden/common/auth/abstractions/devices/responses/device.response"; @@ -8,16 +7,12 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { BadgeModule, ButtonModule, - DialogService, LinkModule, TableDataSource, TableModule, } from "@bitwarden/components"; -import { LoginApprovalDialogComponent } from "../login-approval/login-approval-dialog.component"; - import { DeviceDisplayData } from "./device-management.component"; -import { clearAuthRequestAndResortDevices } from "./resort-devices.helper"; /** Displays user devices in a sortable table view */ @Component({ @@ -28,6 +23,8 @@ import { clearAuthRequestAndResortDevices } from "./resort-devices.helper"; }) export class DeviceManagementTableComponent implements OnChanges { @Input() devices: DeviceDisplayData[] = []; + @Output() onAuthRequestAnswered = new EventEmitter(); + protected tableDataSource = new TableDataSource(); protected readonly columnConfig = [ @@ -51,10 +48,7 @@ export class DeviceManagementTableComponent implements OnChanges { }, ]; - constructor( - private i18nService: I18nService, - private dialogService: DialogService, - ) {} + constructor(private i18nService: I18nService) {} ngOnChanges(changes: SimpleChanges): void { if (changes.devices) { @@ -62,24 +56,10 @@ export class DeviceManagementTableComponent implements OnChanges { } } - protected async approveOrDenyAuthRequest(pendingAuthRequest: DevicePendingAuthRequest | null) { + protected answerAuthRequest(pendingAuthRequest: DevicePendingAuthRequest | null) { if (pendingAuthRequest == null) { return; } - - const loginApprovalDialog = LoginApprovalDialogComponent.open(this.dialogService, { - notificationId: pendingAuthRequest.id, - }); - - const result = await firstValueFrom(loginApprovalDialog.closed); - - if (result !== undefined && typeof result === "boolean") { - // Auth request was approved or denied, so clear the - // pending auth request and re-sort the device array - this.tableDataSource.data = clearAuthRequestAndResortDevices( - this.devices, - pendingAuthRequest, - ); - } + this.onAuthRequestAnswered.emit(pendingAuthRequest); } } diff --git a/libs/angular/src/auth/device-management/device-management.component.html b/libs/angular/src/auth/device-management/device-management.component.html index 6ee50a32e8e..2a91c2daae2 100644 --- a/libs/angular/src/auth/device-management/device-management.component.html +++ b/libs/angular/src/auth/device-management/device-management.component.html @@ -30,11 +30,13 @@ } diff --git a/libs/angular/src/auth/device-management/device-management.component.ts b/libs/angular/src/auth/device-management/device-management.component.ts index dc7700a9410..3ab9b2146c5 100644 --- a/libs/angular/src/auth/device-management/device-management.component.ts +++ b/libs/angular/src/auth/device-management/device-management.component.ts @@ -16,14 +16,18 @@ import { DeviceType, DeviceTypeMetadata } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { MessageListener } from "@bitwarden/common/platform/messaging"; -import { ButtonModule, PopoverModule } from "@bitwarden/components"; +import { ButtonModule, DialogService, PopoverModule } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; +import { LoginApprovalDialogComponent } from "../login-approval"; + import { DeviceManagementComponentServiceAbstraction } from "./device-management-component.service.abstraction"; import { DeviceManagementItemGroupComponent } from "./device-management-item-group.component"; import { DeviceManagementTableComponent } from "./device-management-table.component"; +import { clearAuthRequestAndResortDevices, resortDevices } from "./resort-devices.helper"; export interface DeviceDisplayData { + creationDate: string; displayName: string; firstLogin: Date; icon: string; @@ -66,6 +70,7 @@ export class DeviceManagementComponent implements OnInit { private destroyRef: DestroyRef, private deviceManagementComponentService: DeviceManagementComponentServiceAbstraction, private devicesService: DevicesServiceAbstraction, + private dialogService: DialogService, private i18nService: I18nService, private messageListener: MessageListener, private validationService: ValidationService, @@ -130,6 +135,7 @@ export class DeviceManagementComponent implements OnInit { } return { + creationDate: device.creationDate, displayName: this.devicesService.getReadableDeviceTypeName(device.type), firstLogin: device.creationDate ? new Date(device.creationDate) : new Date(), icon: this.getDeviceIcon(device.type), @@ -141,7 +147,8 @@ export class DeviceManagementComponent implements OnInit { pendingAuthRequest: device.response?.devicePendingAuthRequest ?? null, }; }) - .filter((device) => device !== null); + .filter((device) => device !== null) + .sort(resortDevices); } private async upsertDeviceWithPendingAuthRequest(authRequestId: string) { @@ -151,6 +158,7 @@ export class DeviceManagementComponent implements OnInit { } const upsertDevice: DeviceDisplayData = { + creationDate: "", displayName: this.devicesService.getReadableDeviceTypeName( authRequestResponse.requestDeviceTypeValue, ), @@ -174,8 +182,9 @@ export class DeviceManagementComponent implements OnInit { ); if (existingDevice?.id && existingDevice.creationDate) { - upsertDevice.id = existingDevice.id; + upsertDevice.creationDate = existingDevice.creationDate; upsertDevice.firstLogin = new Date(existingDevice.creationDate); + upsertDevice.id = existingDevice.id; } } @@ -186,10 +195,10 @@ export class DeviceManagementComponent implements OnInit { if (existingDeviceIndex >= 0) { // Update existing device in device list this.devices[existingDeviceIndex] = upsertDevice; - this.devices = [...this.devices]; + this.devices = [...this.devices].sort(resortDevices); } else { // Add new device to device list - this.devices = [upsertDevice, ...this.devices]; + this.devices = [upsertDevice, ...this.devices].sort(resortDevices); } } @@ -227,4 +236,18 @@ export class DeviceManagementComponent implements OnInit { const metadata = DeviceTypeMetadata[type]; return metadata ? (categoryIconMap[metadata.category] ?? defaultIcon) : defaultIcon; } + + protected async handleAuthRequestAnswered(pendingAuthRequest: DevicePendingAuthRequest) { + const loginApprovalDialog = LoginApprovalDialogComponent.open(this.dialogService, { + notificationId: pendingAuthRequest.id, + }); + + const result = await firstValueFrom(loginApprovalDialog.closed); + + if (result !== undefined && typeof result === "boolean") { + // Auth request was approved or denied, so clear the + // pending auth request and re-sort the device array + this.devices = clearAuthRequestAndResortDevices(this.devices, pendingAuthRequest); + } + } } diff --git a/libs/angular/src/auth/device-management/resort-devices.helper.ts b/libs/angular/src/auth/device-management/resort-devices.helper.ts index e739e943ee8..01661c18ef3 100644 --- a/libs/angular/src/auth/device-management/resort-devices.helper.ts +++ b/libs/angular/src/auth/device-management/resort-devices.helper.ts @@ -23,7 +23,7 @@ export function clearAuthRequestAndResortDevices( * * This is a helper function that gets passed to the `Array.sort()` method */ -function resortDevices(deviceA: DeviceDisplayData, deviceB: DeviceDisplayData) { +export function resortDevices(deviceA: DeviceDisplayData, deviceB: DeviceDisplayData) { // Devices with a pending auth request should be first if (deviceA.pendingAuthRequest) { return -1; @@ -40,11 +40,11 @@ function resortDevices(deviceA: DeviceDisplayData, deviceB: DeviceDisplayData) { return 1; } - // Then sort the rest by display name (alphabetically) - if (deviceA.displayName < deviceB.displayName) { + // Then sort the rest by creation date (newest to oldest) + if (deviceA.creationDate > deviceB.creationDate) { return -1; } - if (deviceA.displayName > deviceB.displayName) { + if (deviceA.creationDate < deviceB.creationDate) { return 1; } diff --git a/libs/angular/src/auth/environment-selector/environment-selector.component.html b/libs/angular/src/auth/environment-selector/environment-selector.component.html new file mode 100644 index 00000000000..f6484ea1e5f --- /dev/null +++ b/libs/angular/src/auth/environment-selector/environment-selector.component.html @@ -0,0 +1,48 @@ + +
+ + + + +
+ {{ "accessing" | i18n }}: + +
+
+
diff --git a/libs/angular/src/auth/environment-selector/environment-selector.component.ts b/libs/angular/src/auth/environment-selector/environment-selector.component.ts new file mode 100644 index 00000000000..6fe3eaa92a0 --- /dev/null +++ b/libs/angular/src/auth/environment-selector/environment-selector.component.ts @@ -0,0 +1,75 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnDestroy } from "@angular/core"; +import { Observable, map, Subject } from "rxjs"; + +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports +import { SelfHostedEnvConfigDialogComponent } from "@bitwarden/auth/angular"; +import { + EnvironmentService, + Region, + RegionConfig, +} from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { + DialogService, + LinkModule, + MenuModule, + ToastService, + TypographyModule, +} from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; + +@Component({ + selector: "environment-selector", + templateUrl: "environment-selector.component.html", + standalone: true, + imports: [CommonModule, I18nPipe, MenuModule, LinkModule, TypographyModule], +}) +export class EnvironmentSelectorComponent implements OnDestroy { + protected ServerEnvironmentType = Region; + protected availableRegions = this.environmentService.availableRegions(); + protected selectedRegion$: Observable = + this.environmentService.environment$.pipe( + map((e) => e.getRegion()), + map((r) => this.availableRegions.find((ar) => ar.key === r)), + ); + + private destroy$ = new Subject(); + + constructor( + public environmentService: EnvironmentService, + private dialogService: DialogService, + private toastService: ToastService, + private i18nService: I18nService, + ) {} + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + async toggle(option: Region) { + if (option === null) { + return; + } + + /** + * Opens the self-hosted settings dialog when the self-hosted option is selected. + */ + if (option === Region.SelfHosted) { + const dialogResult = await SelfHostedEnvConfigDialogComponent.open(this.dialogService); + if (dialogResult) { + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("environmentSaved"), + }); + } + // Don't proceed to setEnvironment when the self-hosted dialog is cancelled + return; + } + + await this.environmentService.setEnvironment(option); + } +} diff --git a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.spec.ts b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.spec.ts new file mode 100644 index 00000000000..c662e20b275 --- /dev/null +++ b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.spec.ts @@ -0,0 +1,262 @@ +import { CommonModule } from "@angular/common"; +import { SimpleChange } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ReactiveFormsModule } from "@angular/forms"; +import { mock, MockProxy } from "jest-mock-extended"; + +import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { SelectModule, FormFieldModule, BitSubmitDirective } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; + +import { ManageTaxInformationComponent } from "./manage-tax-information.component"; + +describe("ManageTaxInformationComponent", () => { + let sut: ManageTaxInformationComponent; + let fixture: ComponentFixture; + let mockTaxService: MockProxy; + + beforeEach(async () => { + mockTaxService = mock(); + await TestBed.configureTestingModule({ + declarations: [ManageTaxInformationComponent], + providers: [ + { provide: TaxServiceAbstraction, useValue: mockTaxService }, + { provide: I18nService, useValue: { t: (key: string) => key } }, + ], + imports: [ + CommonModule, + ReactiveFormsModule, + SelectModule, + FormFieldModule, + BitSubmitDirective, + I18nPipe, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(ManageTaxInformationComponent); + sut = fixture.componentInstance; + fixture.autoDetectChanges(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it("creates successfully", () => { + expect(sut).toBeTruthy(); + }); + + it("should initialize with all values empty in startWith", async () => { + // Arrange + sut.startWith = { + country: "", + postalCode: "", + taxId: "", + line1: "", + line2: "", + city: "", + state: "", + }; + + // Act + fixture.detectChanges(); + + // Assert + const startWithValue = sut.startWith; + expect(startWithValue.line1).toHaveLength(0); + expect(startWithValue.line2).toHaveLength(0); + expect(startWithValue.city).toHaveLength(0); + expect(startWithValue.state).toHaveLength(0); + expect(startWithValue.postalCode).toHaveLength(0); + expect(startWithValue.country).toHaveLength(0); + expect(startWithValue.taxId).toHaveLength(0); + }); + + it("should update the tax information protected state when form is updated", async () => { + // Arrange + const line1Value = "123 Street"; + const line2Value = "Apt. 5"; + const cityValue = "New York"; + const stateValue = "NY"; + const countryValue = "USA"; + const postalCodeValue = "123 Street"; + + sut.startWith = { + country: countryValue, + postalCode: "", + taxId: "", + line1: "", + line2: "", + city: "", + state: "", + }; + sut.showTaxIdField = false; + mockTaxService.isCountrySupported.mockResolvedValue(true); + + // Act + await sut.ngOnInit(); + fixture.detectChanges(); + + const line1: HTMLInputElement = fixture.nativeElement.querySelector( + "input[formControlName='line1']", + ); + const line2: HTMLInputElement = fixture.nativeElement.querySelector( + "input[formControlName='line2']", + ); + const city: HTMLInputElement = fixture.nativeElement.querySelector( + "input[formControlName='city']", + ); + const state: HTMLInputElement = fixture.nativeElement.querySelector( + "input[formControlName='state']", + ); + const postalCode: HTMLInputElement = fixture.nativeElement.querySelector( + "input[formControlName='postalCode']", + ); + + line1.value = line1Value; + line2.value = line2Value; + city.value = cityValue; + state.value = stateValue; + postalCode.value = postalCodeValue; + + line1.dispatchEvent(new Event("input")); + line2.dispatchEvent(new Event("input")); + city.dispatchEvent(new Event("input")); + state.dispatchEvent(new Event("input")); + postalCode.dispatchEvent(new Event("input")); + await fixture.whenStable(); + + // Assert + + // Assert that the internal tax information reflects the form + const taxInformation = sut.getTaxInformation(); + expect(taxInformation.line1).toBe(line1Value); + expect(taxInformation.line2).toBe(line2Value); + expect(taxInformation.city).toBe(cityValue); + expect(taxInformation.state).toBe(stateValue); + expect(taxInformation.postalCode).toBe(postalCodeValue); + expect(taxInformation.country).toBe(countryValue); + expect(taxInformation.taxId).toHaveLength(0); + + expect(mockTaxService.isCountrySupported).toHaveBeenCalledWith(countryValue); + expect(mockTaxService.isCountrySupported).toHaveBeenCalledTimes(2); + }); + + it("should not show address fields except postal code if country is not supported for taxes", async () => { + // Arrange + const countryValue = "UNKNOWN"; + sut.startWith = { + country: countryValue, + postalCode: "", + taxId: "", + line1: "", + line2: "", + city: "", + state: "", + }; + sut.showTaxIdField = false; + mockTaxService.isCountrySupported.mockResolvedValue(false); + + // Act + await sut.ngOnInit(); + fixture.detectChanges(); + + const line1: HTMLInputElement = fixture.nativeElement.querySelector( + "input[formControlName='line1']", + ); + const line2: HTMLInputElement = fixture.nativeElement.querySelector( + "input[formControlName='line2']", + ); + const city: HTMLInputElement = fixture.nativeElement.querySelector( + "input[formControlName='city']", + ); + const state: HTMLInputElement = fixture.nativeElement.querySelector( + "input[formControlName='state']", + ); + const postalCode: HTMLInputElement = fixture.nativeElement.querySelector( + "input[formControlName='postalCode']", + ); + + // Assert + expect(line1).toBeNull(); + expect(line2).toBeNull(); + expect(city).toBeNull(); + expect(state).toBeNull(); + //Should be visible + expect(postalCode).toBeTruthy(); + + expect(mockTaxService.isCountrySupported).toHaveBeenCalledWith(countryValue); + expect(mockTaxService.isCountrySupported).toHaveBeenCalledTimes(1); + }); + + it("should not show the tax id field if showTaxIdField is set to false", async () => { + // Arrange + const countryValue = "USA"; + sut.startWith = { + country: countryValue, + postalCode: "", + taxId: "", + line1: "", + line2: "", + city: "", + state: "", + }; + + sut.showTaxIdField = false; + mockTaxService.isCountrySupported.mockResolvedValue(true); + + // Act + await sut.ngOnInit(); + fixture.detectChanges(); + + // Assert + const taxId: HTMLInputElement = fixture.nativeElement.querySelector( + "input[formControlName='taxId']", + ); + expect(taxId).toBeNull(); + + expect(mockTaxService.isCountrySupported).toHaveBeenCalledWith(countryValue); + expect(mockTaxService.isCountrySupported).toHaveBeenCalledTimes(1); + }); + + it("should clear the tax id field if showTaxIdField is set to false after being true", async () => { + // Arrange + const countryValue = "USA"; + const taxIdValue = "A12345678"; + + sut.startWith = { + country: countryValue, + postalCode: "", + taxId: taxIdValue, + line1: "", + line2: "", + city: "", + state: "", + }; + sut.showTaxIdField = true; + + mockTaxService.isCountrySupported.mockResolvedValue(true); + await sut.ngOnInit(); + fixture.detectChanges(); + const initialTaxIdValue = fixture.nativeElement.querySelector( + "input[formControlName='taxId']", + ).value; + + // Act + sut.showTaxIdField = false; + sut.ngOnChanges({ showTaxIdField: new SimpleChange(true, false, false) }); + fixture.detectChanges(); + + // Assert + const taxId = fixture.nativeElement.querySelector("input[formControlName='taxId']"); + expect(taxId).toBeNull(); + + const taxInformation = sut.getTaxInformation(); + expect(taxInformation.taxId).toBeNull(); + expect(initialTaxIdValue).toEqual(taxIdValue); + + expect(mockTaxService.isCountrySupported).toHaveBeenCalledWith(countryValue); + expect(mockTaxService.isCountrySupported).toHaveBeenCalledTimes(1); + }); +}); diff --git a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts index 57306d66b4b..0b87f3f931d 100644 --- a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts +++ b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts @@ -1,6 +1,15 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; +import { + Component, + EventEmitter, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; import { debounceTime } from "rxjs/operators"; @@ -13,7 +22,7 @@ import { CountryListItem, TaxInformation } from "@bitwarden/common/billing/model templateUrl: "./manage-tax-information.component.html", standalone: false, }) -export class ManageTaxInformationComponent implements OnInit, OnDestroy { +export class ManageTaxInformationComponent implements OnInit, OnDestroy, OnChanges { @Input() startWith: TaxInformation; @Input() onSubmit?: (taxInformation: TaxInformation) => Promise; @Input() showTaxIdField: boolean = true; @@ -56,7 +65,7 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy { } submit = async () => { - this.formGroup.markAllAsTouched(); + this.markAllAsTouched(); if (this.formGroup.invalid) { return; } @@ -65,7 +74,7 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy { }; validate(): boolean { - this.formGroup.markAllAsTouched(); + this.markAllAsTouched(); return this.formGroup.valid; } @@ -142,6 +151,14 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy { }); } + ngOnChanges(changes: SimpleChanges): void { + // Clear the value of the tax-id if states have been changed in the parent component + const showTaxIdField = changes["showTaxIdField"]; + if (showTaxIdField && !showTaxIdField.currentValue) { + this.formGroup.controls.taxId.setValue(null); + } + } + ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); diff --git a/libs/angular/src/vault/components/icon.component.html b/libs/angular/src/vault/components/icon.component.html index 2dae3b26cc5..0f14de64e21 100644 --- a/libs/angular/src/vault/components/icon.component.html +++ b/libs/angular/src/vault/components/icon.component.html @@ -1,19 +1,44 @@ - - @let showFooterBorder = !isDrawer || background() === "alt" || bodyHasScrolledFrom().bottom; + @let showFooterBorder = + (!bodyHasScrolledFrom().top && isScrollable) || bodyHasScrolledFrom().bottom;